Metadata-Version: 2.1
Name: ftrack-query
Version: 1.8.3
Summary: Easy query generation for the FTrack API.
Home-page: https://github.com/huntfx/ftrack-query
Download-URL: https://github.com/huntfx/ftrack-query/archive/1.8.3.tar.gz
Author: Peter Hunt
Author-email: peter@huntfx.uk
License: MIT
Project-URL: Documentation, https://github.com/huntfx/ftrack-query/wiki
Project-URL: Source, https://github.com/huntfx/ftrack-query
Project-URL: Issues, https://github.com/huntfx/ftrack-query/issues
Keywords: ftrack,api,query,event
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Requires-Python: >=2.7.9, <4.0
Description-Content-Type: text/markdown
License-File: LICENSE

# ftrack-query
FTrack 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.

It 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.

## Installation
    pip install ftrack_query

## Examples

### Original Syntax
This will build queries attached to the current session, which allows them to be executed directly.

```python
from ftrack_query import FTrackQuery, entity, or_

with FTrackQuery() as session:
    # Create
    note = session.Note.create(
        content='My new note',
        author=session.User('peter'),
        category=session.NoteLabel.where(entity.color!=None, name='Internal').one(),
    )

    # Query
    task = session.Task.where(
        entity.status.name.in_('Lighting', 'Rendering'),
        or_(
            entity.parent == session.Episode.first(),
            entity.parent == None,
        ),
        name='My Task',
    ).order(
        entity.type.name.desc(),
    ).select(
        'name', 'type.name', 'status.name',
    ).first()

    task['notes'].append(note)
    session.commit()
```

# Statement Syntax
In version 1.7, new statement functions were added, to allow queries to be built without an associated `session` object.

These require a `session.execute` call in order to work, but the underlying logic is the same as with the original syntax.

```python
from ftrack_query import FTrackQuery, select, create, update, delete

with FTrackQuery() as session:
    # Query
    stmt = (
        select('Task.name', 'Task.type.name', 'Task.status.name')
        .where(entity.status.name.in_('Lighting', 'Rendering'))
        .order_by(entity.name.desc())
        .offset(5)
        .limit(1)
    )
    task = session.execute(stmt).first()
    print(f'Task found: {task})

    # Create
    stmt = (
        create('Task')
        .values(name='My Task', parent=task)
    )
    task = session.execute(stmt)
    session.commit()
    print(f'Task created: {task}')

    # Update
    stmt = (
        update('Task')
        .where(name='Old Task Name')
        .values(name='New Task Name')
    )
    rows = session.execute(stmt)
    session.commit()
    print(f'Tasks updated: {rows}')

    # Delete
    stmt = (
        delete('Task')
        .where(name='Old Task Name')
    )
    rows = session.execute(stmt)
    session.commit()
    print(f'Tasks deleted: {rows}')
```

### Event Syntax
The event system uses a slightly different query language, this has been added for convenience but generally should not be needed.

```python
from ftrack_query import FTrackQuery, event

with FTrackQuery() as session:
    session.event_hub.subscribe(str(
        event.and_(
            event.topic('ftrack.update'),
            event.data.user.name != getuser(),
        )
    ))
    session.event_hub.wait()
```

# Reference

## FTrackQuery
Main class inherited from `ftrack_api.Session`.

## Query
Every available entity type is an attribute of a session. What was originally `session.query('Note')` is now `session.Note`. This results in the `Query` object, which is used for constructing and executing queries.

### .where(_\*args, \*\*kwargs_)
Filter the result.

Using kwargs is the recommended way, with a syntax like `.where(first_name='Peter', last_name='Hunt')`.

Using args is required for complex queries. This uses the `Comparison` object, which is automatically created when comparing multiple `Query` objects. An example would be `.where(entity.project.metadata.any(entity.key!='disabled'))`.

### .populate(_\*args_) | .select(_\*args_)
Pre-fetch entity attributes.

An an example, in order to iterate through the name of every user, it would be a good idea to prefetch `first_name` and `last_name`, as otherwise two queries will be performed for each individual user.

### .order_by(_attribute_)
Sort the results by an attribute.

The attribute and order can be given in the format `entity.name.desc()`, or as a raw string such as `name descending`.

### .reverse()
Reverse the sorting direction.

### .limit(_value_)
Limit the amount of results to a certain value.

### .offset(_value_)
In the case of using a limit, this applies an offset to the result that is returned.

### .in_(_subquery_) | .not_in(_subquery_)
Perform a check to check if an attribute matches any results.

This can accept a subquery such `.in_('select id from table where x is y')`, or a list of items like `.in_('x', 'y')`.

### .\_\_call\_\_(_value_)
If an entity has a primary key, by calling the value of that primary key, the entity or `None` will be returned.
Currently only `User` supports this.

## Comparison
The `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.

Any comparison can be reversed with the `~` prefix or the `not_` function.

- String Comparison: `entity.attr=='value'`
- Number comparison: `entity.attr>5`
- Pattern Comparison: `entity.attr.like('value%')`
- Time Comparison: `entity.attr.after(arrow.now().floor('day'))`
- Scalar Relationship: `entity.attr.has(subattr='value')`
- Collection Relationship: `entity.attr.any(subattr='value')`
- Subquery Relationship: `entity.attr.in_(subquery)`

## and\_(_\*args, \*\*kwargs_) | or\_(_\*args, \*\*kwargs_)
Join multiple comparisons. `and_` is used by default if nothing is provided.

## Statements
The statement functions build upon the `Query` object, but are not attached to any session. Instead of `session.Note`, it becomes `select('Note')`.

### select(_\*_entity_type_)
A select statement has access to the `Query` methods such as `.where()`.

If multiple arguments are given, it will use these in place of `.populate()` (eg. `select('Task.name', Task.parent')` is the same as `select('Task').populate('name', 'parent')`).

Calling `session.execute(stmt)` will execute the query and return FTrack's own `QueryResult` object, from which `.one()`, `.first()` or `.all()` may be called.

### create(_entity_type_)
A create statement has a `.values()` method used to input the data.

Calling `session.execute(stmt)` will return the created entity.

### update(_entity_type_)
An update statement has access to all of the `Query` methods, but also has a `.values()` method used to input the new data.

Calling `session.execute(stmt)` will return how many entities were found and updated.

### delete(_entity_type_)
A delete statement has access to most of the `Query` methods.

Calling `session.execute(stmt)` will return how many entities were deleted.

A convenience method, `.options(remove_components=True)`, can be used when deleting a `Component`. Enabling this will remove the component from every location before it is deleted.



## Equivalent examples from the [API reference](http://ftrack-python-api.rtd.ftrack.com/en/0.9.0/querying.html):

```python
# Project
select('Project')

# Project where status is active
select('Project').where(status='active')

# Project where status is active and name like "%thrones"
select('Project').where(entity.name.like('%thrones'), status='active')

# session.query('Project where status is active and (name like "%thrones" or full_name like "%thrones")')
select('Project').where(or_(entity.name.like('%thrones'), entity.full_name.like('%thrones')), status='active')

# session.query('Task where project.id is "{0}"'.format(project['id']))
select('Task').where(project=project)

# session.query('Task where project.id is "{0}" and status.type.name is "Done"'.format(project['id']))
select('Task').where(entity.status.type.name == 'Done', project=project)

# session.query('Task where timelogs.start >= "{0}"'.format(arrow.now().floor('day')))
select('Task').where(entity.timelogs.start >= arrow.now().floor('day'))

# session.query('Note where author has (first_name is "Jane" and last_name is "Doe")')
select('Note').where(entity.author.has(first_name='Jane', last_name='Doe'))

# session.query('User where not timelogs any ()')
select('User').where(~entity.timelogs.any())

# projects = session.query('select full_name, status.name from Project')
select('Project.full_name', 'Project.status.name')
# or
select('Project').populate('full_name', 'status.name')

# select name from Project where allocations.resource[Group].memberships any (user.username is "john_doe")
select('Project').select('name').where(entity.allocations.resource['Group'].memberships.any(entity.user.username == 'john_doe'))

# Note where parent_id is "{version_id}" or parent_id in (select id from ReviewSessionObject where version_id is "{version_id}")
select('Note').where(or_(entity.parent_id.in_(select('ReviewSessionObject.id').where(version_id=version_id)), parent_id=version_id))
```


## Planned Changes for 2.0

Since a lot of functionality has been added from the initial version, and old features are no longer needed, v2 is going to have a major overhaul.

- Replace `session.<Entity>` with `session.select(<Entity>)` - this will also remove the `session.<Entity>.get()` shortcut
- Replace `entity.x.y == z` with `attr('x.y') == z`
- Add `session.select`, `session.update`, `session.insert` and `session.delete`. This will allow the same queries to run with or without an attached session.
- All statements will have a `.execute()` method.
- `session.execute(stmt)` will call `stmt.options(session=self).execute()`
