Skip to content

Commit b34e2e0

Browse files
feat: feat(resource_tagging): add summary endpoint mapping
* feat(resource_tagging): add summary endpoint mapping
1 parent 264cebe commit b34e2e0

40 files changed

Lines changed: 536 additions & 257 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ jobs:
2626
runs-on: ${{ github.repository == 'stainless-sdks/cloudflare-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
2727
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
2828
steps:
29-
- uses: actions/checkout@v6
29+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3030

3131
- name: Install uv
32-
uses: astral-sh/setup-uv@v5
32+
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
3333
with:
3434
version: '0.10.2'
3535

@@ -48,10 +48,10 @@ jobs:
4848
id-token: write
4949
runs-on: ${{ github.repository == 'stainless-sdks/cloudflare-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
5050
steps:
51-
- uses: actions/checkout@v6
51+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
5252

5353
- name: Install uv
54-
uses: astral-sh/setup-uv@v5
54+
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
5555
with:
5656
version: '0.10.2'
5757

@@ -66,7 +66,7 @@ jobs:
6666
github.repository == 'stainless-sdks/cloudflare-python' &&
6767
!startsWith(github.ref, 'refs/heads/stl/')
6868
id: github-oidc
69-
uses: actions/github-script@v8
69+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
7070
with:
7171
script: core.setOutput('github_token', await core.getIDToken());
7272

@@ -86,10 +86,10 @@ jobs:
8686
runs-on: ${{ github.repository == 'stainless-sdks/cloudflare-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
8787
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
8888
steps:
89-
- uses: actions/checkout@v6
89+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
9090

9191
- name: Install uv
92-
uses: astral-sh/setup-uv@v5
92+
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
9393
with:
9494
version: '0.10.2'
9595

@@ -110,10 +110,10 @@ jobs:
110110
if: github.repository == 'cloudflare/cloudflare-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
111111

112112
steps:
113-
- uses: actions/checkout@v6
113+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
114114

115115
- name: Install uv
116-
uses: astral-sh/setup-uv@v5
116+
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
117117
with:
118118
version: '0.10.2'
119119
- name: Install dependencies

.github/workflows/detect-breaking-changes.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ jobs:
1616
run: |
1717
echo "FETCH_DEPTH=$(expr ${{ github.event.pull_request.commits }} + 1)" >> $GITHUB_ENV
1818
19-
- uses: actions/checkout@v6
19+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2020
with:
2121
# Ensure we can check out the pull request base in the script below.
2222
fetch-depth: ${{ env.FETCH_DEPTH }}
2323

2424
- name: Install uv
25-
uses: astral-sh/setup-uv@v5
25+
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
2626
with:
2727
version: '0.10.2'
2828
- name: Install dependencies

.github/workflows/publish-pypi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ jobs:
1616
runs-on: ubuntu-latest
1717

1818
steps:
19-
- uses: actions/checkout@v6
19+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2020

2121
- name: Install uv
22-
uses: astral-sh/setup-uv@v5
22+
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
2323
with:
2424
version: '0.9.13'
2525

.github/workflows/release-doctor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
if: github.repository == 'cloudflare/cloudflare-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
2121

2222
steps:
23-
- uses: actions/checkout@v6
23+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
2424

2525
- name: Check release environment
2626
run: |

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 2304
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare%2Fcloudflare-a6c352830d1270d0abb5bb983058ea21815e1bb7d2e163965335dcb0e706f057.yml
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cloudflare/cloudflare-cab0533c32650305eaa27491714d017535dfff5cf1f642d0c5dc06b3906b339f.yml
33
openapi_spec_hash: f88a1f57da1f979400b58e284e8a8191
4-
config_hash: 23fee7da67ba558209f67d29de98f8c2
4+
config_hash: bbf9427136a35ea3704b605309480336

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ show_error_codes = true
155155
#
156156
# We also exclude our `tests` as mypy doesn't always infer
157157
# types correctly and Pyright will still catch any type errors.
158-
exclude = ['src/cloudflare/_files.py', '_dev/.*.py', 'tests/.*', 'src/cloudflare/resources/zero_trust/identity_providers\.py', 'src/cloudflare/resources/zero_trust/access/applications/applications\.py', 'src/cloudflare/resources/workers/ai\.py', 'src/cloudflare/resources/magic_transit/apps\.py']
158+
exclude = ["src/cloudflare/_files.py", "_dev/.*.py", "tests/.*", "src/cloudflare/resources/zero_trust/identity_providers\\.py", "src/cloudflare/resources/zero_trust/access/applications/applications\\.py", "src/cloudflare/resources/workers/ai\\.py", "src/cloudflare/resources/magic_transit/apps\\.py"]
159159

160160
strict_equality = true
161161
implicit_reexport = true

scripts/bootstrap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44

55
cd "$(dirname "$0")/.."
66

7-
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then
7+
if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then
88
brew bundle check >/dev/null 2>&1 || {
99
echo -n "==> Install Homebrew dependencies? (y/N): "
1010
read -r response

src/cloudflare/_files.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import io
44
import os
55
import pathlib
6-
from typing import overload
7-
from typing_extensions import TypeGuard
6+
from typing import Sequence, cast, overload
7+
from typing_extensions import TypeVar, TypeGuard
88

99
import anyio
1010

@@ -17,7 +17,9 @@
1717
HttpxFileContent,
1818
HttpxRequestFiles,
1919
)
20-
from ._utils import is_tuple_t, is_mapping_t, is_sequence_t
20+
from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t
21+
22+
_T = TypeVar("_T")
2123

2224

2325
def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]:
@@ -97,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles
9799
elif is_sequence_t(files):
98100
files = [(key, await _async_transform_file(file)) for key, file in files]
99101
else:
100-
raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
102+
raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
101103

102104
return files
103105

@@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent:
121123
return await anyio.Path(file).read_bytes()
122124

123125
return file
126+
127+
128+
def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T:
129+
"""Copy only the containers along the given paths.
130+
131+
Used to guard against mutation by extract_files without copying the entire structure.
132+
Only dicts and lists that lie on a path are copied; everything else
133+
is returned by reference.
134+
135+
For example, given paths=[["foo", "files", "file"]] and the structure:
136+
{
137+
"foo": {
138+
"bar": {"baz": {}},
139+
"files": {"file": <content>}
140+
}
141+
}
142+
The root dict, "foo", and "files" are copied (they lie on the path).
143+
"bar" and "baz" are returned by reference (off the path).
144+
"""
145+
return _deepcopy_with_paths(item, paths, 0)
146+
147+
148+
def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T:
149+
if not paths:
150+
return item
151+
if is_mapping(item):
152+
key_to_paths: dict[str, list[Sequence[str]]] = {}
153+
for path in paths:
154+
if index < len(path):
155+
key_to_paths.setdefault(path[index], []).append(path)
156+
157+
# if no path continues through this mapping, it won't be mutated and copying it is redundant
158+
if not key_to_paths:
159+
return item
160+
161+
result = dict(item)
162+
for key, subpaths in key_to_paths.items():
163+
if key in result:
164+
result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1)
165+
return cast(_T, result)
166+
if is_list(item):
167+
array_paths = [path for path in paths if index < len(path) and path[index] == "<array>"]
168+
169+
# if no path expects a list here, nothing will be mutated inside it - return by reference
170+
if not array_paths:
171+
return cast(_T, item)
172+
return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item])
173+
return item

src/cloudflare/_models.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
ClassVar,
2626
Protocol,
2727
Required,
28+
Annotated,
2829
ParamSpec,
30+
TypeAlias,
2931
TypedDict,
3032
TypeGuard,
3133
final,
@@ -80,7 +82,15 @@
8082
from ._constants import RAW_RESPONSE_HEADER
8183

8284
if TYPE_CHECKING:
85+
from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
86+
from pydantic_core import CoreSchema, core_schema
8387
from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
88+
else:
89+
try:
90+
from pydantic_core import CoreSchema, core_schema
91+
except ImportError:
92+
CoreSchema = None
93+
core_schema = None
8494

8595
__all__ = ["BaseModel", "GenericModel"]
8696

@@ -399,6 +409,76 @@ def model_dump_json(
399409
)
400410

401411

412+
class _EagerIterable(list[_T], Generic[_T]):
413+
"""
414+
Accepts any Iterable[T] input (including generators), consumes it
415+
eagerly, and validates all items upfront.
416+
417+
Validation preserves the original container type where possible
418+
(e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON)
419+
always emits a list — round-tripping through model_dump() will not
420+
restore the original container type.
421+
"""
422+
423+
@classmethod
424+
def __get_pydantic_core_schema__(
425+
cls,
426+
source_type: Any,
427+
handler: GetCoreSchemaHandler,
428+
) -> CoreSchema:
429+
(item_type,) = get_args(source_type) or (Any,)
430+
item_schema: CoreSchema = handler.generate_schema(item_type)
431+
list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)
432+
433+
return core_schema.no_info_wrap_validator_function(
434+
cls._validate,
435+
list_of_items_schema,
436+
serialization=core_schema.plain_serializer_function_ser_schema(
437+
cls._serialize,
438+
info_arg=False,
439+
),
440+
)
441+
442+
@staticmethod
443+
def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
444+
original_type: type[Any] = type(v)
445+
446+
# Normalize to list so list_schema can validate each item
447+
if isinstance(v, list):
448+
items: list[_T] = v
449+
else:
450+
try:
451+
items = list(v)
452+
except TypeError as e:
453+
raise TypeError("Value is not iterable") from e
454+
455+
# Validate items against the inner schema
456+
validated: list[_T] = handler(items)
457+
458+
# Reconstruct original container type
459+
if original_type is list:
460+
return validated
461+
# str(list) produces the list's repr, not a string built from items,
462+
# so skip reconstruction for str and its subclasses.
463+
if issubclass(original_type, str):
464+
return validated
465+
try:
466+
return original_type(validated)
467+
except (TypeError, ValueError):
468+
# If the type cannot be reconstructed, just return the validated list
469+
return validated
470+
471+
@staticmethod
472+
def _serialize(v: Iterable[_T]) -> list[_T]:
473+
"""Always serialize as a list so Pydantic's JSON encoder is happy."""
474+
if isinstance(v, list):
475+
return v
476+
return list(v)
477+
478+
479+
EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]
480+
481+
402482
def _construct_field(value: object, field: FieldInfo, key: str) -> object:
403483
if value is None:
404484
return field_get_default(field)

src/cloudflare/_qs.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22

33
from typing import Any, List, Tuple, Union, Mapping, TypeVar
44
from urllib.parse import parse_qs, urlencode
5-
from typing_extensions import Literal, get_args
5+
from typing_extensions import get_args
66

7-
from ._types import NotGiven, not_given
7+
from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
88
from ._utils import flatten
99

1010
_T = TypeVar("_T")
1111

12-
13-
ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
14-
NestedFormat = Literal["dots", "brackets"]
15-
1612
PrimitiveData = Union[str, int, float, bool, None]
1713
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
1814
# https://github.com/microsoft/pyright/issues/3555

0 commit comments

Comments
 (0)