shotgrid-orm 0.1.1__tar.gz
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.
- shotgrid_orm-0.1.1/LICENSE +19 -0
- shotgrid_orm-0.1.1/PKG-INFO +601 -0
- shotgrid_orm-0.1.1/README.md +541 -0
- shotgrid_orm-0.1.1/pyproject.toml +143 -0
- shotgrid_orm-0.1.1/setup.cfg +4 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm/__init__.py +1 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm/classes.py +393 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm/sgtypes.py +95 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm.egg-info/PKG-INFO +601 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm.egg-info/SOURCES.txt +16 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm.egg-info/dependency_links.txt +1 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm.egg-info/requires.txt +15 -0
- shotgrid_orm-0.1.1/src/shotgrid_orm.egg-info/top_level.txt +1 -0
- shotgrid_orm-0.1.1/tests/test_createdb.py +57 -0
- shotgrid_orm-0.1.1/tests/test_createscript.py +78 -0
- shotgrid_orm-0.1.1/tests/test_index.py +39 -0
- shotgrid_orm-0.1.1/tests/test_model.py +112 -0
- shotgrid_orm-0.1.1/tests/test_sgorm.py +91 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
2
|
+
|
|
3
|
+
Copyright (c) John Em Tran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
6
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
|
7
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
8
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
9
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
|
12
|
+
substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
15
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
16
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
17
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
18
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
19
|
+
DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: shotgrid_orm
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Shotgrid SQLAlchemy ORM Generator - creates Python classes from a schema.
|
|
5
|
+
Author-email: John Em Tran <johnemtran@gmail.com>
|
|
6
|
+
License: This is the MIT license: http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
|
|
8
|
+
Copyright (c) John Em Tran
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
11
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
|
12
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
13
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
14
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
|
17
|
+
substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
20
|
+
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
21
|
+
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
22
|
+
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
23
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
24
|
+
DEALINGS IN THE SOFTWARE.
|
|
25
|
+
|
|
26
|
+
Project-URL: Homepage, https://github.com/johnetran/shotgrid_orm
|
|
27
|
+
Project-URL: Bug Tracker, https://github.com/johnetran/shotgrid_orm/issues
|
|
28
|
+
Project-URL: Repository, https://github.com/johnetran/shotgrid_orm
|
|
29
|
+
Keywords: shotgrid,shotgun,autodesk,orm,sqlalchemy,flow-production-tracking
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: Topic :: Database
|
|
33
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Operating System :: OS Independent
|
|
42
|
+
Requires-Python: >=3.8
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: shotgun-api3>=3.4.0
|
|
46
|
+
Requires-Dist: sqlacodegen-v2>=0.1.4
|
|
47
|
+
Requires-Dist: SQLAlchemy>=2.0.22
|
|
48
|
+
Requires-Dist: alembic>=1.12.1
|
|
49
|
+
Provides-Extra: dev
|
|
50
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
51
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
52
|
+
Requires-Dist: black; extra == "dev"
|
|
53
|
+
Requires-Dist: ruff; extra == "dev"
|
|
54
|
+
Requires-Dist: mypy; extra == "dev"
|
|
55
|
+
Requires-Dist: build; extra == "dev"
|
|
56
|
+
Requires-Dist: twine; extra == "dev"
|
|
57
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
58
|
+
Requires-Dist: bandit[toml]; extra == "dev"
|
|
59
|
+
Dynamic: license-file
|
|
60
|
+
|
|
61
|
+
# Shotgrid ORM Generator
|
|
62
|
+
|
|
63
|
+

|
|
64
|
+

|
|
65
|
+

|
|
66
|
+
[](https://github.com/johnetran/shotgrid_orm/actions/workflows/test.yml)
|
|
67
|
+
[](https://codecov.io/gh/johnetran/shotgrid_orm)
|
|
68
|
+
[](https://github.com/psf/black)
|
|
69
|
+
|
|
70
|
+
### For Autodesk Flow Production Tracking system (formerly Shotgun/Shotgrid - SG)
|
|
71
|
+
|
|
72
|
+
This tool generates a SQLAlchemy ORM for a Shotgrid schema for the purposes of reporting, BI, data warehousing, and analytics. It does not generate foreign key constraints and primary keys are not auto-increment. This allows maximum freedom to transfer data from Shotgrid into a target database, retaining its native primary keys (IDs).
|
|
73
|
+
|
|
74
|
+
## Overview
|
|
75
|
+

|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
Install from PyPI:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install shotgrid_orm
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Or install from source:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git clone https://github.com/johnetran/shotgrid_orm.git
|
|
89
|
+
cd shotgrid_orm
|
|
90
|
+
pip install -e .
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Requirements
|
|
94
|
+
|
|
95
|
+
- Python 3.8 or higher
|
|
96
|
+
- SQLAlchemy 2.0.45+
|
|
97
|
+
- shotgun-api3 3.9.2+
|
|
98
|
+
- sqlacodegen-v2 0.1.4+
|
|
99
|
+
- alembic 1.16.5+
|
|
100
|
+
|
|
101
|
+
## Features
|
|
102
|
+
|
|
103
|
+
- **Multiple Schema Sources**: Load schemas from JSON files, JSON text, or live Shotgrid connections
|
|
104
|
+
- **Dynamic ORM Generation**: Creates SQLAlchemy 2.0 declarative models with proper type hints
|
|
105
|
+
- **Standalone Script Export**: Generate self-contained Python files with your ORM models
|
|
106
|
+
- **Preserve Shotgrid IDs**: Non-auto-increment primary keys maintain original Shotgrid identifiers
|
|
107
|
+
- **Polymorphic Relationships**: Handles entity and multi-entity fields with automatic _id and _type columns
|
|
108
|
+
- **Alembic Integration**: Built-in support for database migrations
|
|
109
|
+
- **Flexible Design**: No forced foreign key constraints for maximum data transfer flexibility
|
|
110
|
+
|
|
111
|
+
## Quick Start
|
|
112
|
+
|
|
113
|
+
### Basic Usage
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from shotgrid_orm import SGORM, SchemaType
|
|
117
|
+
|
|
118
|
+
# Load schema from JSON file
|
|
119
|
+
sg_orm = SGORM(
|
|
120
|
+
sg_schema_type=SchemaType.JSON_FILE,
|
|
121
|
+
sg_schema_source="schema.json",
|
|
122
|
+
echo=False
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Access entity classes
|
|
126
|
+
Shot = sg_orm["Shot"]
|
|
127
|
+
Asset = sg_orm["Asset"]
|
|
128
|
+
|
|
129
|
+
# Generate standalone Python script
|
|
130
|
+
sg_orm.create_script("sgmodel.py")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Complete Example: Data Warehousing
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import os
|
|
137
|
+
from shotgrid_orm import SGORM, SchemaType
|
|
138
|
+
from sqlalchemy import create_engine
|
|
139
|
+
from sqlalchemy.orm import Session
|
|
140
|
+
|
|
141
|
+
# Connect to live Shotgrid and generate ORM
|
|
142
|
+
sg_orm = SGORM(
|
|
143
|
+
sg_schema_type=SchemaType.SG_SCRIPT,
|
|
144
|
+
sg_schema_source={
|
|
145
|
+
"url": os.getenv("SG_URL"),
|
|
146
|
+
"script": os.getenv("SG_SCRIPT"),
|
|
147
|
+
"api_key": os.getenv("SG_API_KEY")
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Create PostgreSQL database for analytics
|
|
152
|
+
engine = create_engine("postgresql://user:pass@localhost/shotgrid_analytics")
|
|
153
|
+
sg_orm.Base.metadata.create_all(engine)
|
|
154
|
+
|
|
155
|
+
# Now use standard Shotgrid API to fetch data and insert into database
|
|
156
|
+
# This preserves all Shotgrid IDs for future syncing
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Detailed Usage
|
|
160
|
+
|
|
161
|
+
### Schema Loading Options
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from shotgrid_orm import SGORM, SchemaType
|
|
165
|
+
|
|
166
|
+
# Option 1: Load from JSON file
|
|
167
|
+
sg_orm = SGORM(
|
|
168
|
+
sg_schema_type=SchemaType.JSON_FILE,
|
|
169
|
+
sg_schema_source="schema.json"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Option 2: Load from JSON string
|
|
173
|
+
json_text = '{"Project": {...}, "Shot": {...}}'
|
|
174
|
+
sg_orm = SGORM(
|
|
175
|
+
sg_schema_type=SchemaType.JSON_TEXT,
|
|
176
|
+
sg_schema_source=json_text
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Option 3: Connect with script credentials
|
|
180
|
+
sg_orm = SGORM(
|
|
181
|
+
sg_schema_type=SchemaType.SG_SCRIPT,
|
|
182
|
+
sg_schema_source={
|
|
183
|
+
"url": "https://mystudio.shotgunstudio.com",
|
|
184
|
+
"script": "my_script",
|
|
185
|
+
"api_key": "abc123..."
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Option 4: Use existing Shotgun connection
|
|
190
|
+
import shotgun_api3
|
|
191
|
+
sg = shotgun_api3.Shotgun(url, script, key)
|
|
192
|
+
sg_orm = SGORM(
|
|
193
|
+
sg_schema_type=SchemaType.SG_CONNECTION,
|
|
194
|
+
sg_schema_source=sg
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Creating and Managing Databases
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from sqlalchemy import create_engine
|
|
203
|
+
|
|
204
|
+
# SQLite (for local development/testing)
|
|
205
|
+
engine = create_engine("sqlite:///shotgrid.db")
|
|
206
|
+
|
|
207
|
+
# PostgreSQL (recommended for production)
|
|
208
|
+
engine = create_engine("postgresql://user:pass@localhost:5432/shotgrid")
|
|
209
|
+
|
|
210
|
+
# MySQL
|
|
211
|
+
engine = create_engine("mysql+pymysql://user:pass@localhost/shotgrid")
|
|
212
|
+
|
|
213
|
+
# Create all tables
|
|
214
|
+
sg_orm.Base.metadata.create_all(engine)
|
|
215
|
+
|
|
216
|
+
# Drop all tables (use with caution!)
|
|
217
|
+
sg_orm.Base.metadata.drop_all(engine)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Working with Data
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from sqlalchemy.orm import Session
|
|
224
|
+
from sqlalchemy import select
|
|
225
|
+
|
|
226
|
+
# Using dynamically generated classes
|
|
227
|
+
Shot = sg_orm["Shot"]
|
|
228
|
+
Asset = sg_orm["Asset"]
|
|
229
|
+
|
|
230
|
+
session = Session(engine)
|
|
231
|
+
|
|
232
|
+
# Create a record (preserving Shotgrid ID)
|
|
233
|
+
shot = Shot()
|
|
234
|
+
shot.id = 1234 # Use actual Shotgrid ID
|
|
235
|
+
shot.code = "010"
|
|
236
|
+
shot.description = "Opening shot"
|
|
237
|
+
session.add(shot)
|
|
238
|
+
session.commit()
|
|
239
|
+
|
|
240
|
+
# Query records
|
|
241
|
+
shot = session.execute(
|
|
242
|
+
select(Shot).where(Shot.code == "010")
|
|
243
|
+
).scalar_one()
|
|
244
|
+
|
|
245
|
+
# Update records
|
|
246
|
+
shot.description = "Updated description"
|
|
247
|
+
session.commit()
|
|
248
|
+
|
|
249
|
+
# Delete records
|
|
250
|
+
session.delete(shot)
|
|
251
|
+
session.commit()
|
|
252
|
+
|
|
253
|
+
session.close()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Using Generated Scripts
|
|
257
|
+
|
|
258
|
+
After calling `create_script("sgmodel.py")`, you can use the generated module independently:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
import sgmodel
|
|
262
|
+
from sqlalchemy import create_engine, select
|
|
263
|
+
from sqlalchemy.orm import Session
|
|
264
|
+
|
|
265
|
+
engine = create_engine("sqlite:///mydb.db")
|
|
266
|
+
sgmodel.Base.metadata.create_all(engine)
|
|
267
|
+
|
|
268
|
+
session = Session(engine)
|
|
269
|
+
|
|
270
|
+
# Access classes directly
|
|
271
|
+
asset = sgmodel.Asset(id=1, code="HERO_CHAR")
|
|
272
|
+
session.add(asset)
|
|
273
|
+
session.commit()
|
|
274
|
+
|
|
275
|
+
# Or access dynamically via CLASSES dict
|
|
276
|
+
shot_class = sgmodel.CLASSES["Shot"]
|
|
277
|
+
shot = shot_class(id=100, code="010")
|
|
278
|
+
session.add(shot)
|
|
279
|
+
session.commit()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Handling Entity Relationships
|
|
283
|
+
|
|
284
|
+
Entity fields in Shotgrid become `{field}_id` and `{field}_type` columns:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
Shot = sg_orm["Shot"]
|
|
288
|
+
|
|
289
|
+
shot = Shot()
|
|
290
|
+
shot.id = 100
|
|
291
|
+
shot.code = "010"
|
|
292
|
+
|
|
293
|
+
# Single entity field (e.g., project)
|
|
294
|
+
shot.project_id = 1
|
|
295
|
+
shot.project_type = "Project"
|
|
296
|
+
|
|
297
|
+
# Another entity field (e.g., sequence)
|
|
298
|
+
shot.sg_sequence_id = 10
|
|
299
|
+
shot.sg_sequence_type = "Sequence"
|
|
300
|
+
|
|
301
|
+
session.add(shot)
|
|
302
|
+
session.commit()
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Advanced Usage
|
|
306
|
+
|
|
307
|
+
### Ignoring Entities or Fields
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
sg_orm = SGORM(
|
|
311
|
+
sg_schema_type=SchemaType.JSON_FILE,
|
|
312
|
+
sg_schema_source="schema.json",
|
|
313
|
+
ignored_tables=["AppWelcome", "Banner"], # Skip these entities
|
|
314
|
+
ignored_fields=["image_source_entity"] # Skip these fields globally
|
|
315
|
+
)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Using with Alembic Migrations
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
# Initialize Alembic
|
|
322
|
+
alembic init alembic
|
|
323
|
+
|
|
324
|
+
# Generate migration after schema changes
|
|
325
|
+
alembic revision --autogenerate -m "Add new custom fields"
|
|
326
|
+
|
|
327
|
+
# Apply migrations
|
|
328
|
+
alembic upgrade head
|
|
329
|
+
|
|
330
|
+
# Rollback
|
|
331
|
+
alembic downgrade -1
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Bulk Data Transfer from Shotgrid
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
import shotgun_api3
|
|
338
|
+
from shotgrid_orm import SGORM, SchemaType
|
|
339
|
+
from sqlalchemy import create_engine
|
|
340
|
+
from sqlalchemy.orm import Session
|
|
341
|
+
|
|
342
|
+
# Connect to Shotgrid
|
|
343
|
+
sg = shotgun_api3.Shotgun(url, script, key)
|
|
344
|
+
|
|
345
|
+
# Generate ORM from live connection
|
|
346
|
+
sg_orm = SGORM(sg_schema_type=SchemaType.SG_CONNECTION, sg_schema_source=sg)
|
|
347
|
+
|
|
348
|
+
# Setup target database
|
|
349
|
+
engine = create_engine("postgresql://user:pass@localhost/warehouse")
|
|
350
|
+
sg_orm.Base.metadata.create_all(engine)
|
|
351
|
+
session = Session(engine)
|
|
352
|
+
|
|
353
|
+
# Fetch all shots from Shotgrid
|
|
354
|
+
Shot = sg_orm["Shot"]
|
|
355
|
+
shots_data = sg.find("Shot", [], ["id", "code", "description", "project"])
|
|
356
|
+
|
|
357
|
+
# Insert into warehouse database
|
|
358
|
+
for shot_data in shots_data:
|
|
359
|
+
shot = Shot()
|
|
360
|
+
shot.id = shot_data["id"]
|
|
361
|
+
shot.code = shot_data["code"]
|
|
362
|
+
shot.description = shot_data["description"]
|
|
363
|
+
|
|
364
|
+
# Handle entity relationships
|
|
365
|
+
if shot_data.get("project"):
|
|
366
|
+
shot.project_id = shot_data["project"]["id"]
|
|
367
|
+
shot.project_type = shot_data["project"]["type"]
|
|
368
|
+
|
|
369
|
+
session.add(shot)
|
|
370
|
+
|
|
371
|
+
session.commit()
|
|
372
|
+
session.close()
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Common Pitfalls & Solutions
|
|
376
|
+
|
|
377
|
+
### 1. Primary Key Conflicts
|
|
378
|
+
|
|
379
|
+
**Problem**: Attempting to use auto-increment IDs when Shotgrid uses specific IDs.
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
# ❌ Wrong - SQLAlchemy tries to auto-increment
|
|
383
|
+
shot = Shot()
|
|
384
|
+
# id is not set, but SQLAlchemy expects it
|
|
385
|
+
|
|
386
|
+
# ✅ Correct - Always set the ID explicitly
|
|
387
|
+
shot = Shot()
|
|
388
|
+
shot.id = 1234 # Use the actual Shotgrid ID
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**Solution**: Always explicitly set the `id` field to match Shotgrid's ID. This tool intentionally disables auto-increment to preserve Shotgrid IDs.
|
|
392
|
+
|
|
393
|
+
### 2. Entity Field Confusion
|
|
394
|
+
|
|
395
|
+
**Problem**: Trying to set entity relationships as objects instead of ID/type pairs.
|
|
396
|
+
|
|
397
|
+
```python
|
|
398
|
+
# ❌ Wrong - Shotgrid entity fields aren't relationships
|
|
399
|
+
shot.project = project_object
|
|
400
|
+
|
|
401
|
+
# ✅ Correct - Set _id and _type separately
|
|
402
|
+
shot.project_id = 1
|
|
403
|
+
shot.project_type = "Project"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Solution**: Entity fields become two columns: `{field}_id` (integer) and `{field}_type` (string).
|
|
407
|
+
|
|
408
|
+
### 3. Metadata Field Name Clash
|
|
409
|
+
|
|
410
|
+
**Problem**: Shotgrid's `metadata` field conflicts with SQLAlchemy's metadata.
|
|
411
|
+
|
|
412
|
+
**Solution**: This is automatically handled - the field is renamed to `_metadata` in the ORM.
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
# Access Shotgrid's metadata field
|
|
416
|
+
shot._metadata = {"custom": "data"}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 4. Schema Not Found
|
|
420
|
+
|
|
421
|
+
**Problem**: `FileNotFoundError` when loading schema from JSON.
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
# ❌ Wrong - Relative path might not work
|
|
425
|
+
sg_orm = SGORM(SchemaType.JSON_FILE, "schema.json")
|
|
426
|
+
|
|
427
|
+
# ✅ Correct - Use absolute path or ensure working directory
|
|
428
|
+
import os
|
|
429
|
+
schema_path = os.path.join(os.getcwd(), "schema.json")
|
|
430
|
+
sg_orm = SGORM(SchemaType.JSON_FILE, schema_path)
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### 5. Missing Shotgun API Package
|
|
434
|
+
|
|
435
|
+
**Problem**: `ImportError` when trying to connect to live Shotgrid.
|
|
436
|
+
|
|
437
|
+
**Solution**: Install the Shotgrid API:
|
|
438
|
+
```bash
|
|
439
|
+
pip install shotgun-api3
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### 6. NULL vs Empty String
|
|
443
|
+
|
|
444
|
+
**Problem**: Shotgrid treats empty strings differently than NULL.
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
# Be explicit about NULL vs empty string
|
|
448
|
+
shot.description = None # NULL in database
|
|
449
|
+
shot.description = "" # Empty string
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## FAQ
|
|
453
|
+
|
|
454
|
+
### How do I get my Shotgrid schema as JSON?
|
|
455
|
+
|
|
456
|
+
Use the Shotgrid API to export your schema:
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
import shotgun_api3
|
|
460
|
+
import json
|
|
461
|
+
|
|
462
|
+
sg = shotgun_api3.Shotgun(url, script, key)
|
|
463
|
+
schema = {}
|
|
464
|
+
|
|
465
|
+
entities = sg.schema_entity_read()
|
|
466
|
+
for entity_name in entities:
|
|
467
|
+
entity = entities[entity_name]
|
|
468
|
+
fields = sg.schema_field_read(entity_name)
|
|
469
|
+
entity["fields"] = fields
|
|
470
|
+
schema[entity_name] = entity
|
|
471
|
+
|
|
472
|
+
with open("schema.json", "w") as f:
|
|
473
|
+
json.dump(schema, f, indent=2)
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Can I add foreign key constraints manually?
|
|
477
|
+
|
|
478
|
+
Yes! While they're not generated automatically, you can add them:
|
|
479
|
+
|
|
480
|
+
```python
|
|
481
|
+
from sqlalchemy import ForeignKey
|
|
482
|
+
|
|
483
|
+
# After generating the ORM, modify the class
|
|
484
|
+
Shot = sg_orm["Shot"]
|
|
485
|
+
# Add ForeignKey to project_id if you want referential integrity
|
|
486
|
+
# Note: This requires manual modification of generated code
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Does this work with custom Shotgrid fields?
|
|
490
|
+
|
|
491
|
+
Yes! Custom fields are automatically included in the generated ORM. The tool reads the complete schema including all custom fields.
|
|
492
|
+
|
|
493
|
+
### Can I use this for real-time syncing?
|
|
494
|
+
|
|
495
|
+
This tool is designed for one-way data transfer (Shotgrid → Database) for analytics/reporting. For real-time bidirectional sync, consider using Shotgrid's event framework or webhooks.
|
|
496
|
+
|
|
497
|
+
### What database engines are supported?
|
|
498
|
+
|
|
499
|
+
Any database supported by SQLAlchemy 2.0:
|
|
500
|
+
- PostgreSQL (recommended for production)
|
|
501
|
+
- MySQL/MariaDB
|
|
502
|
+
- SQLite (great for development)
|
|
503
|
+
- Oracle
|
|
504
|
+
- Microsoft SQL Server
|
|
505
|
+
|
|
506
|
+
### How do I handle schema changes?
|
|
507
|
+
|
|
508
|
+
Use Alembic for migrations:
|
|
509
|
+
|
|
510
|
+
```bash
|
|
511
|
+
# Generate migration after Shotgrid schema changes
|
|
512
|
+
alembic revision --autogenerate -m "Shotgrid schema update"
|
|
513
|
+
alembic upgrade head
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Or regenerate the ORM and recreate tables (loses data):
|
|
517
|
+
|
|
518
|
+
```python
|
|
519
|
+
sg_orm.Base.metadata.drop_all(engine)
|
|
520
|
+
sg_orm.Base.metadata.create_all(engine)
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Can I filter which entities are included?
|
|
524
|
+
|
|
525
|
+
Yes, use the `ignored_tables` parameter:
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
sg_orm = SGORM(
|
|
529
|
+
sg_schema_type=SchemaType.JSON_FILE,
|
|
530
|
+
sg_schema_source="schema.json",
|
|
531
|
+
ignored_tables=["Banner", "AppWelcome", "PageSetting"]
|
|
532
|
+
)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### How do I access classes dynamically?
|
|
536
|
+
|
|
537
|
+
Use dictionary notation or the `CLASSES` dict:
|
|
538
|
+
|
|
539
|
+
```python
|
|
540
|
+
# From SGORM instance
|
|
541
|
+
Shot = sg_orm["Shot"]
|
|
542
|
+
Shot = sg_orm.classes["Shot"]
|
|
543
|
+
|
|
544
|
+
# From generated script
|
|
545
|
+
import sgmodel
|
|
546
|
+
Shot = sgmodel.CLASSES["Shot"]
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Is this an official Autodesk tool?
|
|
550
|
+
|
|
551
|
+
No, this is a community-developed tool. It uses the official Shotgrid Python API but is not affiliated with or supported by Autodesk.
|
|
552
|
+
|
|
553
|
+
### What about multi-entity fields?
|
|
554
|
+
|
|
555
|
+
Multi-entity fields (one-to-many) create reverse foreign key columns on related tables in the format `{SourceTable}_{field}_id`.
|
|
556
|
+
|
|
557
|
+
## Environment Variables
|
|
558
|
+
|
|
559
|
+
For live Shotgrid connections, set these environment variables:
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
export SG_URL="https://your-studio.shotgunstudio.com"
|
|
563
|
+
export SG_SCRIPT="your_script_name"
|
|
564
|
+
export SG_API_KEY="your_api_key_here"
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
## Known Limitations
|
|
568
|
+
|
|
569
|
+
- **Foreign Keys**: ForeignKey constraints are intentionally not generated to allow flexibility in data transfer. You can add them manually if needed.
|
|
570
|
+
- **Entity Relationships**: Entity and multi-entity types are stored as integer IDs and strings rather than full SQLAlchemy relationship() objects.
|
|
571
|
+
- **Complex Types**: Serializable fields (dicts/JSON) and URL fields are stored as strings. Consider JSON serialization for complex use cases.
|
|
572
|
+
- **Read-Only Fields**: Some Shotgrid field types (like image) are read-only and stored as strings.
|
|
573
|
+
|
|
574
|
+
## Use Cases
|
|
575
|
+
|
|
576
|
+
- **Data Warehousing**: Extract Shotgrid data into analytical databases (PostgreSQL, MySQL, etc.)
|
|
577
|
+
- **Reporting & BI**: Build custom reports using standard SQL tools
|
|
578
|
+
- **Data Migration**: Transfer Shotgrid data between environments while preserving IDs
|
|
579
|
+
- **Backup & Archival**: Create local copies of Shotgrid data with full schema preservation
|
|
580
|
+
- **Custom Integrations**: Build applications that work with Shotgrid data offline
|
|
581
|
+
|
|
582
|
+
## Contributing
|
|
583
|
+
|
|
584
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
585
|
+
|
|
586
|
+
## License
|
|
587
|
+
|
|
588
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
589
|
+
|
|
590
|
+
## Acknowledgments
|
|
591
|
+
|
|
592
|
+
- Built with [SQLAlchemy](https://www.sqlalchemy.org/)
|
|
593
|
+
- Schema code generation powered by [sqlacodegen-v2](https://github.com/ksindi/sqlacodegen)
|
|
594
|
+
- Shotgrid API integration via [shotgun-api3](https://github.com/shotgunsoftware/python-api)
|
|
595
|
+
|
|
596
|
+
## Links
|
|
597
|
+
|
|
598
|
+
- **PyPI**: https://pypi.org/project/shotgrid_orm/
|
|
599
|
+
- **GitHub**: https://github.com/johnetran/shotgrid_orm
|
|
600
|
+
- **Issues**: https://github.com/johnetran/shotgrid_orm/issues
|
|
601
|
+
- **Autodesk Flow Production Tracking**: https://www.autodesk.com/products/flow-production-tracking/
|