{"id":16199859,"url":"https://github.com/huntfx/ftrack-query","last_synced_at":"2025-03-19T05:30:52.822Z","repository":{"id":47953492,"uuid":"233268054","full_name":"huntfx/ftrack-query","owner":"huntfx","description":"Pythonic wrapper for the FTrack API","archived":false,"fork":false,"pushed_at":"2024-06-13T11:05:47.000Z","size":175,"stargazers_count":11,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-17T04:12:25.011Z","etag":null,"topics":["ftrack","ftrack-api","pypi","pypi-package","python","query","query-builder"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/huntfx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-11T17:15:10.000Z","updated_at":"2024-06-13T11:05:50.000Z","dependencies_parsed_at":"2024-01-11T21:31:42.194Z","dependency_job_id":"a1a62872-8cbd-4d88-8ac3-dd528b7ddc0f","html_url":"https://github.com/huntfx/ftrack-query","commit_stats":{"total_commits":115,"total_committers":2,"mean_commits":57.5,"dds":"0.31304347826086953","last_synced_commit":"b9b35eac042b430bb7417a8c259fd92eda846881"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huntfx%2Fftrack-query","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huntfx%2Fftrack-query/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huntfx%2Fftrack-query/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/huntfx%2Fftrack-query/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/huntfx","download_url":"https://codeload.github.com/huntfx/ftrack-query/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244364701,"owners_count":20441458,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ftrack","ftrack-api","pypi","pypi-package","python","query","query-builder"],"created_at":"2024-10-10T09:28:31.621Z","updated_at":"2025-03-19T05:30:52.524Z","avatar_url":"https://github.com/huntfx.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ftrack-query\nFTrack Query is an object-orientated wrapper over the FTrack API. While the default query syntax is powerful, it is entirely text based so dynamic queries can be difficult to construct. This module supports **and**/**or** operators with nested comparisons.\n\nIt is recommended to first read https://ftrack-python-api.readthedocs.io/en/stable/tutorial.html for a basic understanding of how the FTrack API works.\n\n## Installation\n    pip install ftrack_query\n\n## Information\n\nInstead of writing the whole query string at once, a \"statement\" is constructed (_eg. `stmt = select('Task')`_), and the query can be built up by calling methods such as `.where()` and `.populate()` on the statement.\n\nThe [CRUD](https://en.wikipedia.org/wiki/CRUD) methods are all supported (`create`, `select`, `update`, `delete`), but the main functionality is designed for use with `select`. The statements are built with a similar syntax to the main API so it should be straightforward to transition between the two.\n\nNote that this is fully backwards compatible, so existing queries do not need to be rewritten.\n\n\n## Examples\n\nThe below example is for very basic queries:\n\n```python\nfrom ftrack_query import FTrackQuery, attr, create, select, and_, or_\n\nwith FTrackQuery() as session:\n    # Select\n    project = session.select('Project').where(name='Test Project').one()\n\n    # Create\n    task = session.execute(\n        create('Task').values(\n            name='My Task',\n            project=project,\n        )\n    )\n    session.commit()\n\n    # Update\n    rows_updated = session.execute(\n        update('Task')\n        .where(name='Old Task Name')\n        .values(name='New Task Name')\n    )\n    session.commit()\n\n    # Delete\n    rows_deleted = session.execute(\n        delete('Task').where(\n            name='Old Task Name',\n        )\n    )\n    session.commit()\n```\n\nFor a much more complex example:\n\n\n```python\n\nATTR_TYPE = attr('type.name')\n\nTASK_STMT = (\n    select('Task')\n    # Filter the tasks\n    .where(\n        # Get any task without these statuses\n        ~attr('status.name').in_(['Lighting', 'Rendering']),\n        # Check for notes matching any of the following conditions:\n        attr('notes').any(\n            # Ensure note was posted by someone outside the company\n            ~attr('user.email').endswith('@company.com')\n            # Ensure note either not completable or not completed\n            or_(\n                and_(\n                    completed_by=None,\n                    is_todo=True,\n                ),\n                is_todo=False,\n            ),\n        ),\n        # Ensure it has an animation task\n        or_(\n            ATTR_TYPE.contains('Animation'),\n            ATTR_TYPE == 'Anim_Fixes',\n        ),\n    ),\n    # Order the results\n    .order_by(\n        ATTR_TYPE.desc(),  # Equivalent to \"type.name desc\"\n        'name',\n    )\n    # Use projections to populate attributes as part of the query\n    .populate(\n        'name',\n        'notes',\n        'status.name',\n        ATTR_TYPE,\n    )\n    .limit(5)\n)\n\nwith FTrackQuery() as session:\n    # Filter the above query to the result of another query\n    task_stmt = TASK_STMT.where(\n        project_id=session.select('Project').where(name='Test Project').one()['id']\n    )\n\n    # Use the current session to execute the statement\n    tasks = session.execute(task_stmt).all()\n```\n\n\n### Events\nThe event system uses a slightly different query language.\n\n```python\nfrom ftrack_query import FTrackQuery, event\nfrom ftrack_query.event import attr, and_, or_\n\nwith FTrackQuery() as session:\n    session.event_hub.subscribe(str(\n        and_(\n            attr('topic') == 'ftrack.update',\n            attr('data.user.name') != getuser(),\n        )\n    ))\n    session.event_hub.wait()\n```\n\nNote that `attr()`, `and_()`, and `or_()` are present in both `ftrack_query` and `ftrack_query.event`. These are **not** interchangable, so if both are needed, then import `event` and use that as the namespace.\n\n# API Reference\n\n## ftrack_query.FTrackQuery\nMain session inherited from `ftrack_api.Session`.\n\n\n## ftrack_query.and\\_(_\\*args, \\*\\*kwargs_) | ftrack_query.or\\_(_\\*args, \\*\\*kwargs_)\nJoin multiple comparisons.\n\nShortcuts are provided with `\u0026` and `|` (_eg. `attr(a).contains(b) \u0026 attr(x).contains(y)`_).\n\n\n## ftrack_query.not\\_(_\\*args, \\*\\*kwargs_)\nReverse the input comparisons.\n\nA shortcut is provided with `~` (_eg. `~attr(x).contains(y)`_).\n\n\n## ftrack_query.select\nUsed for building the query string.\n\n```python\nfrom ftrack_query import select\n\nstmt = select(entity).where(...).populate(...)\n```\n\nCalling `session.execute(stmt)` will execute the query and return FTrack's own `QueryResult` object, from which `.one()`, `.first()` or `.all()` may be called. Alternatively, by using the shortcut `session.select(entity)`, then this may be skipped.\n\n### where(_\\*args, \\*\\*kwargs_)\nFilter the result.\n\nUsing keywords is the fastest way, such as `.where(first_name='Peter', last_name='Hunt')`.\nFor anything more complex than equality checks, using `attr()` is recommended, such as `.where(attr('project.metadata').any(attr('key') != 'disabled'))`.\n\n### populate(_\\*attrs_)\nPre-fetch entity attributes.\n\nAn an example, in order to iterate through the name of every user, it would be a good idea to call `.populate('first_name', 'last_name')` as part of the query. Without that, it would take 2 separate queries per user, which is known as the [N+1 query problem](https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping).\n\n### order_by(_\\*attrs_) | order(_\\*attrs_) | sort(_\\*attrs_)\nSort the results by an attribute.\n\nThe attribute and order can be given in the format `attr('name').desc()`, or as a raw string such as `name descending`.\nThe order will default to `ascending` if not provided.\n\n### reverse()\nReverse the sorting direction.\n\n### limit(_value_)\nLimit the amount of results to a certain value.\n\nNote: This is incompatible with calling `.first()` or `.one()`.\n\n### offset(_value_)\nIn the case of using a limit, apply an offset to the result that is returned.\n\n### options(_\\*\\*kwargs_)\nFor advanced users only.\n- `page_size`: Set the number of results to be fetched at once from FTrack.\n- `session`: Attach a session object to the query.\n\n### subquery(_attribute='id'_)\nMake the statement a subquery for use within `.in_()`.\n\nThis ensures there's always a \"select from\" as part of the statement.\nManually setting the attribute parameter will override any existing projections.\n\n\n## ftrack_query.create\nUsed for creating new entities.\n\n```python\nfrom ftrack_query import create\n\nstmt = create(entity).values(...)\n```\n\nCalling `session.execute(stmt)` will return the created entity.\n\n### values(_\\*\\*kwargs_)\nValues to create the entity with.\n\n## ftrack_query.update\nUsed to batch update values on multiple entities.\nThis is built off the `select` method so contains a lot of the same methods.\n\n```python\nfrom ftrack_query import update\n\nstmt = update(entity).where(...).values(...)\n```\n\nCalling `session.execute(stmt)` will return how many entities were found and updated.\n\n### where(_\\*args, \\*\\*kwargs_)\nFilter what to update.\n\n### values(_\\*\\*kwargs_)\nValues to update on the entity.\n\n\n## ftrack_query.delete\nUsed to delete entities.\nThis is built off the `select` method so contains a lot of the same methods.\n\n```python\nfrom ftrack_query import delete\n\nstmt = delete(entity).where(...).options(remove_components=True)\n```\n\nCalling `session.execute(stmt)` will return how many entities were deleted.\n\n### where(_\\*args, \\*\\*kwargs_)\nFilter what to update.\n\n### options(_\\*\\*kwargs_)\n- `remove_components`: Remove any `Component` entity from every `Location` containing it before it is deleted. This is not enabled by default as it can't be rolled back.\n\n\n## ftrack_query.attr\nThe `Comparison` object is designed to convert data to a string. It contains a wide array of operators that can be used against any data type, including other `Comparison` objects. The function `attr` is a shortcut to this.\n\nAny comparison can be reversed with the `~` prefix or the `not_` function.\n\n- String Comparison: `attr(key) == 'value'`\n- Number comparison: `attr(key) \u003e 5`\n- Pattern Comparison: `attr(key).like('value%')`\n- Time Comparison: `attr(key).after(arrow.now().floor('day'))`\n- Scalar Relationship: `attr(key).has(subkey='value')`\n- Collection Relationship: `attr(key).any(subkey='value')`\n- Subquery Relationship: `attr(key).in_(subquery)`\n\n### \\_\\_eq\\_\\_(_value_) | \\_\\_ne\\_\\_(_value_) | \\_\\_gt\\_\\_(_value_) | \\_\\_ge\\_\\_(_value_) | \\_\\_lt\\_\\_(_value_) | \\_\\_lt\\_\\_(_value_)\nSimple comparisons.\n\n\n### in\\_(_values_) | not\\_in(_values_)\nPerform a check to check if an attribute matches any results.\n\nThis can accept a subquery such `.in_('select id from table where x is y')`, or a list of items like `.in_(['x', 'y'])`.\n\n### like(_value_) | not\\_like(_value_) | startswith(_value_) | endwith(_value_) | contains(_value_)\nCheck if a string is contained within the query.\nUse a percent sign as the wildcard if using `like` or `not_like`; the rest are shortcuts and do this automatically.\n\n### has\\_(_\\*args, \\*\\*kwargs_) | any\\_(_\\*args, \\*\\*kwargs_)\nTest against scalar and collection relationships.\n\n### before(_values_) | after(_values_)\nTest against dates.\nUsing `arrow` objects is recommended.\n\n\n## Equivalent examples from the [API reference](http://ftrack-python-api.rtd.ftrack.com/en/0.9.0/querying.html):\n\n```python\n# Project\nselect('Project')\n\n# Project where status is active\nselect('Project').where(status='active')\n\n# Project where status is active and name like \"%thrones\"\nselect('Project').where(attr('name').like('%thrones'), status='active')\n\n# session.query('Project where status is active and (name like \"%thrones\" or full_name like \"%thrones\")')\nselect('Project').where(or_(attr('name').like('%thrones'), attr('full_name').like('%thrones')), status='active')\n\n# session.query('Task where project.id is \"{0}\"'.format(project['id']))\nselect('Task').where(project=project)\n\n# session.query('Task where project.id is \"{0}\" and status.type.name is \"Done\"'.format(project['id']))\nselect('Task').where(attr('status.type.name') == 'Done', project=project)\n\n# session.query('Task where timelogs.start \u003e= \"{0}\"'.format(arrow.now().floor('day')))\nselect('Task').where(attr('timelogs.start') \u003e= arrow.now().floor('day'))\n\n# session.query('Note where author has (first_name is \"Jane\" and last_name is \"Doe\")')\nselect('Note').where(attr('author').has(first_name='Jane', last_name='Doe'))\n\n# session.query('User where not timelogs any ()')\nselect('User').where(~attr('timelogs').any())\n\n# projects = session.query('select full_name, status.name from Project')\nselect('Project').populate('full_name', 'status.name')\n\n# select name from Project where allocations.resource[Group].memberships any (user.username is \"john_doe\")\nselect('Project').select('name').where(attr('allocations.resource[Group].memberships').any(attr('user.username') == 'john_doe'))\n\n# Note where parent_id is \"{version_id}\" or parent_id in (select id from ReviewSessionObject where version_id is \"{version_id}\")\nselect('Note').where(or_(attr('parent_id').in_(select('ReviewSessionObject').where(version_id=version_id).subquery()), parent_id=version_id))\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuntfx%2Fftrack-query","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhuntfx%2Fftrack-query","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhuntfx%2Fftrack-query/lists"}