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.
- {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/METADATA +21 -24
- sg_python_api-0.2.0.dist-info/RECORD +58 -0
- shotgun_api3/__init__.py +4 -0
- shotgun_api3/_async_client.py +347 -281
- shotgun_api3/_async_driver.py +25 -55
- shotgun_api3/_async_httpx2.py +92 -59
- shotgun_api3/_async_transport.py +38 -3
- shotgun_api3/_capabilities.py +4 -3
- shotgun_api3/_client.py +296 -422
- shotgun_api3/_config.py +245 -120
- shotgun_api3/_connections.py +144 -0
- shotgun_api3/_crud.py +25 -250
- shotgun_api3/_driver.py +29 -54
- shotgun_api3/_execution.py +1 -3
- shotgun_api3/_files.py +193 -21
- shotgun_api3/_methods.py +59 -411
- shotgun_api3/_protocol.py +27 -53
- shotgun_api3/_transport.py +82 -407
- shotgun_api3/_version.py +7 -2
- shotgun_api3/lib/mockgun/__init__.py +20 -7
- shotgun_api3/lib/mockgun/_activity.py +510 -0
- shotgun_api3/lib/mockgun/_async_client.py +196 -0
- shotgun_api3/lib/mockgun/_dates.py +164 -0
- shotgun_api3/lib/mockgun/_errors.py +18 -0
- shotgun_api3/lib/mockgun/_events.py +1031 -0
- shotgun_api3/lib/mockgun/_follows.py +217 -0
- shotgun_api3/lib/mockgun/_handlers.py +558 -0
- shotgun_api3/lib/mockgun/_links.py +440 -0
- shotgun_api3/lib/mockgun/_new_client.py +92 -0
- shotgun_api3/lib/mockgun/_presets.py +492 -0
- shotgun_api3/lib/mockgun/_query.py +2163 -0
- shotgun_api3/lib/mockgun/_retirement.py +368 -0
- shotgun_api3/lib/mockgun/_schema.py +172 -0
- shotgun_api3/lib/mockgun/_schema_writes.py +425 -0
- shotgun_api3/lib/mockgun/_search.py +378 -0
- shotgun_api3/lib/mockgun/_server.py +169 -0
- shotgun_api3/lib/mockgun/_sessions.py +206 -0
- shotgun_api3/lib/mockgun/_site.py +150 -0
- shotgun_api3/lib/mockgun/_site_data.py +584 -0
- shotgun_api3/lib/mockgun/_storage.py +208 -0
- shotgun_api3/lib/mockgun/_store.py +1446 -0
- shotgun_api3/lib/mockgun/_summaries.py +964 -0
- shotgun_api3/lib/mockgun/_sync_client.py +190 -0
- shotgun_api3/lib/mockgun/_threads.py +226 -0
- shotgun_api3/lib/mockgun/_thumbnails.py +146 -0
- shotgun_api3/lib/mockgun/_transport.py +145 -0
- shotgun_api3/lib/mockgun/_uploads.py +417 -0
- shotgun_api3/lib/mockgun/_wire.py +120 -0
- shotgun_api3/lib/mockgun/mockgun.py +14 -0
- shotgun_api3/lib/sgtimezone.py +24 -1
- sg_python_api-0.1.2.dist-info/RECORD +0 -31
- shotgun_api3/_signatures.py +0 -53
- shotgun_api3/lib/mockgun/_async.py +0 -315
- shotgun_api3/lib/mockgun/_mockgun.py +0 -1666
- {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/WHEEL +0 -0
- {sg_python_api-0.1.2.dist-info → sg_python_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
"""Apply observed field-schema mutations to one in-memory fake site."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from ._errors import MockgunError, ServerFaultError
|
|
8
|
+
from ._schema import SiteSchema
|
|
9
|
+
|
|
10
|
+
_CREATE_ACCEPTED: dict[str, frozenset[str]] = {
|
|
11
|
+
'checkbox': frozenset({'default_value'}),
|
|
12
|
+
'color': frozenset(),
|
|
13
|
+
'entity': frozenset({'valid_types'}),
|
|
14
|
+
'float': frozenset({'description'}),
|
|
15
|
+
'list': frozenset({'valid_values'}),
|
|
16
|
+
'multi_entity': frozenset({'valid_types'}),
|
|
17
|
+
'number': frozenset({'summary_default'}),
|
|
18
|
+
'status_list': frozenset({'valid_values'}),
|
|
19
|
+
'text': frozenset(),
|
|
20
|
+
}
|
|
21
|
+
_KNOWN_TYPES = frozenset(
|
|
22
|
+
{
|
|
23
|
+
'addressing',
|
|
24
|
+
'calculated',
|
|
25
|
+
'checkbox',
|
|
26
|
+
'color',
|
|
27
|
+
'currency',
|
|
28
|
+
'date',
|
|
29
|
+
'date_time',
|
|
30
|
+
'duration',
|
|
31
|
+
'entity',
|
|
32
|
+
'entity_type',
|
|
33
|
+
'float',
|
|
34
|
+
'footage',
|
|
35
|
+
'image',
|
|
36
|
+
'jsonb',
|
|
37
|
+
'list',
|
|
38
|
+
'multi_entity',
|
|
39
|
+
'number',
|
|
40
|
+
'password',
|
|
41
|
+
'percent',
|
|
42
|
+
'pivot_column',
|
|
43
|
+
'serializable',
|
|
44
|
+
'status_list',
|
|
45
|
+
'summary',
|
|
46
|
+
'tag_list',
|
|
47
|
+
'text',
|
|
48
|
+
'timecode',
|
|
49
|
+
'url',
|
|
50
|
+
'uuid',
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
_MAX_DISPLAY_NAME_LENGTH = 80
|
|
54
|
+
_NAME_CHARACTERS = frozenset('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 .-!?')
|
|
55
|
+
_NAME_REPLACEMENTS = str.maketrans({' ': '_', '-': '_', '.': '_', '!': '_', '?': '_'})
|
|
56
|
+
_SUPPORTED_TYPES = frozenset(
|
|
57
|
+
{
|
|
58
|
+
'checkbox',
|
|
59
|
+
'entity',
|
|
60
|
+
'float',
|
|
61
|
+
'list',
|
|
62
|
+
'multi_entity',
|
|
63
|
+
'number',
|
|
64
|
+
'text',
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _create_properties(data_type: str, supplied: dict[str, object]) -> dict[str, object]:
|
|
70
|
+
"""Build the observed property wrappers after validation succeeds."""
|
|
71
|
+
result: dict[str, object] = {
|
|
72
|
+
'default_value': {
|
|
73
|
+
'value': False if data_type == 'checkbox' else None,
|
|
74
|
+
'editable': data_type in {'list', 'checkbox'},
|
|
75
|
+
},
|
|
76
|
+
'summary_default': {
|
|
77
|
+
'value': 'sum' if data_type == 'number' else 'none',
|
|
78
|
+
'editable': True,
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
if data_type == 'list' and 'valid_values' in supplied:
|
|
82
|
+
result['valid_values'] = {'value': deepcopy(supplied['valid_values']), 'editable': True}
|
|
83
|
+
if data_type in {'entity', 'multi_entity'} and 'valid_types' in supplied:
|
|
84
|
+
result['valid_types'] = {
|
|
85
|
+
'value': deepcopy(supplied['valid_types']),
|
|
86
|
+
'editable': data_type == 'entity',
|
|
87
|
+
}
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _entry(
|
|
92
|
+
display_name: str,
|
|
93
|
+
entity_type: str,
|
|
94
|
+
data_type: str,
|
|
95
|
+
properties: dict[str, object],
|
|
96
|
+
supplied: dict[str, object],
|
|
97
|
+
) -> dict[str, object]:
|
|
98
|
+
"""Build a detached schema entry in the observed metadata order."""
|
|
99
|
+
return {
|
|
100
|
+
'name': {'value': display_name, 'editable': True},
|
|
101
|
+
'description': {'value': supplied.get('description', ''), 'editable': True},
|
|
102
|
+
'custom_metadata': {'value': '', 'editable': True},
|
|
103
|
+
'entity_type': {'value': entity_type, 'editable': False},
|
|
104
|
+
'data_type': {'value': data_type, 'editable': False},
|
|
105
|
+
'editable': {'value': True, 'editable': False},
|
|
106
|
+
'mandatory': {'value': False, 'editable': False},
|
|
107
|
+
'unique': {'value': False, 'editable': False},
|
|
108
|
+
'properties': properties,
|
|
109
|
+
'visible': {'value': True, 'editable': True},
|
|
110
|
+
'ui_value_displayable': {'value': True, 'editable': False},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _field_for_write(schema: SiteSchema, entity_type: str, field_name: str) -> dict[str, object]:
|
|
115
|
+
"""Require an existing custom field before updating or deleting it."""
|
|
116
|
+
if not schema.declares(entity_type, field_name):
|
|
117
|
+
raise ServerFaultError(
|
|
118
|
+
f'{field_name} is not a valid field for entity {entity_type}', error_code=103
|
|
119
|
+
)
|
|
120
|
+
if not (field_name.startswith('sg_') or (entity_type, field_name) in schema.site_created):
|
|
121
|
+
raise MockgunError(f'{entity_type}.{field_name} built-in schema write is unsettled')
|
|
122
|
+
return cast('dict[str, object]', schema.fields[entity_type][field_name])
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _field_name(
|
|
126
|
+
schema: SiteSchema, entity_type: str, display_name: object, problems: list[Exception]
|
|
127
|
+
) -> str:
|
|
128
|
+
"""Derive one observed name, considering declared and retired collisions."""
|
|
129
|
+
if not isinstance(display_name, str) or not display_name:
|
|
130
|
+
problems.append(ServerFaultError('display name is empty', error_code=103))
|
|
131
|
+
return ''
|
|
132
|
+
if len(display_name) > _MAX_DISPLAY_NAME_LENGTH or any(
|
|
133
|
+
char not in _NAME_CHARACTERS for char in display_name
|
|
134
|
+
):
|
|
135
|
+
problems.append(MockgunError('schema field display name contains unsettled characters'))
|
|
136
|
+
return ''
|
|
137
|
+
base = 'sg_' + display_name.lower().translate(_NAME_REPLACEMENTS)
|
|
138
|
+
if (entity_type, base) in schema.retired_fields:
|
|
139
|
+
problems.append(
|
|
140
|
+
ServerFaultError(
|
|
141
|
+
'API schema_field_create() failed, there is a retired field with the same '
|
|
142
|
+
f'field_name: {base}. Delete the retired field forever from the Trash Page in '
|
|
143
|
+
'Shotgun and try again.',
|
|
144
|
+
error_code=105,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
return ''
|
|
148
|
+
for candidate in (base, f'{base}_1', f'{base}_2'):
|
|
149
|
+
if (entity_type, candidate) in schema.retired_fields:
|
|
150
|
+
problems.append(
|
|
151
|
+
MockgunError(f'{entity_type}.{candidate} retired collision is unsettled')
|
|
152
|
+
)
|
|
153
|
+
return ''
|
|
154
|
+
if not schema.declares(entity_type, candidate):
|
|
155
|
+
return candidate
|
|
156
|
+
problems.append(MockgunError(f'{entity_type}.{base} third collision is unsettled'))
|
|
157
|
+
return ''
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _properties(properties: Sequence[Mapping[str, object]]) -> tuple[dict[str, object], bool]:
|
|
161
|
+
"""Collect wire property pairs while identifying duplicate names."""
|
|
162
|
+
result: dict[str, object] = {}
|
|
163
|
+
repeated = False
|
|
164
|
+
for property_entry in properties:
|
|
165
|
+
name = property_entry.get('property_name')
|
|
166
|
+
if not isinstance(name, str):
|
|
167
|
+
raise MockgunError('schema field property name is unsettled')
|
|
168
|
+
if name in result:
|
|
169
|
+
repeated = True
|
|
170
|
+
result[name] = property_entry.get('value')
|
|
171
|
+
return result, repeated
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _raise_problems(problems: list[Exception]) -> None:
|
|
175
|
+
"""Choose the settled single error, or reject a competing-error request."""
|
|
176
|
+
if len(problems) > 1:
|
|
177
|
+
raise MockgunError('schema field request has several errors')
|
|
178
|
+
if problems:
|
|
179
|
+
raise problems[0]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _updated_entry(entry: dict[str, object], supplied: dict[str, object]) -> dict[str, object]:
|
|
183
|
+
"""Apply site-wide property values to a detached schema entry."""
|
|
184
|
+
updated = deepcopy(entry)
|
|
185
|
+
for name, value in supplied.items():
|
|
186
|
+
if name == 'visible':
|
|
187
|
+
continue
|
|
188
|
+
if name in {'name', 'description'}:
|
|
189
|
+
wrapper = updated.get(name)
|
|
190
|
+
if isinstance(wrapper, dict):
|
|
191
|
+
cast('dict[str, object]', wrapper)['value'] = value
|
|
192
|
+
else:
|
|
193
|
+
updated[name] = {'value': value, 'editable': True}
|
|
194
|
+
else:
|
|
195
|
+
cast('dict[str, dict[str, object]]', updated['properties'])[name]['value'] = deepcopy(
|
|
196
|
+
value
|
|
197
|
+
)
|
|
198
|
+
return updated
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _valid_names(values: object) -> bool:
|
|
202
|
+
"""Recognize a nonempty ordered list of type or option names."""
|
|
203
|
+
if not isinstance(values, list) or not values:
|
|
204
|
+
return False
|
|
205
|
+
return all(isinstance(value, str) and bool(value) for value in cast('list[object]', values))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _validate_required_create(
|
|
209
|
+
data_type: str, supplied: dict[str, object], problems: list[Exception]
|
|
210
|
+
) -> None:
|
|
211
|
+
"""Check the one required value of each observed field type."""
|
|
212
|
+
if data_type == 'checkbox':
|
|
213
|
+
if 'default_value' not in supplied:
|
|
214
|
+
problems.append(ServerFaultError('checkbox requires default_value', error_code=105))
|
|
215
|
+
elif supplied['default_value'] is not False:
|
|
216
|
+
problems.append(MockgunError('checkbox default_value is unsettled'))
|
|
217
|
+
elif data_type == 'number':
|
|
218
|
+
if supplied.get('summary_default') != 'sum' and (
|
|
219
|
+
'summary_default' in supplied or 'default_value' not in supplied
|
|
220
|
+
):
|
|
221
|
+
problems.append(MockgunError('number summary_default is unsettled'))
|
|
222
|
+
elif data_type in {'list', 'entity', 'multi_entity'}:
|
|
223
|
+
key = 'valid_values' if data_type == 'list' else 'valid_types'
|
|
224
|
+
values: object = supplied.get(key)
|
|
225
|
+
if (
|
|
226
|
+
data_type == 'multi_entity'
|
|
227
|
+
and isinstance(values, list)
|
|
228
|
+
and len(cast('list[object]', values)) > 1
|
|
229
|
+
):
|
|
230
|
+
problems.append(
|
|
231
|
+
ServerFaultError('multi_entity valid_types is invalid', error_code=103)
|
|
232
|
+
)
|
|
233
|
+
elif not _valid_names(cast('object', values)) or (
|
|
234
|
+
data_type == 'entity' and len(cast('list[str]', values)) > 1
|
|
235
|
+
):
|
|
236
|
+
problems.append(MockgunError(f'{data_type} {key} is unsettled'))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _validate_create_type(
|
|
240
|
+
data_type: str, supplied: dict[str, object], problems: list[Exception]
|
|
241
|
+
) -> bool:
|
|
242
|
+
"""Record create faults and indicate whether an entry can be assembled."""
|
|
243
|
+
accepted = _CREATE_ACCEPTED.get(data_type)
|
|
244
|
+
if accepted is not None:
|
|
245
|
+
for name in supplied.keys() - accepted:
|
|
246
|
+
if name == 'default_value' and data_type in {'number', 'float'}:
|
|
247
|
+
problems.append(
|
|
248
|
+
ServerFaultError(f'{data_type} default_value is invalid', error_code=103)
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
problems.append(MockgunError(f'{data_type}.{name} schema property is unsettled'))
|
|
252
|
+
if data_type == 'color':
|
|
253
|
+
problems.append(ServerFaultError('color field creation failed', error_code=103))
|
|
254
|
+
return False
|
|
255
|
+
if data_type == 'status_list':
|
|
256
|
+
if 'valid_values' not in supplied:
|
|
257
|
+
problems.append(ServerFaultError('status_list requires valid_values', error_code=103))
|
|
258
|
+
else:
|
|
259
|
+
problems.append(MockgunError('status_list display_values are unsettled'))
|
|
260
|
+
return False
|
|
261
|
+
if data_type not in _SUPPORTED_TYPES:
|
|
262
|
+
problems.append(MockgunError(f'{data_type} schema field creation is unsettled'))
|
|
263
|
+
return False
|
|
264
|
+
_validate_required_create(data_type, supplied, problems)
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _visible_problem(field_name: str, value: object, project_id: int | None) -> Exception | None:
|
|
269
|
+
"""Reject visibility updates without a Project or boolean setting."""
|
|
270
|
+
if project_id is None:
|
|
271
|
+
return ServerFaultError(f'{field_name}.visible requires a Project', error_code=104)
|
|
272
|
+
return (
|
|
273
|
+
None
|
|
274
|
+
if isinstance(value, bool)
|
|
275
|
+
else MockgunError(f'{field_name}.visible value is unsettled')
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _update_problem(
|
|
280
|
+
field_name: str,
|
|
281
|
+
data_type: str,
|
|
282
|
+
entry: dict[str, object],
|
|
283
|
+
property_pair: tuple[str, object],
|
|
284
|
+
project_id: int | None,
|
|
285
|
+
) -> Exception | None:
|
|
286
|
+
"""Return a rejection for one field-property update, if any."""
|
|
287
|
+
name, value = property_pair
|
|
288
|
+
if name == 'visible':
|
|
289
|
+
return _visible_problem(field_name, value, project_id)
|
|
290
|
+
if name in {'data_type', 'mandatory'}:
|
|
291
|
+
return ServerFaultError(f'{field_name}.{name} cannot be updated', error_code=103)
|
|
292
|
+
if name in {'name', 'description'}:
|
|
293
|
+
return (
|
|
294
|
+
None
|
|
295
|
+
if isinstance(value, str)
|
|
296
|
+
else MockgunError(f'{field_name}.{name} update value is unsettled')
|
|
297
|
+
)
|
|
298
|
+
if name == 'valid_values' and data_type == 'list':
|
|
299
|
+
return (
|
|
300
|
+
None
|
|
301
|
+
if _valid_names(value)
|
|
302
|
+
else MockgunError(f'{field_name}.valid_values update is unsettled')
|
|
303
|
+
)
|
|
304
|
+
if name == 'default_value' and data_type == 'list':
|
|
305
|
+
values = cast('dict[str, dict[str, object]]', entry['properties'])['valid_values']['value']
|
|
306
|
+
return (
|
|
307
|
+
None
|
|
308
|
+
if value is None or value in cast('list[str]', values)
|
|
309
|
+
else MockgunError(f'{field_name}.default_value update is unsettled')
|
|
310
|
+
)
|
|
311
|
+
return MockgunError(f'{field_name}.{name} update is unsettled')
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def create_field(
|
|
315
|
+
schema: SiteSchema,
|
|
316
|
+
entity_type: str,
|
|
317
|
+
data_type: str,
|
|
318
|
+
properties: Sequence[Mapping[str, object]],
|
|
319
|
+
) -> tuple[str, dict[str, object]]:
|
|
320
|
+
"""Create an observed schema field after checking all competing errors.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
schema: Mutable site schema receiving the field.
|
|
324
|
+
entity_type: Entity type receiving the field.
|
|
325
|
+
data_type: Requested schema data type.
|
|
326
|
+
properties: Wire entries containing `property_name` and `value`,
|
|
327
|
+
including the field's display `name`.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
The generated internal field name and its live stored metadata entry.
|
|
331
|
+
|
|
332
|
+
Raises:
|
|
333
|
+
ServerFaultError: If the request has a single modeled schema fault.
|
|
334
|
+
MockgunError: If requested behavior, a property, a name collision, or
|
|
335
|
+
competing error precedence is unsupported.
|
|
336
|
+
"""
|
|
337
|
+
supplied, repeated = _properties(properties)
|
|
338
|
+
problems: list[Exception] = []
|
|
339
|
+
if repeated:
|
|
340
|
+
problems.append(MockgunError('schema_field_create repeated property is unsettled'))
|
|
341
|
+
if not schema.declares(entity_type):
|
|
342
|
+
problems.append(ServerFaultError(f'{entity_type} is not a valid entity', error_code=103))
|
|
343
|
+
known = _KNOWN_TYPES | frozenset(
|
|
344
|
+
cast('str', cast('dict[str, object]', entry['data_type'])['value'])
|
|
345
|
+
for fields in schema.fields.values()
|
|
346
|
+
for entry in cast('dict[str, dict[str, object]]', fields).values()
|
|
347
|
+
)
|
|
348
|
+
if data_type not in known:
|
|
349
|
+
problems.append(ServerFaultError(f'{data_type} is not a valid data type', error_code=103))
|
|
350
|
+
name = _field_name(schema, entity_type, supplied.get('name'), problems)
|
|
351
|
+
supplied_properties = {name: value for name, value in supplied.items() if name != 'name'}
|
|
352
|
+
supported = (
|
|
353
|
+
_validate_create_type(data_type, supplied_properties, problems)
|
|
354
|
+
if data_type in known
|
|
355
|
+
else False
|
|
356
|
+
)
|
|
357
|
+
_raise_problems(problems)
|
|
358
|
+
display_name = cast('str', supplied['name'])
|
|
359
|
+
entry_properties = _create_properties(data_type, supplied_properties) if supported else {}
|
|
360
|
+
entry = _entry(display_name, entity_type, data_type, entry_properties, supplied)
|
|
361
|
+
schema.fields[entity_type][name] = entry
|
|
362
|
+
schema.site_created.add((entity_type, name))
|
|
363
|
+
return name, entry
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def delete_field(schema: SiteSchema, entity_type: str, field_name: str) -> None:
|
|
367
|
+
"""Remove a custom field and reserve its name for this site's lifetime.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
schema: Mutable site schema containing the field.
|
|
371
|
+
entity_type: Entity type containing the field.
|
|
372
|
+
field_name: Internal name of the custom field to remove.
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
ServerFaultError: If the entity or field does not exist.
|
|
376
|
+
MockgunError: If the field is a built-in field whose deletion is
|
|
377
|
+
unsupported.
|
|
378
|
+
"""
|
|
379
|
+
_field_for_write(schema, entity_type, field_name)
|
|
380
|
+
del schema.fields[entity_type][field_name]
|
|
381
|
+
schema.visibility.pop((entity_type, field_name), None)
|
|
382
|
+
schema.site_created.discard((entity_type, field_name))
|
|
383
|
+
schema.retired_fields.add((entity_type, field_name))
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def update_field(
|
|
387
|
+
schema: SiteSchema,
|
|
388
|
+
entity_type: str,
|
|
389
|
+
field_name: str,
|
|
390
|
+
properties: Sequence[Mapping[str, object]],
|
|
391
|
+
*,
|
|
392
|
+
project_id: int | None,
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Apply supported property updates atomically to a custom field.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
schema: Mutable site schema containing the field.
|
|
398
|
+
entity_type: Entity type containing the field.
|
|
399
|
+
field_name: Internal name of the custom field to update.
|
|
400
|
+
properties: Wire entries containing `property_name` and `value`.
|
|
401
|
+
project_id: Project-specific visibility scope, or `None` for an
|
|
402
|
+
unscoped update.
|
|
403
|
+
|
|
404
|
+
Raises:
|
|
405
|
+
ServerFaultError: If the field is absent or the request has a single
|
|
406
|
+
modeled property fault.
|
|
407
|
+
MockgunError: If the field, property update, or competing error
|
|
408
|
+
precedence is unsupported.
|
|
409
|
+
"""
|
|
410
|
+
entry = _field_for_write(schema, entity_type, field_name)
|
|
411
|
+
supplied, repeated = _properties(properties)
|
|
412
|
+
problems: list[Exception] = []
|
|
413
|
+
if repeated:
|
|
414
|
+
problems.append(MockgunError(f'{entity_type}.{field_name} repeated update property'))
|
|
415
|
+
data_type = cast('str', cast('dict[str, object]', entry['data_type'])['value'])
|
|
416
|
+
for name, value in supplied.items():
|
|
417
|
+
problem = _update_problem(field_name, data_type, entry, (name, value), project_id)
|
|
418
|
+
if problem is not None:
|
|
419
|
+
problems.append(problem)
|
|
420
|
+
_raise_problems(problems)
|
|
421
|
+
schema.fields[entity_type][field_name] = _updated_entry(entry, supplied)
|
|
422
|
+
if 'visible' in supplied and project_id is not None:
|
|
423
|
+
schema.visibility.setdefault((entity_type, field_name), {})[project_id] = cast(
|
|
424
|
+
'bool', supplied['visible']
|
|
425
|
+
)
|