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,217 @@
1
+ """Emulate observed follow operations and Note auto-follow side effects."""
2
+
3
+ from collections.abc import Mapping
4
+ from copy import deepcopy
5
+ from typing import cast
6
+
7
+ from ._errors import MockgunError
8
+ from ._links import display_name
9
+ from ._sessions import RequestContext
10
+ from ._store import Row, Store
11
+
12
+
13
+ def _reference(store: Store, operation: str, value: object, role: str) -> tuple[str, int, Row]:
14
+ """Require an active declared record for a follow argument."""
15
+ if not isinstance(value, dict):
16
+ raise MockgunError(f'mockgun cannot {operation} {role}: the entity link is unsettled')
17
+ link = cast('dict[str, object]', value)
18
+ kind = link.get('type')
19
+ identity = link.get('id')
20
+ if not isinstance(kind, str) or not isinstance(identity, int) or isinstance(identity, bool):
21
+ raise MockgunError(
22
+ f'mockgun cannot {operation} {role} {kind} {identity}: invalid identity'
23
+ )
24
+ if role == 'user' and kind != 'HumanUser':
25
+ raise MockgunError(
26
+ f'mockgun cannot {operation} user {kind} {identity}: expected HumanUser'
27
+ )
28
+ if not store.schema.declares(kind):
29
+ raise MockgunError(f'mockgun cannot {operation} {kind} {identity}: undeclared type')
30
+ row = store.row(kind, identity)
31
+ if row is None or row['__retired']:
32
+ raise MockgunError(f'mockgun cannot {operation} {kind} {identity}: missing or retired')
33
+ return kind, identity, row
34
+
35
+
36
+ def _pair(store: Store, operation: str, params: Mapping[str, object]) -> tuple[int, str, int]:
37
+ """Validate the user and target of a follow write."""
38
+ _, human_id, _ = _reference(store, operation, params.get('user'), 'user')
39
+ kind, identity, _ = _reference(store, operation, params.get('entity'), 'entity')
40
+ return human_id, kind, identity
41
+
42
+
43
+ def _project(store: Store, params: Mapping[str, object]) -> dict[str, object] | None:
44
+ """Validate an optional Project restriction for a following query."""
45
+ project = params.get('project')
46
+ if project is None:
47
+ return None
48
+ kind, identity, _ = _reference(store, 'following', project, 'project')
49
+ if kind != 'Project':
50
+ raise MockgunError(f'mockgun cannot following project {kind} {identity}: expected Project')
51
+ return {'type': 'Project', 'id': identity}
52
+
53
+
54
+ def follow(store: Store, params: Mapping[str, object]) -> dict[str, object]:
55
+ """Record an idempotent HumanUser follow and echo the supplied links.
56
+
57
+ Args:
58
+ store: Site records and journaled side tables.
59
+ params: RPC user and entity links.
60
+
61
+ Returns:
62
+ The observed follow result with the caller's link shapes.
63
+
64
+ Raises:
65
+ MockgunError: If the user or target cannot be resolved to an active,
66
+ declared record in the supported follow form.
67
+ """
68
+ followed = True
69
+ store.side_set('follows', _pair(store, 'follow', params), followed)
70
+ return {
71
+ 'followed': True,
72
+ 'user': deepcopy(params['user']),
73
+ 'entity': deepcopy(params['entity']),
74
+ }
75
+
76
+
77
+ def follow_created_note(store: Store, row: Row, *, context: RequestContext) -> None:
78
+ """Follow a created Note or a Reply's Note for its HumanUser actor.
79
+
80
+ Args:
81
+ store: Site records and journaled side tables.
82
+ row: Newly created Note or Reply.
83
+ context: Actor for the enclosing create transaction.
84
+ """
85
+ actor = context.actor
86
+ if actor is None or actor.entity_type != 'HumanUser':
87
+ return
88
+ if row['type'] == 'Note':
89
+ note_id = cast('int', row['id'])
90
+ elif row['type'] == 'Reply':
91
+ entity = row.get('entity')
92
+ if not isinstance(entity, dict) or entity.get('type') != 'Note':
93
+ return
94
+ note_id = cast('int', entity['id'])
95
+ else:
96
+ return
97
+ followed = True
98
+ store.side_set('follows', (actor.entity_id, 'Note', note_id), followed)
99
+
100
+
101
+ def followers(store: Store, params: Mapping[str, object]) -> list[dict[str, object]]:
102
+ """List the observed zero or one HumanUser follower of an active record.
103
+
104
+ Args:
105
+ store: Site records and journaled side tables.
106
+ params: RPC entity link.
107
+
108
+ Returns:
109
+ An empty list or a one-element list containing a follower dictionary
110
+ with stable identity and observed key order.
111
+
112
+ Raises:
113
+ MockgunError: If the target or follower is unavailable, more than one
114
+ follower exists, or required follower metadata is undeclared or
115
+ unsettled.
116
+ """
117
+ kind, identity, _ = _reference(store, 'followers', params.get('entity'), 'entity')
118
+ humans = store.followers(kind, identity)
119
+ if len(humans) > 1:
120
+ raise MockgunError(
121
+ f'mockgun cannot followers {kind} {identity}: order of two followers is unsettled'
122
+ )
123
+ if not humans:
124
+ return []
125
+ if not store.schema.declares('HumanUser', 'sg_status_list'):
126
+ raise MockgunError(
127
+ f'mockgun cannot followers {kind} {identity}: HumanUser.sg_status_list is undeclared'
128
+ )
129
+ _, human_id, human = _reference(
130
+ store, 'followers', {'type': 'HumanUser', 'id': humans[0]}, 'user'
131
+ )
132
+ store.check_known(human, 'sg_status_list')
133
+ status = human.get('sg_status_list')
134
+ name = display_name(store, 'HumanUser', human_id)
135
+ if name is None or not isinstance(status, str):
136
+ raise MockgunError(
137
+ f'mockgun cannot followers {kind} {identity}: '
138
+ f'HumanUser {human_id} name or status is unsettled'
139
+ )
140
+ return [
141
+ {
142
+ 'name': name,
143
+ 'type': 'HumanUser',
144
+ 'id': human_id,
145
+ 'valid': 'valid',
146
+ 'status': status,
147
+ 'uuid': store.link_uuid('HumanUser', human_id),
148
+ }
149
+ ]
150
+
151
+
152
+ def following(store: Store, params: Mapping[str, object]) -> list[dict[str, object]]:
153
+ """List active follows after observed type and project restrictions.
154
+
155
+ Args:
156
+ store: Site records and journaled side tables.
157
+ params: RPC user link and optional project and entity type.
158
+
159
+ Returns:
160
+ Identity links in ascending ID for a single resulting type.
161
+
162
+ Raises:
163
+ MockgunError: If the user, project, or a reached followed record is
164
+ unavailable, a required project field is undeclared or unknown, or
165
+ the result contains multiple entity types with unsettled order.
166
+ """
167
+ _, human_id, _ = _reference(store, 'following', params.get('user'), 'user')
168
+ entity_type = params.get('entity_type')
169
+ if entity_type is not None and not isinstance(entity_type, str):
170
+ raise MockgunError(f'mockgun cannot following entity_type {entity_type}: invalid type')
171
+ project_link = _project(store, params)
172
+ result: list[dict[str, object]] = []
173
+ for key, _ in store.side_items('follows'):
174
+ if key[0] != human_id:
175
+ continue
176
+ kind = cast('str', key[1])
177
+ identity = cast('int', key[2])
178
+ if entity_type is not None and kind != entity_type:
179
+ continue
180
+ _, _, row = _reference(store, 'following', {'type': kind, 'id': identity}, 'entity')
181
+ if project_link is not None:
182
+ if not store.schema.declares(kind, 'project'):
183
+ raise MockgunError(
184
+ f'mockgun cannot following {kind} {identity}: project field is undeclared'
185
+ )
186
+ store.check_known(row, 'project')
187
+ if row.get('project') != project_link:
188
+ continue
189
+ result.append({'type': kind, 'id': identity})
190
+ if len({item['type'] for item in result}) > 1:
191
+ raise MockgunError(
192
+ f'mockgun cannot following HumanUser {human_id}: order across types is unsettled'
193
+ )
194
+ result.sort(key=lambda item: cast('int', item['id']))
195
+ return result
196
+
197
+
198
+ def unfollow(store: Store, params: Mapping[str, object]) -> dict[str, object]:
199
+ """Remove an idempotent follow and echo the supplied links.
200
+
201
+ Args:
202
+ store: Site records and journaled side tables.
203
+ params: RPC user and entity links.
204
+
205
+ Returns:
206
+ The observed unfollow result with the caller's link shapes.
207
+
208
+ Raises:
209
+ MockgunError: If the user or target cannot be resolved to an active,
210
+ declared record in the supported follow form.
211
+ """
212
+ store.side_set('follows', _pair(store, 'unfollow', params), None)
213
+ return {
214
+ 'unfollowed': True,
215
+ 'user': deepcopy(params['user']),
216
+ 'entity': deepcopy(params['entity']),
217
+ }