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.
@@ -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
+ ![PyPI](https://img.shields.io/pypi/v/shotgrid_orm)
64
+ ![Python Version](https://img.shields.io/pypi/pyversions/shotgrid_orm)
65
+ ![License](https://img.shields.io/github/license/johnetran/shotgrid_orm)
66
+ [![Tests](https://github.com/johnetran/shotgrid_orm/actions/workflows/test.yml/badge.svg)](https://github.com/johnetran/shotgrid_orm/actions/workflows/test.yml)
67
+ [![codecov](https://codecov.io/gh/johnetran/shotgrid_orm/branch/main/graph/badge.svg)](https://codecov.io/gh/johnetran/shotgrid_orm)
68
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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
+ ![Shotgrid ORM](doc/ShotgridORM.png)
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/