sg-python-api 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sg_python_api-0.1.0.dist-info → sg_python_api-0.1.1.dist-info}/METADATA +14 -27
- {sg_python_api-0.1.0.dist-info → sg_python_api-0.1.1.dist-info}/RECORD +12 -12
- shotgun_api3/_async_client.py +79 -58
- shotgun_api3/_async_driver.py +2 -14
- shotgun_api3/_client.py +75 -60
- shotgun_api3/_config.py +2 -10
- shotgun_api3/_driver.py +4 -18
- shotgun_api3/_files.py +44 -5
- shotgun_api3/_methods.py +11 -80
- shotgun_api3/_version.py +1 -1
- {sg_python_api-0.1.0.dist-info → sg_python_api-0.1.1.dist-info}/WHEEL +0 -0
- {sg_python_api-0.1.0.dist-info → sg_python_api-0.1.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sg-python-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Python client for Autodesk Flow Production Tracking
|
|
5
5
|
Project-URL: Documentation, https://github.com/dactylo/sg-python-api/tree/main/docs
|
|
6
6
|
Project-URL: Homepage, https://github.com/dactylo/sg-python-api
|
|
@@ -45,8 +45,8 @@ This documentation uses SG for Flow Production Tracking, formerly named Shotgun
|
|
|
45
45
|
|
|
46
46
|
sg-python-api is an independent implementation of the documented API of Autodesk's Python client
|
|
47
47
|
`3.10.2`. Some behavior differs on purpose, so validate your application's workflows against a test
|
|
48
|
-
site before replacing its production client. The [migration guide](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
49
|
-
through the switch, and the [compatibility reference](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
48
|
+
site before replacing its production client. The [migration guide](https://github.com/dactylo/sg-python-api/blob/v0.1.1/docs/migration-guide.md) walks
|
|
49
|
+
through the switch, and the [compatibility reference](https://github.com/dactylo/sg-python-api/blob/v0.1.1/COMPATIBILITY.md) lists the differences.
|
|
50
50
|
|
|
51
51
|
## Installation
|
|
52
52
|
|
|
@@ -55,16 +55,9 @@ requests to a remote site require HTTPS.
|
|
|
55
55
|
|
|
56
56
|
`sg-python-api` and Autodesk's `shotgun-api3` both install the `shotgun_api3` package, so install
|
|
57
57
|
exactly one of them in a Python environment. If the official client is already installed, follow
|
|
58
|
-
the [migration steps](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
58
|
+
the [migration steps](https://github.com/dactylo/sg-python-api/blob/v0.1.1/docs/migration-guide.md#install-the-replacement) to replace it.
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
your application:
|
|
62
|
-
|
|
63
|
-
```sh
|
|
64
|
-
python -m pip install sg-python-api
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
To install from a checkout instead, run:
|
|
60
|
+
From a checkout, install into the Python environment that runs your application:
|
|
68
61
|
|
|
69
62
|
```sh
|
|
70
63
|
python -m pip install .
|
|
@@ -74,12 +67,6 @@ The base installation provides the synchronous `Shotgun` client and both `mockgu
|
|
|
74
67
|
use the native asynchronous client, `AsyncShotgun`, install the `async` extra, which adds the
|
|
75
68
|
`httpx2` HTTP library:
|
|
76
69
|
|
|
77
|
-
```sh
|
|
78
|
-
python -m pip install 'sg-python-api[async]'
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
From a checkout, use:
|
|
82
|
-
|
|
83
70
|
```sh
|
|
84
71
|
python -m pip install '.[async]'
|
|
85
72
|
```
|
|
@@ -143,23 +130,23 @@ operation fails. Without the `async` extra, importing `AsyncShotgun` raises `Imp
|
|
|
143
130
|
|
|
144
131
|
Share an `AsyncShotgun` only among tasks on one event loop, and cancel its work by cancelling the
|
|
145
132
|
task. A cancelled write may already have reached the server, so check its outcome before repeating
|
|
146
|
-
it. The [async reference](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
133
|
+
it. The [async reference](https://github.com/dactylo/sg-python-api/blob/v0.1.1/COMPATIBILITY.md#async-extension-surfaces) covers shutdown and
|
|
147
134
|
cancellation in detail.
|
|
148
135
|
|
|
149
136
|
## Documentation
|
|
150
137
|
|
|
151
138
|
| If you want to... | Read |
|
|
152
139
|
| ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
153
|
-
| Replace Autodesk's client in an existing application | [Migration guide](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
154
|
-
| Check what behaves differently from Autodesk's client | [Compatibility](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
155
|
-
| Tune pooling and page prefetch, or observe requests | [Configuration extensions](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
156
|
-
| Confirm which client loads and run a smoke test | [Testing an installation](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
157
|
-
| Compare measured latency and transfer memory | [Benchmarks](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
158
|
-
| See changes by version | [Changelog](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
140
|
+
| Replace Autodesk's client in an existing application | [Migration guide](https://github.com/dactylo/sg-python-api/blob/v0.1.1/docs/migration-guide.md) |
|
|
141
|
+
| Check what behaves differently from Autodesk's client | [Compatibility](https://github.com/dactylo/sg-python-api/blob/v0.1.1/COMPATIBILITY.md) |
|
|
142
|
+
| Tune pooling and page prefetch, or observe requests | [Configuration extensions](https://github.com/dactylo/sg-python-api/blob/v0.1.1/COMPATIBILITY.md#configuration-and-observer-extensions) |
|
|
143
|
+
| Confirm which client loads and run a smoke test | [Testing an installation](https://github.com/dactylo/sg-python-api/blob/v0.1.1/docs/testing.md) |
|
|
144
|
+
| Compare measured latency and transfer memory | [Benchmarks](https://github.com/dactylo/sg-python-api/blob/v0.1.1/docs/benchmarks.md) |
|
|
145
|
+
| See changes by version | [Changelog](https://github.com/dactylo/sg-python-api/blob/v0.1.1/CHANGELOG.md) |
|
|
159
146
|
|
|
160
147
|
## Development
|
|
161
148
|
|
|
162
|
-
See the [development guide](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
149
|
+
See the [development guide](https://github.com/dactylo/sg-python-api/blob/v0.1.1/docs/development.md) for the required tools, CI, and dependency locks.
|
|
163
150
|
With those tools installed, run:
|
|
164
151
|
|
|
165
152
|
```sh
|
|
@@ -172,6 +159,6 @@ make check
|
|
|
172
159
|
|
|
173
160
|
## License
|
|
174
161
|
|
|
175
|
-
sg-python-api is available under the [MIT License](https://github.com/dactylo/sg-python-api/blob/v0.1.
|
|
162
|
+
sg-python-api is available under the [MIT License](https://github.com/dactylo/sg-python-api/blob/v0.1.1/LICENSE). It is an independent project and is
|
|
176
163
|
not affiliated with or endorsed by Autodesk. Flow Production Tracking is a product of Autodesk,
|
|
177
164
|
Inc.
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
shotgun_api3/__init__.py,sha256=fviJGOi_Gb9jHi5Q9ty5tIbeIvxMtLNrKx_4HJVgemI,2011
|
|
2
|
-
shotgun_api3/_async_client.py,sha256=
|
|
3
|
-
shotgun_api3/_async_driver.py,sha256=
|
|
2
|
+
shotgun_api3/_async_client.py,sha256=I5_4t_WwJNc-3hkDrAVnbofqw4Nil07Jcxfr2CSb18A,112160
|
|
3
|
+
shotgun_api3/_async_driver.py,sha256=NnuMmQP5KrAoANBvWIhcIUe_ljD62pK_7xSIoEIltXw,32308
|
|
4
4
|
shotgun_api3/_async_httpx2.py,sha256=3pjeFsb5racGArvlR50c3w0XtrttXuRU_6E9Y-cBU3c,24387
|
|
5
5
|
shotgun_api3/_async_transport.py,sha256=8iAcoFafEHKRBirBkqdIqMFgqLs1YTgtzgACudD0kM4,4928
|
|
6
6
|
shotgun_api3/_capabilities.py,sha256=RrStvExVC3TAADhfbY2JpYZUXmvE63vXIJy1R-nkEII,5527
|
|
7
|
-
shotgun_api3/_client.py,sha256=
|
|
8
|
-
shotgun_api3/_config.py,sha256=
|
|
7
|
+
shotgun_api3/_client.py,sha256=7ICzGJGKKI-OzuKoCTfqhumrvRaNybN98-br7CJnB9Y,101143
|
|
8
|
+
shotgun_api3/_config.py,sha256=fALxy5xGI3A0nECsRDyls_9A8tvjB0DNSpTu1agNEHw,54931
|
|
9
9
|
shotgun_api3/_crud.py,sha256=Ax36jA2E-lauRf2D-2rmRDj61SilOygtkjKgs5XDCUs,30319
|
|
10
|
-
shotgun_api3/_driver.py,sha256=
|
|
10
|
+
shotgun_api3/_driver.py,sha256=DAtnZW044TWiqscs0G1uwTiluAFtCoR2wKTbJYJHT2g,29498
|
|
11
11
|
shotgun_api3/_errors.py,sha256=fk7v3XbmZK63M272r5wDwF_GgI-4RynPfAWv1P-J-GI,1480
|
|
12
12
|
shotgun_api3/_execution.py,sha256=Kn2Yd9sXIA975stnof7MeuA8bT3dyOuWDIqQnP65LO0,8235
|
|
13
|
-
shotgun_api3/_files.py,sha256=
|
|
14
|
-
shotgun_api3/_methods.py,sha256=
|
|
13
|
+
shotgun_api3/_files.py,sha256=7onuC_ODjGtgysIy3YUpeHKtmUpBHQpYln6XtNe3NK4,36770
|
|
14
|
+
shotgun_api3/_methods.py,sha256=yGyQv7tKpHX3e3nGIVHEbuTNzjsAxQdJVXpmlXXStsw,34184
|
|
15
15
|
shotgun_api3/_protocol.py,sha256=dKr1XqB6Xo56QloZ2MAyN4ulqYkTjw8VYeJRd5QHufQ,45286
|
|
16
16
|
shotgun_api3/_sentinel.py,sha256=KAQzQjJVhTTZPLFY1f88-_vP7U06i2g6ZS7KJcn4vIA,116
|
|
17
17
|
shotgun_api3/_sg_timezone.py,sha256=yr7Wdln9fwfTVGMQSFsgO-eduuTHQ5EbSY5xEVQ2tYw,115
|
|
18
18
|
shotgun_api3/_signatures.py,sha256=_yhNnbkVF-0opaBfg9H6SauBN2RT4hJsHGa3ZO3Ug4s,1622
|
|
19
19
|
shotgun_api3/_transport.py,sha256=LmQ0e2VvAKwFdNy4SyfRHwgKC6GWnwkIN5Wa2fin3AM,30047
|
|
20
|
-
shotgun_api3/_version.py,sha256=
|
|
20
|
+
shotgun_api3/_version.py,sha256=LnmGg2me6J0MbE65mSg2nIknHNNg0Etx_3Jkh_k9BWc,102
|
|
21
21
|
shotgun_api3/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
shotgun_api3/shotgun.py,sha256=X7PLlCC-gR8dJgY_uLYXw_LWN8mz5MsQsSYJldtavTk,1706
|
|
23
23
|
shotgun_api3/lib/__init__.py,sha256=4WGYod6xHcj23rapx5bTDGdjT52H1CAQNMz4BmzNmn0,55
|
|
@@ -25,7 +25,7 @@ shotgun_api3/lib/sgtimezone.py,sha256=nYSaCsIeq_SJh7YgL4yWN7dTVVz9lAfx-G3mzsHb8i
|
|
|
25
25
|
shotgun_api3/lib/mockgun/__init__.py,sha256=2mHjMJAV4mZ8JvnP8WHn6tkdMJoB7UQtNSs99YzNW30,703
|
|
26
26
|
shotgun_api3/lib/mockgun/_async.py,sha256=7bTZFdlSzeoGq9fRbPBAf-X08qDmrkzV9rxskx5AGX8,10812
|
|
27
27
|
shotgun_api3/lib/mockgun/_mockgun.py,sha256=k2cqLMCKpU5oIbUGiYulf1aIU0HLrMB6DQQ3KrCy7WQ,65093
|
|
28
|
-
sg_python_api-0.1.
|
|
29
|
-
sg_python_api-0.1.
|
|
30
|
-
sg_python_api-0.1.
|
|
31
|
-
sg_python_api-0.1.
|
|
28
|
+
sg_python_api-0.1.1.dist-info/METADATA,sha256=bwDwuEXYzQ-8J-igveXXMna_Ulemsdz9xwNLVhdW3mI,7587
|
|
29
|
+
sg_python_api-0.1.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
30
|
+
sg_python_api-0.1.1.dist-info/licenses/LICENSE,sha256=V3moMsQxKDchTd3mtXl5J-ODaNBDvFrapOf1DK7Nx_U,1082
|
|
31
|
+
sg_python_api-0.1.1.dist-info/RECORD,,
|
shotgun_api3/_async_client.py
CHANGED
|
@@ -51,7 +51,6 @@ from ._client import (
|
|
|
51
51
|
expose_constructor_signature,
|
|
52
52
|
)
|
|
53
53
|
from ._config import (
|
|
54
|
-
CompatibilityWarningCode,
|
|
55
54
|
Config,
|
|
56
55
|
RequestContext,
|
|
57
56
|
advance_transport_generation,
|
|
@@ -288,7 +287,6 @@ class _InvocationOptions:
|
|
|
288
287
|
"""Collect non-payload controls for one client invocation."""
|
|
289
288
|
|
|
290
289
|
auth_spec: _protocol.AuthSpec | None = None
|
|
291
|
-
compatibility_warning_codes: tuple[CompatibilityWarningCode, ...] = ()
|
|
292
290
|
include_auth: bool = True
|
|
293
291
|
logical_method_name: str | None = None
|
|
294
292
|
|
|
@@ -523,7 +521,6 @@ class AsyncShotgun:
|
|
|
523
521
|
outbound,
|
|
524
522
|
context,
|
|
525
523
|
include_auth=invocation.options.include_auth,
|
|
526
|
-
compatibility_warning_codes=invocation.options.compatibility_warning_codes,
|
|
527
524
|
_logical_method_name=invocation.options.logical_method_name,
|
|
528
525
|
)
|
|
529
526
|
return _protocol.transform_inbound(result, context.convert_datetimes_to_utc)
|
|
@@ -1220,6 +1217,28 @@ class AsyncShotgun:
|
|
|
1220
1217
|
await self._upload_direct(driver, context, prepared, upload_info)
|
|
1221
1218
|
return await self._upload_link(driver, context, prepared, upload_info)
|
|
1222
1219
|
|
|
1220
|
+
async def _upload_thumbnails_after_write(
|
|
1221
|
+
self,
|
|
1222
|
+
entity_type: str,
|
|
1223
|
+
entity_id: int,
|
|
1224
|
+
thumbnails: dict[str, object],
|
|
1225
|
+
result: dict[str, object],
|
|
1226
|
+
) -> dict[str, object]:
|
|
1227
|
+
"""Upload separated thumbnail paths and merge their current field values."""
|
|
1228
|
+
if not thumbnails:
|
|
1229
|
+
return result
|
|
1230
|
+
for field_name, path in thumbnails.items():
|
|
1231
|
+
upload = (
|
|
1232
|
+
self.upload_filmstrip_thumbnail
|
|
1233
|
+
if field_name == 'filmstrip_image'
|
|
1234
|
+
else self.upload_thumbnail
|
|
1235
|
+
)
|
|
1236
|
+
await upload(entity_type, entity_id, cast('str', path))
|
|
1237
|
+
current = await self.find_one(entity_type, [['id', 'is', entity_id]], list(thumbnails))
|
|
1238
|
+
if current is not None:
|
|
1239
|
+
result.update({field_name: current.get(field_name) for field_name in thumbnails})
|
|
1240
|
+
return result
|
|
1241
|
+
|
|
1223
1242
|
@overload
|
|
1224
1243
|
async def activity_stream_read(self, entity_type: Never) -> dict[str, object]: ...
|
|
1225
1244
|
|
|
@@ -1520,6 +1539,10 @@ class AsyncShotgun:
|
|
|
1520
1539
|
) -> dict[str, object]:
|
|
1521
1540
|
"""Create an entity with the supplied field values.
|
|
1522
1541
|
|
|
1542
|
+
Non-empty `image` and `filmstrip_image` values are file paths. They are
|
|
1543
|
+
uploaded as thumbnails after the entity is created, and the result
|
|
1544
|
+
includes their current field values.
|
|
1545
|
+
|
|
1523
1546
|
Args:
|
|
1524
1547
|
entity_type: Entity type to create.
|
|
1525
1548
|
data: Field values for the new entity.
|
|
@@ -1530,14 +1553,22 @@ class AsyncShotgun:
|
|
|
1530
1553
|
|
|
1531
1554
|
Raises:
|
|
1532
1555
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1533
|
-
|
|
1534
|
-
result is invalid
|
|
1556
|
+
if `data`, its field names, `return_fields`, or the server
|
|
1557
|
+
result is invalid, or if a thumbnail path is not a nonempty
|
|
1558
|
+
file. A thumbnail failure occurs after the entity is created.
|
|
1535
1559
|
ProtocolError: If a server request returns an HTTP error.
|
|
1536
1560
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1537
1561
|
policy.
|
|
1538
1562
|
"""
|
|
1539
|
-
|
|
1540
|
-
|
|
1563
|
+
field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=False)
|
|
1564
|
+
params = _crud.build_create_params(entity_type, field_data, return_fields)
|
|
1565
|
+
result = _crud.require_dictionary_result(await self._call('create', params), 'create')
|
|
1566
|
+
if not thumbnails:
|
|
1567
|
+
return result
|
|
1568
|
+
entity_id = _crud.require_entity_id(result.get('id'), 'create did not return an entity id')
|
|
1569
|
+
return await self._upload_thumbnails_after_write(
|
|
1570
|
+
entity_type, entity_id, thumbnails, result
|
|
1571
|
+
)
|
|
1541
1572
|
|
|
1542
1573
|
async def delete(self, entity_type: str, entity_id: int) -> bool:
|
|
1543
1574
|
"""Retire an entity.
|
|
@@ -1642,6 +1673,15 @@ class AsyncShotgun:
|
|
|
1642
1673
|
except (TransportError, _errors.ProtocolError, OSError):
|
|
1643
1674
|
raise _errors.ShotgunFileDownloadError('attachment download failed') from None
|
|
1644
1675
|
|
|
1676
|
+
async def entity_types(self) -> Never:
|
|
1677
|
+
"""Reject the deprecated alias of `schema_entity_read()` without I/O.
|
|
1678
|
+
|
|
1679
|
+
Raises:
|
|
1680
|
+
ShotgunError: Always, naming `schema_entity_read()` as the
|
|
1681
|
+
replacement.
|
|
1682
|
+
"""
|
|
1683
|
+
raise _methods.entity_types_alias_error()
|
|
1684
|
+
|
|
1645
1685
|
@overload
|
|
1646
1686
|
async def export_page(
|
|
1647
1687
|
self,
|
|
@@ -2085,6 +2125,18 @@ class AsyncShotgun:
|
|
|
2085
2125
|
params = _crud.build_entity_action_params(entity_type, entity_id)
|
|
2086
2126
|
return _crud.require_boolean_result(await self._call('revive', params), 'revive')
|
|
2087
2127
|
|
|
2128
|
+
async def schema(self, entity_type: str) -> Never:
|
|
2129
|
+
"""Reject the deprecated alias of `schema_field_read()` without I/O.
|
|
2130
|
+
|
|
2131
|
+
Args:
|
|
2132
|
+
entity_type: Entity type named in the replacement suggestion.
|
|
2133
|
+
|
|
2134
|
+
Raises:
|
|
2135
|
+
ShotgunError: Always, naming the equivalent `schema_field_read()`
|
|
2136
|
+
call.
|
|
2137
|
+
"""
|
|
2138
|
+
raise _methods.schema_alias_error(entity_type)
|
|
2139
|
+
|
|
2088
2140
|
async def schema_entity_read(
|
|
2089
2141
|
self,
|
|
2090
2142
|
project_entity: dict[str, Any] | None = None,
|
|
@@ -2104,34 +2156,12 @@ class AsyncShotgun:
|
|
|
2104
2156
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2105
2157
|
policy.
|
|
2106
2158
|
"""
|
|
2107
|
-
warnings_codes = _methods.consume_compatibility_warnings(
|
|
2108
|
-
_methods.SCHEMA_ENTITY_READ_WARNINGS,
|
|
2109
|
-
self,
|
|
2110
|
-
)
|
|
2111
2159
|
result = await self._call(
|
|
2112
2160
|
'schema_entity_read',
|
|
2113
2161
|
_methods.build_schema_entity_read_params(project_entity),
|
|
2114
|
-
_InvocationOptions(compatibility_warning_codes=warnings_codes),
|
|
2115
2162
|
)
|
|
2116
2163
|
return _crud.require_dictionary_result(result, 'schema_entity_read')
|
|
2117
2164
|
|
|
2118
|
-
async def entity_types(self) -> dict[str, object]:
|
|
2119
|
-
"""Return entity schema through the deprecated alias.
|
|
2120
|
-
|
|
2121
|
-
Returns:
|
|
2122
|
-
Entity-type schema data returned by the server.
|
|
2123
|
-
|
|
2124
|
-
Raises:
|
|
2125
|
-
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2126
|
-
or if the server result is not a dictionary.
|
|
2127
|
-
ProtocolError: If a server request returns an HTTP error.
|
|
2128
|
-
ValueError: If authenticated HTTP violates the configured origin
|
|
2129
|
-
policy.
|
|
2130
|
-
"""
|
|
2131
|
-
_methods.warn_entity_types_alias()
|
|
2132
|
-
with _methods.entity_types_alias(self):
|
|
2133
|
-
return await self.schema_entity_read()
|
|
2134
|
-
|
|
2135
2165
|
async def schema_field_create(
|
|
2136
2166
|
self,
|
|
2137
2167
|
entity_type: str,
|
|
@@ -2210,10 +2240,6 @@ class AsyncShotgun:
|
|
|
2210
2240
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2211
2241
|
policy.
|
|
2212
2242
|
"""
|
|
2213
|
-
warning_codes = _methods.consume_compatibility_warnings(
|
|
2214
|
-
_methods.SCHEMA_FIELD_READ_WARNINGS,
|
|
2215
|
-
self,
|
|
2216
|
-
)
|
|
2217
2243
|
result = await self._call(
|
|
2218
2244
|
'schema_field_read',
|
|
2219
2245
|
_methods.build_schema_field_read_params(
|
|
@@ -2221,30 +2247,9 @@ class AsyncShotgun:
|
|
|
2221
2247
|
field_name,
|
|
2222
2248
|
project_entity,
|
|
2223
2249
|
),
|
|
2224
|
-
_InvocationOptions(compatibility_warning_codes=warning_codes),
|
|
2225
2250
|
)
|
|
2226
2251
|
return _crud.require_dictionary_result(result, 'schema_field_read')
|
|
2227
2252
|
|
|
2228
|
-
async def schema(self, entity_type: str) -> dict[str, object]:
|
|
2229
|
-
"""Return field schema through the deprecated alias.
|
|
2230
|
-
|
|
2231
|
-
Args:
|
|
2232
|
-
entity_type: Entity type whose fields to return.
|
|
2233
|
-
|
|
2234
|
-
Returns:
|
|
2235
|
-
Field schema data returned by the server.
|
|
2236
|
-
|
|
2237
|
-
Raises:
|
|
2238
|
-
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2239
|
-
or if the server result is invalid.
|
|
2240
|
-
ProtocolError: If a server request returns an HTTP error.
|
|
2241
|
-
ValueError: If authenticated HTTP violates the configured origin
|
|
2242
|
-
policy.
|
|
2243
|
-
"""
|
|
2244
|
-
_methods.warn_schema_alias()
|
|
2245
|
-
with _methods.schema_alias(self):
|
|
2246
|
-
return await self.schema_field_read(entity_type)
|
|
2247
|
-
|
|
2248
2253
|
async def schema_field_update(
|
|
2249
2254
|
self,
|
|
2250
2255
|
entity_type: str,
|
|
@@ -2559,6 +2564,12 @@ class AsyncShotgun:
|
|
|
2559
2564
|
) -> dict[str, object]:
|
|
2560
2565
|
"""Update fields on an existing entity.
|
|
2561
2566
|
|
|
2567
|
+
Non-empty `image` and `filmstrip_image` values are file paths. They are
|
|
2568
|
+
uploaded as thumbnails after the other fields are updated, and the
|
|
2569
|
+
result includes their current field values. An `image` of `None` clears
|
|
2570
|
+
both thumbnails. When no other fields remain, no update request is sent
|
|
2571
|
+
and the result contains only `id` and `type`.
|
|
2572
|
+
|
|
2562
2573
|
Args:
|
|
2563
2574
|
entity_type: Entity type to update.
|
|
2564
2575
|
entity_id: Numeric entity identifier.
|
|
@@ -2570,19 +2581,29 @@ class AsyncShotgun:
|
|
|
2570
2581
|
|
|
2571
2582
|
Raises:
|
|
2572
2583
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2573
|
-
|
|
2574
|
-
|
|
2584
|
+
if `data`, its field names, `multi_entity_update_modes`, or the
|
|
2585
|
+
server result is invalid, or if a thumbnail path is not a
|
|
2586
|
+
nonempty file. A thumbnail failure occurs after the update.
|
|
2575
2587
|
ProtocolError: If a server request returns an HTTP error.
|
|
2576
2588
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2577
2589
|
policy.
|
|
2578
2590
|
"""
|
|
2591
|
+
field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=True)
|
|
2579
2592
|
params = _crud.build_update_params(
|
|
2580
2593
|
entity_type,
|
|
2581
2594
|
entity_id,
|
|
2582
|
-
|
|
2595
|
+
field_data,
|
|
2583
2596
|
multi_entity_update_modes,
|
|
2584
2597
|
)
|
|
2585
|
-
|
|
2598
|
+
result: dict[str, object] = {'id': entity_id, 'type': entity_type}
|
|
2599
|
+
if params['fields']:
|
|
2600
|
+
result = _crud.require_dictionary_result(
|
|
2601
|
+
await self._call('update', params),
|
|
2602
|
+
'update',
|
|
2603
|
+
)
|
|
2604
|
+
return await self._upload_thumbnails_after_write(
|
|
2605
|
+
entity_type, entity_id, thumbnails, result
|
|
2606
|
+
)
|
|
2586
2607
|
|
|
2587
2608
|
async def update_project_last_accessed(
|
|
2588
2609
|
self,
|
shotgun_api3/_async_driver.py
CHANGED
|
@@ -15,7 +15,6 @@ from typing import Protocol, TypeVar, cast
|
|
|
15
15
|
from . import _protocol, _transport
|
|
16
16
|
from ._async_transport import AsyncTransport
|
|
17
17
|
from ._config import (
|
|
18
|
-
CompatibilityWarningCode,
|
|
19
18
|
EventRoute,
|
|
20
19
|
ObserverCallback,
|
|
21
20
|
Outcome,
|
|
@@ -200,14 +199,11 @@ class AsyncExecutionOptions:
|
|
|
200
199
|
"""Control async executor transport choreography.
|
|
201
200
|
|
|
202
201
|
Attributes:
|
|
203
|
-
compatibility_warning_codes: Compatibility warnings emitted before
|
|
204
|
-
attempts.
|
|
205
202
|
logical_method_name: Optional observer-facing operation name.
|
|
206
203
|
redirect_factory: Optional request factory for redirect responses.
|
|
207
204
|
streaming: Whether the response body is consumed as a stream.
|
|
208
205
|
"""
|
|
209
206
|
|
|
210
|
-
compatibility_warning_codes: tuple[CompatibilityWarningCode, ...] = ()
|
|
211
207
|
logical_method_name: str | None = None
|
|
212
208
|
redirect_factory: Callable[['AsyncLiveResponse'], TransportRequest] | None = None
|
|
213
209
|
streaming: bool = False
|
|
@@ -486,10 +482,7 @@ class AsyncDriver:
|
|
|
486
482
|
consumption, or cleanup fails.
|
|
487
483
|
"""
|
|
488
484
|
execution_options = options or AsyncExecutionOptions()
|
|
489
|
-
for event in compatibility_warning_events(
|
|
490
|
-
execution_options.compatibility_warning_codes,
|
|
491
|
-
context.timeout,
|
|
492
|
-
):
|
|
485
|
+
for event in compatibility_warning_events(context.timeout):
|
|
493
486
|
notify_observer(context.on_compatibility_warning, event)
|
|
494
487
|
route = event_route(operation)
|
|
495
488
|
self._transport.checkout()
|
|
@@ -590,7 +583,6 @@ class AsyncDriver:
|
|
|
590
583
|
params: object,
|
|
591
584
|
context: RequestContext,
|
|
592
585
|
include_auth: bool = True,
|
|
593
|
-
compatibility_warning_codes: tuple[CompatibilityWarningCode, ...] = (),
|
|
594
586
|
*,
|
|
595
587
|
_logical_method_name: str | None = None,
|
|
596
588
|
) -> object:
|
|
@@ -601,7 +593,6 @@ class AsyncDriver:
|
|
|
601
593
|
params: Normalized method parameters.
|
|
602
594
|
context: Immutable operation configuration.
|
|
603
595
|
include_auth: Whether to include captured authentication.
|
|
604
|
-
compatibility_warning_codes: Sanitized compatibility notices.
|
|
605
596
|
_logical_method_name: Optional logical operation name when it
|
|
606
597
|
differs from the wire method.
|
|
607
598
|
|
|
@@ -615,10 +606,7 @@ class AsyncDriver:
|
|
|
615
606
|
consumption, or cleanup fails.
|
|
616
607
|
"""
|
|
617
608
|
normalized_params = copy.deepcopy(params)
|
|
618
|
-
options = AsyncExecutionOptions(
|
|
619
|
-
compatibility_warning_codes=compatibility_warning_codes,
|
|
620
|
-
logical_method_name=_logical_method_name,
|
|
621
|
-
)
|
|
609
|
+
options = AsyncExecutionOptions(logical_method_name=_logical_method_name)
|
|
622
610
|
return await self._execute_rpc(
|
|
623
611
|
method_name,
|
|
624
612
|
normalized_params,
|
shotgun_api3/_client.py
CHANGED
|
@@ -26,7 +26,6 @@ from . import _config as config_module
|
|
|
26
26
|
from . import _crud, _errors, _files, _methods, _protocol, _transport
|
|
27
27
|
from ._capabilities import ClientCapabilities, ServerCapabilities
|
|
28
28
|
from ._config import (
|
|
29
|
-
CompatibilityWarningCode,
|
|
30
29
|
Config,
|
|
31
30
|
RequestContext,
|
|
32
31
|
advance_transport_generation,
|
|
@@ -348,7 +347,6 @@ class _InvocationOptions:
|
|
|
348
347
|
"""Collect non-payload controls for one client invocation."""
|
|
349
348
|
|
|
350
349
|
include_auth: bool = True
|
|
351
|
-
compatibility_warning_codes: tuple[CompatibilityWarningCode, ...] = ()
|
|
352
350
|
logical_method_name: str | None = None
|
|
353
351
|
auth_spec: _protocol.AuthSpec | None = None
|
|
354
352
|
|
|
@@ -570,7 +568,6 @@ class Shotgun:
|
|
|
570
568
|
outbound,
|
|
571
569
|
context,
|
|
572
570
|
include_auth=options.include_auth,
|
|
573
|
-
compatibility_warning_codes=options.compatibility_warning_codes,
|
|
574
571
|
_logical_method_name=options.logical_method_name,
|
|
575
572
|
)
|
|
576
573
|
return _protocol.transform_inbound(result, context.convert_datetimes_to_utc)
|
|
@@ -1038,6 +1035,28 @@ class Shotgun:
|
|
|
1038
1035
|
self._upload_direct(driver, context, prepared, upload_info)
|
|
1039
1036
|
return self._upload_link(driver, context, prepared, upload_info)
|
|
1040
1037
|
|
|
1038
|
+
def _upload_thumbnails_after_write(
|
|
1039
|
+
self,
|
|
1040
|
+
entity_type: str,
|
|
1041
|
+
entity_id: int,
|
|
1042
|
+
thumbnails: dict[str, object],
|
|
1043
|
+
result: dict[str, object],
|
|
1044
|
+
) -> dict[str, object]:
|
|
1045
|
+
"""Upload separated thumbnail paths and merge their current field values."""
|
|
1046
|
+
if not thumbnails:
|
|
1047
|
+
return result
|
|
1048
|
+
for field_name, path in thumbnails.items():
|
|
1049
|
+
upload = (
|
|
1050
|
+
self.upload_filmstrip_thumbnail
|
|
1051
|
+
if field_name == 'filmstrip_image'
|
|
1052
|
+
else self.upload_thumbnail
|
|
1053
|
+
)
|
|
1054
|
+
upload(entity_type, entity_id, cast('str', path))
|
|
1055
|
+
current = self.find_one(entity_type, [['id', 'is', entity_id]], list(thumbnails))
|
|
1056
|
+
if current is not None:
|
|
1057
|
+
result.update({field_name: current.get(field_name) for field_name in thumbnails})
|
|
1058
|
+
return result
|
|
1059
|
+
|
|
1041
1060
|
@overload
|
|
1042
1061
|
def activity_stream_read(self, entity_type: Never) -> dict[str, object]: ...
|
|
1043
1062
|
|
|
@@ -1298,6 +1317,10 @@ class Shotgun:
|
|
|
1298
1317
|
) -> dict[str, object]:
|
|
1299
1318
|
"""Create an entity with the supplied field values.
|
|
1300
1319
|
|
|
1320
|
+
Non-empty `image` and `filmstrip_image` values are file paths. They are
|
|
1321
|
+
uploaded as thumbnails after the entity is created, and the result
|
|
1322
|
+
includes their current field values.
|
|
1323
|
+
|
|
1301
1324
|
Args:
|
|
1302
1325
|
entity_type: Entity type to create.
|
|
1303
1326
|
data: Field values for the new entity.
|
|
@@ -1308,14 +1331,20 @@ class Shotgun:
|
|
|
1308
1331
|
|
|
1309
1332
|
Raises:
|
|
1310
1333
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1311
|
-
|
|
1312
|
-
result is invalid
|
|
1334
|
+
if `data`, its field names, `return_fields`, or the server
|
|
1335
|
+
result is invalid, or if a thumbnail path is not a nonempty
|
|
1336
|
+
file. A thumbnail failure occurs after the entity is created.
|
|
1313
1337
|
ProtocolError: If a server request returns an HTTP error.
|
|
1314
1338
|
ValueError: If authenticated HTTP violates the configured origin
|
|
1315
1339
|
policy.
|
|
1316
1340
|
"""
|
|
1317
|
-
|
|
1318
|
-
|
|
1341
|
+
field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=False)
|
|
1342
|
+
params = _crud.build_create_params(entity_type, field_data, return_fields)
|
|
1343
|
+
result = _crud.require_dictionary_result(self._call('create', params), 'create')
|
|
1344
|
+
if not thumbnails:
|
|
1345
|
+
return result
|
|
1346
|
+
entity_id = _crud.require_entity_id(result.get('id'), 'create did not return an entity id')
|
|
1347
|
+
return self._upload_thumbnails_after_write(entity_type, entity_id, thumbnails, result)
|
|
1319
1348
|
|
|
1320
1349
|
def delete(self, entity_type: str, entity_id: int) -> bool:
|
|
1321
1350
|
"""Retire an entity.
|
|
@@ -1422,6 +1451,15 @@ class Shotgun:
|
|
|
1422
1451
|
except (TransportError, _errors.ProtocolError, OSError):
|
|
1423
1452
|
raise _errors.ShotgunFileDownloadError('attachment download failed') from None
|
|
1424
1453
|
|
|
1454
|
+
def entity_types(self) -> Never:
|
|
1455
|
+
"""Reject the deprecated alias of `schema_entity_read()` without I/O.
|
|
1456
|
+
|
|
1457
|
+
Raises:
|
|
1458
|
+
ShotgunError: Always, naming `schema_entity_read()` as the
|
|
1459
|
+
replacement.
|
|
1460
|
+
"""
|
|
1461
|
+
raise _methods.entity_types_alias_error()
|
|
1462
|
+
|
|
1425
1463
|
@overload
|
|
1426
1464
|
def export_page(
|
|
1427
1465
|
self,
|
|
@@ -1900,6 +1938,18 @@ class Shotgun:
|
|
|
1900
1938
|
'revive',
|
|
1901
1939
|
)
|
|
1902
1940
|
|
|
1941
|
+
def schema(self, entity_type: str) -> Never:
|
|
1942
|
+
"""Reject the deprecated alias of `schema_field_read()` without I/O.
|
|
1943
|
+
|
|
1944
|
+
Args:
|
|
1945
|
+
entity_type: Entity type named in the replacement suggestion.
|
|
1946
|
+
|
|
1947
|
+
Raises:
|
|
1948
|
+
ShotgunError: Always, naming the equivalent `schema_field_read()`
|
|
1949
|
+
call.
|
|
1950
|
+
"""
|
|
1951
|
+
raise _methods.schema_alias_error(entity_type)
|
|
1952
|
+
|
|
1903
1953
|
def schema_entity_read(
|
|
1904
1954
|
self,
|
|
1905
1955
|
project_entity: dict[str, Any] | None = None,
|
|
@@ -1923,33 +1973,10 @@ class Shotgun:
|
|
|
1923
1973
|
self._call(
|
|
1924
1974
|
'schema_entity_read',
|
|
1925
1975
|
_methods.build_schema_entity_read_params(project_entity),
|
|
1926
|
-
_InvocationOptions(
|
|
1927
|
-
compatibility_warning_codes=_methods.consume_compatibility_warnings(
|
|
1928
|
-
_methods.SCHEMA_ENTITY_READ_WARNINGS,
|
|
1929
|
-
self,
|
|
1930
|
-
),
|
|
1931
|
-
),
|
|
1932
1976
|
),
|
|
1933
1977
|
'schema_entity_read',
|
|
1934
1978
|
)
|
|
1935
1979
|
|
|
1936
|
-
def entity_types(self) -> dict[str, object]:
|
|
1937
|
-
"""Return entity schema through the deprecated alias.
|
|
1938
|
-
|
|
1939
|
-
Returns:
|
|
1940
|
-
Entity-type schema data returned by the server.
|
|
1941
|
-
|
|
1942
|
-
Raises:
|
|
1943
|
-
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
1944
|
-
or if the server result is not a dictionary.
|
|
1945
|
-
ProtocolError: If a server request returns an HTTP error.
|
|
1946
|
-
ValueError: If authenticated HTTP violates the configured origin
|
|
1947
|
-
policy.
|
|
1948
|
-
"""
|
|
1949
|
-
_methods.warn_entity_types_alias()
|
|
1950
|
-
with _methods.entity_types_alias(self):
|
|
1951
|
-
return self.schema_entity_read()
|
|
1952
|
-
|
|
1953
1980
|
def schema_field_create(
|
|
1954
1981
|
self,
|
|
1955
1982
|
entity_type: str,
|
|
@@ -2042,36 +2069,10 @@ class Shotgun:
|
|
|
2042
2069
|
field_name,
|
|
2043
2070
|
project_entity,
|
|
2044
2071
|
),
|
|
2045
|
-
_InvocationOptions(
|
|
2046
|
-
compatibility_warning_codes=_methods.consume_compatibility_warnings(
|
|
2047
|
-
_methods.SCHEMA_FIELD_READ_WARNINGS,
|
|
2048
|
-
self,
|
|
2049
|
-
),
|
|
2050
|
-
),
|
|
2051
2072
|
),
|
|
2052
2073
|
'schema_field_read',
|
|
2053
2074
|
)
|
|
2054
2075
|
|
|
2055
|
-
def schema(self, entity_type: str) -> dict[str, object]:
|
|
2056
|
-
"""Return field schema through the deprecated alias.
|
|
2057
|
-
|
|
2058
|
-
Args:
|
|
2059
|
-
entity_type: Entity type whose fields to return.
|
|
2060
|
-
|
|
2061
|
-
Returns:
|
|
2062
|
-
Field schema data returned by the server.
|
|
2063
|
-
|
|
2064
|
-
Raises:
|
|
2065
|
-
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2066
|
-
or if the server result is invalid.
|
|
2067
|
-
ProtocolError: If a server request returns an HTTP error.
|
|
2068
|
-
ValueError: If authenticated HTTP violates the configured origin
|
|
2069
|
-
policy.
|
|
2070
|
-
"""
|
|
2071
|
-
_methods.warn_schema_alias()
|
|
2072
|
-
with _methods.schema_alias(self):
|
|
2073
|
-
return self.schema_field_read(entity_type)
|
|
2074
|
-
|
|
2075
2076
|
def schema_field_update(
|
|
2076
2077
|
self,
|
|
2077
2078
|
entity_type: str,
|
|
@@ -2384,6 +2385,12 @@ class Shotgun:
|
|
|
2384
2385
|
) -> dict[str, object]:
|
|
2385
2386
|
"""Update fields on an existing entity.
|
|
2386
2387
|
|
|
2388
|
+
Non-empty `image` and `filmstrip_image` values are file paths. They are
|
|
2389
|
+
uploaded as thumbnails after the other fields are updated, and the
|
|
2390
|
+
result includes their current field values. An `image` of `None` clears
|
|
2391
|
+
both thumbnails. When no other fields remain, no update request is sent
|
|
2392
|
+
and the result contains only `id` and `type`.
|
|
2393
|
+
|
|
2387
2394
|
Args:
|
|
2388
2395
|
entity_type: Entity type to update.
|
|
2389
2396
|
entity_id: Numeric entity identifier.
|
|
@@ -2395,19 +2402,27 @@ class Shotgun:
|
|
|
2395
2402
|
|
|
2396
2403
|
Raises:
|
|
2397
2404
|
ShotgunError: If an RPC fault or terminal transport failure occurs,
|
|
2398
|
-
|
|
2399
|
-
|
|
2405
|
+
if `data`, its field names, `multi_entity_update_modes`, or the
|
|
2406
|
+
server result is invalid, or if a thumbnail path is not a
|
|
2407
|
+
nonempty file. A thumbnail failure occurs after the update.
|
|
2400
2408
|
ProtocolError: If a server request returns an HTTP error.
|
|
2401
2409
|
ValueError: If authenticated HTTP violates the configured origin
|
|
2402
2410
|
policy.
|
|
2403
2411
|
"""
|
|
2412
|
+
field_data, thumbnails = _files.split_thumbnail_uploads(data, keep_image_none=True)
|
|
2404
2413
|
params = _crud.build_update_params(
|
|
2405
2414
|
entity_type,
|
|
2406
2415
|
entity_id,
|
|
2407
|
-
|
|
2416
|
+
field_data,
|
|
2408
2417
|
multi_entity_update_modes,
|
|
2409
2418
|
)
|
|
2410
|
-
|
|
2419
|
+
result: dict[str, object] = {'id': entity_id, 'type': entity_type}
|
|
2420
|
+
if params['fields']:
|
|
2421
|
+
result = _crud.require_dictionary_result(
|
|
2422
|
+
self._call('update', params),
|
|
2423
|
+
'update',
|
|
2424
|
+
)
|
|
2425
|
+
return self._upload_thumbnails_after_write(entity_type, entity_id, thumbnails, result)
|
|
2411
2426
|
|
|
2412
2427
|
def update_project_last_accessed(
|
|
2413
2428
|
self,
|
shotgun_api3/_config.py
CHANGED
|
@@ -21,11 +21,7 @@ from ._sentinel import Unset as _Unset
|
|
|
21
21
|
from ._transport import TimeoutConfig
|
|
22
22
|
from ._version import PACKAGE_VERSION
|
|
23
23
|
|
|
24
|
-
CompatibilityWarningCode: TypeAlias = Literal[
|
|
25
|
-
'deprecated-entity-types-alias',
|
|
26
|
-
'deprecated-schema-alias',
|
|
27
|
-
'legacy-infinite-timeout',
|
|
28
|
-
]
|
|
24
|
+
CompatibilityWarningCode: TypeAlias = Literal['legacy-infinite-timeout']
|
|
29
25
|
EventRoute: TypeAlias = Literal[
|
|
30
26
|
'allocation',
|
|
31
27
|
'download',
|
|
@@ -85,8 +81,6 @@ _ORIGIN_FIELDS = frozenset({'api_path', 'scheme', 'server'})
|
|
|
85
81
|
_ORIGIN_STRING_FIELDS = frozenset({'scheme', 'server'})
|
|
86
82
|
_UNSET: Final = _Unset()
|
|
87
83
|
_WARNING_MESSAGES: Mapping[CompatibilityWarningCode, str] = {
|
|
88
|
-
'deprecated-entity-types-alias': 'The entity_types alias is deprecated.',
|
|
89
|
-
'deprecated-schema-alias': 'The schema alias is deprecated.',
|
|
90
84
|
'legacy-infinite-timeout': 'Explicit None timeout enables legacy infinite waits.',
|
|
91
85
|
}
|
|
92
86
|
_PUBLIC_FIELDS = frozenset(
|
|
@@ -1021,19 +1015,17 @@ def auth_spec(config: Config) -> _protocol.AuthSpec:
|
|
|
1021
1015
|
|
|
1022
1016
|
|
|
1023
1017
|
def compatibility_warning_events(
|
|
1024
|
-
warning_codes: tuple[CompatibilityWarningCode, ...],
|
|
1025
1018
|
timeout: TimeoutConfig,
|
|
1026
1019
|
) -> tuple[CompatibilityWarningEvent, ...]:
|
|
1027
1020
|
"""Build fixed-message compatibility warnings for one operation.
|
|
1028
1021
|
|
|
1029
1022
|
Args:
|
|
1030
|
-
warning_codes: Sanitized compatibility notices from the client.
|
|
1031
1023
|
timeout: Captured operation timeout semantics.
|
|
1032
1024
|
|
|
1033
1025
|
Returns:
|
|
1034
1026
|
Immutable compatibility-warning events in publication order.
|
|
1035
1027
|
"""
|
|
1036
|
-
codes =
|
|
1028
|
+
codes: list[CompatibilityWarningCode] = []
|
|
1037
1029
|
if timeout.connect is None and timeout.read is None:
|
|
1038
1030
|
codes.append('legacy-infinite-timeout')
|
|
1039
1031
|
return tuple(
|
shotgun_api3/_driver.py
CHANGED
|
@@ -12,7 +12,6 @@ from typing import Literal, Protocol, TypeVar, cast, runtime_checkable
|
|
|
12
12
|
|
|
13
13
|
from . import _protocol, _transport
|
|
14
14
|
from ._config import (
|
|
15
|
-
CompatibilityWarningCode,
|
|
16
15
|
EventRoute,
|
|
17
16
|
ObserverCallback,
|
|
18
17
|
Outcome,
|
|
@@ -181,14 +180,11 @@ class ExecutionOptions:
|
|
|
181
180
|
"""Control executor behavior that is not part of retry classification.
|
|
182
181
|
|
|
183
182
|
Attributes:
|
|
184
|
-
compatibility_warning_codes: Compatibility warnings emitted before
|
|
185
|
-
attempts.
|
|
186
183
|
logical_method_name: Optional observer-facing operation name.
|
|
187
184
|
redirect_factory: Optional request factory for redirect responses.
|
|
188
185
|
streaming: Whether the response body is consumed as a stream.
|
|
189
186
|
"""
|
|
190
187
|
|
|
191
|
-
compatibility_warning_codes: tuple[CompatibilityWarningCode, ...] = ()
|
|
192
188
|
logical_method_name: str | None = None
|
|
193
189
|
redirect_factory: 'Callable[[LiveResponse], TransportRequest] | None' = None
|
|
194
190
|
streaming: bool = False
|
|
@@ -443,13 +439,9 @@ class Driver:
|
|
|
443
439
|
raise AssertionError('response consumption ended without an outcome')
|
|
444
440
|
return attempt_result
|
|
445
441
|
|
|
446
|
-
def _emit_warnings(
|
|
447
|
-
self,
|
|
448
|
-
context: RequestContext,
|
|
449
|
-
warning_codes: tuple[CompatibilityWarningCode, ...],
|
|
450
|
-
) -> None:
|
|
442
|
+
def _emit_warnings(self, context: RequestContext) -> None:
|
|
451
443
|
"""Publish fixed-message compatibility warnings."""
|
|
452
|
-
for event in compatibility_warning_events(
|
|
444
|
+
for event in compatibility_warning_events(context.timeout):
|
|
453
445
|
notify_observer(context.on_compatibility_warning, event)
|
|
454
446
|
|
|
455
447
|
def _execute_rpc(
|
|
@@ -623,7 +615,6 @@ class Driver:
|
|
|
623
615
|
params: object,
|
|
624
616
|
context: RequestContext,
|
|
625
617
|
include_auth: bool = True,
|
|
626
|
-
compatibility_warning_codes: tuple[CompatibilityWarningCode, ...] = (),
|
|
627
618
|
*,
|
|
628
619
|
_logical_method_name: str | None = None,
|
|
629
620
|
) -> object:
|
|
@@ -635,8 +626,6 @@ class Driver:
|
|
|
635
626
|
context: Immutable operation configuration.
|
|
636
627
|
include_auth: Whether to include the captured authentication
|
|
637
628
|
payload.
|
|
638
|
-
compatibility_warning_codes: Sanitized compatibility notices
|
|
639
|
-
attached by the client surface.
|
|
640
629
|
_logical_method_name: Optional logical operation name when it
|
|
641
630
|
differs from the wire method.
|
|
642
631
|
|
|
@@ -648,10 +637,7 @@ class Driver:
|
|
|
648
637
|
response contract (`reason` and `content`).
|
|
649
638
|
"""
|
|
650
639
|
normalized_params = copy.deepcopy(params)
|
|
651
|
-
options = ExecutionOptions(
|
|
652
|
-
compatibility_warning_codes=compatibility_warning_codes,
|
|
653
|
-
logical_method_name=_logical_method_name,
|
|
654
|
-
)
|
|
640
|
+
options = ExecutionOptions(logical_method_name=_logical_method_name)
|
|
655
641
|
return self._execute_rpc(
|
|
656
642
|
method_name,
|
|
657
643
|
normalized_params,
|
|
@@ -745,7 +731,7 @@ class Driver:
|
|
|
745
731
|
Exception: If status validation or response consumption fails.
|
|
746
732
|
"""
|
|
747
733
|
execution_options = options or ExecutionOptions()
|
|
748
|
-
self._emit_warnings(context
|
|
734
|
+
self._emit_warnings(context)
|
|
749
735
|
route = event_route(operation)
|
|
750
736
|
for attempt in range(1, context.max_rpc_attempts + 1):
|
|
751
737
|
state = self._attempt_state(operation, context, route, attempt)
|
shotgun_api3/_files.py
CHANGED
|
@@ -34,6 +34,7 @@ _FILE_SERVE_ATTACHMENT = '/file_serve/attachment/'
|
|
|
34
34
|
_MULTIPART_CHUNK_SIZE = 20_000_000
|
|
35
35
|
_Origin: TypeAlias = tuple[str, str, int]
|
|
36
36
|
_PART_LINK_FIELD_COUNT = 2
|
|
37
|
+
_THUMBNAIL_UPLOAD_FIELDS = ('image', 'filmstrip_image')
|
|
37
38
|
_UPLOAD_FILE = '/upload/upload_file'
|
|
38
39
|
|
|
39
40
|
|
|
@@ -992,8 +993,8 @@ def prepare_upload(options: UploadOptions) -> PreparedUpload:
|
|
|
992
993
|
Captured filename, size, and options for one upload.
|
|
993
994
|
|
|
994
995
|
Raises:
|
|
995
|
-
ShotgunError: If the path does not name a
|
|
996
|
-
is not a datetime.
|
|
996
|
+
ShotgunError: If the path does not name a file, the file is empty, or
|
|
997
|
+
`created_at` is not a datetime.
|
|
997
998
|
"""
|
|
998
999
|
created_at = cast('object', options.arguments.created_at)
|
|
999
1000
|
if created_at is not None and not isinstance(created_at, datetime.datetime):
|
|
@@ -1006,11 +1007,14 @@ def prepare_upload(options: UploadOptions) -> PreparedUpload:
|
|
|
1006
1007
|
)
|
|
1007
1008
|
path = Path(options.arguments.path)
|
|
1008
1009
|
try:
|
|
1009
|
-
|
|
1010
|
+
is_file = path.is_file()
|
|
1011
|
+
file_size = path.stat().st_size if is_file else 0
|
|
1010
1012
|
except OSError:
|
|
1011
|
-
file_size = 0
|
|
1013
|
+
is_file, file_size = False, 0
|
|
1014
|
+
if not is_file:
|
|
1015
|
+
raise ShotgunError(f"Path must be a valid file, got '{options.arguments.path}'")
|
|
1012
1016
|
if file_size <= 0:
|
|
1013
|
-
raise ShotgunError(
|
|
1017
|
+
raise ShotgunError(f"Path cannot be an empty file: '{options.arguments.path}'")
|
|
1014
1018
|
return PreparedUpload(filename=path.name, file_size=file_size, options=options)
|
|
1015
1019
|
|
|
1016
1020
|
|
|
@@ -1049,6 +1053,41 @@ def resolve_attachment_url(attachment: object, base_url: str) -> str | None:
|
|
|
1049
1053
|
raise ShotgunError('attachment must be an id or dictionary')
|
|
1050
1054
|
|
|
1051
1055
|
|
|
1056
|
+
def split_thumbnail_uploads(
|
|
1057
|
+
data: object,
|
|
1058
|
+
*,
|
|
1059
|
+
keep_image_none: bool,
|
|
1060
|
+
) -> tuple[object, dict[str, object]]:
|
|
1061
|
+
"""Separate `image` and `filmstrip_image` paths from entity field data.
|
|
1062
|
+
|
|
1063
|
+
Autodesk's client uploads these paths after the entity write instead of
|
|
1064
|
+
sending them as field values.
|
|
1065
|
+
|
|
1066
|
+
Args:
|
|
1067
|
+
data: Public field mapping. A non-dictionary is returned unchanged so
|
|
1068
|
+
that the caller's own validation reports it.
|
|
1069
|
+
keep_image_none: Whether an `image` value of `None` stays in the field
|
|
1070
|
+
data, which lets an update clear both thumbnails.
|
|
1071
|
+
|
|
1072
|
+
Returns:
|
|
1073
|
+
A copy of the field data without the separated fields, and the
|
|
1074
|
+
non-empty paths to upload, with `image` before `filmstrip_image`.
|
|
1075
|
+
"""
|
|
1076
|
+
if not isinstance(data, dict):
|
|
1077
|
+
return data, {}
|
|
1078
|
+
mapping = cast('dict[object, object]', data)
|
|
1079
|
+
fields: dict[object, object] = {}
|
|
1080
|
+
paths: dict[str, object] = {}
|
|
1081
|
+
for name, value in mapping.items():
|
|
1082
|
+
if name not in _THUMBNAIL_UPLOAD_FIELDS or (
|
|
1083
|
+
keep_image_none and name == 'image' and value is None
|
|
1084
|
+
):
|
|
1085
|
+
fields[name] = value
|
|
1086
|
+
elif value:
|
|
1087
|
+
paths[cast('str', name)] = value
|
|
1088
|
+
return fields, {name: paths[name] for name in _THUMBNAIL_UPLOAD_FIELDS if name in paths}
|
|
1089
|
+
|
|
1090
|
+
|
|
1052
1091
|
def thumbnail_source_ready(
|
|
1053
1092
|
result: object,
|
|
1054
1093
|
field_name: Literal['filmstrip_image', 'image'],
|
shotgun_api3/_methods.py
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
"""Shared pure plans for synchronous and asynchronous client methods."""
|
|
2
2
|
|
|
3
|
-
import contextvars
|
|
4
3
|
import dataclasses
|
|
5
4
|
import datetime
|
|
6
5
|
import inspect
|
|
7
|
-
import
|
|
8
|
-
from collections.abc import Callable, Generator
|
|
9
|
-
from contextlib import contextmanager
|
|
6
|
+
from collections.abc import Callable
|
|
10
7
|
from typing import Any, NotRequired, TypedDict, cast
|
|
11
8
|
|
|
12
9
|
from . import _crud, _protocol
|
|
13
|
-
from ._config import CompatibilityWarningCode
|
|
14
10
|
from ._errors import ShotgunError
|
|
15
11
|
from ._signatures import bind_arguments, expose_signature
|
|
16
12
|
|
|
@@ -111,10 +107,6 @@ _METHOD_EXPORT_PAGE_SIGNATURE = inspect.Signature(
|
|
|
111
107
|
),
|
|
112
108
|
return_annotation=str,
|
|
113
109
|
)
|
|
114
|
-
_PENDING_COMPATIBILITY_WARNINGS = tuple[
|
|
115
|
-
object,
|
|
116
|
-
tuple[CompatibilityWarningCode, ...],
|
|
117
|
-
]
|
|
118
110
|
_SCHEDULE_DATE_LENGTH = 10
|
|
119
111
|
_SUMMARIZE_DOCSTRING = """Summarize field data returned by a query.
|
|
120
112
|
|
|
@@ -241,12 +233,6 @@ _WORK_SCHEDULE_UPDATE_SIGNATURE = inspect.Signature(
|
|
|
241
233
|
_WORK_SCHEDULE_UPDATE_PARAMETERS,
|
|
242
234
|
return_annotation=dict[str, object],
|
|
243
235
|
)
|
|
244
|
-
SCHEMA_ENTITY_READ_WARNINGS: contextvars.ContextVar[_PENDING_COMPATIBILITY_WARNINGS | None] = (
|
|
245
|
-
contextvars.ContextVar('SCHEMA_ENTITY_READ_WARNINGS', default=None)
|
|
246
|
-
)
|
|
247
|
-
SCHEMA_FIELD_READ_WARNINGS: contextvars.ContextVar[_PENDING_COMPATIBILITY_WARNINGS | None] = (
|
|
248
|
-
contextvars.ContextVar('SCHEMA_FIELD_READ_WARNINGS', default=None)
|
|
249
|
-
)
|
|
250
236
|
|
|
251
237
|
|
|
252
238
|
@dataclasses.dataclass(frozen=True)
|
|
@@ -1007,43 +993,13 @@ def build_work_schedule_update_params(
|
|
|
1007
993
|
return params
|
|
1008
994
|
|
|
1009
995
|
|
|
1010
|
-
def
|
|
1011
|
-
|
|
1012
|
-
owner: object,
|
|
1013
|
-
) -> tuple[CompatibilityWarningCode, ...]:
|
|
1014
|
-
"""Consume operation-local compatibility warnings exactly once.
|
|
1015
|
-
|
|
1016
|
-
Args:
|
|
1017
|
-
warning_context: Context-local warning state.
|
|
1018
|
-
owner: Client instance expected to own the warning.
|
|
996
|
+
def entity_types_alias_error() -> ShotgunError:
|
|
997
|
+
"""Build the error raised by the deprecated `entity_types` alias.
|
|
1019
998
|
|
|
1020
999
|
Returns:
|
|
1021
|
-
|
|
1000
|
+
A `ShotgunError` naming `schema_entity_read()` as the replacement.
|
|
1022
1001
|
"""
|
|
1023
|
-
|
|
1024
|
-
if pending is None or pending[0] is not owner:
|
|
1025
|
-
return ()
|
|
1026
|
-
warning_context.set(None)
|
|
1027
|
-
return pending[1]
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
@contextmanager
|
|
1031
|
-
def entity_types_alias(owner: object) -> Generator[None, None, None]:
|
|
1032
|
-
"""Scope the deprecated `entity_types` compatibility notice.
|
|
1033
|
-
|
|
1034
|
-
Args:
|
|
1035
|
-
owner: Client instance invoking the alias.
|
|
1036
|
-
|
|
1037
|
-
Yields:
|
|
1038
|
-
Control while the public target method consumes the warning.
|
|
1039
|
-
"""
|
|
1040
|
-
token = SCHEMA_ENTITY_READ_WARNINGS.set(
|
|
1041
|
-
(owner, ('deprecated-entity-types-alias',)),
|
|
1042
|
-
)
|
|
1043
|
-
try:
|
|
1044
|
-
yield
|
|
1045
|
-
finally:
|
|
1046
|
-
SCHEMA_ENTITY_READ_WARNINGS.reset(token)
|
|
1002
|
+
return ShotgunError('Deprecated: use schema_entity_read() instead')
|
|
1047
1003
|
|
|
1048
1004
|
|
|
1049
1005
|
def expose_activity_stream_read_signature(
|
|
@@ -1168,38 +1124,13 @@ def require_string_result(value: object, method_name: str) -> str:
|
|
|
1168
1124
|
return value
|
|
1169
1125
|
|
|
1170
1126
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
"""Scope the deprecated `schema` compatibility notice.
|
|
1127
|
+
def schema_alias_error(entity_type: object) -> ShotgunError:
|
|
1128
|
+
"""Build the error raised by the deprecated `schema` alias.
|
|
1174
1129
|
|
|
1175
1130
|
Args:
|
|
1176
|
-
|
|
1131
|
+
entity_type: Caller-supplied entity type, rendered with `str()`.
|
|
1177
1132
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1133
|
+
Returns:
|
|
1134
|
+
A `ShotgunError` naming the equivalent `schema_field_read()` call.
|
|
1180
1135
|
"""
|
|
1181
|
-
|
|
1182
|
-
(owner, ('deprecated-schema-alias',)),
|
|
1183
|
-
)
|
|
1184
|
-
try:
|
|
1185
|
-
yield
|
|
1186
|
-
finally:
|
|
1187
|
-
SCHEMA_FIELD_READ_WARNINGS.reset(token)
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
def warn_entity_types_alias() -> None:
|
|
1191
|
-
"""Warn at the caller of the deprecated `entity_types` method."""
|
|
1192
|
-
warnings.warn(
|
|
1193
|
-
'entity_types() is deprecated; use schema_entity_read()',
|
|
1194
|
-
DeprecationWarning,
|
|
1195
|
-
stacklevel=3,
|
|
1196
|
-
)
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
def warn_schema_alias() -> None:
|
|
1200
|
-
"""Warn at the caller of the deprecated `schema` method."""
|
|
1201
|
-
warnings.warn(
|
|
1202
|
-
'schema() is deprecated; use schema_field_read()',
|
|
1203
|
-
DeprecationWarning,
|
|
1204
|
-
stacklevel=3,
|
|
1205
|
-
)
|
|
1136
|
+
return ShotgunError(f"Deprecated: use schema_field_read('{entity_type}') instead")
|
shotgun_api3/_version.py
CHANGED
|
File without changes
|
|
File without changes
|