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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sg-python-api
3
- Version: 0.1.0
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.0/docs/migration-guide.md) walks
49
- through the switch, and the [compatibility reference](https://github.com/dactylo/sg-python-api/blob/v0.1.0/COMPATIBILITY.md) lists the differences.
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.0/docs/migration-guide.md#install-the-replacement) to replace it.
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
- When a release is available on your package index, install into the Python environment that runs
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.0/COMPATIBILITY.md#async-extension-surfaces) covers shutdown and
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.0/docs/migration-guide.md) |
154
- | Check what behaves differently from Autodesk's client | [Compatibility](https://github.com/dactylo/sg-python-api/blob/v0.1.0/COMPATIBILITY.md) |
155
- | Tune pooling and page prefetch, or observe requests | [Configuration extensions](https://github.com/dactylo/sg-python-api/blob/v0.1.0/COMPATIBILITY.md#configuration-and-observer-extensions) |
156
- | Confirm which client loads and run a smoke test | [Testing an installation](https://github.com/dactylo/sg-python-api/blob/v0.1.0/docs/testing.md) |
157
- | Compare measured latency and transfer memory | [Benchmarks](https://github.com/dactylo/sg-python-api/blob/v0.1.0/docs/benchmarks.md) |
158
- | See changes by version | [Changelog](https://github.com/dactylo/sg-python-api/blob/v0.1.0/CHANGELOG.md) |
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.0/docs/development.md) for the required tools, CI, and dependency locks.
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.0/LICENSE). It is an independent project and is
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=or11Le6Y2WB3TVTVj_7QiLfOLpDbwjs1p3VVAQhFHLQ,111070
3
- shotgun_api3/_async_driver.py,sha256=ldrETCNgihOcuoGYVqt8M4u2R1euNkvp3TRXLYQbzJ0,32838
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=n4lbXG9UxQhrv1IhwsJd8dD41JIq_Xh-_zqDueSguW8,100184
8
- shotgun_api3/_config.py,sha256=hCKJtaO06wZFJEz-yIw2SagDeWqZMHpuqSmS33qG2Q4,55264
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=sPkiseKisJTMSNAxkCvdGXc_H1kKS6sg-rJQLKXSVgU,30139
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=a1PfHKV4OMi6j7BQG2rkMPCcaNTB5uAWNiqCh64AEXg,35223
14
- shotgun_api3/_methods.py,sha256=8dD5BUN_5hnGAYomUEAX1dHZnI4Cliid6PrrP8AhhPI,36132
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=yK498PuHvOUCFnIC1QKSqvFXzUBBuOMHBsVo5gSpFCE,102
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.0.dist-info/METADATA,sha256=7Wzieh6ix8BwZJ_oD-B4_G-bO6m5pPDr1wzC5EFYhUQ,7789
29
- sg_python_api-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
30
- sg_python_api-0.1.0.dist-info/licenses/LICENSE,sha256=V3moMsQxKDchTd3mtXl5J-ODaNBDvFrapOf1DK7Nx_U,1082
31
- sg_python_api-0.1.0.dist-info/RECORD,,
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,,
@@ -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
- or if `data`, its field names, `return_fields`, or the server
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
- params = _crud.build_create_params(entity_type, data, return_fields)
1540
- return _crud.require_dictionary_result(await self._call('create', params), 'create')
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
- or if `data`, its field names, `multi_entity_update_modes`, or
2574
- the server result is invalid.
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
- data,
2595
+ field_data,
2583
2596
  multi_entity_update_modes,
2584
2597
  )
2585
- return _crud.require_dictionary_result(await self._call('update', params), 'update')
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,
@@ -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
- or if `data`, its field names, `return_fields`, or the server
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
- params = _crud.build_create_params(entity_type, data, return_fields)
1318
- return _crud.require_dictionary_result(self._call('create', params), 'create')
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
- or if `data`, its field names, `multi_entity_update_modes`, or
2399
- the server result is invalid.
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
- data,
2416
+ field_data,
2408
2417
  multi_entity_update_modes,
2409
2418
  )
2410
- return _crud.require_dictionary_result(self._call('update', params), 'update')
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 = list(warning_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(warning_codes, context.timeout):
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, execution_options.compatibility_warning_codes)
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 nonempty file or `created_at`
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
- file_size = path.stat().st_size if path.is_file() else 0
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('File must exist and be non-empty')
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 warnings
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 consume_compatibility_warnings(
1011
- warning_context: contextvars.ContextVar[_PENDING_COMPATIBILITY_WARNINGS | None],
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
- Compatibility warning codes for the target call.
1000
+ A `ShotgunError` naming `schema_entity_read()` as the replacement.
1022
1001
  """
1023
- pending = warning_context.get()
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
- @contextmanager
1172
- def schema_alias(owner: object) -> Generator[None, None, None]:
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
- owner: Client instance invoking the alias.
1131
+ entity_type: Caller-supplied entity type, rendered with `str()`.
1177
1132
 
1178
- Yields:
1179
- Control while the public target method consumes the warning.
1133
+ Returns:
1134
+ A `ShotgunError` naming the equivalent `schema_field_read()` call.
1180
1135
  """
1181
- token = SCHEMA_FIELD_READ_WARNINGS.set(
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
@@ -1,3 +1,3 @@
1
1
  """Package version shared by public exports and compatibility metadata."""
2
2
 
3
- PACKAGE_VERSION = '0.1.0'
3
+ PACKAGE_VERSION = '0.1.1'