sg-python-api 0.1.2__py3-none-any.whl → 0.2.0__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.
Files changed (56) hide show
  1. {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/METADATA +21 -24
  2. sg_python_api-0.2.0.dist-info/RECORD +58 -0
  3. shotgun_api3/__init__.py +4 -0
  4. shotgun_api3/_async_client.py +347 -281
  5. shotgun_api3/_async_driver.py +25 -55
  6. shotgun_api3/_async_httpx2.py +92 -59
  7. shotgun_api3/_async_transport.py +38 -3
  8. shotgun_api3/_capabilities.py +4 -3
  9. shotgun_api3/_client.py +296 -422
  10. shotgun_api3/_config.py +245 -120
  11. shotgun_api3/_connections.py +144 -0
  12. shotgun_api3/_crud.py +25 -250
  13. shotgun_api3/_driver.py +29 -54
  14. shotgun_api3/_execution.py +1 -3
  15. shotgun_api3/_files.py +193 -21
  16. shotgun_api3/_methods.py +59 -411
  17. shotgun_api3/_protocol.py +27 -53
  18. shotgun_api3/_transport.py +82 -407
  19. shotgun_api3/_version.py +7 -2
  20. shotgun_api3/lib/mockgun/__init__.py +20 -7
  21. shotgun_api3/lib/mockgun/_activity.py +510 -0
  22. shotgun_api3/lib/mockgun/_async_client.py +196 -0
  23. shotgun_api3/lib/mockgun/_dates.py +164 -0
  24. shotgun_api3/lib/mockgun/_errors.py +18 -0
  25. shotgun_api3/lib/mockgun/_events.py +1031 -0
  26. shotgun_api3/lib/mockgun/_follows.py +217 -0
  27. shotgun_api3/lib/mockgun/_handlers.py +558 -0
  28. shotgun_api3/lib/mockgun/_links.py +440 -0
  29. shotgun_api3/lib/mockgun/_new_client.py +92 -0
  30. shotgun_api3/lib/mockgun/_presets.py +492 -0
  31. shotgun_api3/lib/mockgun/_query.py +2163 -0
  32. shotgun_api3/lib/mockgun/_retirement.py +368 -0
  33. shotgun_api3/lib/mockgun/_schema.py +172 -0
  34. shotgun_api3/lib/mockgun/_schema_writes.py +425 -0
  35. shotgun_api3/lib/mockgun/_search.py +378 -0
  36. shotgun_api3/lib/mockgun/_server.py +169 -0
  37. shotgun_api3/lib/mockgun/_sessions.py +206 -0
  38. shotgun_api3/lib/mockgun/_site.py +150 -0
  39. shotgun_api3/lib/mockgun/_site_data.py +584 -0
  40. shotgun_api3/lib/mockgun/_storage.py +208 -0
  41. shotgun_api3/lib/mockgun/_store.py +1446 -0
  42. shotgun_api3/lib/mockgun/_summaries.py +964 -0
  43. shotgun_api3/lib/mockgun/_sync_client.py +190 -0
  44. shotgun_api3/lib/mockgun/_threads.py +226 -0
  45. shotgun_api3/lib/mockgun/_thumbnails.py +146 -0
  46. shotgun_api3/lib/mockgun/_transport.py +145 -0
  47. shotgun_api3/lib/mockgun/_uploads.py +417 -0
  48. shotgun_api3/lib/mockgun/_wire.py +120 -0
  49. shotgun_api3/lib/mockgun/mockgun.py +14 -0
  50. shotgun_api3/lib/sgtimezone.py +24 -1
  51. sg_python_api-0.1.2.dist-info/RECORD +0 -31
  52. shotgun_api3/_signatures.py +0 -53
  53. shotgun_api3/lib/mockgun/_async.py +0 -315
  54. shotgun_api3/lib/mockgun/_mockgun.py +0 -1666
  55. {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/WHEEL +0 -0
  56. {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,144 @@
1
+ """Build and apply TCP keepalive socket options."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import socket
6
+ from typing import TYPE_CHECKING, Any, cast
7
+
8
+ import urllib3
9
+ from urllib3.connection import HTTPConnection, HTTPSConnection
10
+
11
+ if TYPE_CHECKING:
12
+ from collections.abc import Callable, Iterable
13
+
14
+ from urllib3.response import HTTPResponse
15
+
16
+ SocketOption = tuple[int, int, int]
17
+
18
+
19
+ def _keepalive_timings(target: int) -> tuple[int, int, int]:
20
+ """Map a nominal target to whole-second probe timings."""
21
+ idle = target // 2
22
+ interval = -(-(target - idle) // 3)
23
+ return idle, interval, 3
24
+
25
+
26
+ def keepalive_socket_options(target: int | None) -> tuple[SocketOption, ...]:
27
+ """Return the available options for a nominal keepalive target.
28
+
29
+ Args:
30
+ target: Validated target in seconds, or `None` to disable keepalive.
31
+
32
+ Returns:
33
+ Enablement first, followed by available platform timing options.
34
+ """
35
+ if target is None:
36
+ return ()
37
+
38
+ idle, interval, count = _keepalive_timings(target)
39
+ options: list[SocketOption] = [(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)]
40
+ idle_option = getattr(socket, 'TCP_KEEPIDLE', None)
41
+ if idle_option is None:
42
+ idle_option = getattr(socket, 'TCP_KEEPALIVE', None)
43
+ if idle_option is not None:
44
+ options.append((socket.IPPROTO_TCP, idle_option, idle))
45
+ if hasattr(socket, 'TCP_KEEPINTVL'):
46
+ options.append((socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval))
47
+ if hasattr(socket, 'TCP_KEEPCNT'):
48
+ options.append((socket.IPPROTO_TCP, socket.TCP_KEEPCNT, count))
49
+ return tuple(options)
50
+
51
+
52
+ def tune_socket(sock: object, options: Iterable[SocketOption]) -> None:
53
+ """Apply each supported socket option without failing on OS refusals.
54
+
55
+ Args:
56
+ sock: Socket-like object, which may lack `setsockopt`.
57
+ options: Options to apply in order.
58
+ """
59
+ setsockopt = getattr(sock, 'setsockopt', None)
60
+ if setsockopt is None:
61
+ return
62
+ for level, option, value in options:
63
+ try:
64
+ setsockopt(level, option, value)
65
+ except OSError:
66
+ continue
67
+
68
+
69
+ def connection_pool_classes(
70
+ idle_limit: float | None,
71
+ keepalive: tuple[SocketOption, ...],
72
+ clock: Callable[[], float],
73
+ ) -> dict[str, type[urllib3.HTTPConnectionPool]]:
74
+ """Create isolated HTTP and HTTPS pool classes with one connection policy.
75
+
76
+ Args:
77
+ idle_limit: Validated idle limit in seconds, or `None` to disable
78
+ expiry.
79
+ keepalive: Socket options to apply after connection setup.
80
+ clock: Monotonic clock returning seconds.
81
+
82
+ Returns:
83
+ New pool classes indexed by scheme, with policy-specific connection
84
+ classes.
85
+ """
86
+
87
+ class _HTTPConnection(HTTPConnection):
88
+ """Track response arrival and tune each socket after connection setup."""
89
+
90
+ _clock = staticmethod(clock)
91
+ _idle_limit = idle_limit
92
+ _keepalive = keepalive
93
+ _response_at: float | None = None
94
+
95
+ def connect(self) -> None:
96
+ """Open the connection and apply best-effort keepalive tuning.
97
+
98
+ Raises:
99
+ OSError: If connection setup fails.
100
+ urllib3.exceptions.HTTPError: If connection or proxy setup
101
+ fails.
102
+ """
103
+ self._response_at = None
104
+ super().connect()
105
+ tune_socket(self.sock, self._keepalive)
106
+
107
+ def getresponse(self) -> HTTPResponse:
108
+ """Record time after headers and any preloaded body are received.
109
+
110
+ Raises:
111
+ OSError: If receiving the response fails.
112
+ urllib3.exceptions.HTTPError: If the response cannot be
113
+ received.
114
+ """
115
+ response = super().getresponse()
116
+ self._response_at = self._clock()
117
+ return response
118
+
119
+ @property
120
+ def is_connected(self) -> bool:
121
+ """Return whether the connection is unexpired and still connected."""
122
+ if (
123
+ self._idle_limit is not None
124
+ and self._response_at is not None
125
+ and self._clock() - self._response_at > self._idle_limit
126
+ ):
127
+ return False
128
+ return super().is_connected
129
+
130
+ class _HTTPSConnection(_HTTPConnection, HTTPSConnection):
131
+ """Apply the connection policy around HTTPS setup, including proxy tunnels."""
132
+
133
+ class _HTTPConnectionPool(urllib3.HTTPConnectionPool):
134
+ """Pool HTTP connections under this policy."""
135
+
136
+ # urllib3's connection protocols reject its concrete classes under strict typing.
137
+ ConnectionCls = cast('Any', _HTTPConnection)
138
+
139
+ class _HTTPSConnectionPool(urllib3.HTTPSConnectionPool):
140
+ """Pool HTTPS connections under this policy."""
141
+
142
+ ConnectionCls = cast('Any', _HTTPSConnection)
143
+
144
+ return {'http': _HTTPConnectionPool, 'https': _HTTPSConnectionPool}
shotgun_api3/_crud.py CHANGED
@@ -2,187 +2,26 @@
2
2
 
3
3
  import copy
4
4
  import dataclasses
5
- import inspect
6
- from collections.abc import Callable
7
5
  from typing import Any, Literal, cast
8
6
 
9
7
  from . import _protocol
10
8
  from ._errors import ShotgunError
11
- from ._signatures import bind_arguments, expose_signature
12
-
13
- _FIND_DOCSTRING = """Find entities matching the supplied filters.
14
-
15
- Args:
16
- entity_type: Entity type to find.
17
- filters: Filters in list or dict syntax.
18
- fields: Optional fields to return; defaults to `id`.
19
- order: Optional field and direction sort specifications.
20
- filter_operator: `'all'` or `'any'` for list-syntax filters.
21
- limit: Maximum entities to return, or `0` for no limit.
22
- retired_only: Return retired entities instead of active entities.
23
- page: Specific page to return, or `0` to read all pages.
24
- include_archived_projects: Include entities from archived projects.
25
- additional_filter_presets: Optional server-side filter presets.
26
-
27
- Returns:
28
- Matching entities in server order.
29
-
30
- Raises:
31
- TypeError: If arguments do not match the documented signature.
32
- ShotgunError: If an RPC fault or terminal transport failure
33
- occurs, or if filters, paging, ordering, or a server result is invalid.
34
- ProtocolError: If a server request returns an HTTP error.
35
- ValueError: If authenticated HTTP violates the configured origin policy.
36
- """
37
- _FIND_ONE_DOCSTRING = """Return the first entity matching the supplied filters.
38
-
39
- Args:
40
- entity_type: Entity type to find.
41
- filters: Filters in list or dict syntax.
42
- fields: Optional fields to return; defaults to `id`.
43
- order: Optional field and direction sort specifications.
44
- filter_operator: `'all'` or `'any'` for list-syntax filters.
45
- retired_only: Return a retired entity instead of an active entity.
46
- include_archived_projects: Include entities from archived projects.
47
- additional_filter_presets: Optional server-side filter presets.
48
-
49
- Returns:
50
- The first matching entity, or `None` when no entity matches.
51
-
52
- Raises:
53
- TypeError: If arguments do not match the documented signature.
54
- ShotgunError: If an RPC fault or terminal transport failure
55
- occurs, or if filters, paging, ordering, or a server result is invalid.
56
- ProtocolError: If a server request returns an HTTP error.
57
- ValueError: If authenticated HTTP violates the configured origin policy.
58
- """
59
- _FIND_ONE_PARAMETERS = (
60
- inspect.Parameter('entity_type', inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
61
- inspect.Parameter(
62
- 'filters',
63
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
64
- annotation=list[Any] | tuple[Any, ...] | dict[str, Any],
65
- ),
66
- inspect.Parameter(
67
- 'fields',
68
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
69
- default=None,
70
- annotation=list[str] | None,
71
- ),
72
- inspect.Parameter(
73
- 'order',
74
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
75
- default=None,
76
- annotation=list[dict[str, str]] | None,
77
- ),
78
- inspect.Parameter(
79
- 'filter_operator',
80
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
81
- default=None,
82
- annotation=str | None,
83
- ),
84
- inspect.Parameter(
85
- 'retired_only',
86
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
87
- default=False,
88
- annotation=bool,
89
- ),
90
- inspect.Parameter(
91
- 'include_archived_projects',
92
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
93
- default=True,
94
- annotation=bool,
95
- ),
96
- inspect.Parameter(
97
- 'additional_filter_presets',
98
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
99
- default=None,
100
- annotation=object,
101
- ),
102
- )
103
- _FIND_ONE_SIGNATURE = inspect.Signature(
104
- _FIND_ONE_PARAMETERS,
105
- return_annotation=dict[str, object] | None,
106
- )
107
- _FIND_PARAMETERS = (
108
- inspect.Parameter('entity_type', inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=str),
109
- inspect.Parameter(
110
- 'filters',
111
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
112
- annotation=list[Any] | tuple[Any, ...] | dict[str, Any],
113
- ),
114
- inspect.Parameter(
115
- 'fields',
116
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
117
- default=None,
118
- annotation=list[str] | None,
119
- ),
120
- inspect.Parameter(
121
- 'order',
122
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
123
- default=None,
124
- annotation=list[dict[str, str]] | None,
125
- ),
126
- inspect.Parameter(
127
- 'filter_operator',
128
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
129
- default=None,
130
- annotation=str | None,
131
- ),
132
- inspect.Parameter(
133
- 'limit',
134
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
135
- default=0,
136
- annotation=int,
137
- ),
138
- inspect.Parameter(
139
- 'retired_only',
140
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
141
- default=False,
142
- annotation=bool,
143
- ),
144
- inspect.Parameter(
145
- 'page',
146
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
147
- default=0,
148
- annotation=int,
149
- ),
150
- inspect.Parameter(
151
- 'include_archived_projects',
152
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
153
- default=True,
154
- annotation=bool,
155
- ),
156
- inspect.Parameter(
157
- 'additional_filter_presets',
158
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
159
- default=None,
160
- annotation=object,
161
- ),
162
- )
163
- _FIND_SIGNATURE = inspect.Signature(
164
- _FIND_PARAMETERS,
165
- return_annotation=list[dict[str, object]],
166
- )
167
- _METHOD_FIND_ONE_SIGNATURE = inspect.Signature(
168
- (
169
- inspect.Parameter('self', inspect.Parameter.POSITIONAL_OR_KEYWORD),
170
- *_FIND_ONE_PARAMETERS,
171
- ),
172
- return_annotation=dict[str, object] | None,
173
- )
174
- _METHOD_FIND_SIGNATURE = inspect.Signature(
175
- (
176
- inspect.Parameter('self', inspect.Parameter.POSITIONAL_OR_KEYWORD),
177
- *_FIND_PARAMETERS,
178
- ),
179
- return_annotation=list[dict[str, object]],
180
- )
181
9
 
182
10
 
183
11
  @dataclasses.dataclass(frozen=True)
184
- class _FindOneOptions:
185
- """Collect exact public `find_one` arguments for validation."""
12
+ class FindOneOptions:
13
+ """Collect exact public `find_one` arguments for validation.
14
+
15
+ Attributes:
16
+ entity_type: Entity type to find.
17
+ filters: Filters in list or dict syntax.
18
+ fields: Optional fields to return; defaults to `id`.
19
+ order: Optional field and direction sort specifications.
20
+ filter_operator: `'all'` or `'any'` for list-syntax filters.
21
+ retired_only: Return a retired entity instead of an active entity.
22
+ include_archived_projects: Include entities from archived projects.
23
+ additional_filter_presets: Optional server-side filter presets.
24
+ """
186
25
 
187
26
  entity_type: str
188
27
  filters: list[Any] | tuple[Any, ...] | dict[str, Any]
@@ -196,11 +35,11 @@ class _FindOneOptions:
196
35
 
197
36
  @dataclasses.dataclass(frozen=True)
198
37
  class FindOptions:
199
- """Collect the exact public `find` arguments for runtime validation.
38
+ """Hold the public `find` arguments in a frozen configuration record.
200
39
 
201
40
  Attributes:
202
41
  entity_type: Type of entity to query.
203
- filters: Query filters or filter preset structure.
42
+ filters: Query filters in list, tuple, or dict syntax.
204
43
  fields: Optional fields to return.
205
44
  order: Optional field ordering directives.
206
45
  filter_operator: Optional logical operator joining filters.
@@ -346,54 +185,6 @@ def _validate_nonnegative_integer(value: object, name: str) -> None:
346
185
  raise ShotgunError(f'{name} must be a nonnegative integer')
347
186
 
348
187
 
349
- def bind_find(
350
- args: tuple[object, ...],
351
- kwargs: dict[str, object],
352
- client_name: str,
353
- ) -> FindOptions:
354
- """Bind runtime arguments with client-specific `find` errors.
355
-
356
- Args:
357
- args: Positional public arguments.
358
- kwargs: Keyword public arguments.
359
- client_name: Public client class name for error attribution.
360
-
361
- Returns:
362
- Bound find options retaining the supplied argument values without
363
- copying mutable containers.
364
-
365
- Raises:
366
- TypeError: If the arguments do not match the public signature.
367
- """
368
- bound = bind_arguments(_FIND_SIGNATURE, args, kwargs, f'{client_name}.find()')
369
- options = cast('Callable[..., FindOptions]', FindOptions)
370
- return options(**bound.arguments)
371
-
372
-
373
- def bind_find_one(
374
- args: tuple[object, ...],
375
- kwargs: dict[str, object],
376
- client_name: str,
377
- ) -> _FindOneOptions:
378
- """Bind runtime arguments with client-specific `find_one` errors.
379
-
380
- Args:
381
- args: Positional public arguments.
382
- kwargs: Keyword public arguments.
383
- client_name: Public client class name for error attribution.
384
-
385
- Returns:
386
- Bound find-one options retaining the supplied argument values without
387
- copying mutable containers.
388
-
389
- Raises:
390
- TypeError: If the arguments do not match the public signature.
391
- """
392
- bound = bind_arguments(_FIND_ONE_SIGNATURE, args, kwargs, f'{client_name}.find_one()')
393
- options = cast('Callable[..., _FindOneOptions]', _FindOneOptions)
394
- return options(**bound.arguments)
395
-
396
-
397
188
  def build_entity_action_params(entity_type: str, entity_id: int) -> dict[str, object]:
398
189
  """Build parameters for delete and revive operations.
399
190
 
@@ -447,36 +238,18 @@ def build_page_params(plan: _FindPlan, page_number: int, convert_utc: bool) -> o
447
238
 
448
239
  Returns:
449
240
  Detached transformed page parameters.
241
+
242
+ Raises:
243
+ ValueError: If a naive datetime cannot be converted to UTC.
244
+ OverflowError: If converting an aware datetime to UTC exceeds the
245
+ supported datetime range.
246
+ OSError: If the platform cannot resolve a local datetime.
450
247
  """
451
248
  built = dict(plan.params)
452
249
  built['paging'] = {**plan.paging, 'current_page': page_number}
453
250
  return _protocol.transform_outbound(built, convert_utc)
454
251
 
455
252
 
456
- def expose_find_one_signature(function: Callable[..., object]) -> Callable[..., object]:
457
- """Expose the documented `find_one` signature and documentation.
458
-
459
- Args:
460
- function: Variadic method implementation.
461
-
462
- Returns:
463
- The same function with its public runtime contract attached.
464
- """
465
- return expose_signature(function, _METHOD_FIND_ONE_SIGNATURE, _FIND_ONE_DOCSTRING)
466
-
467
-
468
- def expose_find_signature(function: Callable[..., object]) -> Callable[..., object]:
469
- """Expose the documented `find` signature and documentation.
470
-
471
- Args:
472
- function: Variadic method implementation.
473
-
474
- Returns:
475
- The same function with its public runtime contract attached.
476
- """
477
- return expose_signature(function, _METHOD_FIND_SIGNATURE, _FIND_DOCSTRING)
478
-
479
-
480
253
  def finalize_find(
481
254
  records: list[dict[str, object]],
482
255
  options: FindOptions,
@@ -499,7 +272,7 @@ def finalize_find(
499
272
  return _protocol.inject_local_paths(records, local_path_field)
500
273
 
501
274
 
502
- def find_options_from_one(options: _FindOneOptions) -> FindOptions:
275
+ def find_options_from_one(options: FindOneOptions) -> FindOptions:
503
276
  """Convert bound find-one arguments into a one-result find plan.
504
277
 
505
278
  Args:
@@ -544,11 +317,13 @@ def prepare_find(options: FindOptions) -> _PreparedFind:
544
317
  Detached inputs ready for server-aware paging.
545
318
 
546
319
  Raises:
547
- ShotgunError: If paging or ordering is invalid.
320
+ ShotgunError: If fields, paging, or ordering is invalid.
548
321
  Fault: If filter syntax is invalid.
549
322
  """
550
323
  _validate_nonnegative_integer(options.limit, 'limit')
551
324
  _validate_nonnegative_integer(options.page, 'page')
325
+ if isinstance(options.fields, (str, bytes)):
326
+ raise ShotgunError('find fields must be an iterable of strings or None')
552
327
  translated_filters = copy.deepcopy(
553
328
  _protocol.translate_filters(
554
329
  _protocol.require_filter_container(options.filters, options.filter_operator),