Full Code of shotgunsoftware/python-api for AI

master 921114c50dc6 cached
84 files
2.3 MB
599.3k tokens
1067 symbols
1 requests
Download .txt
Showing preview only (2,397K chars total). Download the full file or copy to clipboard to get everything.
Repository: shotgunsoftware/python-api
Branch: master
Commit: 921114c50dc6
Files: 84
Total size: 2.3 MB

Directory structure:
gitextract_6psy39k2/

├── .coveragerc
├── .flake8
├── .gitattributes
├── .gitignore
├── .pre-commit-config.yaml
├── HISTORY.rst
├── LICENSE
├── README.md
├── SECURITY.md
├── azure-pipelines-templates/
│   ├── run-tests.yml
│   └── type_checking.yml
├── azure-pipelines.yml
├── developer/
│   └── README.md
├── docs/
│   ├── advanced/
│   │   ├── iron_python.rst
│   │   └── packaging.rst
│   ├── advanced.rst
│   ├── authentication.rst
│   ├── changelog.rst
│   ├── cookbook/
│   │   ├── attachments.rst
│   │   ├── examples/
│   │   │   ├── ami_handler.rst
│   │   │   ├── ami_version_packager.rst
│   │   │   ├── basic_create_shot.rst
│   │   │   ├── basic_create_shot_task_template.rst
│   │   │   ├── basic_create_version_link_shot.rst
│   │   │   ├── basic_delete_shot.rst
│   │   │   ├── basic_find_shot.rst
│   │   │   ├── basic_sg_instance.rst
│   │   │   ├── basic_update_shot.rst
│   │   │   ├── basic_upload_thumbnail_version.rst
│   │   │   └── svn_integration.rst
│   │   ├── smart_cut_fields.rst
│   │   ├── tasks/
│   │   │   ├── split_tasks.rst
│   │   │   ├── task_dependencies.rst
│   │   │   └── updating_tasks.rst
│   │   ├── tasks.rst
│   │   ├── tutorials.rst
│   │   └── usage_tips.rst
│   ├── cookbook.rst
│   ├── index.rst
│   ├── installation.rst
│   └── reference.rst
├── setup.py
├── shotgun_api3/
│   ├── __init__.py
│   ├── lib/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── certifi/
│   │   │   ├── __init__.py
│   │   │   ├── __main__.py
│   │   │   ├── cacert.pem
│   │   │   ├── core.py
│   │   │   └── py.typed
│   │   ├── httplib2/
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   ├── cacerts.txt
│   │   │   ├── certs.py
│   │   │   ├── error.py
│   │   │   ├── iri2uri.py
│   │   │   └── socks.py
│   │   ├── mockgun/
│   │   │   ├── __init__.py
│   │   │   ├── errors.py
│   │   │   ├── mockgun.py
│   │   │   └── schema.py
│   │   ├── pyparsing.py
│   │   ├── requirements.txt
│   │   └── sgtimezone.py
│   ├── py.typed
│   └── shotgun.py
├── software_credits
├── tests/
│   ├── __init__.py
│   ├── base.py
│   ├── empty.txt
│   ├── example_config
│   ├── mockgun/
│   │   ├── schema.pickle
│   │   └── schema_entity.pickle
│   ├── requirements.txt
│   ├── run_appveyor.bat
│   ├── test_api.py
│   ├── test_api_long.py
│   ├── test_client.py
│   ├── test_config_file
│   ├── test_mockgun.py
│   ├── test_proxy.py
│   └── test_unit.py
└── update_httplib2.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .coveragerc
================================================
# Copyright (c) 2018 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.
#
# coverage.py configuration (https://pypi.org/project/coverage/)
#

[run]
source=shotgun_api3
omit=
    shotgun_api3/lib/httplib2/*
    shotgun_api3/lib/certifi/*
    shotgun_api3/lib/pyparsing.py


================================================
FILE: .flake8
================================================
# Copyright (c) 2019 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.

[flake8]
max-line-length = 120
exclude = shotgun_api3/lib/httplib2/*,tests/httplib2test.py


================================================
FILE: .gitattributes
================================================
# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto

# Force the following filetypes to have unix eols, so Windows does not break them
*.pickle text eol=lf


================================================
FILE: .gitignore
================================================
# Copyright (c) 2019 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.

#python specific
*.pyc

## generic files to ignore
*~
*.lock
*.DS_Store
*.swp
*.out
*.bak

# test related
tests/config
.coverage
.cache
.travis-solo
htmlcov
test-output.xml
coverage.xml

# setup related
build
dist
shotgun_api3.egg-info
/%1


================================================
FILE: .pre-commit-config.yaml
================================================
# Copyright (c) 2024, Shotgun Software Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  - Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
#  - Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
#  - Neither the name of the Shotgun Software Inc nor the names of its
#    contributors may be used to endorse or promote products derived from this
#    software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Styles the code properly

# Exclude Third Pary components
exclude: "shotgun_api3/lib/.*"

# List of super useful formatters.
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
    - id: check-ast
    - id: check-case-conflict
    - id: check-executables-have-shebangs
    - id: check-merge-conflict
    - id: end-of-file-fixer
    - id: requirements-txt-fixer
    - id: trailing-whitespace

  - repo: https://github.com/psf/black
    rev: 26.1.0
    hooks:
    - id: black


================================================
FILE: HISTORY.rst
================================================
*********************************************
Flow Production Tracking Python API Changelog
*********************************************

Here you can see the full list of changes between each Python API release.

v3.10.1 (2026 Feb 10)
=====================

- Update bundled certifi to version 2026.1.4.

v3.10.0 (2026 Feb 3)
====================

- Add support for Python 3.13.

v3.9.2 (2025 Dec 10)
====================

- Add ``export_page`` method to Shotgun class.
- Documentation has been updated to reflect this addition.

v3.9.1 (2025 Nov 25)
====================

- Removed the deprecated ``CACertsHTTPSConnection`` class, which was no longer needed after dropping Python 2 support.
- Added basic type annotations throughout the package to improve IDE support and code completion. Note: Some typing improvements are still in progress and will be refined in future releases. Special thanks to @chadrik for this contribution!
- Introduced a new environment variable ``SHOTGUN_ALLOW_OLD_PYTHON`` to temporarily bypass Python version warnings for users still on Python 3.7 or 3.8. While this provides flexibility during transition, we strongly recommend upgrading to Python 3.9 or newer for continued support and security updates.
- Enhanced payload optimization for entity dictionaries, making it more flexible and preventing potential issues when working with special fields like ``type`` and ``url``.
- Updated attachment documentation with detailed information about the ``relative_path`` field and its usage.
- Python versions older than 3.9 are now deprecated. A runtime warning will be displayed during initialization if you're using Python 3.7 or 3.8. Please plan to upgrade to Python 3.9 or newer as these older versions will not be supported in future releases.

v3.9.0 (2025 Sep 10)
====================

- Removed Python 2 code.
- Removed the six module. Note: if your code depends on the six library previously included in this package, you will need to update it, as it is no longer supported.

v3.8.5 (2025 Jul 31)
====================

- We don't want to retry on general exceptions (e.g. timeout or remote disconnection)
  because we might send a resource modification request (create, batch create, etc) and
  we can end up duplicating things.

v3.8.4 (2025 Jun 11)
====================

- Replace ``utcfromtimestamp`` to prevent breaking changes in Python 3.12.

v3.8.3 (2025 May 22)
====================

- Add improvements to Mockgun.
  Ensure string comparison are case insensitive.
  Ignore duplicate entities when ``multi_entity_update_mode`` is added.
  Support for ``None`` in mockgun when using ordering.
  Thank you rlessardrodeofx, slingshotsys, and MHendricks for your contributions.
- Minor fixes on unit tests and documentation.


v3.8.2 (2025 Mar 11)
====================

- Prevent flaky disconnection when uploading thumbnails on publish.
  There's a flaky disconnection when the publisher uploads the thumbnail to the server.
  The most common errors were: ``Connection closed by peer`` and ``URLopen error EOF occurred in violation of protocol ssl.c:1006``.

v3.8.1 (2025 Feb 25)
====================

- Upgrade certifi to 2024.12.14.
- Apply black 25.1.0 formatting to the source code.
- Update Software Credits

v3.8.0 (2025 Feb 7)
===================

- Extend the payload optimizations to the ``in`` and ``not_in`` filters and
  the ``update`` method.
- The payload optimization is now enabled by default.
  It can be disabled with the ``SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION``
  environment variable.

v3.7.0 (2024 Dec 9)
===================
- Remove unnecessary data in the payload when combining related queries before sending it to the server.
  This would improve overall performance decreasing network latency and server processing.
  See documentation for more information.


v3.6.2 (2024 Aug 13)
====================
- Remove Ticket entity reference and prepare this to run in CI.
- Condition auth for Jenkins environment.
- Update certifi to 2024.7.4.
- FIRST PHASE Python2 removing.

v3.6.1 (2024 Jun 6)
===================
- Adds multi_entity_update_modes support to mockgun ``update()`` and ``batch()`` methods.
- Implements a retry strategy only when encountering an URLError or SSLEOFError.
- Fixes the issue with deleting prefix and suffix for ``display_name`` variable at the moment of upload for a local install.
- Clarifies the use of ``_build_opener`` in ``download_attachment()``.

v3.6.0 (2024 May 1)
===================
- Drop support for Python 2.7
- certifi version changed to 2024.2.2
- Documentation update

v3.5.1 (2024 Apr 3)
===================
- Documentation: Revert to Shotgun in the API Reference headers to keep consistency with the API methods
- Mockgun: fix entity data type
- Mockgun: add support for ``add_user_agent`` and ``set_session_uuid`` methods

v3.5.0 (2024 Mar 26)
====================
- Rebranding component for Flow Production Tracking

v3.4.2 (2024 Feb 6)
===================
- Add support for Python 3.11

v3.4.1 (2024 Jan 29)
====================
- Flaky Tests
- Documentation: Fix issue regarding "in" filter prototype
- Documentation: Travis badge image is no working anymore
- Documentation: Add ``user_subscription_read`` and ``user_subscription_create`` methods
- Update Python Certifi license block
- Add methods for the user_subscriptions API end points
- Retry ShotGrid request also on error 504
- Retry S3 uploads on error 500
- Comment typing annotation breaks Python 2 compatibility
- Add field type ``entity_type`` to mockgun


v3.4.0 (2023 Sep 22)
====================
- Started support for Python 3.10 for CI.
- Add documentation for PublishedFiles preset filters.
- Upgrade httplib2 to 0.22.0.
- Update licensing.
- Updates Autodesk URLs.
- Fix flaky tests.

v3.3.6 (2023 Aug 29)
====================
- Update docs for entity fields.
- Fix typo.
- Fix incorrect hint.
- Reformat code examples to prevent text overflow.
- Bump certifi from 2020.06.20 to 2022.12.7 in /shotgun_api3/lib.
- Skip SG-MIM entities.
- Replace shotgunsoftware references.
- Deprecation of Python 2.
- Security upgrade certifi to latest version 2023.07.22.

v3.3.5 (2023 Jan 5)
====================
- Add "Setting Up Your Environment with the Python API" to Python Docs (python-api docs).
- [Python API Documentation] Update Python version requirements.
- Rename Shotgun to Shotgrid in every about text like tk-multi-demo git repository.
- Rename Shotgun servers to ShotGrid servers in the documentation.

v3.3.4 (2022 June 9)
====================
- Adds Retries on 503 Errors when uploading to S3.
- Updates AMI Documentation to Support Python 3.
- Adds Python 3.9 coverage in Azure Pipeline CI tests.
- Fixes git protocol for the installation.

v3.3.3 (2021 December 1)
==========================
- Replaces shotgunsoftware urls with Autodesk Knowledge Network and ShotGrid Developer Documentation pages.

v3.3.2 (2021 September 27)
==========================
- Updates version of httplib2.

v3.3.1 (2021 July 12)
=====================
- Implements retries with incremental backoff on 502 errors.

v3.3.0 (2021 Jun 7)
===================
- Updates documentation and error messages to mention ShotGrid.

v3.2.6 (2020 Nov 24)
=====================
- Now includes ``certifi`` and defaults to using the certificates provided with that module.

v3.2.4 (2020 May 25)
=====================
- Updates httplib2 to v0.18.0.

v3.2.3 (2020 Apr 21)
=====================
- Fixes an import bug in httplib2 by using the `forked repository <https://github.com/shotgunsoftware/httplib2>`_.

v3.2.2 (2019 Dec 11)
=====================
- Upgrades packaged six module to 1.13.0
- Adds ``platform`` and ``normalize_platform`` to sgsix module to provide unified platform value across Python 2/3
- Changes httplib import procedure to emulate direct import of the module
- Adds test to ensure httplib2 is importable as expected

v3.2.1 (2019 Oct 29)
=====================
- Returns a specific error from ``share_thumbnail`` when the source thumbnail is a 'transient' thumbnail.

v3.2.0 (2019 Sept 23)
=====================
- Adds a new ``project_entity`` parameter to  ``schema_field_update`` that allows to modify field visibility for a given project.

v3.1.2 (2019 Sept 17)
=====================
- Adds an optional `localized` property on the Shotgun object which allows to retrieve localized display names on
  methods ``schema_entity_read()``, ``schema_field_read()``, and ``schema_read()``.

v3.1.1 (2019 August 29)
=======================
- Fixes a regression on Python 2.7.0-2.7.9 on Windows with the mimetypes module.

v3.1.0 (2019 July 29)
=====================
- Adds support for Python 3.7

v3.0.41 (2019 June 28)
======================
- Adds an optional sleep between retries specified via the `SHOTGUN_API_RETRY_INTERVAL` environment variable, or by setting `sg.config.rpc_attempt_interval`.

v3.0.40 (2019 March 13)
=======================
- Updates encoding method to use shutil when uploading, to avoid memory and overflow errors when reading large files. (contributed by @eestrada)

v3.0.39 (2019 February 20)
==========================
- Ensures the certificates packaged with the API and those specified via the `SHOTGUN_API_CACERTS` environment variable
  are used when uploading a file.

v3.0.38 (2019 February 7)
=========================
- Upgrades the version of ``httplib2`` to ``0.12.0``, which fixes SNI issues. Note this
  version contains a more recent list of certificate authorities. If you are running Shotgun locally and have
  signed your https certificate with an outdated certificate authority, the Shotgun connection will be rejected.

v3.0.37 (2018 July 19)
======================

- Proper support added for unicode and utf-8 string paths given to upload methods, and a sane error is raised when an unusable string encoding is used.
- Adds support for querying preferences from Shotgun via the new preferences_read method.
- Under-the-hood changes to add support for direct to s3 uploads to Shotgun. This change should be transparent to users.

v3.0.36 (2018 April 3)
======================

- Fixes an error where ``connect=False`` during ``__init__`` would still connect to Shotgun.
- Adds support for ``SHOTGUN_API_CACERTS`` when uploading and downloading files.
- Properly handles failed downloads due to malware scanning.

v3.0.35 (2017 December 8)
=========================

- Add exception UserCredentialsNotAllowedForSSOAuthenticationFault.
  Triggered when attempting to initiate a connection with a username/password
  pair on an SSO-enabled Shotgun site.

v3.0.34 (2017 September 18)
===========================

- Optimized pagination strategy for Shotgun 7.4+
- Switched from a hard-coded value of 500 for "records_per_page" to a server-defined value. We will be experimenting with higher values with the goal of increasing performance for large result sets.

v3.0.33 (2017 July 18)
======================

- Raise an exception when uploading an empty file using :meth:`upload`, :meth:`upload_thumbnail`
  or :meth:`upload_filmstrip_thumbnail` before calling out to the server.
- Multiple enhancements and bugfixes to Mockgun
- Added ``nav_search_string()`` and ``nav_search_entity()`` methods as experimental, internal methods for querying SG hierarchy.
- Introduces a :meth:`following` query method, that accepts a user entity and optionally an entity type and/or project.

v3.0.32 (2016 Sep 22)
=====================

- Optimized import speed of the API on Python 2.7.
- Integrated the latest fixes to the ``mimetypes`` module.
- Added ``nav_expand()`` method as an experimental, internal method for querying SG hierarchy.
- Ported all documentation to sphinx. See http://developer.shotgridsoftware.com/python-api.
- Moved Changelog to dedicated HISTORY file.

v3.0.31 (2016 May 18)
=====================

- Add optional ``additional_filter_presets`` argument to :meth:`find` and :meth:`find_one`

v3.0.30 (2016 Apr 25)
=====================

- Add option to use add/remove/set modes when updating multi-entity fields.
- Add explicit file handler close to download_attachment.
- Add basic :meth:`find` ordering support to mockgun.
- Allow for product specific authorization parameters.

v3.0.29 (2016 Mar 7)
====================

- Reverted the change to the default field names for image uploading.

v3.0.28 (2016 Mar 3)
====================

- Refactored nested classing of ``sgtimezone`` library to allow for serializable timestamps.

v3.0.27 (2016 Feb 18)
=====================

- Make sure HTTP proxy authentication works with the ``@`` character in a password.
- Make sure sudo authentication test works with Shotgun versions after v6.3.10.
- Smarter uploading of thumbnails and filmstrips with the :meth:`upload` method.
- Improve Travis build integration of the Python-API to run the full suite of
  API tests instead of just the unit and client tests.

v3.0.26 (2016 Feb 1)
====================

- Updating testing framework to use environment variables inconjunction with existing
  ``example_config`` file so that commits and pull requests are automatically run on travis-ci.
- Fix to prevent stripping out case-sensitivity of a URL if the user passes their credentials to
  ``config.server`` as an authorization header.

v3.0.25 (2016 Jan 12)
=====================

- Add handling for Python versions incompatible with SHA-2 (see `this blog post
  <https://www.shotgridsoftware.com/blog/important-ssl-certificate-renewal-and-sha-2/>`_).
- Add ``SHOTGUN_FORCE_CERTIFICATE_VALIDATION`` environment variable to prevent disabling certficate
  validation when SHA-2 validation is not available.
- Add SSL info to user-agent header.

v3.0.24 (2016 Jan 08)
=====================

- Not released.

v3.0.23 (2015 Oct 26)
=====================

- Fix for `python bug #23371 <http://bugs.python.org/issue23371>`_ on Windows loading mimetypes
  module (thanks `@patrickwolf <http://github.com/patrickwolf>`_).
- Fix for tests on older versions of python.
- Sanitize authentication values before raising error.

v3.0.22 (2015 Sept 9)
=====================

- Added method :meth:`text_search` which allows an API client to access the Shotgun global search
  and auto completer.
- Added method :meth:`activity_stream_read` which allows an API client to access the activity
  stream for a given Shotgun entity.
- Added method :meth:`note_thread_read` which allows an API client to download an entire Note
  conversation, including Replies and Attachments, using a single API call.
- Added an experimental ``mockgun`` module which can be used to emulate the Shotgun API, for
  example inside unit test rigs.
- [minor] Improved docstrings.

v3.0.21 (2015 Aug 13)
=====================

- Update bundled ``httplib2`` module to latest v0.9.1 - fixes some bugs

v3.0.20 (2015 Jun 10)
=====================

- Add authentication support for Shotgun servers with two-factor authentication turned on.

v3.0.19 (2015 Mar 25)
=====================

- Add ability to authenticate with Shotgun using ``session_token``.
- Add  :meth:`get_session_token` method for obtaining token to authenticate with.
- Add new ``AuthenticationFault`` exception type to indicate when server communication has failed
  due to authentication reasons.
- Add support for ``SHOTGUN_API_CACERTS`` environment variable to provide location of external
  SSL certificates file.
- Fixes and updates to various tests.

v3.0.18 (2015 Mar 13)
=====================

- Add ability to query the per-project visibility status for entities, fields and statuses.
  (requires Shotgun server >= v5.4.4)

v3.0.17 (2014 Jul 10)
=====================

- Add ability to update ``last_accessed_by_current_user`` on Project.
- Add workaround for `bug #9291 in Python 2.7 <http://bugs.python.org/issue9291>`_ affecting
  mimetypes library on Windows.
- Add platform and Python version to user-agent (eg. ``shotgun-json (3.0.17); Python 2.7 (Mac)``)

v3.0.16 (2014 May 23)
=====================

- Add flag to ignore entities from archived Projects.
- Add support for differentiating between zero and ``None`` for number fields.
- Add ability to act as a different user.

v3.0.15 (2014 Mar 6)
====================

- Fixed bug which allowed a value of ``None`` for password parameter in
  :meth:`authenticate_human_user`
- Add :meth:`follow`, :meth:`unfollow` and :meth:`followers` methods.
- Add ability to login as HumanUser.
- Ensure that webm/mp4 mime types are always available.
- Updated link to video tour in README.
- Fixes and updates to various tests.

v3.0.14 (2013 Jun 26)
=====================

- added: additional tests for thumbnails.
- added: support for downloading from s3 in :meth:`download_attachment`. Accepts an Attachment
  entity dict as a parameter (is still backwards compatible with passing in an Attachment id).
- added: optional ``file_path`` parameter to :meth:`download_attachment` to write data directly to
  disk instead of loading into memory. (thanks to Adam Goforth `@aag <https://github.com/aag>`_)

v3.0.13 (2013 Apr 11)
=====================

- fixed: #20856 :meth:`authenticate_human_user` login was sticky and would be used for permissions
  and logging.

v3.0.12 (2013 Feb 22)
=====================
*no tag*

- added: #18171 New ``ca_certs`` argument to the :class:`Shotgun` constructor to specify the
  certificates to use in SSL validation.
- added: ``setup.py`` doesn't compress the installed ``.egg`` file which makes the
  ``cacerts.txt`` file accessible.

v3.0.11 (2013 Jan 31)
=====================

- added: nested filter syntax (see :ref:`filter_syntax`)

v3.0.10 (2013 Jan 25)
=====================

- added: :meth:`add_user_agent()` and :meth:`reset_user_agent` methods to allow client code to add
  strings to track.
- added: Changed default ``user-agent`` to include API version.
- updated: advanced summarize filter support.
- fixed: #19830 :meth:`share_thumbnail` errors when source has no thumbnail.

v3.0.9 (2012 Dec 05)
====================

- added: :meth:`share_thumbnail` method to share the same thumbnail record and media between
  entities.
- added: proxy handling to methods that transfer binary data (ie. :meth:`upload`,
  :meth:`upload_thumbnail`, etc.).
- updated: default logging level to WARN.
- updated: documentation for :meth:`summarize()` method, previously released but without
  documentation.
- fixed: unicode strings not always being encoded correctly.
- fixed: :meth:`create()` generates error when ``return_fields`` is None.
- fixed: clearing thumbnail by setting ``image`` value to ``None`` not working as expected.
- fixed: some html entities being returned sanitized via API.
- improved: ``simplejson`` fallback now uses relative imports to match other bundled packages.
- improved: various error messages are now clearer and more informative.
- installation is now ``pip`` compatible.

v3.0.9.beta2 (2012 Mar 19)
==========================

- use relative imports for included libraries when using Python v2.5 or later.
- replace sideband request for ``image`` (thumbnail) field with native support (requires Shotgun
  server >= v3.3.0. Request will still work on older versions but fallback to slow sideband
  method).
- allow setting ``image`` and ``filmstrip_thumbnail`` in data dict on :meth:`create` and
  :meth:`update` (thanks `@hughmacdonald <https://github.com/HughMacdonald>`_).
- fixed bug causing ``Attachment.tag_list`` to be set to ``"None"`` (str) for uploads.

v3.0.9.beta1 (2012 Feb 23)
==========================

- added support for access to WorkDayRules (requires Shotgun server >= v3.2.0).
- added support for filmstrip thumbnails (requires Shotgun server >= v3.1.0).
- fixed :meth:`download_attachment` pointing to incorrect url.
- fixed some issues with module import paths.

v3.0.8 (2011 Oct 7)
===================

- now uses JSON as a transport rather than XML-RPC. This provides as much as a 40% speed boost.
- added the :meth:`summarize` method.
- refactored single file into package.
- tests added (Thanks to Aaron Morton `@amorton <https://github.com/amorton>`_).
- return all strings as ascii for backwards compatibility, added ``ensure_ascii`` parameter to
  enable returning unicode.

v3.0.7 (2011 Apr 04)
====================

- fix: :meth:`update()` method should return a ``dict`` object not a ``list``.

v3.0.6 (2010 Jan 25)
====================

- optimization: don't request ``paging_info`` unless required (and server support is available).

v3.0.5 (2010 Dec 20)
====================

- officially remove support for old ``api3_preview`` controller.
- :meth:`find`: allow requesting a specific page of results instead of returning them all at once.
- add support for ``session_uuid`` parameter for communicating with a web browser session.

v3.0.4 (2010 Nov 22)
====================

- fix for issue where :meth:`create` method was returning list type instead of dictionary.
- support new style classes (thanks to Alex Schworer `@schworer <https://github.com/schworer>`_).

v3.0.3 (2010 Nov 12)
====================

- add support for local files. Injects convenience info into returned hash for local file links.
- add support for authentication through http proxy server.

v3.0.2 (2010 Aug 27)
====================

- add :meth:`revive` method to revive deleted entities.

v3.0.1 (2010 May 10)
====================

- :meth:`find`: default sorting to ascending, if not set (instead of requiring
  ascending/descending).
- :meth:`upload` and :meth:`upload_thumbnail`: pass auth info through.

v3.0 (2010 May 5)
=================

- non-beta!
- add :meth:`batch` method to do multiple :meth:`create`, :meth:`update`, and :meth:`delete`
  operations in one request to the server (requires Shotgun server to be v1.13.0 or higher).

v3.0b8 (2010 Feb 19)
====================

- fix python gotcha about using lists / dictionaries as defaults (`see this page for more info <http://www.ferg.org/projects/python_gotchas.html#contents_item_6>`_).
- add :meth:`schema_read` method.

v3.0b7 (2009 Nov 30)
====================

- add additional retries for connection errors and a catch for broken pipe exceptions.

v3.0b6 (2009 Oct 20)
====================

- add support for ``HTTP/1.1 keepalive``, which greatly improves performance for multiple
  requests.
- add more helpful error if server entered is not ``http`` or ``https``
- add support assigning tags to file uploads (for Shotgun version >= 1.10.6).

v3.0b5 (2009 Sept 29)
=====================

- fixed deprecation warnings to raise ``Exception`` class for python 2.5.

v3.0b4 (2009 July 3)
====================

- made :meth:`upload` and :meth:`upload_thumbnail` methods more backwards compatible.
- changes to :meth:`find_one`: now defaults to no ``filter_operator``.

v3.0b3 (2009 June 24)
=====================

- fixed :meth:`upload` and :meth:`upload_thumbnail` methods.
- added :meth:`download_attachment` method.
- added ``schema_*`` methods for accessing entities and fields.
- added support for http proxy servers.
- added ``__version__`` string.
- removed ``RECORDS_PER_PAGE`` global (can just set ``records_per_page`` on the Shotgun object
  after initializing it).
- removed ``api_ver`` from the constructor, as this class is only designed to work with API v3.


================================================
FILE: LICENSE
================================================
Copyright (c) 2009-2011, Shotgun Software Inc
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 - Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

 - Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 - Neither the name of the Shotgun Software Inc nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Portions of code (xml-rpc client libs from standard python distro):

Copyright (c) 1999-2002 by Secret Labs AB
Copyright (c) 1999-2002 by Fredrik Lundh

By obtaining, using, and/or copying this software and/or its
associated documentation, you agree that you have read, understood,
and will comply with the following terms and conditions:

Permission to use, copy, modify, and distribute this software and
its associated documentation for any purpose and without fee is
hereby granted, provided that the above copyright notice appears in
all copies, and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
Secret Labs AB or the author not be used in advertising or publicity
pertaining to distribution of the software without specific, written
prior permission.

SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
ABILITY AND FITNESS.  IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
OF THIS SOFTWARE.


================================================
FILE: README.md
================================================
[![Supported Python versions: 3.9, 3.10, 3.11, 3.13](https://img.shields.io/badge/Python-3.9_|_3.10_|_3.11_|_3.13-blue?logo=python&logoColor=f5f5f5)](https://www.python.org/ "Supported Python versions")
[![Reference Documentation](http://img.shields.io/badge/Reference-documentation-blue.svg?logo=wikibooks&logoColor=f5f5f5)](http://developer.shotgridsoftware.com/python-api)

[![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master)
[![Coverage Status](https://codecov.io/gh/shotgunsoftware/python-api/branch/master/graph/badge.svg)](https://codecov.io/gh/shotgunsoftware/python-api)

# Flow Production Tracking Python API

Autodesk provides a simple Python-based API for accessing Flow Production Tracking and integrating with other tools. This is the official API that is maintained by Autodesk (https://www.autodesk.com/support)

The latest version can always be found at http://github.com/shotgunsoftware/python-api

## Documentation
Tutorials and detailed documentation about the Python API are available at http://developer.shotgridsoftware.com/python-api.

Some useful direct links:

* [Installing](http://developer.shotgridsoftware.com/python-api/installation.html)
* [Tutorials](http://developer.shotgridsoftware.com/python-api/cookbook/tutorials.html)
* [API Reference](http://developer.shotgridsoftware.com/python-api/reference.html)
* [Data Types](http://developer.shotgridsoftware.com/python-api/reference.html#data-types)
* [Filter Syntax](http://developer.shotgridsoftware.com/python-api/reference.html#filter-syntax)

## Changelog

You can see the [full history of the Python API on the documentation site](http://developer.shotgridsoftware.com/python-api/changelog.html).


## Tests

Integration and unit tests are provided.

- All tests require:
    - [pytest](https://docs.pytest.org/) and related plugins
    - (Note: Running `pip install -r tests/requirements.txt` will install all required packages)
- A `tests/config` file (you can copy an example from `tests/example_config`).
- Tests can be run individually like this: `pytest tests/test_client.py`
- To run all tests: `pytest`
- To run tests with coverage: `pytest --cov shotgun_api3 --cov-report html`
- `test_client` and `test_unit` use mock server interaction and do not require a Flow Production Tracking instance to be available (no modifications to `tests/config` are necessary).
- `test_api` and `test_api_long` *do* require a Flow Production Tracking instance, with a script key available for the tests. The server and script user values must be supplied in the `tests/config` file. The tests will add test data to your server based on information in your config. This data will be manipulated by the tests, and should not be used for other purposes.


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Security

At Autodesk, we know that the security of your data is critical to your studio’s
operation.
As the industry shifts to the cloud, Flow Production Tracking knows that security and service
models are more important than ever.

The confidentiality, integrity, and availability of your content is at the top
of our priority list.
Not only do we have a team of Flow Production Tracking engineers dedicated to platform security
and performance, we are also backed by Autodesk’s security team, also invests
heavily in the security for broad range of industries and customers.
We constantly reassess, develop, and improve our risk management program because
we know that the landscape of security is ever-changing.

If you believe you have found a security vulnerability in any Flow Production Tracking-owned
repository, please report it to us as described below.


## Reporting Security Issues

**Please do not report security vulnerabilities through public GitHub issues.**

Instead, please report them by sending a message to
[Autodesk Trust Center](https://www.autodesk.com/trust/contact-us).

Please include as much information as you can provide such as locations,
configurations, reproduction steps, exploit code, impact, etc.


## Additional Information

Please check out the [Flow Production Tracking Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html).


================================================
FILE: azure-pipelines-templates/run-tests.yml
================================================
# -----------------------------------------------------------------------------
# Copyright (c) 2009-2021, Shotgun Software Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  - Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
#  - Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
#  - Neither the name of the Shotgun Software Inc nor the names of its
#    contributors may be used to endorse or promote products derived from this
#    software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

parameters:
  # Using the most formal way of defining parameters so we don't define a
  # default value for mandatory parameters. This way, CI will fail if we forget
  # to pass it when invoking the template.

  - name: codecov_download_url
    type: string

  - name: os_name
    type: string

  - name: python_version
    type: string

  - name: vm_image
    type: string

jobs:
- job:
  displayName: "${{ parameters.os_name }} - Python ${{ parameters.python_version }}"
  pool:
    vmImage: ${{ parameters.vm_image }}

  variables:
    group: sg-credentials

  # These are the steps that will be executed inside each job.
  steps:
  # Specifies which version of Python we want to use.
  # TODO: We should provide `githubToken` if we want to download a python release.
  #       Otherwise we may hit the GitHub anonymous download limit.
  - task: UsePythonVersion@0
    inputs:
      versionSpec: ${{ parameters.python_version }}

  # Install all dependencies needed for running the tests. This command is good
  # for all OSes
  - task: Bash@3
    displayName: Install dependencies
    inputs:
      targetType: inline
      script: |
        set -e
        python -m pip install --upgrade pip
        python -m pip install --upgrade setuptools wheel
        python -m pip install --upgrade --requirement tests/requirements.txt
      # Need to use the "python -m" prefix to make Windows happy.

  # The {{}} syntax is meant for the the pre-processor of Azure pipeline. Every statement inside
  # a {{}} block will be evaluated and substituted before the file is parsed to create the jobs.
  # So here we're inserting an extra step if the template is being invoked for Windows.
  - ${{ if eq(parameters.os_name, 'Windows') }}:
    # On Windows, we need to update the certificates, the cert store is missing the newer one
    # from Amazon like some clients experienced a while back. Who would have thought Microsoft
    # would have been out of date! ;)
    - powershell: |
        $cert_url = "https://www.amazontrust.com/repository/SFSRootCAG2.cer"
        $cert_file = New-TemporaryFile
        Invoke-WebRequest -Uri $cert_url -UseBasicParsing -OutFile $cert_file.FullName
        Import-Certificate -FilePath $cert_file.FullName -CertStoreLocation Cert:\LocalMachine\Root
      displayName: Updating OS Certificates

  # Runs the tests and generates both test report and code coverage
  - task: Bash@3
    displayName: Running tests
    inputs:
      targetType: inline
      script: |
        set -e
        cp ./tests/example_config ./tests/config
        python -m pytest \
          --cov \
          --cov-report xml:coverage.xml \
          --durations=0 \
          --nunit-xml=test-results.xml \
          --verbose \
    env:
      # Tell Pytest that we're running in a CI environment
      CI: 1

      # Pass the values needed to authenticate with the Flow Production Tracking site and create some entities.
      # Remember, on a pull request from a client or on forked repos, those variables
      # will be empty!
      SG_SERVER_URL: $(ci_site)
      SG_SCRIPT_NAME: $(ci_site_script_name)
      SG_API_KEY: $(ci_site_script_key)
      # The unit tests manipulate the user and project during the tests, which can cause collisions,
      # so sandbox each build variant.
      # Ideally, we would use the agent name here. The problem is that the agent name is in a build
      # variable, so we can't edit it's value through a ${{replace(a,b,c)}} expression, which are evaluated before
      # build variables are available. Because of this, we need to find another way to generate a
      # unique login. So instead, we'll use the the name of the platform and the python version,
      # which should make it unique.
      SG_HUMAN_LOGIN: $(python_api_human_login)-${{ parameters.os_name }}-${{ parameters.python_version }}
      # This will give a user name like 'something macOS 2.7'
      SG_HUMAN_NAME: $(python_api_human_name) ${{ parameters.os_name }} ${{ parameters.python_version }}
      SG_HUMAN_PASSWORD: $(python_api_human_password)
      # So, first, we need to make sure that two builds running at the same time do not manipulate
      # the same entities, so we're sandboxing build nodes based on their name.
      SG_PROJECT_NAME: Python API CI - $(Agent.Name)
      # The entities created and then reused between tests assume that the same user is always
      # manipulating them. Because different builds will be assigned different agents and therefore
      # different projects, it means each project needs to have an entity specific to a given user.
      # Again, this would have been a lot simpler if we could simply have had a login based on the
      # agent name, but alas, the agent name has a space in it which needs to be replaced to something
      # else and string substitution can't be made on build variables, only template parameters.
      SG_ASSET_CODE: CI-$(python_api_human_login)-${{ parameters.os_name }}-${{ parameters.python_version }}
      SG_VERSION_CODE: CI-$(python_api_human_login)-${{ parameters.os_name }}-${{ parameters.python_version }}
      SG_SHOT_CODE: CI-$(python_api_human_login)-${{ parameters.os_name }}-${{ parameters.python_version }}
      SG_TASK_CONTENT: CI-$(python_api_human_login)-${{ parameters.os_name }}-${{ parameters.python_version }}
      SG_PLAYLIST_CODE: CI-$(python_api_human_login)-${{ parameters.os_name }}-${{ parameters.python_version }}

  # Explicit call to PublishTestResults@2 and PublishCodeCoverageResults@2 here
  # instead of relying on pytest-azurepipelines because pytest-azurepipelines
  # does not seem to be maintained anymore and is still using earlier versions
  # of the PublishTestResults and PublishCodeCoverageResults Azure Pipelines
  # jobs.

  - task: PublishTestResults@2
    displayName: Publish test results
    inputs:
      failTaskOnFailureToPublishResults: true
      failTaskOnMissingResultsFile: true
      testResultsFiles: test-results.xml
      testResultsFormat: NUnit
      testRunTitle: "${{ parameters.os_name }} - Python ${{ parameters.python_version }}"
    condition: succeededOrFailed()

  - task: PublishCodeCoverageResults@2
    displayName: Publish code coverage
    inputs:
      summaryFileLocation: coverage.xml
      failIfCoverageEmpty: true

  - task: Bash@3
    displayName: Uploading coverage to Codecov.io
    inputs:
      targetType: inline
      script: |
        set -e
        curl --remote-name --silent "${{ parameters.codecov_download_url }}"
        chmod +x codecov
        ./codecov  \
          --file coverage.xml \
          --flags "${{ parameters.os_name }}" \
          --flags "Python-${{ parameters.python_version }}" \
          --name "Tested on ${{ parameters.os_name }} with Python ${{ parameters.python_version }}" \


================================================
FILE: azure-pipelines-templates/type_checking.yml
================================================
# Copyright (c) 2025, Shotgun Software Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  - Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
#  - Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
#  - Neither the name of the Shotgun Software Inc nor the names of its
#    contributors may be used to endorse or promote products derived from this
#    software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

jobs:
- job: type_checking
  displayName: Type Checking (beta)
  pool:
    vmImage: 'ubuntu-latest'

  steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: 3.9
      addToPath: True
      architecture: 'x64'

  - script: |
      pip install --upgrade pip setuptools wheel
      pip install --upgrade mypy
    displayName: Install dependencies

  # Placeholder to future static type checking. For now we just run mypy and skip all known errors.
  - bash: mypy shotgun_api3/shotgun.py --follow-imports skip --pretty --no-strict-optional --disable-error-code arg-type --disable-error-code assignment --disable-error-code return --disable-error-code return-value --disable-error-code attr-defined
    displayName: Run type checking


================================================
FILE: azure-pipelines.yml
================================================
# -----------------------------------------------------------------------------
# Copyright (c) 2009-2021, Shotgun Software Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  - Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
#  - Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
#  - Neither the name of the Shotgun Software Inc nor the names of its
#    contributors may be used to endorse or promote products derived from this
#    software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

resources:
  repositories:
    - repository: templates
      type: github
      name: shotgunsoftware/tk-ci-tools
      ref: refs/heads/master
      endpoint: shotgunsoftware
      # Despite using the "tk-" prefix, tk-ci-tools is not a Toolkit only tool.
      # We use it to avoid duplicating and maintaining CI pipeline code.

# We've stored some variables in Azure. They contain credentials
# and are encrypted. They are also not available to clients.
# This statement says which variable groups this repo requires.
# We have two: one that can be shared by all repos that use CI
# and another that is used just by the Python API, which creates
# HumanUser's and as such need a password, which we can't
# hardcode in the .yml files.
variables:
  - group: sg-credentials

# We want builds to trigger for 3 reasons:
# - The master branch sees new commits
# - Each PR should get rebuilt when commits are added to it.
trigger:
  branches:
    include:
    - master
pr:
  branches:
    include:
    - "*"

parameters:
  - name: python_versions
    type: object
    default:
      - '3.9'
      - '3.10'
      - '3.11'
      - '3.13'

  - name: os_versions
    type: object
    default:
      - name: Linux
        vm_image: ubuntu-latest
        codecov_download_url: https://uploader.codecov.io/latest/linux/codecov

      - name: macOS
        vm_image: macOS-latest
        codecov_download_url: https://uploader.codecov.io/v0.7.3/macos/codecov
        # macOS & codecov note:
        # In Nov 2024 (SG-36700), we pinned macOS to codecov v0.7.3 because the
        # macOS-latest Azure Pipelines agent is Intel (x86_64), but Codecov
        # started shipping arm64-only binaries.
        # When we will change the macOs image to a arm64 arch, we can use the
        # "latest" version again.

      - name: Windows
        vm_image: windows-latest
        codecov_download_url: https://uploader.codecov.io/latest/windows/codecov.exe

# This here is the list of jobs we want to run for our build.
# Jobs run in parallel.
jobs:
- template: build-pipeline.yml@templates
  parameters:
    # Python API does not follow the exact same Python version lifecycle than
    # Toolkit. So we prefer to control the test execution here instead.
    has_unit_tests: false

    has_ui_resources: false

- template: azure-pipelines-templates/type_checking.yml

- ${{ each os_version in parameters.os_versions }}:
  - ${{ each python_version in parameters.python_versions }}:
    - template: azure-pipelines-templates/run-tests.yml
      parameters:
        codecov_download_url: ${{ os_version.codecov_download_url }}
        os_name: ${{ os_version.name }}
        python_version: ${{ python_version }}
        vm_image: ${{ os_version.vm_image }}


================================================
FILE: developer/README.md
================================================

# Updating HTTPLib2

The API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder. To update the copy to a more recent version of the API, you can run the `update_httplib2.py` script at the root of this repository like this:

    python update_httplib2.py vX.Y.Z

where `vX.Y.Z` is a release found on `httplib2`'s [release page](https://github.com/httplib2/httplib2/releases).


# Release process

## Packaging up new release

1) Update the Changelog in the `HISTORY.rst` file
    - Add bullet points for any changes that have happened since the previous release. This may include changes you did not make so look at the commit history and make sure we don't miss anything. If you notice something was done that wasn't added to the changelog, hunt down that engineer and make them feel guilty for not doing so. This is a required step in making changes to the API.
    - Try and match the language of previous change log messages. We want to keep a consistent voice.
    - Make sure the date of the release matches today. We try and keep this TBD until we're ready to do a release so it's easy to catch that it needs to be updated.
    - Make sure the version number is filled out and correct. We follow semantic versioning.
2) Ensure any changes or additions to public methods are documented
    - Ensure that doc strings are updated in the code itself to work with Sphinx and are correctly formatted.
    - Examples are always good especially if this a new feature or method.
    - Think about a new user to the API trying to figure out how to use the features you're documenting.
3) Update the version value in `python-api/setup.py`  to match the version you are packaging. This controls what version users will get when installing via pip.
4) Update the `__version__` value in `shotgun_api3/shotgun.py` to the version you're releasing. This identifies the current version within the API itself.
5) Commit these changes in master with a commit message like `packaging for the vx.x.x release`.
6) Create a tag based off of the master branch called `vx.x.x` to match the version number you're releasing.
7) Push master and your tag to Github.
8) Update the Releases page with your new release.
    - The release should already be there from your tag but if not, create a new one.
    - Add more detailed information regarding the changes in this release. This is a great place to add examples, and reasons for the change!

## Letting the world know
Post a message in the [Pipeline Community channel](https://community.shotgridsoftware.com/c/pipeline).

## Prepare for the Next Dev Cycle
1) Update the `__version__` value in `shotgun_api3/shotgun.py` to the next version number with `.dev` appended to it. For example, `v3.0.24.dev`
2) Add a new section to the Changelog in the `HISTORY.rst` file with the next version number and a TBD date
```
    **v3.0.24 - TBD**
       + TBD
```
3) Commit the changes to master with a commit message like `Bump version to v3.0.24.dev`
4) Push master to Github


================================================
FILE: docs/advanced/iron_python.rst
================================================
**********
IronPython
**********

We do not test against IronPython and cannot be sure that we won't introduce breaking changes or
that we will be compatible with future releases of IronPython. While we don't officially support
IronPython, we certainly will do our best to figure out any issues that come up while using it and
how to avoid them.


Legacy Information
------------------

This following information is provided for historical purposes only.

As of July 9, 2015 you can look at this fork of the repo to see what changes were needed as of that
date to make things work. The original fork was as of v3.0.20 of the API. Big thanks to our awesome
clients Pixomondo for making their work public and letting us refer to it:

https://github.com/Pixomondo/python-api/tree/v3.0.20.ipy

v3.0.20 can be used with IronPython with a little bit of added work:

- The Python API uses the zlib module to handle decompressing the gzipped response from the server.
  There's no built-in zlib module in IronPython, but there's a potential solution from Jeff Hardy at
  https://bitbucket.org/jdhardy/ironpythonzlib/src/. And the blog post about it here
  http://blog.jdhardy.ca/2008/12/solving-zlib-problem-ironpythonzlib.html

- If you encounter ``LookupError: unknown encoding: idna``, you can force utf-8 by changing
  iri2uri.py ~ln 71 from ``authority = authority.encode('idna')`` to
  ``authority = authority.encode('utf-8')``

- If you encounter an SSL error such as ``SSL3_READ_BYTES:sslv3 alert handshake failure``, then the
  lower level SSL library backing python's network infrastructure is attempting to connect to our
  servers via SSLv3, which is no longer supported. You can use the code from this gist to force the
  SSL connections to use a specific protocol. The forked repo linked above has an example of how to
  do that to force the use of TLSv1.


================================================
FILE: docs/advanced/packaging.rst
================================================
.. _packaging:

################################################
Packaging an application with py2app (or py2exe)
################################################

You can create standalone applications with Python scripts by using
`py2app <https://pythonhosted.org/py2app/>`_ on OS X or `py2exe <http://www.py2exe.org/>`_ on
Windows. This is often done to more easily distribute applications that have a GUI based on
toolkits like Tk, Qt or others.

There are caveats you need to be aware of when creating such an app.

********************************
HTTPS Validation and cacerts.txt
********************************
When creating the connection to Flow Production Tracking, a file is used to validate the Flow Production Tracking
certificate. This file is located at ``shotgun_api3/lib/httplib2/cacerts.txt``. Because this file is not a Python
file imported by your application, py2app will not know to include it in your package, it will
need to be explicitly specified in your ``setup.py`` file (edit the path based on the location
where your ``shotgun_api3`` package is located)::

    DATA_FILES = [
        ('shotgun_api3', ['/shotgun/src/python-api/shotgun_api3/lib/httplib2/cacerts.txt'])
    ]

Once you create your py2app package its contents should include two files (among others) in the
following structure::

    ./Contents/Resources/shotgun_api3/cacerts.txt
    ./Contents/Resources/my_script.py

Where in ``my_script.py`` you can access the ``cacerts.txt`` file using a relative path to pass it
into the Flow Production Tracking connection's constructor::

    ca_certs = os.path.join(os.path.dirname(__file__), 'shotgun_api3', 'cacerts.txt')
    sg = shotgun_api3.Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', 'script_key',
                              ca_certs=ca_certs)

The process for py2exe should be similar.


================================================
FILE: docs/advanced.rst
================================================
.. _advanced_topics:

###############
Advanced Topics
###############

Below are some more advanced topics regarding usage of the Python API. If you would like to see
something that's missing here, please feel free to contact support at https://www.autodesk.com/support
with your suggestions and we'll get it added!

.. toctree::
    :maxdepth: 1

    advanced/packaging
    advanced/iron_python
    changelog


================================================
FILE: docs/authentication.rst
================================================
##############
Authentication
##############

In order to communicate with your server via the API, you must provide valid authentication credentials. The API allows you to authenticate with user-based, or script-based credentials.

*************************
User-based Authentication
*************************
When authenticating as a user, you provide your normal login and password when instantiating your :class:`shotgun_api3.Shotgun` object. The actions performed by this instance will be limited to your permission level just as they are in the  web application. ::

    sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com",
                              login="rhendriks",
                              password="c0mPre$Hi0n")


***************************
Script-based Authentication
***************************
In order to authenticate as a script, your script must be :ref:`registered with Flow Production Tracking and have a valid API key <setting_up_shotgrid>`. When creating your :class:`shotgun_api3.Shotgun` object, provide the ``script_name`` and ``api_key``.::

    sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com",
                              script_name="compress",
                              api_key="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")

.. note:: When using script-based authentication, we **strongly** recommend you register each script separately with Flow Production Tracking and have individual API keys for each. This allows you to track down each of your scripts and the actions they are performing much more accurately in the event logs.


.. _setting_up_shotgrid:

Adding Script Users
===================
If you'll be using script-based authentication, you need to create a Script entity in Flow Production Tracking. To create a new key, click the + button on the "Scripts" page in the Admin section and give your script a useful name. It's a good idea to add any other relevant information that be be helpful to your other friendly Flow Production Tracking users such as a description of what the script does that is using this key, the email address of the maintainer, etc.:

.. image:: images/scripts_page.png

Once you save your new Script entity, Flow Production Tracking will automatically generate an application key which will act as the script's password. The key will look something like this: ``0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef``.

Why have different application keys for different scripts?
==========================================================
We recommend you create a new Script entity (and application key) for each script that is using script-based authentication so you can accurately log what scripts are doing what in case one of them causes problems. This will also allow you to better see what scripts are performing what actions in the EventLog. We've found that even though you may *think* you'll probably never need to know, the extra 2 minutes of setup now can prevent hours of headache in the future.

Event Logging
=============
By default, events generated by scripts using an script-based authentication are logged in Flow Production Tracking's event log. You can turn this off by un-checking the "Generate Events" checkbox either in the script detail page or from the main Scripts admin page in Flow Production Tracking.

.. note:: Turning off event logging will also prevent any email notifications from being triggered by your scripts since the email notifier relies on the event log to find events to notify for.

Scripts using user-based authentication will generate events similarly to if you were performing the same actions in the Flow Production Tracking web application, though there is some additional metadata stored in the ``EventLogEntry`` that identifies the event as created from a script acting on behalf of the user.

Why would you want to turn event logging off for scripts?
---------------------------------------------------------
It is an optimization that is not used often, but some users have integration scripts that are pushing data into Flow Production Tracking just for reference, like publishes from their asset management system. This publish data is never changed later, so the data itself has the entire history, and the events would just clutter the event log. The event log can grow very large. So if you have no need to audit the history of what your script does, and it's generating an large amount of event log entries, you may find it's not necessary to create these events.

***********
Permissions
***********
Users and scripts are both bound by the restrictions of their permission role in Flow Production Tracking. The permission role is assigned by the **Permission Role** field for each entity type.

For Scripts, the default permission role is "API Admin User" which allows full access to create, update, and delete entities and fields, including editing the "date created" audit field and creating event log entries. If you have other permission roles for ApiUsers, you can set the default role that will be assigned when a new script is created, in your Flow Production Tracking site preferences.

When using user-based authentication in your script, it will be bound by the permission role assigned to you in Flow Production Tracking. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception.

.. seealso:: `Permissions Documentation <https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_site_configuration_ar_permissions_html>`_


================================================
FILE: docs/changelog.rst
================================================
.. currentmodule:: shotgun_api3.shotgun.Shotgun

.. include:: ../HISTORY.rst


================================================
FILE: docs/cookbook/attachments.rst
================================================
.. _attachments:

################################
Details About Working With Files
################################

The Flow Production Tracking web application stores Files as Attachment entities. You can see these on a Files page,
or a Files tab on a detail page, for example. You can access Attachments via the API to create and
modify uploaded files, url links, and local files, and link them to other entities (Shots,
Versions, etc). This entity works a lot like other entity types within Flow Production Tracking with a few
exceptions which are detailed below.

.. note::
    If you are simply looking for information about how to upload and link things in Flow Production Tracking, this
    doc is not for you. Instead look at the :meth:`~shotgun_api3.Shotgun.upload` and
    :meth:`~shotgun_api3.Shotgun.upload_thumbnail` methods.

    This doc describes the detailed structure of the Attachment entities that represent files
    in Flow Production Tracking and how to interact with them. If that sounds cool too, then read on!

.. versionadded:: 3.0.3

*****************
Default structure
*****************
The following is a list of the default fields that Flow Production Tracking creates for Attachments. Your server
instance may look slightly different depending on your own customizations. Many of these fields are
optional and some are automatically filled in. These exceptions are listed below in the
descriptions of each field.

- **description** (:obj:`str`):
    Optional field to provide descriptive text about the file.

- **this_file** (:obj:`dict`):
     The actual file reference. Within the dictionary is a ``link_type`` key which designates the
     Attachment as an uploaded file, a url link, or a local file. There are additional keys
     returned for :ref:`local_files`. You cannot modify this field after you have created an
     Attachment. See below for examples of this field.

- **filename** (:obj:`str`):
    For uploaded files only. This is automatically assigned when the file is uploaded and stores
    the filename of the file.

- **file_size** (:obj:`int`):
    For uploaded files only. This is automatically assigned when the file is uploaded and stores
    the size of the file in bytes.

- **id** (:obj:`int`):
    The internal Flow Production Tracking id for this Attachment entity.

- **attachment_links** (:obj:`list`):
    A list of entity dictionaries used for linking Attachments to multiple entities.

- **open_notes** (:obj:`list`):
    A List of Note entities linked to the current Attachment that have a status that does not
    equal 'clsd'. *Read-only*

- **open_notes_count** (:obj:`int`):
    An integer count of the list of Note entities linked to the current Attachment that have a
    status that does not equal 'clsd'. *(Read-only)*

- **project** (:obj:`dict`):
    *(Required)* The Project entity that this Attachment belongs to. This must be assigned when
    creating an Attachment.

- **attachment_reference_links** (:obj:`list`):
    Similar to ``attachment_links`` but used specifically for linking files to multiple entities as
    reference.

- **sg_status_list** (:obj:`str`):
    Status value returned as the short code.

- **tag_list** (:obj:`list`):
    List of tags (as strings) that are currently assigned to the Attachment.

- **image** (:obj:`str`):
    The url location of the thumbnail image assigned to this Attachment. For uploads, Flow Production Tracking
    automatically tries to create a thumbnail from the file.
    See :ref:`interpreting_image_field_strings`. Alternatively, you can assign your
    own thumbnail to an Attachment using the :meth:`~shotgun_api3.Shotgun.upload_thumbnail` method.

- **sg_type** (:obj:`str`):
    An optional field for designating different types of Attachments

- **processing_status** (:obj:`str`):
    Reflects the status of the attachment (File entity).
    When processing the thumbnail, this field is set to ‘Thumbnail Pending’.


File type structures (``this_file``)
====================================

Depending on the type of file the Attachment entity is representing, the value of ``this_file``
will vary.

- **Uploads**
    Designated by ``link_type: 'upload'``, this represents a file that was uploaded to Flow Production Tracking.
    Uploading files to Flow Production Tracking can be done using the :meth:`~shotgun_api3.Shotgun.upload` method.
    You cannot create an Attachment with an uploaded file directly.

    ::

      {'content_type': 'image/jpeg',
       'link_type': 'upload',
       'name': 'western1FULL.jpg',
       'url': 'https://my-site.shotgrid.autodesk.com/file_serve/attachment/538'}

- **Web links**
    Designated by ``link_type: 'web'``, this is represents a url link. Examples include an
    ``http://`` link to another server or a custom protocol used to launch a local application
    like ``rvlink://`` or ``cinesync://``
    ::

      {'content_type': None,
       'link_type': 'web',
       'name': 'Join GUN12158',
       'url': 'cinesync://session/GUN12158'}

- **Local Files**
    Designated by ``link_type: 'local'``, this is represents a local file link. Additional keys
    are provided in order to give access to the relative path information on other platforms.

    .. seealso:: :ref:`local_files`

    ::

      {
        'content_type': 'video/quicktime',
        'link_type': 'local',
        'local_path': '/Users/kp/Movies/testing/test_movie_002.mov',
        'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov',
        'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov',
        'local_path_windows': 'M:\\macusers\\kp\\Movies\\testing\\test_movie_002.mov',
        'local_storage': {
          'id': 1,
          'name': 'Dailies Directories',
          'type': 'LocalStorage',
        },
        'name': 'my_test_movie.mov',
        'relative_path': 'testing/test_movie_002.mov',
        'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov',
      }


********************
Creating Attachments
********************

Web Links
=========
::

    myurl = {
      'url': 'http://apple.com/itunes',
      'name': 'Apple: iTunes'
    }
    data = {
        'this_file': myurl,
        'project': {'type':'Project','id':64}
    }
    result = sg.create('Attachment', data)


Uploads
=======
Uploads cannot be created directly on Attachments. Instead, you need to use the
:meth:`~shotgun_api3.Shotgun.upload` method.

Make sure to have retries for file uploads. Failures when uploading will occasionally happen. When
it does, immediately retrying to upload usually works.


Local Files
===========
See :ref:`creating_local_files`.

********************
Updating Attachments
********************
You cannot modify the ``this_file`` field after you create an Attachment. If you need to provide a
different file, you will have to create a new Attachment entity. Otherwise, the process for
updating Attachments is exactly like updating other entity types in Flow Production Tracking and is the same for all
Attachment types. See :meth:`~shotgun_api3.Shotgun.update` for more info.


********************
Deleting Attachments
********************
The process of deleting an Attachment is just like other entities in Flow Production Tracking. See
:meth:`~shotgun_api3.Shotgun.delete` for more info.

.. _local_files:

*****************************
Working With Local File Types
*****************************

We added support for linking to local files in the UI in Flow Production Tracking Server v2.1. This doc covers how
to work with these local file links using the API.

Requirements
============

- Python API v3.0.3+
- Flow Production Tracking Server v2.1.10+

Structure of Local File Values
==============================

There is a key in the dictionary that represents file/link fields called ``link_type`` which can be
one of ``local``, ``upload``, ``web``. This is used to determine what type of link the field value
contains. For local files this value is always ``local`` and there are additional keys that
are available:

- **content_type** (:obj:`str`):
    The mime-type of the associated local file. This is assigned
    automatically using a best-guess based on the file extension. You can override this by setting
    this explicitly.

- **link_type** (:obj:`str`) *read-only*:
    Always 'local' for local files.

- **name** (:obj:`str`):
    the display name of the local file. This is set to the filename by
    default but can be overridden by setting this explicitly.

- **local_path** (:obj:`str`):
    The full path to the file on the current platform. The Python API tries to determine the
    platform it is currently running on and then copies the value from the corresponding key above
    to this field for convenience.

- **local_path_linux** (:obj:`str`) *read-only*:
    Full path to file on Linux as defined by the LocalStorage (or ``None`` if no Linux path is set)

- **local_path_mac** (:obj:`str`) *read-only*:
    Full path to file on Mac OS X as defined by the LocalStorage (or ``None`` if no Mac path is set)

- **local_path_windows** (:obj:`str`) *read-only*:
    Full path to file on Windows as defined by the LocalStorage (or ``None`` if no Windows path
    is set)

- **local_storage** (:obj:`dict`) *read-only*:
    A dictionary representing which LocalStorage entity is applied for this local file link.

- **relative_path** (:obj:`str`) *read-only*:
    The path to the file relative to the ``local_storage`` root.

- **url** (:obj:`str`) *read-only*:
    A file URI (``file://``) path provided for convenience pointing to the value in the ``local_path``

Reading Local File Fields
=========================

::

    fields = ['sg_uploaded_movie']
    result = sg.find('Version', [['id', 'is', 123]], fields)

Returns::

    {
        'id': 123,
        'sg_uploaded_movie': {
            'content_type': None,
            'link_type': 'local',
            'local_path': '/Users/kp/Movies/testing/test_movie_001_.mov',
            'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_001_.mov',
            'local_path_mac': '/Users/kp/Movies/testing/test_movie_001_.mov',
            'local_path_windows': 'M:\\macusers\\kp\\Movies\\testing\\test_movie_001_.mov',
            'local_storage': {
                'id': 1,
                'name': 'Dailies Directories',
                'type': 'LocalStorage',
            },
            'relative_path': 'testing/test_movie_001_.mov',
            'name': 'my_test_movie.mov',
            'url': 'file:///Users/kp/Movies/testing/test_movie_001_.mov',
        },
        'type': 'Version',
    }

.. note::
    When viewing results that include file/link fields with local file link values, all of the
    keys will be returned regardless of whether there are values in them. So in the above example,
    if there was no Windows path set for the local storage, ``local_path_windows`` would be
    ``None``.

.. _creating_local_files:

Creating & Updating Local file Fields
=====================================

When setting a file/link field value to a local file, only the ``local_path`` is mandatory. Flow Production Tracking
will automatically select the appropriate matching local storage for your file based on the path.
You can optionally specify the ``name`` and ``content_type`` fields if you wish to override their
defaults. Any other keys that are provided will be ignored.

* **content_type** :obj:`str`:
    Optionally set the mime-type of the associated local file. This is assigned automatically
    using a best-guess based on the file extension.

* **name** :obj:`str`:
    Optional display name of the local file. This is set to the filename by default.

* **local_path** :obj:`str`:
    The full local path to the file. Flow Production Tracking will find the ``LocalStorage``
    that has the most specific match to this path and automatically assign that LocalStorage to
    the file.
    Alternative to ``relative_path``

* **local_storage** :obj:`dict`:
    The reference to an existing ``LocalStorage``.
    Must contain ``type: LocalStorage`` plus either an ``id`` or a ``name``

* **relative_path** :obj:`str`:
    The path to the file relative ``local_storage`` root.
    Requires ``local_storage``
    Only accepting slash ``/`` separated path. Does not accept Windows path.
    Alternative to ``local_path``

Example 1: Using ``local_path``
-------------------------------

::

    result = sg.update(
        'Version',
        123,
        {
            'sg_uploaded_movie': {
                'local_path': '/Users/kp/Movies/testing/test_movie_002.mov',
                'name': 'Better Movie',
            }
        )

Returns::

    {
        'id':123,
        'sg_uploaded_movie': {
            'content_type': 'video/quicktime',
            'link_type': 'local',
            'name': 'my_test_movie.mov',
            'local_path': '/Users/kp/Movies/testing/test_movie_002.mov'
            'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov'
            'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov'
            'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_002.mov'
            'local_storage': {
                'id': 1,
                'name': 'Dailies Directories',
                'type': 'LocalStorage'
            },
            'relative_path': 'testing/test_movie_002.mov',
            'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov',
        },
        'type': 'Version',
    }

The ``content_type`` was assigned a best-guess value based on the file extension. Flow Production Tracking selected
the most appropriate specific LocalStorage match and assigned it to local_storage automatically.


Example 2: Using ``relative_path``
----------------------------------

::

    result = sg.update(
        'Version',
        123,
        {
            'sg_uploaded_movie': {
                'local_storage': {
                    'type': 'LocalStorage',
                    'name': 'Dailies Directories',
                },
                'relative_path': 'testing/test_movie_002.mov',
            }
        )

Returns::

    {
        'id':123,
        'sg_uploaded_movie': {
            'content_type': 'video/quicktime',
            'link_type': 'local',
            'name': 'my_test_movie.mov',
            'local_path': '/Users/kp/Movies/testing/test_movie_002.mov',
            'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov',
            'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov',
            'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_002.mov',
            'local_storage': {
                'id': 1,
                'name': 'Dailies Directories',
                'type': 'LocalStorage'
            },
            'relative_path': 'testing/test_movie_002.mov',
            'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov'
        },
        'type': 'Version',
    }


Un-setting local file field values
==================================

Removing a a local file field value is simple. Just set the value to ``None``::

    data = {'sg_uploaded_movie': None}
    result = sg.update('Version', 123, data)

Returns::

    {'id':123,
     'sg_uploaded_movie': None,
     'type': 'Version'}]


================================================
FILE: docs/cookbook/examples/ami_handler.rst
================================================
.. _ami_handler:

###############################
Handling Action Menu Item Calls
###############################

This is an example ActionMenu Python class to handle the ``GET`` request sent from an
ActionMenuItem. It doesn't manage dispatching custom protocols but rather takes the arguments
from any ``GET`` data and parses them into the easily accessible and correctly typed instance
variables for your Python scripts.

Available as a Gist at https://gist.github.com/3253287

.. seealso::
    Our `support site has more information about Action Menu Items
    <https://developer.shotgridsoftware.com/python-api/cookbook/examples/ami_handler.html>`_.

************
GET vs. POST
************

Action Menu Items that open a url via `http` or `https` to another web server send their data
via ``POST``. If you're using a custom protocol the data is sent via ``GET``.

.. note::
    Browsers limit the length of a ``GET`` request. If you exceed this limit by attempting to
    select a lot of rows and launch your custom protocol, you may encounter
    "Failed to load resource" errors in your console.

.. seealso::
    This `Stack Overflow article "What is the maximum length of a URL in different browsers?"
    <http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers>`_

::

    #!/usr/bin/env python
    # encoding: utf-8

    # ---------------------------------------------------------------------------------------------
    # Description
    # ---------------------------------------------------------------------------------------------
    """
    The values sent by the Action Menu Item are in the form of a GET request that is similar to the
    format: myCoolProtocol://doSomethingCool?user_id=24&user_login=shotgun&title=All%20Versions&...

    In a more human-readable state that would translate to something like this:
    {
        'project_name': 'Demo Project',
         'user_id': '24',
         'title': 'All Versions',
         'user_login': 'shotgun',
         'sort_column': 'created_at',
         'entity_type': 'Version',
         'cols': 'created_at',
         'ids': '5,2',
         'selected_ids': '2,5',
         'sort_direction': 'desc',
         'project_id': '4',
         'session_uuid': 'd8592bd6-fc41-11e1-b2c5-000c297a5f50',
         'column_display_names':
        [
            'Version Name',
             'Thumbnail',
             'Link',
             'Artist',
             'Description',
             'Status',
             'Path to frames',
             'QT',
             'Date Created'
        ]
    }

    This simple class parses the url into easy to access types variables from the parameters,
    action, and protocol sections of the url. This example url
    myCoolProtocol://doSomethingCool?user_id=123&user_login=miled&title=All%20Versions&...
    would be parsed like this:

        (string) protocol: myCoolProtocol
        (string) action: doSomethingCool
        (dict)   params: user_id=123&user_login=miled&title=All%20Versions&...

    The parameters variable will be returned as a dictionary of string key/value pairs. Here's
    how to instantiate:

      sa = ShotgunAction(sys.argv[1]) # sys.argv[1]

      sa.params['user_login'] # returns 'miled'
      sa.params['user_id'] # returns 123
      sa.protocol # returns 'myCoolProtocol'
    """


    # ---------------------------------------------------------------------------------------------
    # Imports
    # ---------------------------------------------------------------------------------------------
    import sys, os
    import logging as logger

    # ---------------------------------------------------------------------------------------------
    # Variables
    # ---------------------------------------------------------------------------------------------
    # location to write logfile for this script
    # logging is a bit of overkill for this class, but can still be useful.
    logfile = os.path.dirname(sys.argv[0]) + "/shotgun_action.log"


    # ----------------------------------------------
    # Generic ShotgunAction Exception Class
    # ----------------------------------------------
    class ShotgunActionException(Exception):
        pass


    # ----------------------------------------------
    # ShotgunAction Class to manage ActionMenuItem call
    # ----------------------------------------------
    class ShotgunAction:
        def __init__(self, url):
            self.logger = self._init_log(logfile)
            self.url = url
            self.protocol, self.action, self.params = self._parse_url()

            # entity type that the page was displaying
            self.entity_type = self.params["entity_type"]

            # Project info (if the ActionMenuItem was launched from a page not belonging
            # to a Project (Global Page, My Page, etc.), this will be blank
            if "project_id" in self.params:
                self.project = {
                    "id": int(self.params["project_id"]),
                    "name": self.params["project_name"],
                }
            else:
                self.project = None

            # Internal column names currently displayed on the page
            self.columns = self.params["cols"]

            # Human readable names of the columns currently displayed on the page
            self.column_display_names = self.params["column_display_names"]

            # All ids of the entities returned by the query (not just those visible on the page)
            self.ids = []
            if len(self.params["ids"]) > 0:
                ids = self.params["ids"].split(",")
                self.ids = [int(id) for id in ids]

            # All ids of the entities returned by the query in filter format ready
            # to use in a find() query
            self.ids_filter = self._convert_ids_to_filter(self.ids)

            # ids of entities that were currently selected
            self.selected_ids = []
            if len(self.params["selected_ids"]) > 0:
                sids = self.params["selected_ids"].split(",")
                self.selected_ids = [int(id) for id in sids]

            # All selected ids of the entities returned by the query in filter format ready
            # to use in a find() query
            self.selected_ids_filter = self._convert_ids_to_filter(self.selected_ids)

            # sort values for the page
            # (we don't allow no sort anymore, but not sure if there's legacy here)
            if "sort_column" in self.params:
                self.sort = {
                    "column": self.params["sort_column"],
                    "direction": self.params["sort_direction"],
                }
            else:
                self.sort = None

            # title of the page
            self.title = self.params["title"]

            # user info who launched the ActionMenuItem
            self.user = {"id": self.params["user_id"], "login": self.params["user_login"]}

            # session_uuid
            self.session_uuid = self.params["session_uuid"]

        # ----------------------------------------------
        # Set up logging
        # ----------------------------------------------
        def _init_log(self, filename="shotgun_action.log"):
            try:
                logger.basicConfig(
                    level=logger.DEBUG,
                    format="%(asctime)s %(levelname)-8s %(message)s",
                    datefmt="%Y-%b-%d %H:%M:%S",
                    filename=filename,
                    filemode="w+",
                )
            except IOError as e:
                raise ShotgunActionException("Unable to open logfile for writing: %s" % e)
            logger.info("ShotgunAction logging started.")
            return logger

            # ----------------------------------------------

        # Parse ActionMenuItem call into protocol, action and params
        # ----------------------------------------------
        def _parse_url(self):
            logger.info("Parsing full url received: %s" % self.url)

            # get the protocol used
            protocol, path = self.url.split(":", 1)
            logger.info("protocol: %s" % protocol)

            # extract the action
            action, params = path.split("?", 1)
            action = action.strip("/")
            logger.info("action: %s" % action)

            # extract the parameters
            # 'column_display_names' and 'cols' occurs once for each column displayed so we store it as a list
            params = params.split("&")
            p = {"column_display_names": [], "cols": []}
            for arg in params:
                key, value = map(urllib.parse.unquote, arg.split("=", 1))
                if key == "column_display_names" or key == "cols":
                    p[key].append(value)
                else:
                    p[key] = value
            params = p
            logger.info("params: %s" % params)
            return (protocol, action, params)

        # ----------------------------------------------
        # Convert IDs to filter format to us in find() queries
        # ----------------------------------------------
        def _convert_ids_to_filter(self, ids):
            filter = []
            for id in ids:
                filter.append(["id", "is", id])
            logger.debug("parsed ids into: %s" % filter)
            return filter


    # ----------------------------------------------
    # Main Block
    # ----------------------------------------------
    if __name__ == "__main__":
        try:
            sa = ShotgunAction(sys.argv[1])
            logger.info("ShotgunAction: Firing... %s" % (sys.argv[1]))
        except IndexError as e:
            raise ShotgunActionException("Missing GET arguments")
        logger.info("ShotgunAction process finished.")


================================================
FILE: docs/cookbook/examples/ami_version_packager.rst
================================================
.. _ami_version_packager:

########################################################
Using an ActionMenuItem to Package Versions for a Client
########################################################

This is an example script to demonstrate how you can use an ActionMenuItem to launch a local
script to package up files for a client. It performs the following:

-  Downloads Attachments from a specified field for all selected entities.
-  Creates an archive.
-  Copies the archive to a specified directory.

It is intended to be used in conjunction with the script dicussed in :ref:`ami_handler`.

::

    #!/usr/bin/env python
    # encoding: utf-8
    """
    version_packager.py

    This example script is meant to be run from an ActionMenuItem in Flow Production Tracking. The menu item uses a custom
    protocol in order to launch this script, and is followed by the action 'package4client'. So the full
    url would be something like launchme://package4client?.... See:
    https://developer.shotgridsoftware.com/python-api/cookbook/examples/ami_handler.html

    It uses the example ActionMenu Python class also located in our docs for parsing the ActionMenuItem
    POST variables. For more information about it and accessing the variables in the ActionMenuItem POST request,
    See: http://developer.shotgridsoftware.com/python-api/examples/ami_handler

    The purpose of this script is to download attachment files from Flow Production Tracking, create an archive of them
    and copy them to a specified directory. You can invoke it with the following minimal example to connect
    to Flow Production Tracking, download any file that exists in the specified field ('sg_qt') for each selected_id passed from the
    ActionMenu. Then it will create a single archive of the files and move it to the specified directory
    ('/path/where/i/want/to/put/the/archive/'). The archive is named with the Project Name, timestamp, and user
    login who ran the ActionMenuItem ('Demo_Project_2010-04-29-172210_kp.tar.gz'):

        sa = ShotgunAction(sys.argv[1])
        sg = shotgun_connect()
        if sa.action == 'package4client':
            r = packageFilesForClient('sg_qt','/path/where/i/want/to/put/the/archive/')

    """

    # ---------------------------------------------------------------------------------------------
    # Imports
    # ---------------------------------------------------------------------------------------------
    import sys, os
    import logging as logger
    import subprocess
    import re
    from datetime import datetime

    from shotgun_api3 import Shotgun
    from shotgun_action import ShotgunAction
    from pprint import pprint

    # ---------------------------------------------------------------------------------------------
    # Variables
    # ---------------------------------------------------------------------------------------------
    # Flow Production Tracking server auth info
    shotgun_conf = {
        'url':'https://my-site.shotgrid.autodesk.com',
        'name':'YOUR_SCRIPT_NAME_HERE',
        'key':'YOUR_SCRIPT_KEY_HERE'
        }

    # location to write logfile for this script
    logfile = os.path.dirname(sys.argv[0])+"/version_packager.log"

    # temporary directory to download movie files to and create thumbnail files in
    file_dir = os.path.dirname(sys.argv[0])+"/tmp"

    # compress command
    # tar czf /home/user/backup_www.tar.gz -C / var/www/html
    compress_cmd = "tar czf %s -C / %s"



    # ----------------------------------------------
    # Generic Flow Production Tracking Exception Class
    # ----------------------------------------------
    class ShotgunException(Exception):
        pass



    # ----------------------------------------------
    # Set up logging
    # ----------------------------------------------
    def init_log(filename="version_packager.log"):
        try:
            logger.basicConfig(level=logger.DEBUG,
                            format='%(asctime)s %(levelname)-8s %(message)s',
                            datefmt='%Y-%b-%d %H:%M:%s',
                            filename=filename,
                            filemode='w+')
        except IOError, e:
            raise ShotgunException ("Unable to open logfile for writing: %s" % e)
        logger.info("Version Packager logging started.")
        return logger


    # ----------------------------------------------
    # Extract Attachment id from entity field
    # ----------------------------------------------
    def extract_attachment_id(attachment):
        # extract the Attachment id from the url location
        attachment_id = attachment['url'].rsplit('/',1)[1]
        try:
            attachment_id = int(attachment_id)
        except:
            # not an integer.
            return None
            # raise ShotgunException("invalid Attachment id returned. Expected an integer: %s "% attachment_id)

        return attachment_id


    # ----------------------------------------------
    # Download Movie to Disk
    # ----------------------------------------------
    def download_attachment_to_disk(attachment,destination_filename):
        attachment_id = extract_attachment_id(attachment)
        if type(attachment_id) != int:
            return None
        # download the attachment file from Flow Production Tracking and write it to local disk
        logger.info("Downloading Attachment #%s" % (attachment_id))
        stream = sg.download_attachment(attachment_id)
        try:
            file = open(destination_filename, 'w')
            file.write(stream)
            file.close()
            logger.info("Downloaded attachment %s" % (destination_filename))
            return True
        except e:
            raise ShotgunException("unable to write attachment to disk: %s"% e)


    # ----------------------------------------------
    # Compress files
    # ----------------------------------------------
    def compress_files(files,destination_filename):
        destination_filename += ".tar.gz"
        files = [path.lstrip("/") for path in files]
        squish_me = compress_cmd % (destination_filename, " ".join(files) )
        logger.info("Compressing %s files..." % len(files))
        logger.info("Running command: %s" % squish_me)
        try:
            output = subprocess.Popen(squish_me, shell=True, stdout=subprocess.PIPE).stdout.read()
            logger.info('tar/gzip command returned: %s' % output)
        except e:
            raise ShotgunException("unable compress files: %s"% e)
        logger.info("compressed files to: %s" % destination_filename)
        return destination_filename


    # ----------------------------------------------
    # Remove downloaded files
    # ----------------------------------------------
    def remove_downloaded_files(files):
        remove_me = 'rm %s' % ( " ".join(files) )
        logger.info("Removing %s files..." % len(files))
        logger.info("Running command: %s" % remove_me)
        try:
            output = subprocess.Popen(remove_me, shell=True, stdout=subprocess.PIPE).stdout.read()
            logger.info('rm command returned: %s' % output)
            logger.info("removed downloaded files")
            return True
        except e:
            logger.error("unable remove files: %s"% e)
            return False


    # ----------------------------------------------
    # Copy files
    # ----------------------------------------------
    def copy_files(files,destination_directory):
        if type(files) == list:
            files = " ".join(files)
        copy_me_args = "%s %s" % (files, destination_directory)
        logger.info("Running command: mv %s" % copy_me_args)
        try:
            result = subprocess.Popen("mv " + copy_me_args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            # 0 = success, 1 = recoverable issues
            if result.returncode > 0:
                response = result.stderr.read()
                logger.error("Copy failed: %s"% response)
                raise ShotgunException("Copy failed: %s"% response)
        except OSError, e:
            raise ShotgunException("unable copy files: %s"% e)

        logger.info("copied files to: %s" % destination_directory)
        return destination_directory



    def packageFilesForClient(file_field,destination_dir):

        # get entities matching the selected ids
        logger.info("Querying Shotgun for %s %ss" % (len(sa.selected_ids_filter), sa.params['entity_type']))
        entities = sg.find(sa.params['entity_type'],sa.selected_ids_filter,['id','code',file_field],filter_operator='any')

        # download the attachments for each entity, zip them, and copy to destination directory
        files = []
        for e in entities:
            if not e[file_field]:
                logger.info("%s #%s: No file exists. Skippinsa." % (sa.params['entity_type'], e['id']))
            else:
                logger.info("%s #%s: %s" % (sa.params['entity_type'], e['id'], e[file_field]))
                path_to_file = file_dir+"/"+re.sub(r"\s+", '_', e[file_field]['name'])
                result = download_attachment_to_disk(e[file_field], path_to_file )

                # only include attachments. urls won't return true
                if result:
                    files.append(path_to_file)

        # compress files
        # create a nice valid destination filename
        project_name = ''
        if 'project_name' in sa.params:
            project_name = re.sub(r"\s+", '_', sa.params['project_name'])+'_'
        dest_filename = project_name+datetime.today().strftime('%Y-%m-%d-%H%M%S')+"_"+sa.params['user_login']
        archive = compress_files(files,file_dir+"/"+dest_filename)

        # now that we have the archive, remove the downloads
        r = remove_downloaded_files(files)

        # copy to directory
        result = copy_files([archive],destination_dir)

        return True


    # ----------------------------------------------
    # Main Block
    # ----------------------------------------------
    if __name__ == "__main__":
        init_log(logfile)

        try:
            sa = ShotgunAction(sys.argv[1])
            logger.info("Firing... %s" % (sys.argv[1]) )
        except IndexError, e:
            raise ShotgunException("Missing POST arguments")

        sg = Shotgun(shotgun_conf['url'], shotgun_conf['name'], shotgun_conf['key'],convert_datetimes_to_utc=convert_tz)

        if sa.action == 'package4client':
            result = packageFilesForClient('sg_qt','/Users/kp/Documents/shotgun/dev/api/files/')
        else:
            raise ShotgunException("Unknown action... :%s" % sa.action)


        print("\nVersion Packager done!")


================================================
FILE: docs/cookbook/examples/basic_create_shot.rst
================================================
.. _example_create_shot:

Create A Shot
=============

Building the data and calling :meth:`~shotgun_api3.Shotgun.create`
------------------------------------------------------------------
To create a Shot, you need to provide the following values:

- ``project`` is a link to the Project the Shot belongs to. It should be a dictionary like
  ``{"type": "Project", "id": 123}`` where ``id`` is the ``id`` of the Project.
- ``code`` (this is the field that stores the name Shot)
- optionally any other info you want to provide

Example::

    data = {
        'project': {"type":"Project","id": 4},
        'code': '100_010',
        'description': 'Open on a beautiful field with fuzzy bunnies',
        'sg_status_list': 'ip'
    }
    result = sg.create('Shot', data)


This will create a new Shot named "100_010" in the Project "Gunslinger" (which has an ``id`` of 4).

- ``data`` is a list of key/value pairs where the key is the column name to update and the value
  is the the value to set.
- ``sg`` is the Flow Production Tracking API instance you created in :ref:`example_sg_instance`.
- ``create()`` is the :meth:`shotgun_api3.Shotgun.create` API method we are calling. We pass in the
  entity type we're searching for and the data we're setting.

.. rubric:: Result

The variable ``result`` now contains a dictionary hash with the Shot information you created.::

    {
        'code': '100_010',
        'description': 'Open on a beautiful field with fuzzy bunnies',
        'id': 40435,
        'project': {'id': 4, 'name': 'Demo Project', 'type': 'Project'},
        'sg_status_list': 'ip',
        'type': 'Shot'
    }

In addition, Flow Production Tracking has returned the ``id`` that it has assigned to the Shot, as well as a
``type`` value. ``type`` is provided for convenience simply to help you identify what entity type
this dictionary represents. It does not correspond to any field in Flow Production Tracking.

Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results
representing an entity.

The Complete Example for creating a Shot
----------------------------------------
::

    #!/usr/bin/env python

    # --------------------------------------
    # Imports
    # --------------------------------------
    import shotgun_api3
    from pprint import pprint # useful for debugging

    # --------------------------------------
    # Globals
    # --------------------------------------
    # make sure to change this to match your Flow Production Tracking server and auth credentials.
    SERVER_PATH = "https://my-site.shotgrid.autodesk.com"
    SCRIPT_NAME = 'my_script'
    SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1'

    # --------------------------------------
    # Main
    # --------------------------------------
    if __name__ == '__main__':

        sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY)

        # --------------------------------------
        # Create a Shot with data
        # --------------------------------------
        data = {
            'project': {"type":"Project","id": 4},
            'code': '100_010',
            'description': 'Open on a beautiful field with fuzzy bunnies',
            'sg_status_list': 'ip'
        }
        result = sg.create('Shot', data)
        pprint(result)
        print("The id of the {} is {}.".format(result['type'], result['id']))

And here is the output::

    {'code': '100_010',
     'description': 'Open on a beautiful field with fuzzy bunnies',
     'id': 40435,
     'project': {'id': 4, 'name': 'Demo Project', 'type': 'Project'},
     'sg_status_list': 'ip',
     'type': 'Shot'}
    The id of the Shot is 40435.


================================================
FILE: docs/cookbook/examples/basic_create_shot_task_template.rst
================================================
Create a Shot with a Task Template
==================================
Creating a new Shot with a Task Template is just like linking it to another entity, but Flow Production Tracking will apply the Task Template in the background and create the appropriate Tasks on the Shot for you.

Find the Task Template
----------------------
First we need to find the Task Template we're going to apply. We'll assume you know the name of the Task Template you want to use.
::

    filters = [['code','is', '3D Shot Template' ]]
    template = sg.find_one('TaskTemplate', filters)


The Resulting Task Template
---------------------------

Assuming the task template was found, we will now have something like this in our ``template``
variable::

    {'type': 'TaskTemplate', 'id': 12}

Create the Shot
---------------
Now we can create the Shot with the link to the ``TaskTemplate`` to apply.
::

    data = {'project': {'type': 'Project','id': 4},
            'code': '100_010',
            'description': 'dark shot with wicked cool ninjas',
            'task_template': template }
    result = sg.create('Shot', data)

This will create a new Shot named "100_010" linked to the TaskTemplate "3D Shot Template" and
Flow Production Tracking will then create the Tasks defined in the template and link them to the Shot you just
created.

- ``data`` is a list of key/value pairs where the key is the column name to update and the value is
  the value.
- ``project`` and `code` are required
- ``description`` is just a text field that you might want to update as well
- ``task_template`` is another entity column where we set the Task Template which has the Tasks we
  wish to create by default on this Shot. We found the specific template we wanted to assign in the
  previous block by searching

Create Shot Result
------------------
The variable ``result`` now contains the dictionary of the new Shot that was created.
::

    {
        'code': '010_010',
        'description': 'dark shot with wicked cool ninjas',
        'id': 2345,
        'project': {'id': 4, 'name': 'Gunslinger', 'type': 'Project'},
        'task_template': {'id': 12,
                       'name': '3D Shot Template',
                       'type': 'TaskTemplate'},
        'type': 'Shot'
    }


If we now search for the Tasks linked to the Shot, we'll find the Tasks that match our
``TaskTemplate``::

    tasks = sg.find('Task', filters=[['entity', 'is', result]])

.. note:: You can use an entity dictionary that was returned from the API in a filter as we have
    done above. Flow Production Tracking will only look at the ``id`` and ``type`` keys and will ignore the rest.
    This is a handy way to pass around entity dictionaries without having to reformat them.

Now the ``tasks`` variable contains the following::

    [{'id': 3253, 'type': 'Task'},
     {'id': 3254, 'type': 'Task'},
     {'id': 3255, 'type': 'Task'},
     {'id': 3256, 'type': 'Task'},
     {'id': 3257, 'type': 'Task'}]


================================================
FILE: docs/cookbook/examples/basic_create_version_link_shot.rst
================================================
Create a Version Linked to a Shot
=================================
You've just created a sweet new Version of your shot. Now you want to update Flow Production Tracking and create a
new ``Version`` entity linked to the Shot.

Find the Shot
-------------
First we need to find the Shot since we'll need to know know its ``id`` in order to link our Version
to it.
::

    filters = [ ['project', 'is', {'type': 'Project', 'id': 4}],
                ['code', 'is', '100_010'] ]
    shot = sg.find_one('Shot', filters)


Find the Task
-------------
Now we find the Task that the Version relates to, again so we can use the ``id`` to link it to the
Version we're creating. For this search we'll use the Shot ``id`` (which we have now in the ``shot``
variable from the previous search) and the Task Name, which maps to the ``content`` field.
::

    filters = [ ['project', 'is', {'type': 'Project', 'id': 4}],
                ['entity', 'is',{'type':'Shot', 'id': shot['id']}],
                ['content', 'is', 'Animation'] ]
    task = sg.find_one('Task', filters)

.. note:: Linking a Task to the Version is good practice. By doing so it is easy for users to see
    at what stage a particular Version was created, and opens up other possibilities for tracking
    in Flow Production Tracking. We highly recommend doing this whenever possible.

Create the Version
------------------
Now we can create the Version with the link to the Shot and the Task::

    data = { 'project': {'type': 'Project','id': 4},
             'code': '100_010_anim_v1',
             'description': 'first pass at opening shot with bunnies',
             'sg_path_to_frames': '/v1/gun/s100/010/frames/anim/100_010_animv1_jack.#.jpg',
             'sg_status_list': 'rev',
             'entity': {'type': 'Shot', 'id': shot['id']},
             'sg_task': {'type': 'Task', 'id': task['id']},
             'user': {'type': 'HumanUser', 'id': 165} }
    result = sg.create('Version', data)

This will create a new Version named '100_010_anim_v1' linked to the 'Animation' Task for Shot
'100_010' in the Project 'Gunslinger'.

- ``data`` is a list of key/value pairs where the key is the column name to update and the value is
  the value to set.
- ``project`` and ``code`` are required
- ``description`` and ``sg_path_to_frames`` are just text fields that you might want to update as
  well
- ``sg_status_list`` is the status column for the Version. Here we are setting it to "rev" (Pending
  Review) so that it will get reviewed in the next dailies session and people will "ooh" and "aaah".
- ``entity`` is where we link this version to the Shot. Entity columns are always handled with this
  format. You must provide the entity ``type`` and its ``id``.
- ``sg_task`` is another entity link field specifically for the Version's Task link.  This uses the
  same entity format as the Shot link, but pointing to the Task entity this time.
- ``user`` is another entity column where we set the artist responsible for this masterpiece. In
  this example, I know the 'id' that corresponds to this user, but if you don't know the id you can
  look it up by searching on any of the fields, similar to what we did for the Shot above, like::

    filters = [['login', 'is', 'jschmoe']]
    user = sg.find('HumanUser', filters)

The ``result`` variable now contains the ``id`` of the new Version that was created::

    214


Upload a movie for review in Screening Room
-------------------------------------------
If Screening Room's transcoding feature is enabled on your site (hosted sites have this by
default), then you can use the :meth:`~shotgun_api3.Shotgun.upload` method to upload a QuickTime
movie, PDF, still image, etc. to the ``sg_uploaded_movie`` field on a Version.  Once the movie is
uploaded, it will automatically be queued for transcoding.  When transcoding is complete, the
Version will be playable in the Screening Room app, or in the Overlay player by clicking on the
Play button that will appear on the Version's thumbnail.

.. note:: Transcoding also generates a thumbnail and filmstrip thumbnail automatically.


================================================
FILE: docs/cookbook/examples/basic_delete_shot.rst
================================================
Delete A Shot
=============

Calling :meth:`~shotgun_api3.Shotgun.delete`
--------------------------------------------
Deleting an entity in Flow Production Tracking is pretty straight-forward. No extraneous steps required.::

    result = sg.delete("Shot", 40435)

Delete Shot Result
------------------
If the Shot was deleted successfully ``result`` will contain::

    True

The Complete Example for deleting a Shot
----------------------------------------
::

    #!/usr/bin/env python

    # --------------------------------------
    # Imports
    # --------------------------------------
    import shotgun_api3
    from pprint import pprint # useful for debugging

    # --------------------------------------
    # Globals
    # --------------------------------------
    # make sure to change this to match your Flow Production Tracking server and auth credentials.
    SERVER_PATH = "https://my-site.shotgrid.autodesk.com"
    SCRIPT_NAME = 'my_script'
    SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1'

    # --------------------------------------
    # Main
    # --------------------------------------
    if __name__ == '__main__':

        sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY)

        # --------------------------------------
        # Delete a Shot by id
        # --------------------------------------
        result = sg.delete("Shot", 40435)
        pprint(result)

And here is the output::

    True


================================================
FILE: docs/cookbook/examples/basic_find_shot.rst
================================================
.. _example_find_shot:

Find a Shot
===========

Building the Query
------------------
We are going to assume we know the 'id' of the Shot we're looking for in this example.::

    filters = [['id', 'is', 40435]]
    result = sg.find_one('Shot', filters)

Pretty simple right? Well here's a little more insight into what's going on.

- ``filters`` is an list of filter conditions. In this example we are filtering for Shots where
  the ``id`` column is **40435**.
- ``sg`` is the Flow Production Tracking API instance.
- ``find_one()`` is the :meth:`~shotgun_api3.Shotgun.find_one` API method we are calling. We
  provide it with the entity type we're searching for and our filters.


Seeing the Result
-----------------
So what does this return? The variable result now contains::

    {'type': 'Shot','id': 40435}

By default, :meth:`~shotgun_api3.Shotgun.find_one` returns a single dictionary object with
the ``type`` and ``id`` fields. So in this example, we found a Shot matching that id, and Flow Production Tracking
returned it as a dictionary object with ``type`` and ``id`` keys .

How do we know that result contains the Shot dictionary object? You can trust us... but just to be
sure, the :mod:`pprint` (PrettyPrint) module from the Python standard library is a really good tool
to help with debugging. It will print out objects in a nicely formatted way that makes things
easier to read. So we'll add that to the import section of our script.::

    import shotgun_api3
    from pprint import pprint # useful for debugging

The Complete Example for finding a Shot
---------------------------------------
::

    #!/usr/bin/env python

    # --------------------------------------
    # Imports
    # --------------------------------------
    import shotgun_api3
    from pprint import pprint # useful for debugging

    # --------------------------------------
    # Globals
    # --------------------------------------
    # make sure to change this to match your Flow Production Tracking server and auth credentials.
    SERVER_PATH = "https://my-site.shotgrid.autodesk.com"
    SCRIPT_NAME = 'my_script'
    SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1'

    # --------------------------------------
    # Main
    # --------------------------------------
    if __name__ == '__main__':

        sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY)

        # --------------------------------------
        # Find a Shot by id
        # --------------------------------------
        filters = [['id', 'is', 40435]]
        result = sg.find_one('Shot', filters)
        pprint(result)

And here is the output::

    {'type': 'Shot','id': 40435}


================================================
FILE: docs/cookbook/examples/basic_sg_instance.rst
================================================
.. _example_sg_instance:

Create a Flow Production Tracking API instance
==============================================

This example shows you how to establish your initial connection to Flow Production Tracking using script-based
authentication. ``sg`` represents your Flow Production Tracking API instance. Be sure you've read
:ref:`Setting Up Flow Production Tracking for API Access <setting_up_shotgrid>`.
::

    import pprint # Useful for debugging

    import shotgun_api3

    SERVER_PATH = "https://my-site.shotgrid.autodesk.com"
    SCRIPT_NAME = 'my_script'
    SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1'

    sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY)

    # Just for demo purposes, this will print out property and method names available on the
    # sg connection object
    pprint.pprint([symbol for symbol in sorted(dir(sg)) if not symbol.startswith('_')])

For further information on what you can do with this Flow Production Tracking object you can read the
:ref:`API reference <apireference>`.


================================================
FILE: docs/cookbook/examples/basic_update_shot.rst
================================================
Update A Shot
=============

Building the data and calling :meth:`~shotgun_api3.Shotgun.update`
------------------------------------------------------------------
To update a Shot, you need to provide the ``id`` of the Shot and a list of fields you want to
update.::

    data = {
        'description': 'Open on a beautiful field with fuzzy bunnies',
        'sg_status_list': 'ip'
        }
    result = sg.update('Shot', 40435, data)

This will update the ``description`` and the ``sg_status_list`` fields for the Shot with ``id`` of
**40435**.

- ``data`` is a list of key/value pairs where the key is the field name to update and the value to
  update it to.
- ``sg`` is the Flow Production Tracking API instance.
- ``update()`` is the :meth:`shotgun_api3.Shotgun.update` API method we are calling. We provide it
  with the entity type we're updating, the ``id`` of the entity, and the data we're updating it
  with.

Result
------
The variable ``result`` now contains the Shot object that with the updated values.::

    {
        'description': 'Opening establishing shot with titles and fuzzy bunnies',
        'sg_status_list': 'ip',
        'type': 'Shot',
        'id': 40435
    }

In addition, Flow Production Tracking has returned the ``id`` for the Shot, as well as a ``type`` value. ``type``
is provided for convenience simply to help you identify what entity type this dictionary represents.
It does not correspond to any field in Flow Production Tracking.

Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results
representing an entity.

The Complete Example for updating a Shot
----------------------------------------
::

    #!/usr/bin/env python

    # --------------------------------------
    # Imports
    # --------------------------------------
    import shotgun_api3
    from pprint import pprint # useful for debugging

    # --------------------------------------
    # Globals
    # --------------------------------------
    # make sure to change this to match your Flow Production Tracking server and auth credentials.
    SERVER_PATH = "https://my-site.shotgrid.autodesk.com"
    SCRIPT_NAME = 'my_script'
    SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1'

    # --------------------------------------
    # Main
    # --------------------------------------
    if __name__ == '__main__':

        sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY)

        # --------------------------------------
        # Update Shot with data
        # --------------------------------------
        data = {
            'description': 'Open on a beautiful field with fuzzy bunnies',
            'sg_status_list': 'ip'
            }
        result = sg.update('Shot', 40435, data)
        pprint(result)

And here is the output::

    {'description': 'Opening establishing shot with titles and fuzzy bunnies',
     'id': 40435,
     'sg_status_list': 'ip',
     'type': 'Shot'}


================================================
FILE: docs/cookbook/examples/basic_upload_thumbnail_version.rst
================================================
Upload a Thumbnail for a Version
================================

So you've created a new Version of a Shot, and you've updated Flow Production Tracking, but now you want to upload a
beauty frame to display as the thumbnail for your Version. We'll assume you already have the image
made (located on your machine at ``/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg``) . And since
you've just created your Version in Flow Production Tracking, you know its ``id`` is **214**.

.. note:: If you upload a movie file or image to the ``sg_uploaded_movie`` field and you have
    transcoding enabled on your server (the default for hosted sites), a thumbnail will be
    generated automatically as well as a filmstrip thumbnail (if possible).
    This is a basic example of how to manually provide or replace a thumbnail image.

Upload the Image using :meth:`~shotgun_api3.Shotgun.upload_thumbnail`
---------------------------------------------------------------------
::

    sg.upload_thumbnail("Version", 214, "/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg")


Flow Production Tracking will take care of resizing the thumbnail for you. If something does go wrong, an exception
will be thrown and you'll see the error details.

.. note:: The result returned by :meth:`~shotgun_api3.Shotgun.upload_thumbnail` is an integer
    representing the id of a special ``Attachment`` entity in Flow Production Tracking. Working with Attachments
    is beyond the scope of this example. :)


================================================
FILE: docs/cookbook/examples/svn_integration.rst
================================================
.. _svn_integration:

############################
Subversion (SVN) Integration
############################

Integrating Flow Production Tracking with Subversion consists of two basic parts:

- Setup a post-commit hook in Subversion.
- Create a Flow Production Tracking API script to create the Revision in Flow Production Tracking. This script will be called by
  the post-commit hook.

****************
Post-Commit Hook
****************

To setup the post-commit hook:

- Locate the ``post-commit.tmpl`` file, which is found inside the ``hooks`` folder in your
  repository directory.  This is a template script that has lots of useful comments and can serve
  as a starting point for the real thing.
- Create your very own executable script, and save it in the same ``hooks`` folder, name it
  ``post-commit``, and give it executable permission.
- In your ``post-commit`` script, invoke your Flow Production Tracking API script.

If this is entirely new to you, we highly suggest reading up on the topic. O'Reilly has `a free
online guide for Subversion 1.5 and 1.6
<http://svnbook.red-bean.com/nightly/en/svn.reposadmin.create.html#svn.reposadmin.create.hooks>`_

Here's an example of a post-commit hook that we've made for Subversion 1.6 using an executable
Unix shell script.  The last line invokes "shotgun_api_script.py" which is our Python script that
will do all the heavy lifting.  Lines 4 thru 8 queue up some objects that we'll use later on.

.. code-block:: sh
   :linenos:

    #!/bin/sh
    # POST-COMMIT HOOK

    REPOS="$1"
    REV="$2"

    export AUTHOR="$(svnlook author $REPOS --revision $REV)"
    export COMMENT="$(svnlook log $REPOS --revision $REV)"

    /Absolute/path/to/shotgun_api_script.py

Explanation of selected lines
=============================

- lines ``4-5``: After the commit, Subversion leaves us two string objects in the environment:
  ``REPOS`` and ``REV``  (the repository path and the revision number, respectively).
- lines ``7-8``: Here we use the shell command ``export`` to create two more string objects in the
  environment:  ``AUTHOR`` and ``COMMENT``. To get each value, we use the ``svnlook`` command with
  our ``REPOS`` and ``REV`` values, first with the ``author``, and then with ``log`` subcommand.
  These are actually the first two original lines of code - everything else to this point was
  pre-written already in the ``post-commit.tmpl`` file. nice :)
- line ``10``: This is the absolute path to our Flow Production Tracking API Script.

***********************************
Flow Production Tracking API Script
***********************************

This script will create the Revision and populate it with some metadata using the Flow Production Tracking Python
API. It will create our Revision in Flow Production Tracking along with the author, comment, and because we use
Trac (a web-based interface for Subversion), it will also populate a URL field with a clickable
link to the Revision.

.. code-block:: python
   :linenos:


    #!/usr/bin/env python
    # ---------------------------------------------------------------------------------------------

    # ---------------------------------------------------------------------------------------------
    # Imports
    # ---------------------------------------------------------------------------------------------
    import sys
    from shotgun_api3_preview import Shotgun
    import os

    # ---------------------------------------------------------------------------------------------
    # Globals - update all of these values to those of your studio
    # ---------------------------------------------------------------------------------------------
    SERVER_PATH = 'https ://my-site.shotgrid.autodesk.com' # or http:
    SCRIPT_USER = 'script_name'
    SCRIPT_KEY = '3333333333333333333333333333333333333333'
    REVISIONS_PATH = 'https ://serveraddress/trac/changeset/' # or other web-based UI
    PROJECT = {'type':'Project', 'id':27}

    # ---------------------------------------------------------------------------------------------
    # Main
    # ---------------------------------------------------------------------------------------------
    if __name__ == '__main__':

       sg = Shotgun(SERVER_PATH, SCRIPT_USER, SCRIPT_KEY)

       # Set Python variables from the environment objects
       revision_code = os.environ['REV']
       repository = os.environ['REPOS']
       description = os.environ['COMMENT']
       author = os.environ['AUTHOR']

       # Set the Trac path for this specific revision
       revision_url = REVISIONS_PATH + revision_code

       # Validate that author is a valid Flow Production Tracking HumanUser
       result = sg.find_one("HumanUser", [['login', 'is', author]])
       if result:
           # Create Revision
           url = {'content_type':'http_url', 'url':revision_url, 'name':'Trac'}
           parameters = {'project':PROJECT,
                           'code':str(revision_code),
                           'description':description,
                           'attachment':url,
                           'created_by':{"type":"HumanUser", "id":result['id']}
                           }
           revision = sg.create("Revision", parameters)
           print("created Revision #"+str(revision_code))

       # Send error message if valid HumanUser is not found
       else:
           print("Unable to find a valid Flow Production Tracking User with login: {}, Revision not created in Flow Production Tracking.".format(author))



Explanation of selected lines:
==============================

- line ``14``: This should be the URL to your instance of Flow Production Tracking.
- lines ``15-16``: Make sure you get these values from the "Scripts" page in the Admin section of
  the Flow Production Tracking web application. If you're not sure how to do this, check out :ref:`authentication`.
- line ``17``: This is the address of Trac, our web-based interface that we use with Subversion.
  You may use a different interface, or none at all, so feel free to adjust this line or ignore it
  as your case may be.
- line ``18``: Every Revision in Flow Production Tracking must have a Project, which is passed to the API as a
  dictionary with two keys, the ``type`` and the ``id``.  Of course the ``type`` value will always
  remain ``Project`` (case sensitive), but the ``id`` will change by Project.  To find out the
  ``id`` of your Project, go to the Projects page in the Flow Production Tracking web application, locate the
  Project where you want your Revisions created, and then locate its ``id`` field (which you may
  need to display - if you don't see it, right click on any column header then select
  "Insert Column" > "Id").  Note that for this example we assume that all Revisions in this
  Subversion repository will belong to the same Project.
- lines ``28-31``: Grab the values from the objects that were left for us in the environment.
- line ``34``: Add the Revision number to complete the path of our Trac url.
- line ``37``: Make sure that a valid User exists in Flow Production Tracking.  In our example, we assume that our
  Users' Flow Production Tracking logins match their Subversion names.  If the user exists in Flow Production Tracking, that
  user's ``id`` will be returned as ``result['id']``, which we will need later on in line 46.
- lines ``40-48``: Use all the meta data we've gathered to create a Revision in Flow Production Tracking. If none
  of these lines make any sense, check out more on the :meth:`~shotgun_api3.Shotgun.create` method
  here.   Line 41 deserves special mention: notice that we define a dictionary called ``url`` that
  has three important keys: ``content_type``, ``url``, and ``name``, and we then pass this in as
  the value for the ``attachment`` field when we create the Revision.  If you're even in doubt,
  double check the syntax and requirements for the different field types here.

***************
Troubleshooting
***************

My post-commit script is simply not running. I can run it manually, but commits are not triggering it.
======================================================================================================

Make sure that the script is has explicitly been made executable and that all users who will
invoke it have appropriate permissions for the script and that folders going back to root.

My Flow Production Tracking API script is not getting called by the post-commit hook.
=====================================================================================

Make sure that the script is called using its absolute path.


================================================
FILE: docs/cookbook/smart_cut_fields.rst
================================================
.. _smart_cut_fields:

################
Smart Cut Fields
################

.. warning::
    Smart Cut Fields should be considered deprecated. ShotGrid v7.0, introduced a new version of
    cut support. `Read the Cut Support Documentation here <https://knowledge.autodesk.com/support/shotgrid/learn-explore/caas/CloudHelp/cloudhelp/ENU/SG-Editorial/files/SG-Editorial-ed-cut-schema-html-html.html>`_.

If you want to work with 'smart' cut fields through the API, your script should use a corresponding
'raw' fields for all updates. The 'smart_cut_fields' are primarily for display in the UI, the real
data is stored in a set of 'raw' fields that have different names.

************
Smart Fields
************

In the UI these fields attempt to calculate values based on data entered into the various fields.
These fields can be queried via the API using the find() method, but not updated. Note that we are
deprecating this feature and recommend creating your own cut fields from scratch, which will not
contain any calculations which have proven to be too magical at times.

- ``smart_cut_duration``
- ``smart_cut_in``
- ``smart_cut_out``
- ``smart_cut_summary_display`` *
- ``smart_cut_duration_display`` *
- ``smart_head_duration``
- ``smart_head_in``
- ``smart_head_out``
- ``smart_tail_duration``
- ``smart_tail_in``
- ``smart_tail_out``
- ``smart_working_duration`` *

.. note:: \* these are special summary fields that have no corresponding "raw" field.

**********
Raw Fields
**********

These are the "raw" fields that can be queried and updated through the API:

- ``cut_duration``
- ``cut_in``
- ``cut_out``
- ``head_duration``
- ``head_in``
- ``head_out``
- ``tail_duration``
- ``tail_in``
- ``tail_out``


================================================
FILE: docs/cookbook/tasks/split_tasks.rst
================================================
.. _split_tasks:

###########
Split Tasks
###########

Split tasks can be created and edited via the API but must comply to some rules. Before going
further, a good understanding of :ref:`how Flow Production Tracking handles task dates is useful <updating_tasks>`.

********
Overview
********

The Task entity has a field called ``splits`` which is a list of dictionaries. Each dictionary
in the list has two string keys, ``start`` and ``end``, who's values are strings representing dates
in the ``YYYY-mm-dd`` format.

::

    [{'start': '2012-12-11', 'end': '2012-12-12'}, {'start': '2012-12-18', 'end': '2012-12-19'}]

- Splits should be ordered from eldest to newest.
- There should be gaps between each split.

  - Gaps are defined as at least one working day. Non-workdays such as weekends and holidays
    are not gaps.

If there are multiple splits but there between two or more splits there is no gap, an error will be
raised. For example::

    >>> sg.update('Task', 2088, {'splits':[{'start':'2012-12-10', 'end':'2012-12-11'}, {'start':'2012-12-12', 'end':'2012-12-14'}, {'start':'2012-12-19', 'end':'2012-12-20'}]})
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/shotgun/src/python-api/shotgun_api3/shotgun.py", line 600, in update
        record = self._call_rpc("update", params)
      File "/shotgun/src/python-api/shotgun_api3/shotgun.py", line 1239, in _call_rpc
        self._response_errors(response)
      File "/shotgun/src/python-api/shotgun_api3/shotgun.py", line 1435, in _response_errors
        "Unknown Error"))
        shotgun_api3.shotgun.Fault: API update() CRUD ERROR #5: Update failed for [Task.splits]: (task.rb) The start date in split segment 2 is only one calendar day away from  the end date of the previous segment. There must be calendar days between split segments.

Alternately, a split value can be set to ``None`` to remove splits (you can also use an empty list).
This will preserve the ``start_date`` and ``due_date`` values but recalculate the ``duration`` value
while appropriately considering all workday rules in effect.

********************************************************
How Do Splits Influence Dates And Dates Influence Splits
********************************************************

- If splits are specified the supplied ``start_date``, ``due_date`` and ``duration`` fields will be ignored.
- The ``start_date`` will be inferred from the earliest split.
- The ``due_date`` will be inferred from the last split.
- If the ``start_date`` is changed on a task that has splits the first split will be moved to start
  on the new ``start_date`` and all further splits will be moved while maintaining gap lengths
  between splits and respecting workday rules.
- If the ``due_date`` is changed on a task that has splits the last split will be moved to end on
  the new ``due_date`` and all prior splits will be moved while maintaining gap lengths between
  splits and respecting workday rules.
- If the ``duration`` is changed two scenarios are possible

    - In the case of a longer duration, additional days will be added to the end of the last split
    - In the case of a shorter duration splits, starting with the latest ones, will be either
      removed or shortened until the new duration is met.

Examples for splitting Tasks
============================
Throughout the following examples, each successive one will build on the previous.

start_date, due_date and duration being ignored
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

    sg.update('Task', 2088, {
        'start_date': '2012-12-06',
        'due_date': '2012-12-23',
        'duration': 3600,
        'splits': [
            {'start': '2012-12-11', 'end': '2012-12-12'},
            {'start': '2012-12-18', 'end': '2012-12-19'}
        ]
    })

    # Task = {
    #     'start_date': '2012-12-11',
    #     'due_date': '2012-12-19',
    #     'duration': 2400,
    #     'splits': [
    #         {'start': '2012-12-11', 'end': '2012-12-12'},
    #         {'start': '2012-12-18', 'end': '2012-12-19'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_1.png

Moving the start_date of a split task
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

    sg.update('Task', 2088, {
        'start_date': '2012-12-10'
    })

    # Task = {
    #     'start_date': '2012-12-10',
    #     'due_date': '2012-12-18',
    #     'splits': [
    #         {'start': '2012-12-10', 'end': '2012-12-11'},
    #         {'start': '2012-12-14', 'end': '2012-12-18'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_2.png

Moving the due_date of a split task
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

::

    sg.update('Task', 2088, {
        'due_date': '2012-12-19'
    })

    # Task = {
    #     'start_date': '2012-12-10',
    #     'due_date': '2012-12-19',
    #     'splits': [
    #         {'start': '2012-12-10', 'end': '2012-12-11'},
    #         {'start': '2012-12-14', 'end': '2012-12-19'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_3.png

Setting a longer duration
~~~~~~~~~~~~~~~~~~~~~~~~~

::

    sg.update('Task', 2088, {
        'duration': 4200
    })

    # Task = {
    #     'start_date': '2012-12-10',
    #     'due_date': '2012-12-21',
    #     'duration': 4200,
    #     'splits': [
    #         {'start': '2012-12-10', 'end': '2012-12-11'},
    #         {'start': '2012-12-14', 'end': '2012-12-21'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_4.png

Setting a shorter duration
~~~~~~~~~~~~~~~~~~~~~~~~~~

::

    sg.update('Task', 2088, {
        'duration': 2400
    })

    # Task = {
    #     'start_date': '2012-12-10',
    #     'due_date': '2012-12-18',
    #     'duration': 2400,
    #     'splits': [
    #         {'start': '2012-12-10', 'end': '2012-12-11'},
    #         {'start': '2012-12-14', 'end': '2012-12-18'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_5.png

Another example of shorter duration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We won't be using the previous result for this example but rather, the following:

.. image:: /images/split_tasks_6.png

who's duration we will shorten past the last split.

::

    sg.update('Task', 2088, {
        'duration': 1800
    })

    # Task = {
    #     'start_date': '2012-12-10',
    #     'due_date': '2012-12-18',
    #     'duration': 2400,
    #     'splits': [
    #         {'start': '2012-12-10', 'end': '2012-12-11'},
    #         {'start': '2012-12-14', 'end': '2012-12-18'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_7.png

Setting the due_date in a gap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When a due date is set in a gap later splits are removed and the day of the due date is considered
a day when work will be done.

For this example let's assume as a starting point the result of the 5th example:

.. image:: /images/split_tasks_8.png

::

    sg.update('Task', 2088, {
        'due_date': '2012-12-13'
    })

    # Task = {
    #     'start_date': '2012-12-10',
    #     'due_date': '2012-12-13',
    #     'duration': 1800,
    #     'splits': [
    #         {'start': '2012-12-10', 'end': '2012-12-11'},
    #         {'start': '2012-12-13', 'end': '2012-12-13'}
    #     ]
    # }

Result:

.. image:: /images/split_tasks_9.png


================================================
FILE: docs/cookbook/tasks/task_dependencies.rst
================================================
.. _task_dependencies:

#################
Task Dependencies
#################

Task dependencies work the same way in the API as they do in the UI. You can filter and sort on
any of the fields. For information about Task Dependencies in Flow Production Tracking, check out the `main
documentation page on our support site
<https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Producer_pr_scheduling_tasks_pr_gantt_chart_tasks_html>`_

************
Create Tasks
************

Let's create a couple of Tasks and create dependencies between them. First we'll create a "Layout"
Task for our Shot::

    data = {
        'project': {'type':'Project', 'id':65},
        'content': 'Layout',
        'start_date': '2010-04-28',
        'due_date': '2010-05-05',
        'entity': {'type':'Shot', 'id':860}
        }
    result = sg.create(Task, data)


Returns::

    {'content': 'Layout',
     'due_date': '2010-05-05',
     'entity': {'id': 860, 'name': 'bunny_010_0010', 'type': 'Shot'},
     'id': 556,
     'project': {'id': 65, 'name': 'Demo Animation Project', 'type': 'Project'},
     'start_date': '2010-04-28',
     'type': 'Task'}


Now let's create an "Anm" Task for our Shot::

    data = {
        'project': {'type':'Project', 'id':65},
        'content': 'Anm',
        'start_date': '2010-05-06',
        'due_date': '2010-05-12',
        'entity': {'type':'Shot', 'id':860}
        }
    result = sg.create(Task, data)

Returns::

    {'content': 'Anm',
     'due_date': '2010-05-12',
     'entity': {'id': 860, 'name': 'bunny_010_0010', 'type': 'Shot'},
     'id': 557,
     'project': {'id': 65, 'name': 'Demo Animation Project', 'type': 'Project'},
     'start_date': '2010-05-06,
     'type': 'Task'}


*******************
Create A Dependency
*******************

Tasks each have an ``upstream_tasks`` field and a ``downstream_tasks`` field. Each field is a
list ``[]`` type and can contain zero, one, or multiple Task entity dictionaries representing the
dependent Tasks.
There are four dependency types from which you can choose: ``finish-to-start-next-day``, ``start-to-finish-next-day``, ``start-to-start``, ``finish-to-finish``.
If no dependency type is provided the default ``finish-to-start-next-day`` will be used.
Here is how to create a dependency between our "Layout" and "Anm" Tasks::

    # make 'Layout' an upstream Task to 'Anm'. (aka, make 'Anm' dependent on 'Layout') with finish-to-start-next-day dependency type
    data = {
        'upstream_tasks':[{'type':'Task','id':556, 'dependency_type': 'finish-to-start-next-day'}]
    }
    result = sg.update('Task', 557, data)

Returns::

    [{'id': 557,
      'type': 'Task',
      'upstream_tasks': [{'id': 556, 'name': 'Layout', 'type': 'Task'}]}]

This will also automatically update the `downstream_tasks` field on 'Layout' to include the 'Anm' Task.

***********************
Query Task Dependencies
***********************

Task Dependencies each have a ``dependent_task_id`` and a ``task_id`` fields.
They correspond to ``upstream_task`` and ``downstream_task`` ids of the dependency accordingly.
Here is how to get a TaskDependency using a ``dependent_task_id`` and a ``task_id`` fields::

    filters = [["dependent_task_id", "is", 72], ["task_id", "is", 75]]
    result = sg.find_one('TaskDependency', filters)

Returns::

    {'type': 'TaskDependency', 'id': 128}

****************************
Updating the Dependency type
****************************

When updating the dependency type for the existing dependencies,
update the ``dependency_type`` field of the TaskDependency directly::

    result = sg.update('TaskDependency', 128, {'dependency_type': 'start-to-start'})

Returns::

    {'dependency_type': 'start-to-start', 'type': 'TaskDependency', 'id': 128}

**********************************
Query Tasks with Dependency Fields
**********************************

So now lets look at the Tasks we've created and their dependency-related fields::

    filters = [
        ['entity', 'is', {'type':'Shot', 'id':860}]
    ]
    fields = [
        'content',
        'start_date',
        'due_date',
        'upstream_tasks',
        'downstream_tasks',
        'dependency_violation',
        'pinned'
        ]
    result = sg.find("Task", filters, fields)

Returns::

    [{'content': 'Layout',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'Anm', 'id': 557}],
      'due_date': '2010-05-05',
      'id': 556,
      'pinned': False,
      'start_date': '2010-04-28',
      'type': 'Task',
      'upstream_tasks': []},
     {'content': 'Anm',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'FX', 'id': 558}],
      'due_date': '2010-05-12',
      'id': 557,
      'pinned': False,
      'start_date': '2010-05-06',
      'type': 'Task',
      'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]},
    ...

*Note that we have also created additional Tasks for this Shot but we're going to focus on these
first two for simplicity.*

*****************************************************************
Updating the End Date on a Task with Downstream Task Dependencies
*****************************************************************

If we update the ``due_date`` field on our "Layout" Task, we'll see that the "Anm" Task dates
will automatically get pushed back to keep the dependency satisfied::

    result = sg.update('Task', 556, {'due_date': '2010-05-07'})

Returns::

    [{'due_date': '2010-05-07', 'type': 'Task', 'id': 556}]

Our Tasks now look like this (notice the new dates on the "Anm" Task)::

    [{'content': 'Layout',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'Anm', 'id': 557}],
      'due_date': '2010-05-07',
      'id': 556,
      'pinned': False,
      'start_date': '2010-04-28',
      'type': 'Task',
      'upstream_tasks': []},
     {'content': 'Anm',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'FX', 'id': 558}],
      'due_date': '2010-05-14',
      'id': 557,
      'pinned': False,
      'start_date': '2010-05-10',
      'type': 'Task',
      'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]},
    ...


**********************************************************
Creating a Dependency Violation by pushing up a Start Date
**********************************************************

Task Dependencies can work nicely if you are pushing out an end date for a Task as it will just
recalculate the dates for all of the dependent Tasks. But what if we push up the Start Date of our
"Anm" Task to start before our "Layout" Task is scheduled to end?

::

    result = sg.update('Task', 557, {'start_date': '2010-05-06'})

Returns::

    [{'type': 'Task', 'start_date': '2010-05-06', 'id': 557}]

Our Tasks now look like this::

    [{'content': 'Layout',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'Anm', 'id': 557}],
      'due_date': '2010-05-07',
      'id': 556,
      'pinned': False,
      'start_date': '2010-04-28',
      'type': 'Task',
      'upstream_tasks': []},
     {'content': 'Anm',
      'dependency_violation': True,
      'downstream_tasks': [{'type': 'Task', 'name': 'FX', 'id': 558}],
      'due_date': '2010-05-12',
      'id': 557,
      'pinned': True,
      'start_date': '2010-05-06',
      'type': 'Task',
      'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]},
     ...

Because the "Anm" Task ``start_date`` depends on the ``due_date`` of the "Layout" Task, this
change creates a dependency violation. The update succeeds, but Flow Production Tracking has also set the
``dependency_violation`` field to ``True`` and has also updated the ``pinned`` field to ``True``.

The ``pinned`` field simply means that if the upstream Task(s) are moved, the "Anm" Task will no
longer get moved with it. The dependency is still there (in ``upstream_tasks``) but the Task is
now "pinned" to those dates until the Dependency Violation is resolved.

***********************************************************
Resolving a Dependency Violation by updating the Start Date
***********************************************************

We don't want that violation there. Let's revert that change so the Start Date for "Anm" is after
the End Date of "Layout"::

    result = sg.update('Task', 557, {'start_date': '2010-05-10'})

Returns::

    [{'type': 'Task', 'start_date': '2010-05-10', 'id': 557}]

Our Tasks now look like this::

    [{'content': 'Layout',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'Anm', 'id': 557}],
      'due_date': '2010-05-07',
      'id': 556,
      'pinned': False,
      'start_date': '2010-04-28',
      'type': 'Task',
      'upstream_tasks': []},
     {'content': 'Anm',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'FX', 'id': 558}],
      'due_date': '2010-05-14',
      'id': 557,
      'pinned': True,
      'start_date': '2010-05-10',
      'type': 'Task',
      'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]},
     ...

The ``dependency_violation`` field has now been set back to ``False`` since there is no longer
a violation. But notice that the ``pinned`` field is still ``True``. We will have to manually
update that if we want the Task to travel with its dependencies again::

    result = sg.update('Task', 557, {'pinned': False})

Returns::

    [{'pinned': False, 'type': 'Task', 'id': 557}]

Our Tasks now look like this::

    [{'content': 'Layout',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'Anm', 'id': 557}],
      'due_date': '2010-05-07',
      'id': 556,
      'pinned': False,
      'start_date': '2010-04-28',
      'type': 'Task',
      'upstream_tasks': []},
     {'content': 'Anm',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'FX', 'id': 558}],
      'due_date': '2010-05-14',
      'id': 557,
      'pinned': False,
      'start_date': '2010-05-10',
      'type': 'Task',
      'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]},
     ...

Looks great. But that's an annoying manual process. What if we want to just reset a Task so that
it automatically gets updated so that the Start Date is after its dependent Tasks?

*******************************************************************
Updating the ``pinned`` field on a Task with a Dependency Violation
*******************************************************************

Let's go back a couple of steps to where our "Anm" Task had a Dependency Violation because we had
moved the Start Date up before the "Layout" Task End Date. Remember that the ``pinned`` field
was also ``True``. If we simply update the ``pinned`` field to be ``False``, Flow Production Tracking will also
automatically update the Task dates to satisfy the upstream dependencies and reset the
``dependency_violation`` value to ``False``::

    result = sg.update('Task', 557, {'pinned': False})

Returns::

    [{'pinned': False, 'type': 'Task', 'id': 557}]


Our Tasks now look like this::

    [{'content': 'Layout',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'Anm', 'id': 557}],
      'due_date': '2010-05-07',
      'id': 556,
      'pinned': False,
      'start_date': '2010-04-28',
      'type': 'Task',
      'upstream_tasks': []},
     {'content': 'Anm',
      'dependency_violation': False,
      'downstream_tasks': [{'type': 'Task', 'name': 'FX', 'id': 558}],
      'due_date': '2010-05-14',
      'id': 557,
      'pinned': False,
      'start_date': '2010-05-10',
      'type': 'Task',
      'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]},
    ...


Notice by updating ``pinned`` to ``False``, Flow Production Tracking also updated the ``start_date`` and
``due_date`` fields of our "Anm" Task so it will satisfy the upstream Task dependencies. And since
that succeeded, the ``dependency_violation`` field has also been set to ``False``

*******************************************
``dependency_violation`` field is read-only
*******************************************

The ``dependency_violation`` field is the only dependency-related field that is read-only. Trying
to modify it will generate a Fault::

    result = sg.update('Task', 557, {'dependency_violation': False})

Returns::

    # --------------------------------------------------------------------------------
    # XMLRPC Fault 103:
    # API update() Task.dependency_violation is read only:
    # {"value"=>false, "field_name"=>"dependency_violation"}
    # --------------------------------------------------------------------------------
    # Traceback (most recent call last):
    # ...


================================================
FILE: docs/cookbook/tasks/updating_tasks.rst
================================================
.. _updating_tasks:

########################################################
Updating Task Dates: How Flow Production Tracking Thinks
########################################################

When updating Task dates in an API update() request, there is no specified order to the values that
are passed in. Flow Production Tracking also does automatic calculation of the``start_date``,``due_date``, and ``duration`` fields for convenience. In order to clarify how updates are handled by Flow Production Tracking we are
providing some general rules below and examples of what will happen when you make updates to your
Tasks.

**************
General Rules
**************

- Updating the ``start_date`` automatically updates the ``due_date`` (``duration`` remains constant)
- Updating the ``due_date`` automatically updates the ``duration`` (``start_date`` remains constant)
- Updating the ``duration`` automatically updates the ``due_date`` (``start_date`` remains constant)
- When updating Task values, Flow Production Tracking sets schedule fields (``milestone``, ``duration``,
  ``start_date``, ``due_date``) after all other fields, because the Project and Task Assignees
  affect schedule calculations.
- If ``start_date`` and ``due_date`` are both set, ``duration`` is ignored (``duration`` can often
  be wrong since it's easy to calculate scheduling incorrectly).
- If both ``start_date`` and ``due_date`` are provided, Flow Production Tracking sets ``start_date`` before
  ``due_date``.
- Set ``milestone`` before other schedule fields (because ``start_date``, ``due_date``, and
  ``duration`` get lost if ``milestone`` is not set to ``False`` first)
- If ``milestone`` is being set to ``True``, ``duration`` is ignored.
- If ``milestone`` is set to ``True`` and ``start_date`` and ``due_date`` are also being set to
  conflicting values, an Exception is raised.
- If ``due_date`` and ``duration`` are set together (without ``start_date``), ``duration`` is set
  first, then ``due_date`` (otherwise setting ``duration`` will change ``due_date`` after it is
  set).

***************************
Examples for updating Tasks
***************************

The following examples show what the resulting Task object will look like after being run on the
initial Task object listed under the header of each section.

The ``duration`` values in the following examples assume your Flow Production Tracking instance is set to
10-hour work days. If your server is configured with a different setting, the ``duration`` values
will vary.

.. note:: The ``duration`` field stores ``duration`` values in minutes


----

.. rubric:: Universal

Regardless of current values on the Task, this behavior rules::

    Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

**Update start_date and due_date**

``duration`` is ignored if also provided. It is instead set automatically as (``due_date`` -
``start_date``)

::

    sg.update ('Task', 123, {'start_date':'2011-05-25', 'due_date':'2011-05-30', 'duration':1200})
    # Task = {'start_date': '2011-05-25', 'due_date': '2011-05-30', 'duration': 2400, 'id':123}

- ``start_date`` is updated.
- ``due_date`` is updated.
- ``duration`` is calculated as (``due_date`` - ``start_date``)

.. note:: The value provided in the update() is ignored (and in this case was also incorrect).

**Update start_date and duration**

::

    sg.update ('Task', 123, {'start_date':'2011-05-25', 'duration':3600})
    # Task = {'start_date': '2011-05-25', 'due_date': '2011-06-01', 'duration': 3600, 'id':123}

- ``start_date`` is updated.
- ``duration`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``).

**Update due_date and duration**

::

    sg.update ('Task', 123, {'due_date': '2011-05-20', 'duration':3600})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-20', 'duration': 600, 'id':123}

- ``duration`` is updated.
- ``due_date`` is updated.
- ``duration`` is calculated as (``due_date`` - ``start_date``)

.. note:: This means the ``duration`` provided is overwritten.


----

.. rubric:: Task has start_date only

If the Task only has a ``start_date`` value and has no other date values, this is how updates
will behave.

::

    Task = {'start_date': '2011-05-20', 'due_date': None, 'duration': None, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-25', 'due_date': None, 'duration': None, 'id':123}

- Only ``start_date`` is updated.

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

- ``due_date`` is updated.
- ``duration`` is updated to (``due_date`` - ``start_date``).

**Update duration**

::

    sg.update ('Task', 123, {'duration':2400})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

- ``duration`` is updated.
- ``due_date`` is set to (``start_date`` + ``duration``)


----

.. rubric:: Task has due_date only

If the Task only has a ``due_date`` value and has no other date values, this is how updates
will behave.

::

    # Task = {'start_date': None, 'due_date': '2011-05-25', 'duration': None, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-20'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

- ``start_date`` is updated.
- ``duration`` is updated to (``due_date`` - ``start_date``).

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-20'})
    # Task = {'start_date': None, 'due_date': '2011-05-20', 'duration': None, 'id':123}

- only ``due_date`` is updated.

**Update duration**

::

    sg.update ('Task', 123, {'duration':2400})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

- ``duration`` is updated.
- ``start_date`` is set to (``due_date`` - ``duration``)


----

.. rubric:: Task has duration only

If the Task only has a ``duration`` value and has no other date values, this is how updates
will behave.

::

    # Task = {'start_date': None, 'due_date': None, 'duration': 2400, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-20'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

- ``start_date`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``).

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

- ``due_date`` is updated.
- ``start_date`` is updated to (``due_date`` - ``duration``)

**Update duration**

::

    sg.update ('Task', 123, {'duration':3600})
    # Task = {'start_date': None, 'due_date': None, 'duration': 3600, 'id':123}

- only ``duration`` is updated.


----

.. rubric:: Task has start_date and due_date

If the Task has ``start_date`` and ``due_date`` values but has no ``duration``, this is how updates
will behave.

::

    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': None, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-25', 'due_date': '2011-05-25', 'duration': 600, 'id':123}

- ``start_date`` is updated.
- ``duration`` is updated to (``due_date`` - ``start_date``).

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-30'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-30', 'duration': 4200, 'id':123}

- ``due_date`` is updated.
- ``duration`` is updated to (``due_date`` - ``start_date``)

**Update duration**

::

    sg.update ('Task', 123, {'duration':3600})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-27', 'duration': 3600, 'id':123}

- ``duration`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``)


----

.. rubric:: Task has start_date and duration

If the Task has ``start_date`` and ``duration`` values but has no ``due_date``, this is how updates
will behave.

::

    # Task = {'start_date': '2011-05-20', 'due_date': None, 'duration': 2400, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-25', 'due_date': '2011-05-30', 'duration': 2400, 'id':123}

- ``start_date`` is updated.
- ``due_date`` is updated to (``start_date`` +``duration``).

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-30'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-30', 'duration': 4200, 'id':123}

- ``due_date`` is updated.
- ``duration`` is updated to (``due_date`` - ``start_date``).

**Update duration**

::

    sg.update ('Task', 123, {'duration':3600})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-27', 'duration': 3600, 'id':123}

- ``duration`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``)


----

.. rubric:: Task has due_date and duration

If the Task has ``due_date`` and ``duration`` values but has no ``start_date``, this is how updates
will behave.

::

    # Task = {'start_date': None, 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-25', 'due_date': '2011-05-30', 'duration': 2400, 'id':123}

- ``start_date`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``).

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-30'})
    # Task = {'start_date': '2011-05-25', 'due_date': '2011-05-30', 'duration': 2400, 'id':123}

- ``due_date`` is updated.
- ``start_date`` is updated to (``due_date`` - ``duration``).

**Update duration**

::

    sg.update ('Task', 123, {'duration':3600})
    # Task = {'start_date': '2011-05-18', 'due_date': '2011-05-25', 'duration': 3600, 'id':123}

- ``duration`` is updated.
- ``start_date`` is updated to (``due_date`` - ``duration``)


----

.. rubric:: Task has start_date ,due_date, and duration

If the Task has ``start_date``, ``due_date``, and ``duration``, this is how updates
will behave.

::

    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': 2400, 'id':123}

**Update start_date**

::

    sg.update ('Task', 123, {'start_date':'2011-05-25'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-30', 'duration': 2400, 'id':123}

- ``start_date`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``).

**Update due_date**

::

    sg.update ('Task', 123, {'due_date':'2011-05-30'})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-30', 'duration': 4200, 'id':123}

- ``due_date`` is updated.
- ``duration`` is updated to (``due_date`` - ``start_date``)

**Update duration**

::

    sg.update ('Task', 123, {'duration':3600})
    # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-27', 'duration': 3600, 'id':123}

- ``duration`` is updated.
- ``due_date`` is updated to (``start_date`` + ``duration``)


================================================
FILE: docs/cookbook/tasks.rst
================================================
##################
Working With Tasks
##################

Tasks have various special functionality available in the UI that can also be queried and
manipulated through the API. The sections below cover these topics.

.. toctree::
    :maxdepth: 2

    tasks/updating_tasks
    tasks/task_dependencies
    tasks/split_tasks


================================================
FILE: docs/cookbook/tutorials.rst
================================================
########
Examples
########

Here's a list of various simple tutorials to walk through that should provide you with a good base
understanding of how to use the Flow Production Tracking API and what you can do with it.

*****
Basic
*****

.. toctree::
    :maxdepth: 1

    examples/basic_sg_instance
    examples/basic_create_shot
    examples/basic_find_shot
    examples/basic_update_shot
    examples/basic_delete_shot
    examples/basic_create_shot_task_template
    examples/basic_create_version_link_shot
    examples/basic_upload_thumbnail_version

********
Advanced
********

.. toctree::
    :maxdepth: 1

    examples/ami_handler
    examples/ami_version_packager
    examples/svn_integration


================================================
FILE: docs/cookbook/usage_tips.rst
================================================
##############
API Usage Tips
##############

Below is a list of helpful tips when using the Flow Production Tracking API3. We have tried to make the API very
simple to use with predictable results while remaining a powerful tool to integrate with your
pipeline. However, there's always a couple of things that crop up that our users might not be
aware of. Those are the types of things you'll find below. We'll be adding to this document over
time as new questions come up from our users that exhibit these types of cases.

*********
Importing
*********

We strongly recommend you import the entire `shotgun_api3` module instead of just importing the
:class:`shotgun_api3.Shotgun` class from the module. There is other important functionality that
is managed at the module level which may not work as expected if you only import the
:class:`shotgun_api3.Shotgun` object.

Do::

    import shotgun_api3

Don't::

    from shotgun_api3 import Shotgun

***************
Multi-threading
***************
The Flow Production Tracking API is not thread-safe. If you want to do threading we strongly suggest that you use
one connection object per thread and not share the connection.

.. _entity-fields:

*************
Entity Fields
*************

When you do a :meth:`~shotgun_api3.Shotgun.find` or a :meth:`~shotgun_api3.Shotgun.create` call
that returns a field of type **entity** or **multi-entity** (for example the 'Assets' column on Shot),
the entities are returned in a standard dictionary::

    {'type': 'Asset', 'name': 'redBall', 'id': 1}

For each entity returned, you will get a ``type``, ``name``, and ``id`` key. This does not mean
there are fields named ``type`` and ``name`` on the Asset. These are only used to provide a
consistent way to represent entities returned via the API.

- ``type``: the entity type (CamelCase)
- ``name``: the display name of the entity. For most entity types this is the value of the ``code``
  field but not always. For example, on the Ticket and Delivery entities the ``name`` key would
  contain the value of the ``title`` field.

.. _custom_entities:

**************
CustomEntities
**************
Entity types are always referenced by their original names. So if you enable CustomEntity01 and
call it **Widget**. When you access it via the API, you'll still use CustomEntity01 as the
``entity_type``.

If you want to be able to remember what all of your CustomEntities represent in a way where you
don't need to go look it up all the time when you're writing a new script, we'd suggest creating
a mapping table or something similar and dumping it in a shared module that your studio uses.
Something like the following::

    # studio_globals.py

    entity_type_map = {
      'Widget': 'CustomEntity01',
      'Foobar': 'CustomEntity02',
      'Baz': 'CustomNonProjectEntity01,
    }

    # or even simpler, you could use a global like this
    ENTITY_WIDGET = 'CustomEntity01'
    ENTITY_FOOBAR = 'CustomEntity02'
    ENTITY_BAZ = 'CustomNonProjectEntity01'

Then when you're writing scripts, you don't need to worry about remembering which Custom Entity
"Foobars" are, you just use your global::

    import shotgun_api3
    import studio_globals

    sg = shotgun_api3.Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', '0123456789abcdef0123456789abcdef0123456')
    result = sg.find(studio_globals.ENTITY_WIDGET,
                     filters=[['sg_status_list', 'is', 'ip']],
                     fields=['code', 'sg_shot'])

.. _connection_entities:

******************
ConnectionEntities
******************

Connection entities exist behind the scenes for any many-to-many relationship. Most of the time
you won't need to pay any attention to them. But in some cases, you may need to track information
on the instance of one entity's relationship to another.

For example, when viewing a list of Versions on a Playlist, the Sort Order (``sg_sort_order``) field is an
example of a field that resides on the connection entity between Playlists and Versions. This
connection entity is appropriately called `PlaylistVersionConnection`. Because any Version can
exist in multiple Playlists, the sort order isn't specific to the Version, it's specific to
each _instance_ of the Version in a Playlist. These instances are tracked using connection
entities in Shtogun and are accessible just like any other entity type in Flow Production Tracking.

To find information about your Versions in the Playlist "Director Review" (let's say it has an
``id`` of 4). We'd run a query like so::

    filters = [['playlist', 'is', {'type':'Playlist', 'id':4}]]
    fields = ['playlist.Playlist.code', 'sg_sort_order', 'version.Version.code', 'version.Version.user', 'version.Version.entity']
    order=[{'column':'sg_sort_order','direction':'asc'}]
    result = sg.find('PlaylistVersionConnection', filters, fields, order)


Which returns the following::

    [{'id': 28,
      'playlist.Playlist.code': 'Director Review',
      'sg_sort_order': 1.0,
      'type': 'PlaylistVersionConnection',
      'version.Version.code': 'bunny_020_0010_comp_v003',
      'version.Version.entity': {'id': 880,
                                 'name': 'bunny_020_0010',
                                 'type': 'Shot'},
      'version.Version.user': {'id': 19, 'name': 'Artist 1', 'type': 'HumanUser'}},
     {'id': 29,
      'playlist.Playlist.code': 'Director Review',
      'sg_sort_order': 2.0,
      'type': 'PlaylistVersionConnection',
      'version.Version.code': 'bunny_020_0020_comp_v003',
      'version.Version.entity': {'id': 881,
                                 'name': 'bunny_020_0020',
                                 'type': 'Shot'},
      'version.Version.user': {'id': 12, 'name': 'Artist 8', 'type': 'HumanUser'}},
     {'id': 30,
      'playlist.Playlist.code': 'Director Review',
      'sg_sort_order': 3.0,
      'type': 'PlaylistVersionConnection',
      'version.Version.code': 'bunny_020_0030_comp_v003',
      'version.Version.entity': {'id': 882,
                                 'name': 'bunny_020_0030',
                                 'type': 'Shot'},
      'version.Version.user': {'id': 33, 'name': 'Admin 5', 'type': 'HumanUser'}},
     {'id': 31,
      'playlist.Playlist.code': 'Director Review',
      'sg_sort_order': 4.0,
      'type': 'PlaylistVersionConnection',
      'version.Version.code': 'bunny_020_0040_comp_v003',
      'version.Version.entity': {'id': 883,
                                 'name': 'bunny_020_0040',
                                 'type': 'Shot'},
      'version.Version.user': {'id': 18, 'name': 'Artist 2', 'type': 'HumanUser'}},
     {'id': 32,
      'playlist.Playlist.code': 'Director Review',
      'sg_sort_order': 5.0,
      'type': 'PlaylistVersionConnection',
      'version.Version.code': 'bunny_020_0050_comp_v003',
      'version.Version.entity': {'id': 884,
                                 'name': 'bunny_020_0050',
                                 'type': 'Shot'},
      'version.Version.user': {'id': 15, 'name': 'Artist 5', 'type': 'HumanUser'}}]


- ``version`` is the Version record for this connection instance.
- ``playlist`` is the Playlist record for this connection instance.
- ``sg_sort_order`` is the sort order field on the connection instance.

We can pull in field values from the linked Playlist and Version entities using dot notation like
``version.Version.code``. The syntax is ``fieldname.EntityType.fieldname``. In this example,
``PlaylistVersionConnection`` has a field named ``version``. That field contains a ``Version``
entity. The field we are interested on the Version is ``code``. Put those together with our f
riend the dot and we have ``version.Version.code``.

************************************************************
Flow Production Tracking UI fields not available via the API
************************************************************

Summary type fields like Query Fields and Pipeline Step summary fields are currently only available
via the UI. Some other fields may not work as expected through the API because they are "display
only" fields made available for convenience and are only available in the browser UI.

HumanUser
=========

- ``name``: This is a UI-only field that is a combination of the ``firstname`` + ``' '`` +
  ``lastname``.

Shot
====

**Smart Cut Fields**: These fields are available only in the browser UI. You can read more about
smart cut fields and the API in the :ref:`Smart Cut Fields doc <smart_cut_fields>`::

    smart_cut_in
    smart_cut_out
    smart_cut_duration
    smart_cut_summary_display
    smart_duration_summary_display
    smart_head_in
    smart_head_out
    smart_head_duration
    smart_tail_in
    smart_tail_out
    smart_tail_duration
    smart_working_duration


Pipeline Step summary fields on entities
========================================

The Pipeline Step summary fields on entities that have Tasks aren't currently available via the API
and are calculated on the client side in the UI. These fields are like ``step_0``, or ``step_13``.
Note that the Pipeline Step entity itself is available via the API as the entity type ``Step``.

Query Fields
============

Query fields are also summary fields like Pipeline Steps, the query is run from the client side UI
and therefore is not currently supported in the API.

************
Audit Fields
************
You can set the ``created_by`` and ``created_at`` fields via the API at creation time. This is
often useful for when you're importing or migrating data from another source and want to keep the
history in tact. However, you cannot set the ``updated_by`` and ``updated_at`` fields. These are
automatically set whenever an entity is created or updated.

.. _logging:

*****************************
Logging Messages from the API
*****************************

The API uses standard python logging but does not define a handler.

To see the logging output in stdout, define a streamhandler in your script::

    import logging
    import shotgun_api3 as shotgun
    logging.basicConfig(level=logging.DEBUG)

To write logging output from the Flow Production Tracking API to a file, define a file handler in your script::

    import logging
    import shotgun_api3 as shotgun
    logging.basicConfig(level=logging.DEBUG, filename='/path/to/your/log')

To suppress the logging output from the API in a script which uses logging, set the level of the
Flow Production Tracking logger to a higher level::

    import logging
    import shotgun_api3 as shotgun
    sg_log = logging.getLogger('shotgun_api3')
    sg_log.setLevel(logging.ERROR)

*************
Optimizations
*************

.. _combining-related-queries:

Combining Related Queries
=========================
Reducing round-trips for data via the API can significantly improve the speed of your application.
Much like "Bubble Fields" / "Field Hopping" in the UI, we can poll Flow Production Tracking for data on the fields
of entities linked to our main query, both as a part of the query parameters as well as in the
data returned.

Starting with a simple and common example, many queries require knowing what project your data is
associated with. Without using "field hopping" in an API call, you would first get the project and
then use that data for your follow up query, like so::

    # Get the project
    project_name = 'Big Buck Bunny'
    sg_project = sg.find("Project", [['name', 'is', project_name]])

    # Use project result to get associated shots
    sg_shots = sg.find("Shot", [['project', 'is', sg_project]], ['code'])

With "field hopping" you can combine these queries into::

    # Get all shots on 'Big Buck Bunny' project
    project_name = 'Big Buck Bunny'
    sg_shots = sg.find("Shot", [['project.Project.name', 'is', project_name]], ['code'])

As you can see above, the syntax is to use "``.``" dot notation, joining field names to entity
types in a chain. In this example we start with the field ``project`` on the ``Shot`` entity, then
specify we're looking for the "name" field on the Project entity by specifying ``Project.name``.

Now that we've demonstrated querying using dot notation, let's take a look at returning linked data
by adding the status of each Sequence entity associated with each Shot in our previous query::

    # Get shot codes and sequence status all in one query
    project_name = 'Big Buck Bunny'
    sg_shots = sg.find("Shot", [['project.Project.name', 'is', project_name]],
                       ['code', 'sg_sequence.Sequence.sg_status_list'])

The previous examples use the :meth:`~shotgun_api3.Shotgun.find` method. However, it's also applicable
to the :meth:`~shotgun_api3.Shotgun.create` method.

.. note::
    Due to performance concerns with deep linking, we only support using dot notation chains for
    single-entity relationships. This means that if you try to pull data through a multi-entity
    field you will not get the desired result.


================================================
FILE: docs/cookbook.rst
================================================
************
API Cookbook
************

Here we have a collection of useful information you can use for reference when writing your API
scripts. From usage tips and gotchas to deeper examples of working with entities like Tasks and
Files, there's a lot of example code in here for you to play with.

.. rubric:: Usage Tips

These are some best-practices and good guidelines to follow when developing your scripts.
You'll also find some gotchas you can avoid.

.. toctree::
    :maxdepth: 2

    cookbook/usage_tips

.. rubric:: Examples

Some basic example scripts that we walk you through from beginning to end. Feel free to copy
and paste any of these into your own scripts.

.. toctree::
    :maxdepth: 3

    cookbook/tutorials

.. rubric:: Working With Files

You'll probably be doing some work with files at your studio. This is a deep dive into some of
the inners of how Flow Production Tracking handles files (also called Attachments) and the different ways to link
to them.

.. toctree::
    :maxdepth: 2

    cookbook/attachments

.. rubric:: Working With Tasks

Scheduling is a complex beast. Flow Production Tracking can handle lots of different types of functionality around
scheduling like split tasks, dependencies, and more. These docs walk you through the details of
how Flow Production Tracking thinks when it's handling Task changes and how you can make your scripts do what you
need to do.

.. toctree::
    :maxdepth: 2

    cookbook/tasks

.. rubric:: Smart Cut Fields

Smart Cut Fields are deprecated in favor of the
`new cut support added in ShotGrid v7.0 <https://knowledge.autodesk.com/support/shotgrid/learn-explore/caas/CloudHelp/cloudhelp/ENU/SG-Editorial/files/SG-Editorial-ed-cut-schema-html-html.html>`_.
This documentation remains only to support studios who may not have upgraded to the new cut support
features.

.. toctree::
    :maxdepth: 2

    cookbook/smart_cut_fields


================================================
FILE: docs/index.rst
================================================
###########################################
Flow Production Tracking Python API library
###########################################
Release |version|. (:ref:`Installation <installation>`)

.. image:: https://img.shields.io/badge/shotgun-api-blue.svg



Flow Production Tracking (FPTR) provides a simple Python-based API for accessing FPTR and integrating with other tools.
The Flow Production Tracking API3, also known as "Python API", allows users to integrate their tools with Flow Production Tracking very easily. Using this simple
but powerful python module, you can quickly get your scripts integrated with Flow Production Tracking's CRUD-based
API.

Because the needs of every studio can prove to be very different, we don't include a lot of
"automation" or "smarts" in our API. We have kept it pretty low-level and leave most of those
decisions to you. The API is powerful enough you can write your own "smarts" in a wrapper on top
of the of the FPTR API3.

.. _pythonoverviewvideo:

Overview Video of Setting Up Your Environment with the Python API
=================================================================

.. raw:: html

   <iframe width="560" height="315" src="https://www.youtube.com/embed/RYEBQDJiXAs" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

In addition to basic metadata, the API contains methods for managing media including thumbnails,
filmstrip thumbnails, images, uploaded, and both locally and remotely linked media like
Quicktimes, etc.

**Example**::

    sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com",
                              login="rhendriks",
                              password="c0mPre$Hi0n")
    sg.find("Shot", filters=[["sg_status_list", "is", "ip"]], fields=["code", "sg_status_list"])

**Output**::

    [{'code': 'bunny_020_0170',
      'id': 896,
      'sg_sequence': {'id': 5, 'name': 'bunny_020', 'type': 'Sequence'},
      'sg_status_list': 'ip',
      'type': 'Shot'},
     {'code': 'bunny_020_0200',
      'id': 899,
      'sg_sequence': {'id': 5, 'name': 'bunny_020', 'type': 'Sequence'},
      'sg_status_list': 'ip',
      'type': 'Shot'},
     {'code': 'bunny_030_0080',
      'id': 907,
      'sg_sequence': {'id': 6, 'name': 'bunny_030', 'type': 'Sequence'},
      'sg_status_list': 'ip',
      'type': 'Shot'}]


**********
User Guide
**********
.. toctree::
    :maxdepth: 2

    installation
    authentication
    reference
    cookbook
    advanced


Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`


================================================
FILE: docs/installation.rst
================================================
############
Installation
############

********************
Minimum Requirements
********************

.. note::
    Some features of the API are only supported by more recent versions of the Flow Production Tracking server.
    These features are added to the Python API in a backwards compatible way so that existing
    scripts will continue to function as expected. Accessing a method that is not supported for
    your version of Flow Production Tracking will raise an appropriate exception. In general, we attempt to
    document these where possible.

Python versions
===============

The Python API library supports the following Python versions: `3.9`, `3.10`,
`3.11`, and `3.13`.
We recommend using Python 3.13.

.. important::
    Python versions older than 3.9 are no longer supported as of March 2025 and compatibility will be discontinued after
    March 2026.


******************************
Installing into ``PYTHONPATH``
******************************
You can  `download the latest release from Github <https://github.com/shotgunsoftware/python-api/releases>`_
or `clone the repo <https://github.com/shotgunsoftware/python-api>`_ to your local filesystem.
You'll need to save it somewhere your local Python installation can find it.

.. seealso:: For more information on ``PYTHONPATH`` and using modules in Python, see
    http://docs.python.org/tutorial/modules.html

.. note::
    :ref:`Visit the introduction to the Python API <pythonoverviewvideo>` to see an overview video of Setting Up Your Environment with the Python API.

***********************
Installing with ``pip``
***********************

Installing the Master Branch From Github
========================================
If you wish to install the current master, use the following command::

    pip install git+https://github.com/shotgunsoftware/python-api.git

.. note:: The master branch contains the latest revisions and while largely considered "stable"  it
    is not an official packaged release.

Installing A specific Version From Github
=========================================
To install a specific version of the package from Github, run the following command. This example
installs the v3.0.26 tag, replace the version tag with the one you want::

    pip install git+https://github.com/shotgunsoftware/python-api.git@v3.0.26


``requirements.txt``
~~~~~~~~~~~~~~~~~~~~
If you're using pip with `requirements.txt`, add the following line::

    git+https://github.com/shotgunsoftware/python-api.git


****************************
Installing with ``setup.py``
****************************

From a local copy of the repository, you can run ``python setup.py install`` to copy the package inside your python ``site-packages``. Note that while ``setuptools`` will complain about syntax errors when installing the library, the library is fully functional.


================================================
FILE: docs/reference.rst
================================================
.. currentmodule:: shotgun_api3

.. _apireference:

#############
API Reference
#############


*****************************
``shotgun`` Module Attributes
*****************************

The :mod:`~shotgun_api3.shotgun` module is a container for the :class:`~shotgun.Shotgun`
class. There are a couple of useful attributes to note.

.. automodule:: shotgun_api3.shotgun
    :members: LOG
    :private-members:
    :special-members:

***************
Shotgun()
***************

.. autoclass:: Shotgun
    :show-inheritance:

***************
Shotgun Methods
***************

The majority of functionality is contained within the :class:`~shotgun_api3.Shotgun` class.
The documentation for all of the methods you'll need in your scripts lives in here.

.. rubric:: Connection & Authentication

.. autosummary::
    :nosignatures:

    Shotgun.connect
    Shotgun.close
    Shotgun.authenticate_human_user
    Shotgun.get_session_token
    Shotgun.add_user_agent
    Shotgun.reset_user_agent
    Shotgun.set_session_uuid
    Shotgun.info

.. rubric:: CRUD Methods

.. autosummary::
    :nosignatures:

    Shotgun.create
    Shotgun.find
    Shotgun.find_one
    Shotgun.update
    Shotgun.delete
    Shotgun.revive
    Shotgun.batch
    Shotgun.summarize
    Shotgun.note_thread_read
    Shotgun.text_search
    Shotgun.update_project_last_accessed
    Shotgun.work_schedule_read
    Shotgun.work_schedule_update
    Shotgun.preferences_read
    Shotgun.export_page

.. rubric:: Working With Files

.. autosummary::
    :nosignatures:

    Shotgun.upload
    Shotgun.upload_thumbnail
    Shotgun.upload_filmstrip_thumbnail
    Shotgun.download_attachment
    Shotgun.get_attachment_download_url
    Shotgun.share_thumbnail

.. rubric:: Activity Stream

.. autosummary::
    :nosignatures:

    Shotgun.activity_stream_read
    Shotgun.follow
    Shotgun.unfollow
    Shotgun.followers
    Shotgun.following

.. rubric:: Working with the Shotgun Schema and Preferences

.. autosummary::
    :nosignatures:

    Shotgun.schema_entity_read
    Shotgun.schema_field_read
    Shotgun.schema_field_create
    Shotgun.schema_field_update
    Shotgun.schema_field_delete
    Shotgun.schema_read
    Shotgun.schema
    Shotgun.entity_types


Connection & Authentication
===========================

These methods are used for connecting and authenticating with your Flow Production Tracking server. Most of
this is done automatically when you instantiate your instance. But if you need finer-grain
control, these methods are available.

.. automethod:: Shotgun.connect
.. automethod:: Shotgun.close
.. automethod:: Shotgun.authenticate_human_user
.. automethod:: Shotgun.get_session_token
.. automethod:: Shotgun.get_auth_cookie_handler
.. automethod:: Shotgun.add_user_agent
.. automethod:: Shotgun.reset_user_agent
.. automethod:: Shotgun.set_session_uuid
.. automethod:: Shotgun.info

Subscription Management
=======================

These methods are used for reading and assigning user subscriptions.

.. automethod:: Shotgun.user_subscriptions_read
.. automethod:: Shotgun.user_subscriptions_create

CRUD Methods
============

These are the main methods for creating, reading, updating, and deleting information. There are
also some specialized convenience methods for accessing particular types of information.

.. automethod:: Shotgun.create
.. automethod:: Shotgun.find
.. automethod:: Shotgun.find_one
.. automethod:: Shotgun.update
.. automethod:: Shotgun.delete
.. automethod:: Shotgun.revive
.. automethod:: Shotgun.batch
.. automethod:: Shotgun.summarize
.. automethod:: Shotgun.note_thread_read
.. automethod:: Shotgun.text_search
.. automethod:: Shotgun.update_project_last_accessed
.. automethod:: Shotgun.work_schedule_read
.. automethod:: Shotgun.work_schedule_update
.. automethod:: Shotgun.preferences_read
.. automethod:: Shotgun.export_page

Working With Files
==================

Methods that handle uploading and downloading files including thumbnails.

.. seealso:: :ref:`attachments`

.. automethod:: Shotgun.upload
.. automethod:: Shotgun.upload_thumbnail
.. automethod:: Shotgun.upload_filmstrip_thumbnail
.. automethod:: Shotgun.download_attachment
.. automethod:: Shotgun.get_attachment_download_url
.. automethod:: Shotgun.share_thumbnail

Activity Stream
===============

Methods that relate to the activity stream and following of entities in Flow Production Tracking.

.. automethod:: Shotgun.activity_stream_read
.. automethod:: Shotgun.follow
.. automethod:: Shotgun.unfollow
.. automethod:: Shotgun.followers
.. automethod:: Shotgun.following

Working with the Shotgun Schema
===============================

Methods allow you to introspect and modify the Shotgun schema.

.. automethod:: Shotgun.schema_entity_read
.. automethod:: Shotgun.schema_field_read
.. automethod:: Shotgun.schema_field_create
.. automethod:: Shotgun.schema_field_update
.. automethod:: Shotgun.schema_field_delete
.. automethod:: Shotgun.schema_read
.. automethod:: Shotgun.schema
.. automethod:: Shotgun.entity_types

**********
Exceptions
**********

These are the various exceptions that the Flow Production Tracking API will raise.

.. autoclass:: shotgun_api3.ShotgunError
    :show-inheritance:
    :inherited-members:
    :members:

.. autoclass:: shotgun_api3.ShotgunFileDownloadError
    :show-inheritance:
    :inherited-members:
    :members:

.. autoclass:: shotgun_api3.Fault
    :show-inheritance:
    :inherited-members:
    :members:

.. autoclass:: shotgun_api3.AuthenticationFault
    :show-inheritance:
    :inherited-members:
    :members:

.. autoclass:: shotgun_api3.MissingTwoFactorAuthenticationFault
    :show-inheritance:
    :inherited-members:
    :members:


.. _filter_syntax:

*************
Filter Syntax
*************

Basic Filters
=============

Filters are represented as a list of conditions that will be combined using the supplied
filter_operator (``any`` or ``all``). Each condition follows the basic simple form::

    [<field>, <relation>, <value(s)>]

Basic Example
-------------
Using the default filter_operator ``"all"``, the following filters will return all Shots whose status
is "ip" AND is linked to Asset #9::

    filters = [
        ["sg_status_list", "is", "ip"],
        ["assets", "is", {"type": "Asset", "id": 9}]
    ]
    result = sg.find("Shot", filters)


Complex Filters
===============

.. versionadded::3.0.11

Complex filters can be a dictionary that represents a complex sub-condition of the form::

    {"filter_operator": "any", "filters": [<list of conditions>]}

Complex Example
---------------
Using the default filter_operator ``"all"``, the following filters will return all Shots whose status
is "ip" AND is linked to either Asset #9 OR Asset #23::

    filters = [
        ["sg_status_list", "is", "ip"],
        {
            "filter_operator": "any",
            "filters": [
                ["assets", "is", {"type": "Asset", "id": 9}],
                ["assets", "is", {"type": "Asset", "id": 23}]
            ]
        }
    ]
    result = sg.find("Shot", filters)


Operators and Arguments
=======================

::

    Operator                    Arguments
    --------                    ---------
    'is'                        [field_value] | None
    'is_not'                    [field_value] | None
    'less_than'                 [field_value] | None
    'greater_than'              [field_value] | None
    'contains'                  [field_value] | None
    'not_contains'              [field_value] | None
    'starts_with'               [string]
    'ends_with'                 [string]
    'between'                   [[field_value] | None, [field_value] | None]
    'not_between'               [[field_value] | None, [field_value] | None]
    'in_last'                   [[int], 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR']
                                       # note that brackets are not literal (eg. ['start_date', 'in_last', 1, 'DAY'])
    'in_next'                   [[int], 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR']
                                       # note that brackets are not literal (eg. ['start_date', 'in_next', 1, 'DAY'])
    'in'                        [[field_value], ...]        # Array of field values
    'type_is'                   [string] | None             # Shotgun entity type
    'type_is_not'               [string] | None             # Shotgun entity type
    'in_calendar_day'           [int]                       # Offset (e.g. 0 = today, 1 = tomorrow,
                                                            # -1 = yesterday)
    'in_calendar_week'          [int]                       # Offset (e.g. 0 = this week, 1 = next week,
                                                            # -1 = last week)
    'in_calendar_month'         [int]                       # Offset (e.g. 0 = this month, 1 = next month,
                                                            # -1 = last month)
    'name_contains'             [string]
    'name_not_contains'         [string]
    'name_starts_with'          [string]
    'name_ends_with'            [string]


Valid Operators By Data Type
============================

::

    addressing                  'is'
                                'is_not'
                                'contains'
                                'not_contains'
                                'in'
                                'type_is'
                                'type_is_not'
                                'name_contains'
                                'name_not_contains'
                                'name_starts_with'
                                'name_ends_with'

    checkbox                    'is'
                                'is_not'

    currency                    'is'
                                'is_not'
                                'less_than'
                                'greater_than'
                                'between'
                                'not_between'
                                'in'
                                'not_in'

    date                        'is'
                                'is_not'
                                'greater_than'
                                'less_than'
                                'in_last'
                                'not_in_last'
                                'in_next'
                                'not_in_next'
                                'in_calendar_day'
                                'in_calendar_week'
                                'in_calendar_month'
                                'in_calendar_year'
                                'between'
                                'in'
                                'not_in'

    date_time                   'is'
                                'is_not'
                                'greater_than'
                                'less_than'
                                'in_last'
                                'not_in_last'
                                'in_next'
                                'not_in_next'
                                'in_calendar_day'
                                'in_calendar_week'
                                'in_calendar_month'
                                'in_calendar_year'
                                'between'
                                'in'
                                'not_in'

    duration                    'is'
                                'is_not'
                                'greater_than'
                                'less_than'
                                'between'
                                'in'
                                'not_in'

    entity                      'is'
                                'is_not'
                                'type_is'
                                'type_is_not'
                                'name_contains'
                                'name_not_contains'
                                'name_is'
                                'in'
                                'not_in'

    float                       'is'
                                'is_not'
                                'greater_than'
                                'less_than'
                                'between'
                                'in'
                                'not_in'

    image                       'is' ** Note: For both 'is' and 'is_not'
Download .txt
gitextract_6psy39k2/

├── .coveragerc
├── .flake8
├── .gitattributes
├── .gitignore
├── .pre-commit-config.yaml
├── HISTORY.rst
├── LICENSE
├── README.md
├── SECURITY.md
├── azure-pipelines-templates/
│   ├── run-tests.yml
│   └── type_checking.yml
├── azure-pipelines.yml
├── developer/
│   └── README.md
├── docs/
│   ├── advanced/
│   │   ├── iron_python.rst
│   │   └── packaging.rst
│   ├── advanced.rst
│   ├── authentication.rst
│   ├── changelog.rst
│   ├── cookbook/
│   │   ├── attachments.rst
│   │   ├── examples/
│   │   │   ├── ami_handler.rst
│   │   │   ├── ami_version_packager.rst
│   │   │   ├── basic_create_shot.rst
│   │   │   ├── basic_create_shot_task_template.rst
│   │   │   ├── basic_create_version_link_shot.rst
│   │   │   ├── basic_delete_shot.rst
│   │   │   ├── basic_find_shot.rst
│   │   │   ├── basic_sg_instance.rst
│   │   │   ├── basic_update_shot.rst
│   │   │   ├── basic_upload_thumbnail_version.rst
│   │   │   └── svn_integration.rst
│   │   ├── smart_cut_fields.rst
│   │   ├── tasks/
│   │   │   ├── split_tasks.rst
│   │   │   ├── task_dependencies.rst
│   │   │   └── updating_tasks.rst
│   │   ├── tasks.rst
│   │   ├── tutorials.rst
│   │   └── usage_tips.rst
│   ├── cookbook.rst
│   ├── index.rst
│   ├── installation.rst
│   └── reference.rst
├── setup.py
├── shotgun_api3/
│   ├── __init__.py
│   ├── lib/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── __init__.py
│   │   ├── certifi/
│   │   │   ├── __init__.py
│   │   │   ├── __main__.py
│   │   │   ├── cacert.pem
│   │   │   ├── core.py
│   │   │   └── py.typed
│   │   ├── httplib2/
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   ├── cacerts.txt
│   │   │   ├── certs.py
│   │   │   ├── error.py
│   │   │   ├── iri2uri.py
│   │   │   └── socks.py
│   │   ├── mockgun/
│   │   │   ├── __init__.py
│   │   │   ├── errors.py
│   │   │   ├── mockgun.py
│   │   │   └── schema.py
│   │   ├── pyparsing.py
│   │   ├── requirements.txt
│   │   └── sgtimezone.py
│   ├── py.typed
│   └── shotgun.py
├── software_credits
├── tests/
│   ├── __init__.py
│   ├── base.py
│   ├── empty.txt
│   ├── example_config
│   ├── mockgun/
│   │   ├── schema.pickle
│   │   └── schema_entity.pickle
│   ├── requirements.txt
│   ├── run_appveyor.bat
│   ├── test_api.py
│   ├── test_api_long.py
│   ├── test_client.py
│   ├── test_config_file
│   ├── test_mockgun.py
│   ├── test_proxy.py
│   └── test_unit.py
└── update_httplib2.py
Download .txt
SYMBOL INDEX (1067 symbols across 21 files)

FILE: shotgun_api3/lib/certifi/core.py
  function exit_cacert_ctx (line 10) | def exit_cacert_ctx() -> None:
  function where (line 21) | def where() -> str:
  function contents (line 46) | def contents() -> str:
  function where (line 56) | def where() -> str:
  function contents (line 82) | def contents() -> str:

FILE: shotgun_api3/lib/httplib2/__init__.py
  function has_timeout (line 58) | def has_timeout(timeout):
  function _build_ssl_context (line 142) | def _build_ssl_context(
  function _get_end2end_headers (line 191) | def _get_end2end_headers(response):
  function _errno_from_exception (line 200) | def _errno_from_exception(e):
  function parse_uri (line 223) | def parse_uri(uri):
  function urlnorm (line 232) | def urlnorm(uri):
  function safename (line 253) | def safename(filename):
  function _normalize_headers (line 280) | def _normalize_headers(headers):
  function _convert_byte_str (line 289) | def _convert_byte_str(s):
  function _parse_cache_control (line 295) | def _parse_cache_control(headers):
  function _entry_disposition (line 314) | def _entry_disposition(response_headers, request_headers):
  function _decompressContent (line 389) | def _decompressContent(response, new_content):
  function _bind_write_headers (line 415) | def _bind_write_headers(msg):
  function _updateCache (line 432) | def _updateCache(request_headers, response_headers, content, cache, cach...
  function _cnonce (line 474) | def _cnonce():
  function _wsse_username_token (line 481) | def _wsse_username_token(cnonce, iso_now, password):
  class Authentication (line 496) | class Authentication(object):
    method __init__ (line 497) | def __init__(self, credentials, host, request_uri, headers, response, ...
    method depth (line 504) | def depth(self, request_uri):
    method inscope (line 508) | def inscope(self, host, request_uri):
    method request (line 513) | def request(self, method, request_uri, headers, content):
    method response (line 518) | def response(self, response, content):
    method __eq__ (line 528) | def __eq__(self, auth):
    method __ne__ (line 531) | def __ne__(self, auth):
    method __lt__ (line 534) | def __lt__(self, auth):
    method __gt__ (line 537) | def __gt__(self, auth):
    method __le__ (line 540) | def __le__(self, auth):
    method __ge__ (line 543) | def __ge__(self, auth):
    method __bool__ (line 546) | def __bool__(self):
  class BasicAuthentication (line 550) | class BasicAuthentication(Authentication):
    method __init__ (line 551) | def __init__(self, credentials, host, request_uri, headers, response, ...
    method request (line 554) | def request(self, method, request_uri, headers, content):
  class DigestAuthentication (line 562) | class DigestAuthentication(Authentication):
    method __init__ (line 566) | def __init__(self, credentials, host, request_uri, headers, response, ...
    method request (line 581) | def request(self, method, request_uri, headers, content, cnonce=None):
    method response (line 617) | def response(self, response, content):
  class HmacDigestAuthentication (line 633) | class HmacDigestAuthentication(Authentication):
    method __init__ (line 638) | def __init__(self, credentials, host, request_uri, headers, response, ...
    method request (line 680) | def request(self, method, request_uri, headers, content):
    method response (line 704) | def response(self, response, content):
  class WsseAuthentication (line 711) | class WsseAuthentication(Authentication):
    method __init__ (line 720) | def __init__(self, credentials, host, request_uri, headers, response, ...
    method request (line 723) | def request(self, method, request_uri, headers, content):
  class GoogleLoginAuthentication (line 738) | class GoogleLoginAuthentication(Authentication):
    method __init__ (line 739) | def __init__(self, credentials, host, request_uri, headers, response, ...
    method request (line 767) | def request(self, method, request_uri, headers, content):
  class FileCache (line 784) | class FileCache(object):
    method __init__ (line 790) | def __init__(self, cache, safe=safename):  # use safe=lambda x: md5.ne...
    method get (line 796) | def get(self, key):
    method set (line 807) | def set(self, key, value):
    method delete (line 813) | def delete(self, key):
  class Credentials (line 819) | class Credentials(object):
    method __init__ (line 820) | def __init__(self):
    method add (line 823) | def add(self, name, password, domain=""):
    method clear (line 826) | def clear(self):
    method iter (line 829) | def iter(self, domain):
  class KeyCerts (line 835) | class KeyCerts(Credentials):
    method add (line 839) | def add(self, key, cert, domain, password):
    method iter (line 842) | def iter(self, domain):
  class AllHosts (line 848) | class AllHosts(object):
  class ProxyInfo (line 852) | class ProxyInfo(object):
    method __init__ (line 857) | def __init__(
    method astuple (line 899) | def astuple(self):
    method isgood (line 910) | def isgood(self):
    method applies_to (line 913) | def applies_to(self, hostname):
    method bypass_host (line 916) | def bypass_host(self, hostname):
    method __repr__ (line 931) | def __repr__(self):
  function proxy_info_from_environment (line 939) | def proxy_info_from_environment(method="http"):
  function proxy_info_from_url (line 952) | def proxy_info_from_url(url, method="http", noproxy=None):
  class HTTPConnectionWithTimeout (line 982) | class HTTPConnectionWithTimeout(http.client.HTTPConnection):
    method __init__ (line 993) | def __init__(self, host, port=None, timeout=None, proxy_info=None):
    method connect (line 1000) | def connect(self):
  class HTTPSConnectionWithTimeout (line 1069) | class HTTPSConnectionWithTimeout(http.client.HTTPSConnection):
    method __init__ (line 1078) | def __init__(
    method connect (line 1116) | def connect(self):
  class Http (line 1211) | class Http(object):
    method __init__ (line 1226) | def __init__(
    method close (line 1309) | def close(self):
    method __getstate__ (line 1319) | def __getstate__(self):
    method __setstate__ (line 1329) | def __setstate__(self, state):
    method _auth_from_challenge (line 1333) | def _auth_from_challenge(self, host, request_uri, headers, response, c...
    method add_credentials (line 1343) | def add_credentials(self, name, password, domain=""):
    method add_certificate (line 1348) | def add_certificate(self, key, cert, domain, password=None):
    method clear_credentials (line 1353) | def clear_credentials(self):
    method _conn_request (line 1359) | def _conn_request(self, conn, request_uri, method, body, headers):
    method _request (line 1433) | def _request(
    method _normalize_headers (line 1512) | def _normalize_headers(self, headers):
    method request (line 1519) | def request(
  class Response (line 1754) | class Response(dict):
    method __init__ (line 1772) | def __init__(self, info):
    method __getattr__ (line 1795) | def __getattr__(self, name):

FILE: shotgun_api3/lib/httplib2/auth.py
  function _parse_authentication_info (line 37) | def _parse_authentication_info(headers, headername="authentication-info"):
  function _parse_www_authenticate (line 52) | def _parse_www_authenticate(headers, headername="www-authenticate"):

FILE: shotgun_api3/lib/httplib2/certs.py
  function where (line 27) | def where():

FILE: shotgun_api3/lib/httplib2/error.py
  class HttpLib2Error (line 2) | class HttpLib2Error(Exception):
  class HttpLib2ErrorWithResponse (line 8) | class HttpLib2ErrorWithResponse(HttpLib2Error):
    method __init__ (line 9) | def __init__(self, desc, response, content):
  class RedirectMissingLocation (line 15) | class RedirectMissingLocation(HttpLib2ErrorWithResponse):
  class RedirectLimit (line 19) | class RedirectLimit(HttpLib2ErrorWithResponse):
  class FailedToDecompressContent (line 23) | class FailedToDecompressContent(HttpLib2ErrorWithResponse):
  class UnimplementedDigestAuthOptionError (line 27) | class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse):
  class UnimplementedHmacDigestAuthOptionError (line 31) | class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse):
  class MalformedHeader (line 35) | class MalformedHeader(HttpLib2Error):
  class RelativeURIError (line 39) | class RelativeURIError(HttpLib2Error):
  class ServerNotFoundError (line 43) | class ServerNotFoundError(HttpLib2Error):
  class ProxiesUnavailableError (line 47) | class ProxiesUnavailableError(HttpLib2Error):

FILE: shotgun_api3/lib/httplib2/iri2uri.py
  function encode (line 48) | def encode(c):
  function iri2uri (line 60) | def iri2uri(uri):
  class Test (line 78) | class Test(unittest.TestCase):
    method test_uris (line 79) | def test_uris(self):
    method test_iri (line 94) | def test_iri(self):

FILE: shotgun_api3/lib/httplib2/socks.py
  class ProxyError (line 55) | class ProxyError(Exception):
  class GeneralProxyError (line 59) | class GeneralProxyError(ProxyError):
  class Socks5AuthError (line 63) | class Socks5AuthError(ProxyError):
  class Socks5Error (line 67) | class Socks5Error(ProxyError):
  class Socks4Error (line 71) | class Socks4Error(ProxyError):
  class HTTPError (line 75) | class HTTPError(ProxyError):
  function setdefaultproxy (line 119) | def setdefaultproxy(
  function wrapmodule (line 130) | def wrapmodule(module):
  class socksocket (line 145) | class socksocket(socket.socket):
    method __init__ (line 152) | def __init__(
    method __recvall (line 164) | def __recvall(self, count):
    method sendall (line 177) | def sendall(self, content, *args):
    method __rewriteproxy (line 185) | def __rewriteproxy(self, header):
    method __getauthheader (line 208) | def __getauthheader(self):
    method setproxy (line 212) | def setproxy(
    method __negotiatesocks5 (line 251) | def __negotiatesocks5(self, destaddr, destport):
    method getproxysockname (line 354) | def getproxysockname(self):
    method getproxypeername (line 360) | def getproxypeername(self):
    method getpeername (line 366) | def getpeername(self):
    method __negotiatesocks4 (line 373) | def __negotiatesocks4(self, destaddr, destport):
    method __negotiatehttp (line 424) | def __negotiatehttp(self, destaddr, destport):
    method connect (line 469) | def connect(self, destpair):

FILE: shotgun_api3/lib/mockgun/errors.py
  class MockgunError (line 36) | class MockgunError(Exception):

FILE: shotgun_api3/lib/mockgun/mockgun.py
  class Shotgun (line 133) | class Shotgun(object):
    method set_schema_paths (line 153) | def set_schema_paths(cls, schema_path, schema_entity_path):
    method get_schema_paths (line 166) | def get_schema_paths(cls):
    method __init__ (line 175) | def __init__(self,
    method get_session_token (line 225) | def get_session_token(self):
    method schema_read (line 228) | def schema_read(self):
    method schema_field_create (line 231) | def schema_field_create(self, entity_type, data_type, display_name, pr...
    method schema_field_update (line 234) | def schema_field_update(self, entity_type, field_name, properties):
    method schema_field_delete (line 237) | def schema_field_delete(self, entity_type, field_name):
    method schema_entity_read (line 240) | def schema_entity_read(self):
    method schema_field_read (line 243) | def schema_field_read(self, entity_type, field_name=None):
    method find (line 249) | def find(
    method find_one (line 343) | def find_one(
    method batch (line 353) | def batch(self, requests):
    method create (line 372) | def create(self, entity_type, data, return_fields=None):
    method update (line 417) | def update(self, entity_type, entity_id, data, multi_entity_update_mod...
    method delete (line 427) | def delete(self, entity_type, entity_id):
    method revive (line 438) | def revive(self, entity_type, entity_id):
    method upload (line 449) | def upload(self, entity_type, entity_id, path, field_name=None, displa...
    method upload_thumbnail (line 452) | def upload_thumbnail(self, entity_type, entity_id, path, **kwargs):
    method add_user_agent (line 455) | def add_user_agent(self, agent):
    method set_session_uuid (line 458) | def set_session_uuid(self, session_uuid):
    method _validate_entity_type (line 464) | def _validate_entity_type(self, entity_type):
    method _validate_entity_data (line 468) | def _validate_entity_data(self, entity_type, data):
    method _validate_entity_fields (line 553) | def _validate_entity_fields(self, entity_type, fields):
    method _get_default_value (line 565) | def _get_default_value(self, entity_type, field):
    method _get_new_row (line 573) | def _get_new_row(self, entity_type):
    method _compare (line 584) | def _compare(self, field_type: str, lval: Any, operator: str, rval: An...
    method _get_field_from_row (line 704) | def _get_field_from_row(self, entity_type, row, field):
    method _get_field_type (line 760) | def _get_field_type(self, entity_type, field):
    method _row_matches_filter (line 768) | def _row_matches_filter(self, entity_type, row, sg_filter, retired_only):
    method _rearrange_filters (line 801) | def _rearrange_filters(self, filters: list) -> None:
    method _row_matches_filters (line 848) | def _row_matches_filters(self, entity_type, row, filters, filter_opera...
    method _update_row (line 862) | def _update_row(self, entity_type, row, data, multi_entity_update_mode...
    method _validate_entity_exists (line 888) | def _validate_entity_exists(self, entity_type, entity_id):

FILE: shotgun_api3/lib/mockgun/schema.py
  class SchemaFactory (line 39) | class SchemaFactory(object):
    method get_schemas (line 50) | def get_schemas(cls, schema_path: str, schema_entity_path: str) -> tuple:
    method _read_file (line 82) | def _read_file(cls, path):
  function generate_schema (line 94) | def generate_schema(shotgun, schema_file_path, schema_entity_file_path):

FILE: shotgun_api3/lib/pyparsing.py
  class SimpleNamespace (line 149) | class SimpleNamespace: pass
  function _enable_all_warnings (line 189) | def _enable_all_warnings():
  function _ustr (line 235) | def _ustr(obj):
  function _xml_escape (line 269) | def _xml_escape(data):
  function conditionAsParseAction (line 287) | def conditionAsParseAction(fn, message=None, fatal=False):
  class ParseBaseException (line 299) | class ParseBaseException(Exception):
    method __init__ (line 303) | def __init__(self, pstr, loc=0, msg=None, elem=None):
    method _from_exception (line 315) | def _from_exception(cls, pe):
    method __getattr__ (line 322) | def __getattr__(self, aname):
    method __str__ (line 337) | def __str__(self):
    method __repr__ (line 347) | def __repr__(self):
    method markInputline (line 349) | def markInputline(self, markerString=">!<"):
    method __dir__ (line 359) | def __dir__(self):
  class ParseException (line 362) | class ParseException(ParseBaseException):
    method explain (line 386) | def explain(exc, depth=16):
  class ParseFatalException (line 455) | class ParseFatalException(ParseBaseException):
  class ParseSyntaxException (line 460) | class ParseSyntaxException(ParseFatalException):
  class RecursiveGrammarException (line 481) | class RecursiveGrammarException(Exception):
    method __init__ (line 485) | def __init__(self, parseElementList):
    method __str__ (line 488) | def __str__(self):
  class _ParseResultsWithOffset (line 491) | class _ParseResultsWithOffset(object):
    method __init__ (line 492) | def __init__(self, p1, p2):
    method __getitem__ (line 494) | def __getitem__(self, i):
    method __repr__ (line 496) | def __repr__(self):
    method setOffset (line 498) | def setOffset(self, i):
  class ParseResults (line 501) | class ParseResults(object):
    method __new__ (line 544) | def __new__(cls, toklist=None, name=None, asList=True, modal=True):
    method __init__ (line 553) | def __init__(self, toklist=None, name=None, asList=True, modal=True, i...
    method __getitem__ (line 592) | def __getitem__(self, i):
    method __setitem__ (line 601) | def __setitem__(self, k, v, isinstance=isinstance):
    method __delitem__ (line 614) | def __delitem__(self, i):
    method __contains__ (line 635) | def __contains__(self, k):
    method __len__ (line 638) | def __len__(self):
    method __bool__ (line 641) | def __bool__(self):
    method __iter__ (line 645) | def __iter__(self):
    method __reversed__ (line 648) | def __reversed__(self):
    method _iterkeys (line 651) | def _iterkeys(self):
    method _itervalues (line 657) | def _itervalues(self):
    method _iteritems (line 660) | def _iteritems(self):
    method keys (line 683) | def keys(self):
    method values (line 687) | def values(self):
    method items (line 691) | def items(self):
    method haskeys (line 695) | def haskeys(self):
    method pop (line 700) | def pop(self, *args, **kwargs):
    method get (line 755) | def get(self, key, defaultValue=None):
    method insert (line 778) | def insert(self, index, insStr):
    method append (line 799) | def append(self, item):
    method extend (line 814) | def extend(self, itemseq):
    method clear (line 833) | def clear(self):
    method __getattr__ (line 840) | def __getattr__(self, name):
    method __add__ (line 846) | def __add__(self, other):
    method __iadd__ (line 851) | def __iadd__(self, other):
    method __radd__ (line 867) | def __radd__(self, other):
    method __repr__ (line 875) | def __repr__(self):
    method __str__ (line 878) | def __str__(self):
    method _asStringList (line 881) | def _asStringList(self, sep=''):
    method asList (line 892) | def asList(self):
    method asDict (line 909) | def asDict(self):
    method copy (line 945) | def copy(self):
    method asXML (line 956) | def asXML(self, doctag=None, namedItemsOnly=False, indent="", formatte...
    method __lookup (line 1017) | def __lookup(self, sub):
    method getName (line 1024) | def getName(self):
    method dump (line 1064) | def dump(self, indent='', full=True, include_list=True, _depth=0):
    method pprint (line 1129) | def pprint(self, *args, **kwargs):
    method __getstate__ (line 1157) | def __getstate__(self):
    method __setstate__ (line 1164) | def __setstate__(self, state):
    method __getnewargs__ (line 1174) | def __getnewargs__(self):
    method __dir__ (line 1177) | def __dir__(self):
    method from_dict (line 1181) | def from_dict(cls, other, name=None):
  function col (line 1210) | def col (loc, strg):
  function lineno (line 1224) | def lineno(loc, strg):
  function line (line 1236) | def line(loc, strg):
  function _defaultStartDebugAction (line 1246) | def _defaultStartDebugAction(instring, loc, expr):
  function _defaultSuccessDebugAction (line 1249) | def _defaultSuccessDebugAction(instring, startloc, endloc, expr, toks):
  function _defaultExceptionDebugAction (line 1252) | def _defaultExceptionDebugAction(instring, loc, expr, exc):
  function nullDebugAction (line 1255) | def nullDebugAction(*args):
  function _trim_arity (line 1282) | def _trim_arity(func, maxargs=2):
  class ParserElement (line 1350) | class ParserElement(object):
    method setDefaultWhitespaceChars (line 1356) | def setDefaultWhitespaceChars(chars):
    method inlineLiteralsUsing (line 1372) | def inlineLiteralsUsing(cls):
    method _trim_traceback (line 1394) | def _trim_traceback(cls, tb):
    method __init__ (line 1399) | def __init__(self, savelist=False):
    method copy (line 1422) | def copy(self):
    method setName (line 1451) | def setName(self, name):
    method setResultsName (line 1466) | def setResultsName(self, name, listAllMatches=False):
    method _setResultsName (line 1489) | def _setResultsName(self, name, listAllMatches=False):
    method setBreak (line 1498) | def setBreak(self, breakFlag=True):
    method setParseAction (line 1517) | def setParseAction(self, *fns, **kwargs):
    method addParseAction (line 1566) | def addParseAction(self, *fns, **kwargs):
    method addCondition (line 1576) | def addCondition(self, *fns, **kwargs):
    method setFailAction (line 1601) | def setFailAction(self, fn):
    method _skipIgnorables (line 1614) | def _skipIgnorables(self, instring, loc):
    method preParse (line 1627) | def preParse(self, instring, loc):
    method parseImpl (line 1639) | def parseImpl(self, instring, loc, doActions=True):
    method postParse (line 1642) | def postParse(self, instring, loc, tokenlist):
    method _parseNoCache (line 1646) | def _parseNoCache(self, instring, loc, doActions=True, callPreParse=Tr...
    method tryParse (line 1733) | def tryParse(self, instring, loc):
    method canParseNext (line 1739) | def canParseNext(self, instring, loc):
    class _UnboundedCache (line 1747) | class _UnboundedCache(object):
      method __init__ (line 1748) | def __init__(self):
    class _FifoCache (line 1770) | class _FifoCache(object):
      method __init__ (line 1771) | def __init__(self, size):
      method __init__ (line 1800) | def __init__(self, size):
    class _FifoCache (line 1799) | class _FifoCache(object):
      method __init__ (line 1771) | def __init__(self, size):
      method __init__ (line 1800) | def __init__(self, size):
    method _parseCache (line 1834) | def _parseCache(self, instring, loc, doActions=True, callPreParse=True):
    method resetCache (line 1860) | def resetCache():
    method enablePackrat (line 1866) | def enablePackrat(cache_size_limit=128):
    method parseString (line 1900) | def parseString(self, instring, parseAll=False):
    method scanString (line 1958) | def scanString(self, instring, maxMatches=_MAX_INT, overlap=False):
    method transformString (line 2032) | def transformString(self, instring):
    method searchString (line 2080) | def searchString(self, instring, maxMatches=_MAX_INT):
    method split (line 2112) | def split(self, instring, maxsplit=_MAX_INT, includeSeparators=False):
    method __add__ (line 2137) | def __add__(self, other):
    method __radd__ (line 2175) | def __radd__(self, other):
    method __sub__ (line 2190) | def __sub__(self, other):
    method __rsub__ (line 2202) | def __rsub__(self, other):
    method __mul__ (line 2214) | def __mul__(self, other):
    method __rmul__ (line 2288) | def __rmul__(self, other):
    method __or__ (line 2291) | def __or__(self, other):
    method __ror__ (line 2306) | def __ror__(self, other):
    method __xor__ (line 2318) | def __xor__(self, other):
    method __rxor__ (line 2330) | def __rxor__(self, other):
    method __and__ (line 2342) | def __and__(self, other):
    method __rand__ (line 2354) | def __rand__(self, other):
    method __invert__ (line 2366) | def __invert__(self):
    method __iter__ (line 2372) | def __iter__(self):
    method __getitem__ (line 2377) | def __getitem__(self, key):
    method __call__ (line 2413) | def __call__(self, name=None):
    method suppress (line 2433) | def suppress(self):
    method leaveWhitespace (line 2440) | def leaveWhitespace(self):
    method setWhitespaceChars (line 2449) | def setWhitespaceChars(self, chars):
    method parseWithTabs (line 2458) | def parseWithTabs(self):
    method ignore (line 2467) | def ignore(self, other):
    method setDebugActions (line 2491) | def setDebugActions(self, startAction, successAction, exceptionAction):
    method setDebug (line 2501) | def setDebug(self, flag=True):
    method __str__ (line 2544) | def __str__(self):
    method __repr__ (line 2547) | def __repr__(self):
    method streamline (line 2550) | def streamline(self):
    method checkRecursion (line 2555) | def checkRecursion(self, parseElementList):
    method validate (line 2558) | def validate(self, validateTrace=None):
    method parseFile (line 2564) | def parseFile(self, file_or_filename, parseAll=False):
    method __eq__ (line 2586) | def __eq__(self, other):
    method __ne__ (line 2595) | def __ne__(self, other):
    method __hash__ (line 2598) | def __hash__(self):
    method __req__ (line 2601) | def __req__(self, other):
    method __rne__ (line 2604) | def __rne__(self, other):
    method matches (line 2607) | def matches(self, testString, parseAll=True):
    method runTests (line 2627) | def runTests(self, tests, parseAll=True, comment='#',
  class _PendingSkip (line 2790) | class _PendingSkip(ParserElement):
    method __init__ (line 2793) | def __init__(self, expr, must_skip=False):
    method __add__ (line 2800) | def __add__(self, other):
    method __repr__ (line 2816) | def __repr__(self):
    method parseImpl (line 2819) | def parseImpl(self, *args):
  class Token (line 2823) | class Token(ParserElement):
    method __init__ (line 2827) | def __init__(self):
  class Empty (line 2831) | class Empty(Token):
    method __init__ (line 2834) | def __init__(self):
  class NoMatch (line 2841) | class NoMatch(Token):
    method __init__ (line 2844) | def __init__(self):
    method parseImpl (line 2851) | def parseImpl(self, instring, loc, doActions=True):
  class Literal (line 2855) | class Literal(Token):
    method __init__ (line 2869) | def __init__(self, matchString):
    method parseImpl (line 2889) | def parseImpl(self, instring, loc, doActions=True):
  class _SingleCharLiteral (line 2894) | class _SingleCharLiteral(Literal):
    method parseImpl (line 2895) | def parseImpl(self, instring, loc, doActions=True):
  class Keyword (line 2903) | class Keyword(Token):
    method __init__ (line 2930) | def __init__(self, matchString, identChars=None, caseless=False):
    method parseImpl (line 2951) | def parseImpl(self, instring, loc, doActions=True):
    method copy (line 2970) | def copy(self):
    method setDefaultKeywordChars (line 2976) | def setDefaultKeywordChars(chars):
  class CaselessLiteral (line 2981) | class CaselessLiteral(Literal):
    method __init__ (line 2992) | def __init__(self, matchString):
    method parseImpl (line 2999) | def parseImpl(self, instring, loc, doActions=True):
  class CaselessKeyword (line 3004) | class CaselessKeyword(Keyword):
    method __init__ (line 3014) | def __init__(self, matchString, identChars=None):
  class CloseMatch (line 3017) | class CloseMatch(Token):
    method __init__ (line 3050) | def __init__(self, match_string, maxMismatches=1):
    method parseImpl (line 3059) | def parseImpl(self, instring, loc, doActions=True):
  class Word (line 3086) | class Word(Token):
    method __init__ (line 3139) | def __init__(self, initChars, bodyChars=None, min=1, max=0, exact=0, a...
    method parseImpl (line 3196) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 3224) | def __str__(self):
  class _WordRegex (line 3245) | class _WordRegex(Word):
    method parseImpl (line 3246) | def parseImpl(self, instring, loc, doActions=True):
  class Char (line 3255) | class Char(_WordRegex):
    method __init__ (line 3260) | def __init__(self, charset, asKeyword=False, excludeChars=None):
  class Regex (line 3269) | class Regex(Token):
    method __init__ (line 3293) | def __init__(self, pattern, flags=0, asGroupList=False, asMatch=False):
    method parseImpl (line 3338) | def parseImpl(self, instring, loc, doActions=True):
    method parseImplAsGroupList (line 3351) | def parseImplAsGroupList(self, instring, loc, doActions=True):
    method parseImplAsMatch (line 3360) | def parseImplAsMatch(self, instring, loc, doActions=True):
    method __str__ (line 3369) | def __str__(self):
    method sub (line 3380) | def sub(self, repl):
  class QuotedString (line 3409) | class QuotedString(Token):
    method __init__ (line 3448) | def __init__(self, quoteChar, escChar=None, escQuote=None, multiline=F...
    method parseImpl (line 3513) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 3548) | def __str__(self):
  class CharsNotIn (line 3560) | class CharsNotIn(Token):
    method __init__ (line 3580) | def __init__(self, notChars, min=1, max=0, exact=0):
    method parseImpl (line 3605) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 3621) | def __str__(self):
  class White (line 3635) | class White(Token):
    method __init__ (line 3669) | def __init__(self, ws=" \t\r\n", min=1, max=0, exact=0):
    method parseImpl (line 3689) | def parseImpl(self, instring, loc, doActions=True):
  class _PositionToken (line 3705) | class _PositionToken(Token):
    method __init__ (line 3706) | def __init__(self):
  class GoToColumn (line 3712) | class GoToColumn(_PositionToken):
    method __init__ (line 3716) | def __init__(self, colno):
    method preParse (line 3720) | def preParse(self, instring, loc):
    method parseImpl (line 3729) | def parseImpl(self, instring, loc, doActions=True):
  class LineStart (line 3738) | class LineStart(_PositionToken):
    method __init__ (line 3760) | def __init__(self):
    method parseImpl (line 3764) | def parseImpl(self, instring, loc, doActions=True):
  class LineEnd (line 3769) | class LineEnd(_PositionToken):
    method __init__ (line 3773) | def __init__(self):
    method parseImpl (line 3778) | def parseImpl(self, instring, loc, doActions=True):
  class StringStart (line 3789) | class StringStart(_PositionToken):
    method __init__ (line 3793) | def __init__(self):
    method parseImpl (line 3797) | def parseImpl(self, instring, loc, doActions=True):
  class StringEnd (line 3804) | class StringEnd(_PositionToken):
    method __init__ (line 3807) | def __init__(self):
    method parseImpl (line 3811) | def parseImpl(self, instring, loc, doActions=True):
  class WordStart (line 3821) | class WordStart(_PositionToken):
    method __init__ (line 3830) | def __init__(self, wordChars=printables):
    method parseImpl (line 3835) | def parseImpl(self, instring, loc, doActions=True):
  class WordEnd (line 3842) | class WordEnd(_PositionToken):
    method __init__ (line 3850) | def __init__(self, wordChars=printables):
    method parseImpl (line 3856) | def parseImpl(self, instring, loc, doActions=True):
  class ParseExpression (line 3865) | class ParseExpression(ParserElement):
    method __init__ (line 3869) | def __init__(self, exprs, savelist=False):
    method append (line 3891) | def append(self, other):
    method leaveWhitespace (line 3896) | def leaveWhitespace(self):
    method ignore (line 3905) | def ignore(self, other):
    method __str__ (line 3917) | def __str__(self):
    method streamline (line 3927) | def streamline(self):
    method validate (line 3961) | def validate(self, validateTrace=None):
    method copy (line 3967) | def copy(self):
    method _setResultsName (line 3972) | def _setResultsName(self, name, listAllMatches=False):
  class And (line 3986) | class And(ParseExpression):
    class _ErrorStop (line 4004) | class _ErrorStop(Empty):
      method __init__ (line 4005) | def __init__(self, *args, **kwargs):
    method __init__ (line 4010) | def __init__(self, exprs, savelist=True):
    method streamline (line 4030) | def streamline(self):
    method parseImpl (line 4048) | def parseImpl(self, instring, loc, doActions=True):
    method __iadd__ (line 4073) | def __iadd__(self, other):
    method checkRecursion (line 4078) | def checkRecursion(self, parseElementList):
    method __str__ (line 4085) | def __str__(self):
  class Or (line 4095) | class Or(ParseExpression):
    method __init__ (line 4112) | def __init__(self, exprs, savelist=False):
    method streamline (line 4119) | def streamline(self):
    method parseImpl (line 4125) | def parseImpl(self, instring, loc, doActions=True):
    method __ixor__ (line 4186) | def __ixor__(self, other):
    method __str__ (line 4191) | def __str__(self):
    method checkRecursion (line 4200) | def checkRecursion(self, parseElementList):
    method _setResultsName (line 4205) | def _setResultsName(self, name, listAllMatches=False):
  class MatchFirst (line 4218) | class MatchFirst(ParseExpression):
    method __init__ (line 4235) | def __init__(self, exprs, savelist=False):
    method streamline (line 4242) | def streamline(self):
    method parseImpl (line 4248) | def parseImpl(self, instring, loc, doActions=True):
    method __ior__ (line 4272) | def __ior__(self, other):
    method __str__ (line 4277) | def __str__(self):
    method checkRecursion (line 4286) | def checkRecursion(self, parseElementList):
    method _setResultsName (line 4291) | def _setResultsName(self, name, listAllMatches=False):
  class Each (line 4304) | class Each(ParseExpression):
    method __init__ (line 4361) | def __init__(self, exprs, savelist=True):
    method streamline (line 4368) | def streamline(self):
    method parseImpl (line 4373) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 4422) | def __str__(self):
    method checkRecursion (line 4431) | def checkRecursion(self, parseElementList):
  class ParseElementEnhance (line 4437) | class ParseElementEnhance(ParserElement):
    method __init__ (line 4441) | def __init__(self, expr, savelist=False):
    method parseImpl (line 4459) | def parseImpl(self, instring, loc, doActions=True):
    method leaveWhitespace (line 4465) | def leaveWhitespace(self):
    method ignore (line 4472) | def ignore(self, other):
    method streamline (line 4484) | def streamline(self):
    method checkRecursion (line 4490) | def checkRecursion(self, parseElementList):
    method validate (line 4497) | def validate(self, validateTrace=None):
    method __str__ (line 4505) | def __str__(self):
  class FollowedBy (line 4516) | class FollowedBy(ParseElementEnhance):
    method __init__ (line 4538) | def __init__(self, expr):
    method parseImpl (line 4542) | def parseImpl(self, instring, loc, doActions=True):
  class PrecededBy (line 4551) | class PrecededBy(ParseElementEnhance):
    method __init__ (line 4579) | def __init__(self, expr, retreat=None):
    method parseImpl (line 4602) | def parseImpl(self, instring, loc=0, doActions=True):
  class NotAny (line 4626) | class NotAny(ParseElementEnhance):
    method __init__ (line 4649) | def __init__(self, expr):
    method parseImpl (line 4656) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 4661) | def __str__(self):
  class _MultipleMatch (line 4670) | class _MultipleMatch(ParseElementEnhance):
    method __init__ (line 4671) | def __init__(self, expr, stopOn=None):
    method stopOn (line 4679) | def stopOn(self, ender):
    method parseImpl (line 4685) | def parseImpl(self, instring, loc, doActions=True):
    method _setResultsName (line 4714) | def _setResultsName(self, name, listAllMatches=False):
  class OneOrMore (line 4728) | class OneOrMore(_MultipleMatch):
    method __str__ (line 4754) | def __str__(self):
  class ZeroOrMore (line 4763) | class ZeroOrMore(_MultipleMatch):
    method __init__ (line 4774) | def __init__(self, expr, stopOn=None):
    method parseImpl (line 4778) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 4784) | def __str__(self):
  class _NullToken (line 4794) | class _NullToken(object):
    method __bool__ (line 4795) | def __bool__(self):
    method __str__ (line 4798) | def __str__(self):
  class Optional (line 4801) | class Optional(ParseElementEnhance):
    method __init__ (line 4840) | def __init__(self, expr, default=__optionalNotMatched):
    method parseImpl (line 4846) | def parseImpl(self, instring, loc, doActions=True):
    method __str__ (line 4860) | def __str__(self):
  class SkipTo (line 4869) | class SkipTo(ParseElementEnhance):
    method __init__ (line 4927) | def __init__(self, other, include=False, ignore=None, failOn=None):
    method parseImpl (line 4940) | def parseImpl(self, instring, loc, doActions=True):
  class Forward (line 4987) | class Forward(ParseElementEnhance):
    method __init__ (line 5014) | def __init__(self, other=None):
    method __lshift__ (line 5017) | def __lshift__(self, other):
    method __ilshift__ (line 5030) | def __ilshift__(self, other):
    method leaveWhitespace (line 5033) | def leaveWhitespace(self):
    method streamline (line 5037) | def streamline(self):
    method validate (line 5044) | def validate(self, validateTrace=None):
    method __str__ (line 5054) | def __str__(self):
    method copy (line 5074) | def copy(self):
    method _setResultsName (line 5082) | def _setResultsName(self, name, listAllMatches=False):
  class TokenConverter (line 5093) | class TokenConverter(ParseElementEnhance):
    method __init__ (line 5097) | def __init__(self, expr, savelist=False):
  class Combine (line 5101) | class Combine(TokenConverter):
    method __init__ (line 5119) | def __init__(self, expr, joinString="", adjacent=True):
    method ignore (line 5129) | def ignore(self, other):
    method postParse (line 5136) | def postParse(self, instring, loc, tokenlist):
  class Group (line 5146) | class Group(TokenConverter):
    method __init__ (line 5161) | def __init__(self, expr):
    method postParse (line 5165) | def postParse(self, instring, loc, tokenlist):
  class Dict (line 5168) | class Dict(TokenConverter):
    method __init__ (line 5207) | def __init__(self, expr):
    method postParse (line 5211) | def postParse(self, instring, loc, tokenlist):
  class Suppress (line 5236) | class Suppress(TokenConverter):
    method postParse (line 5258) | def postParse(self, instring, loc, tokenlist):
    method suppress (line 5261) | def suppress(self):
  class OnlyOnce (line 5265) | class OnlyOnce(object):
    method __init__ (line 5268) | def __init__(self, methodCall):
    method __call__ (line 5271) | def __call__(self, s, l, t):
    method reset (line 5277) | def reset(self):
  function traceParseAction (line 5280) | def traceParseAction(f):
  function delimitedList (line 5328) | def delimitedList(expr, delim=",", combine=False):
  function countedArray (line 5349) | def countedArray(expr, intExpr=None):
  function _flatten (line 5385) | def _flatten(L):
  function matchPreviousLiteral (line 5394) | def matchPreviousLiteral(expr):
  function matchPreviousExpr (line 5424) | def matchPreviousExpr(expr):
  function _escapeRegexRangeChars (line 5453) | def _escapeRegexRangeChars(s):
  function oneOf (line 5461) | def oneOf(strs, caseless=False, useRegex=True, asKeyword=False):
  function dictOf (line 5548) | def dictOf(key, value):
  function originalTextFor (line 5587) | def originalTextFor(expr, asString=True):
  function ungroup (line 5629) | def ungroup(expr):
  function locatedExpr (line 5635) | def locatedExpr(expr):
  function srange (line 5678) | def srange(s):
  function matchOnlyAtCol (line 5710) | def matchOnlyAtCol(n):
  function replaceWith (line 5719) | def replaceWith(replStr):
  function removeQuotes (line 5734) | def removeQuotes(s, l, t):
  function tokenMap (line 5749) | def tokenMap(func, *args):
  function _makeTags (line 5805) | def _makeTags(tagStr, xml,
  function makeHTMLTags (line 5842) | def makeHTMLTags(tagStr):
  function makeXMLTags (line 5866) | def makeXMLTags(tagStr):
  function withAttribute (line 5874) | def withAttribute(*args, **attrDict):
  function withClass (line 5945) | def withClass(classname, namespace=''):
  function infixNotation (line 5987) | def infixNotation(baseExpr, opList, lpar=Suppress('('), rpar=Suppress(')...
  function nestedExpr (line 6126) | def nestedExpr(opener="(", closer=")", content=None, ignoreExpr=quotedSt...
  function indentedBlock (line 6230) | def indentedBlock(blockStatementExpr, indentStack, indent=True):
  function replaceHTMLEntity (line 6362) | def replaceHTMLEntity(t):
  class pyparsing_common (line 6397) | class pyparsing_common:
    method convertToDate (line 6604) | def convertToDate(fmt="%Y-%m-%d"):
    method convertToDatetime (line 6629) | def convertToDatetime(fmt="%Y-%m-%dT%H:%M:%S.%f"):
    method stripHTMLTags (line 6664) | def stripHTMLTags(s, l, tokens):
  class _lazyclassproperty (line 6697) | class _lazyclassproperty(object):
    method __init__ (line 6698) | def __init__(self, fn):
    method __get__ (line 6703) | def __get__(self, obj, cls):
  class unicode_set (line 6715) | class unicode_set(object):
    method _get_chars_for_ranges (line 6732) | def _get_chars_for_ranges(cls):
    method printables (line 6742) | def printables(cls):
    method alphas (line 6747) | def alphas(cls):
    method nums (line 6752) | def nums(cls):
    method alphanums (line 6757) | def alphanums(cls):
  class pyparsing_unicode (line 6762) | class pyparsing_unicode(unicode_set):
    class Latin1 (line 6768) | class Latin1(unicode_set):
    class LatinA (line 6772) | class LatinA(unicode_set):
    class LatinB (line 6776) | class LatinB(unicode_set):
    class Greek (line 6780) | class Greek(unicode_set):
    class Cyrillic (line 6788) | class Cyrillic(unicode_set):
    class Chinese (line 6792) | class Chinese(unicode_set):
    class Japanese (line 6796) | class Japanese(unicode_set):
      class Kanji (line 6800) | class Kanji(unicode_set):
      class Hiragana (line 6804) | class Hiragana(unicode_set):
      class Katakana (line 6808) | class Katakana(unicode_set):
    class Korean (line 6812) | class Korean(unicode_set):
    class CJK (line 6816) | class CJK(Chinese, Japanese, Korean):
    class Thai (line 6820) | class Thai(unicode_set):
    class Arabic (line 6824) | class Arabic(unicode_set):
    class Hebrew (line 6828) | class Hebrew(unicode_set):
    class Devanagari (line 6832) | class Devanagari(unicode_set):
  class pyparsing_test (line 6856) | class pyparsing_test:
    class reset_pyparsing_context (line 6861) | class reset_pyparsing_context:
      method __init__ (line 6884) | def __init__(self):
      method save (line 6887) | def save(self):
      method restore (line 6903) | def restore(self):
      method __enter__ (line 6922) | def __enter__(self):
      method __exit__ (line 6925) | def __exit__(self, *args):
    class TestParseResultsAsserts (line 6928) | class TestParseResultsAsserts:
      method assertParseResultsEquals (line 6932) | def assertParseResultsEquals(
      method assertParseAndCheckList (line 6944) | def assertParseAndCheckList(
      method assertParseAndCheckDict (line 6956) | def assertParseAndCheckDict(
      method assertRunTestResults (line 6968) | def assertRunTestResults(
      method assertRaisesParseException (line 7032) | def assertRaisesParseException(self, exc_type=ParseException, msg=No...

FILE: shotgun_api3/lib/sgtimezone.py
  class SgTimezone (line 23) | class SgTimezone(object):
    method __init__ (line 38) | def __init__(self):
    method UTC (line 43) | def UTC(cls):
    method LocalTimezone (line 51) | def LocalTimezone(cls):
  class UTC (line 59) | class UTC(tzinfo):
    method utcoffset (line 65) | def utcoffset(self, dt):
    method tzname (line 68) | def tzname(self, dt):
    method dst (line 71) | def dst(self, dt):
  class LocalTimezone (line 75) | class LocalTimezone(tzinfo):
    method utcoffset (line 81) | def utcoffset(self, dt):
    method dst (line 90) | def dst(self, dt):
    method tzname (line 99) | def tzname(self, dt):
    method _isdst (line 106) | def _isdst(self, dt):

FILE: shotgun_api3/shotgun.py
  class OrderItem (line 112) | class OrderItem(TypedDict):
  class GroupingItem (line 116) | class GroupingItem(TypedDict):
  class BaseEntity (line 121) | class BaseEntity(TypedDict, total=False):
  class ShotgunError (line 130) | class ShotgunError(Exception):
  class ShotgunFileDownloadError (line 138) | class ShotgunFileDownloadError(ShotgunError):
  class ShotgunThumbnailNotReady (line 146) | class ShotgunThumbnailNotReady(ShotgunError):
  class Fault (line 154) | class Fault(ShotgunError):
  class AuthenticationFault (line 162) | class AuthenticationFault(Fault):
  class MissingTwoFactorAuthenticationFault (line 170) | class MissingTwoFactorAuthenticationFault(Fault):
  class UserCredentialsNotAllowedForSSOAuthenticationFault (line 179) | class UserCredentialsNotAllowedForSSOAuthenticationFault(Fault):
  class UserCredentialsNotAllowedForOxygenAuthenticationFault (line 188) | class UserCredentialsNotAllowedForOxygenAuthenticationFault(Fault):
  class ServerCapabilities (line 201) | class ServerCapabilities(object):
    method __init__ (line 211) | def __init__(self, host: str, meta: Dict[str, Any]) -> None:
    method _ensure_support (line 250) | def _ensure_support(self, feature: Dict[str, Any], raise_hell: bool = ...
    method _ensure_json_supported (line 278) | def _ensure_json_supported(self) -> None:
    method ensure_include_archived_projects (line 284) | def ensure_include_archived_projects(self) -> None:
    method ensure_per_project_customization (line 292) | def ensure_per_project_customization(self) -> bool:
    method ensure_support_for_additional_filter_presets (line 300) | def ensure_support_for_additional_filter_presets(self) -> bool:
    method ensure_user_following_support (line 308) | def ensure_user_following_support(self) -> bool:
    method ensure_paging_info_without_counts_support (line 316) | def ensure_paging_info_without_counts_support(self) -> bool:
    method ensure_return_image_urls_support (line 324) | def ensure_return_image_urls_support(self) -> bool:
    method __str__ (line 332) | def __str__(self) -> str:
  class ClientCapabilities (line 340) | class ClientCapabilities(object):
    method __init__ (line 357) | def __init__(self):
    method __str__ (line 377) | def __str__(self):
  class _Config (line 385) | class _Config(object):
    method __init__ (line 390) | def __init__(self, sg: "Shotgun"):
    method set_server_params (line 445) | def set_server_params(self, base_url: str) -> None:
    method records_per_page (line 467) | def records_per_page(self) -> int:
  class Shotgun (line 480) | class Shotgun(object):
    method __init__ (line 498) | def __init__(
    method _split_url (line 739) | def _split_url(self, base_url: str) -> Tuple[Optional[str], Optional[s...
    method server_info (line 771) | def server_info(self) -> Dict[str, Any]:
    method server_caps (line 789) | def server_caps(self) -> ServerCapabilities:
    method connect (line 804) | def connect(self) -> None:
    method close (line 815) | def close(self) -> None:
    method info (line 824) | def info(self) -> Dict[str, Any]:
    method find_one (line 855) | def find_one(
    method find (line 935) | def find(
    method _construct_read_parameters (line 1134) | def _construct_read_parameters(
    method _add_project_param (line 1174) | def _add_project_param(
    method _translate_update_params (line 1183) | def _translate_update_params(
    method summarize (line 1209) | def summarize(
    method create (line 1417) | def create(
    method update (line 1505) | def update(
    method delete (line 1590) | def delete(self, entity_type: str, entity_id: int) -> bool:
    method revive (line 1614) | def revive(self, entity_type: str, entity_id: int) -> bool:
    method batch (line 1632) | def batch(self, requests: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    method work_schedule_read (line 1747) | def work_schedule_read(
    method work_schedule_update (line 1822) | def work_schedule_update(
    method export_page (line 1884) | def export_page(self, page_id, format, layout_name=None):
    method follow (line 1912) | def follow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict...
    method unfollow (line 1940) | def unfollow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Di...
    method followers (line 1967) | def followers(self, entity: Dict[str, Any]) -> List[Dict[str, Any]]:
    method following (line 1995) | def following(
    method schema_entity_read (line 2031) | def schema_entity_read(
    method schema_read (line 2077) | def schema_read(
    method schema_field_read (line 2151) | def schema_field_read(
    method schema_field_create (line 2220) | def schema_field_create(
    method schema_field_update (line 2263) | def schema_field_update(
    method schema_field_delete (line 2310) | def schema_field_delete(self, entity_type: str, field_name: str) -> bool:
    method add_user_agent (line 2327) | def add_user_agent(self, agent: str) -> None:
    method reset_user_agent (line 2339) | def reset_user_agent(self) -> None:
    method set_session_uuid (line 2359) | def set_session_uuid(self, session_uuid: str) -> None:
    method share_thumbnail (line 2375) | def share_thumbnail(
    method upload_thumbnail (line 2521) | def upload_thumbnail(
    method upload_filmstrip_thumbnail (line 2554) | def upload_filmstrip_thumbnail(
    method upload (line 2605) | def upload(
    method _upload_to_storage (line 2694) | def _upload_to_storage(
    method _upload_to_sg (line 2784) | def _upload_to_sg(
    method _get_attachment_upload_info (line 2865) | def _get_attachment_upload_info(
    method download_attachment (line 2914) | def download_attachment(
    method get_auth_cookie_handler (line 3035) | def get_auth_cookie_handler(self) -> urllib.request.HTTPCookieProcessor:
    method get_attachment_download_url (line 3067) | def get_attachment_download_url(
    method authenticate_human_user (line 3127) | def authenticate_human_user(
    method update_project_last_accessed (line 3176) | def update_project_last_accessed(
    method note_thread_read (line 3224) | def note_thread_read(
    method text_search (line 3301) | def text_search(
    method activity_stream_read (line 3399) | def activity_stream_read(
    method nav_expand (line 3497) | def nav_expand(self, path: str, seed_entity_field=None, entity_fields=...
    method nav_search_string (line 3517) | def nav_search_string(
    method nav_search_entity (line 3538) | def nav_search_entity(
    method get_session_token (line 3563) | def get_session_token(self) -> str:
    method preferences_read (line 3587) | def preferences_read(self, prefs: Optional[List] = None) -> Dict[str, ...
    method user_subscriptions_read (line 3610) | def user_subscriptions_read(self) -> List:
    method user_subscriptions_create (line 3622) | def user_subscriptions_create(
    method _build_opener (line 3645) | def _build_opener(self, handler) -> urllib.request.OpenerDirector:
    method _get_certs_file (line 3667) | def _get_certs_file(cls, ca_certs):
    method schema (line 3727) | def schema(self, entity_type):
    method entity_types (line 3736) | def entity_types(self):
    method _call_rpc (line 3746) | def _call_rpc(
    method _auth_params (line 3816) | def _auth_params(self) -> Dict[str, Any]:
    method _sanitize_auth_params (line 3871) | def _sanitize_auth_params(self, params: Dict[str, Any]) -> Dict[str, A...
    method _build_payload (line 3882) | def _build_payload(
    method _encode_payload (line 3902) | def _encode_payload(self, payload) -> bytes:
    method _make_call (line 3913) | def _make_call(
    method _http_request (line 3965) | def _http_request(
    method _make_upload_request (line 3991) | def _make_upload_request(
    method _parse_http_status (line 4005) | def _parse_http_status(self, status: Tuple) -> None:
    method _decode_response (line 4023) | def _decode_response(
    method _json_loads (line 4046) | def _json_loads(self, body: str) -> Any:
    method _response_errors (line 4049) | def _response_errors(self, sg_response):
    method _visit_data (line 4095) | def _visit_data(self, data: T, visitor) -> T:
    method _transform_outbound (line 4115) | def _transform_outbound(self, data: T) -> T:
    method _transform_inbound (line 4162) | def _transform_inbound(self, data: T) -> T:
    method _get_connection (line 4195) | def _get_connection(self) -> Http:
    method _close_connection (line 4224) | def _close_connection(self) -> None:
    method _parse_records (line 4243) | def _parse_records(self, records: List) -> List:
    method _build_thumb_url (line 4299) | def _build_thumb_url(self, entity_type: str, entity_id: int) -> str:
    method _dict_to_list (line 4346) | def _dict_to_list(
    method _dict_to_extra_data (line 4369) | def _dict_to_extra_data(
    method _upload_file_to_storage (line 4380) | def _upload_file_to_storage(self, path: str, storage_url: str) -> None:
    method _multipart_upload_file_to_storage (line 4400) | def _multipart_upload_file_to_storage(
    method _get_upload_part_link (line 4444) | def _get_upload_part_link(
    method _upload_data_to_storage (line 4486) | def _upload_data_to_storage(
    method _complete_multipart_upload (line 4543) | def _complete_multipart_upload(
    method _requires_direct_s3_upload (line 4578) | def _requires_direct_s3_upload(
    method _send_form (line 4621) | def _send_form(self, url: str, params: Dict[str, Any]) -> str:
  class FormPostHandler (line 4666) | class FormPostHandler(urllib.request.BaseHandler):
    method http_request (line 4673) | def http_request(self, request):
    method encode (line 4694) | def encode(self, params, files, boundary=None, buffer=None):
    method https_request (line 4749) | def https_request(self, request):
  function _translate_filters (line 4753) | def _translate_filters(filters: Union[List, Tuple], filter_operator) -> ...
  function _translate_filters_dict (line 4762) | def _translate_filters_dict(sg_filter: Dict[str, Any]) -> Dict[str, Any]:
  function _translate_filters_list (line 4783) | def _translate_filters_list(filters):
  function _translate_filters_simple (line 4799) | def _translate_filters_simple(sg_filter):
  function _version_str (line 4822) | def _version_str(version) -> str:
  function _optimize_filter_field (line 4829) | def _optimize_filter_field(

FILE: tests/base.py
  class TestBase (line 22) | class TestBase(unittest.TestCase):
    method __init__ (line 40) | def __init__(self, *args, **kws):
    method setUpClass (line 45) | def setUpClass(cls):
    method setUp (line 64) | def setUp(self, auth_mode="ApiUser"):
    method tearDown (line 116) | def tearDown(self):
  class MockTestBase (line 120) | class MockTestBase(TestBase):
    method setUp (line 123) | def setUp(self):
    method _setup_mock (line 129) | def _setup_mock(self, s3_status_code_error=503):
    method _mock_http (line 166) | def _mock_http(self, data, headers=None, status=None):
    method _assert_http_method (line 200) | def _assert_http_method(self, method, params, check_auth=True):
    method _setup_mock_data (line 219) | def _setup_mock_data(self):
  class LiveTestBase (line 232) | class LiveTestBase(TestBase):
    method setUp (line 235) | def setUp(self, auth_mode=None):
    method setUpClass (line 254) | def setUpClass(cls):
    method _setup_db (line 277) | def _setup_db(cls, config, sg):
    method gen_entity (line 344) | def gen_entity(self, entity_type, **kwargs):
    method find_one_await_thumbnail (line 368) | def find_one_await_thumbnail(
  class HumanUserAuthLiveTestBase (line 389) | class HumanUserAuthLiveTestBase(LiveTestBase):
    method setUp (line 395) | def setUp(self):
  class SessionTokenAuthLiveTestBase (line 399) | class SessionTokenAuthLiveTestBase(LiveTestBase):
    method setUp (line 405) | def setUp(self):
  class SgTestConfig (line 409) | class SgTestConfig(object):
    method __init__ (line 412) | def __init__(self):
    method config_keys (line 421) | def config_keys(self):
    method read_config (line 441) | def read_config(self, config_path):
  function _find_or_create_entity (line 453) | def _find_or_create_entity(sg, entity_type, data, identifyiers=None):

FILE: tests/test_api.py
  class TestShotgunApi (line 39) | class TestShotgunApi(base.LiveTestBase):
    method setUp (line 40) | def setUp(self):
    method test_info (line 45) | def test_info(self):
    method test_server_dates (line 50) | def test_server_dates(self):
    method test_batch (line 56) | def test_batch(self):
    method test_empty_batch (line 92) | def test_empty_batch(self):
    method test_create_update_delete (line 97) | def test_create_update_delete(self):
    method test_last_accessed (line 129) | def test_last_accessed(self):
    method test_get_session_token (line 134) | def test_get_session_token(self):
    method test_upload_download (line 140) | def test_upload_download(self):
    method test_upload_to_sg (line 274) | def test_upload_to_sg(self, mock_send_form):
    method test_upload_thumbnail_in_create (line 328) | def test_upload_thumbnail_in_create(self):
    method test_upload_thumbnail_for_version (line 380) | def test_upload_thumbnail_for_version(self):
    method test_upload_thumbnail_for_task (line 418) | def test_upload_thumbnail_for_task(self):
    method test_upload_thumbnail_with_upload_function (line 454) | def test_upload_thumbnail_with_upload_function(self):
    method test_requires_direct_s3_upload (line 468) | def test_requires_direct_s3_upload(self):
    method test_linked_thumbnail_url (line 542) | def test_linked_thumbnail_url(self):
    method test_share_thumbnail (line 590) | def test_share_thumbnail(self):
    method test_share_thumbnail_not_ready (line 674) | def test_share_thumbnail_not_ready(self, mock_send_form):
    method test_share_thumbnail_returns_error (line 693) | def test_share_thumbnail_returns_error(self, mock_send_form):
    method test_deprecated_functions (line 707) | def test_deprecated_functions(self):
    method test_simple_summary (line 712) | def test_simple_summary(self):
    method test_summary_include_archived_projects (line 726) | def test_summary_include_archived_projects(self):
    method test_summary_values (line 745) | def test_summary_values(self):
    method test_json_dumps_default_ensure_ascii_disabled (line 830) | def test_json_dumps_default_ensure_ascii_disabled(self):
    method test_work_schedule (line 849) | def test_work_schedule(self):
    method test_preferences_read (line 947) | def test_preferences_read(self):
  class TestDataTypes (line 1006) | class TestDataTypes(base.LiveTestBase):
    method setUp (line 1013) | def setUp(self):
    method test_set_checkbox (line 1016) | def test_set_checkbox(self):
    method test_set_color (line 1026) | def test_set_color(self):
    method test_set_date (line 1036) | def test_set_date(self):
    method test_set_date_time (line 1046) | def test_set_date_time(self):
    method test_set_duration (line 1061) | def test_set_duration(self):
    method test_set_entity (line 1071) | def test_set_entity(self):
    method test_set_float (line 1081) | def test_set_float(self):
    method test_set_list (line 1091) | def test_set_list(self):
    method test_set_multi_entity (line 1101) | def test_set_multi_entity(self):
    method test_set_number (line 1151) | def test_set_number(self):
    method test_set_status_list (line 1161) | def test_set_status_list(self):
    method test_set_tag_list (line 1171) | def test_set_tag_list(self):
    method test_set_text (line 1181) | def test_set_text(self):
    method test_set_text_html_entity (line 1191) | def test_set_text_html_entity(self):
    method assert_set_field (line 1201) | def assert_set_field(
  class TestUtc (line 1220) | class TestUtc(base.LiveTestBase):
    method setUp (line 1223) | def setUp(self):
    method test_convert_to_utc (line 1231) | def test_convert_to_utc(self):
    method test_no_convert_to_utc (line 1243) | def test_no_convert_to_utc(self):
    method _assert_expected (line 1255) | def _assert_expected(self, sg, date_time, expected):
  class TestFind (line 1264) | class TestFind(base.LiveTestBase):
    method setUp (line 1265) | def setUp(self):
    method test_find (line 1278) | def test_find(self):
    method _id_in_result (line 1297) | def _id_in_result(self, entity_type, filters, expected_id):
    method test_in_relation_comma_id (line 1317) | def test_in_relation_comma_id(self):
    method test_in_relation_list_id (line 1325) | def test_in_relation_list_id(self):
    method test_not_in_relation_id (line 1333) | def test_not_in_relation_id(self):
    method test_in_relation_comma_text (line 1341) | def test_in_relation_comma_text(self):
    method test_in_relation_list_text (line 1349) | def test_in_relation_list_text(self):
    method test_not_in_relation_text (line 1357) | def test_not_in_relation_text(self):
    method test_in_relation_comma_color (line 1365) | def test_in_relation_comma_color(self):
    method test_in_relation_list_color (line 1377) | def test_in_relation_list_color(self):
    method test_not_in_relation_color (line 1389) | def test_not_in_relation_color(self):
    method test_in_relation_comma_date (line 1401) | def test_in_relation_comma_date(self):
    method test_in_relation_list_date (line 1413) | def test_in_relation_list_date(self):
    method test_not_in_relation_date (line 1425) | def test_not_in_relation_date(self):
    method test_in_relation_comma_duration (line 1439) | def test_in_relation_comma_duration(self):
    method test_in_relation_list_duration (line 1457) | def test_in_relation_list_duration(self):
    method test_not_in_relation_duration (line 1481) | def test_not_in_relation_duration(self):
    method test_in_relation_comma_entity (line 1506) | def test_in_relation_comma_entity(self):
    method test_in_relation_list_entity (line 1518) | def test_in_relation_list_entity(self):
    method test_not_in_relation_entity (line 1530) | def test_not_in_relation_entity(self):
    method test_in_relation_comma_entity_type (line 1542) | def test_in_relation_comma_entity_type(self):
    method test_in_relation_list_entity_type (line 1551) | def test_in_relation_list_entity_type(self):
    method test_not_in_relation_entity_type (line 1560) | def test_not_in_relation_entity_type(self):
    method test_in_relation_comma_float (line 1571) | def test_in_relation_comma_float(self):
    method test_in_relation_list_float (line 1588) | def test_in_relation_list_float(self):
    method test_not_in_relation_float (line 1604) | def test_not_in_relation_float(self):
    method test_in_relation_comma_list (line 1620) | def test_in_relation_comma_list(self):
    method test_in_relation_list_list (line 1632) | def test_in_relation_list_list(self):
    method test_not_in_relation_list (line 1644) | def test_not_in_relation_list(self):
    method test_in_relation_comma_multi_entity (line 1656) | def test_in_relation_comma_multi_entity(self):
    method test_in_relation_list_multi_entity (line 1672) | def test_in_relation_list_multi_entity(self):
    method test_not_in_relation_multi_entity (line 1690) | def test_not_in_relation_multi_entity(self):
    method test_in_relation_comma_number (line 1708) | def test_in_relation_comma_number(self):
    method test_in_relation_list_number (line 1720) | def test_in_relation_list_number(self):
    method test_not_in_relation_number (line 1732) | def test_not_in_relation_number(self):
    method test_in_relation_comma_status_list (line 1744) | def test_in_relation_comma_status_list(self):
    method test_in_relation_list_status_list (line 1756) | def test_in_relation_list_status_list(self):
    method test_not_in_relation_status_list (line 1768) | def test_not_in_relation_status_list(self):
    method test_in_relation_comma_uuid (line 1780) | def test_in_relation_comma_uuid(self):
    method test_in_relation_list_uuid (line 1795) | def test_in_relation_list_uuid(self):
    method test_not_in_relation_uuid (line 1812) | def test_not_in_relation_uuid(self):
    method test_find_in (line 1829) | def test_find_in(self):
    method test_unsupported_filters (line 1858) | def test_unsupported_filters(self):
    method test_zero_is_not_none (line 1884) | def test_zero_is_not_none(self):
    method test_include_archived_projects (line 1928) | def test_include_archived_projects(self):
  class TestExportPage (line 1950) | class TestExportPage(base.LiveTestBase):
    method setUp (line 1952) | def setUp(self):
    method test_export_page_unavailable (line 1955) | def test_export_page_unavailable(self):
    method test_export_page_format_missing (line 1978) | def test_export_page_format_missing(self):
    method test_export_page_missing_page_id (line 1993) | def test_export_page_missing_page_id(self):
    method test_export_page_without_layout_name (line 2009) | def test_export_page_without_layout_name(self, mock_request):
  class TestFollow (line 2029) | class TestFollow(base.LiveTestBase):
    method test_follow_unfollow (line 2031) | def test_follow_unfollow(self):
    method test_followers (line 2050) | def test_followers(self):
    method test_following (line 2071) | def test_following(self):
  class TestErrors (line 2138) | class TestErrors(base.TestBase):
    method setUp (line 2139) | def setUp(self):
    method test_bad_auth (line 2143) | def test_bad_auth(self):
    method test_status_not_200 (line 2231) | def test_status_not_200(self, mock_request):
    method test_make_call_retry (line 2239) | def test_make_call_retry(self, mock_request):
    method test_sanitized_auth_params (line 2297) | def test_sanitized_auth_params(self, mock_open):
    method test_upload_empty_file (line 2315) | def test_upload_empty_file(self):
    method test_upload_missing_file (line 2335) | def test_upload_missing_file(self):
  class TestScriptUserSudoAuth (line 2360) | class TestScriptUserSudoAuth(base.LiveTestBase):
    method setUp (line 2361) | def setUp(self):
    method test_user_is_creator (line 2370) | def test_user_is_creator(self):
  class TestHumanUserSudoAuth (line 2401) | class TestHumanUserSudoAuth(base.TestBase):
    method setUp (line 2402) | def setUp(self):
    method test_human_user_sudo_auth_fails (line 2405) | def test_human_user_sudo_auth_fails(self):
  class TestHumanUserAuth (line 2435) | class TestHumanUserAuth(base.HumanUserAuthLiveTestBase):
    method test_humanuser_find (line 2440) | def test_humanuser_find(self):
    method test_humanuser_upload_thumbnail_for_version (line 2459) | def test_humanuser_upload_thumbnail_for_version(self):
  class TestSessionTokenAuth (line 2496) | class TestSessionTokenAuth(base.SessionTokenAuthLiveTestBase):
    method test_humanuser_find (line 2501) | def test_humanuser_find(self):
    method test_humanuser_upload_thumbnail_for_version (line 2523) | def test_humanuser_upload_thumbnail_for_version(self):
  class TestProjectLastAccessedByCurrentUser (line 2563) | class TestProjectLastAccessedByCurrentUser(base.LiveTestBase):
    method test_logged_in_user (line 2565) | def test_logged_in_user(self):
    method test_pass_in_user (line 2600) | def test_pass_in_user(self):
    method test_sudo_as_user (line 2634) | def test_sudo_as_user(self):
  class TestActivityStream (line 2668) | class TestActivityStream(base.LiveTestBase):
    method setUp (line 2673) | def setUp(self):
    method tearDown (line 2706) | def tearDown(self):
    method test_simple (line 2726) | def test_simple(self):
    method test_limit (line 2749) | def test_limit(self):
    method test_extra_fields (line 2765) | def test_extra_fields(self):
  class TestNoteThreadRead (line 2791) | class TestNoteThreadRead(base.LiveTestBase):
    method setUp (line 2796) | def setUp(self):
    method _check_note (line 2803) | def _check_note(self, data, note_id, additional_fields):
    method _check_reply (line 2825) | def _check_reply(self, data, reply_id, additional_fields):
    method _check_attachment (line 2845) | def _check_attachment(self, data, attachment_id, additional_fields):
    method test_simple (line 2866) | def test_simple(self):
    method test_complex (line 2945) | def test_complex(self):
  class TestTextSearch (line 3002) | class TestTextSearch(base.LiveTestBase):
    method setUp (line 3007) | def setUp(self):
    method tearDown (line 3030) | def tearDown(self):
    method test_simple (line 3050) | def test_simple(self):
    method test_limit (line 3070) | def test_limit(self):
    method test_entity_filter (line 3083) | def test_entity_filter(self):
    method test_complex_entity_filter (line 3099) | def test_complex_entity_filter(self):
  class TestReadAdditionalFilterPresets (line 3127) | class TestReadAdditionalFilterPresets(base.LiveTestBase):
    method test_simple_case (line 3132) | def test_simple_case(self):
    method test_find_one (line 3158) | def test_find_one(self):
    method test_filter_with_no_name (line 3183) | def test_filter_with_no_name(self):
    method test_invalid_filter (line 3206) | def test_invalid_filter(self):
    method test_filter_not_iterable (line 3229) | def test_filter_not_iterable(self):
    method test_filter_not_list_of_iterable (line 3252) | def test_filter_not_list_of_iterable(self):
    method test_multiple_latest_filters (line 3275) | def test_multiple_latest_filters(self):
    method test_modify_visibility (line 3304) | def test_modify_visibility(self):
  class TestLibImports (line 3388) | class TestLibImports(base.LiveTestBase):
    method test_import_httplib (line 3394) | def test_import_httplib(self):
  function _get_path (line 3415) | def _get_path(url):

FILE: tests/test_api_long.py
  class TestShotgunApiLong (line 21) | class TestShotgunApiLong(base.LiveTestBase):
    method test_automated_find (line 22) | def test_automated_find(self):
    method test_schema (line 97) | def test_schema(self):
    method test_schema_with_project (line 141) | def test_schema_with_project(self):

FILE: tests/test_client.py
  function b64encode (line 35) | def b64encode(val):
  class TestShotgunClient (line 42) | class TestShotgunClient(base.MockTestBase):
    method setUp (line 45) | def setUp(self):
    method test_detect_client_caps (line 54) | def test_detect_client_caps(self):
    method test_detect_server_caps (line 69) | def test_detect_server_caps(self):
    method test_server_version_json (line 88) | def test_server_version_json(self):
    method test_extra_auth_params (line 107) | def test_extra_auth_params(self):
    method test_session_uuid (line 127) | def test_session_uuid(self):
    method test_url (line 148) | def test_url(self):
    method test_b64encode (line 168) | def test_b64encode(self):
    method test_read_config (line 177) | def test_read_config(self):
    method test_split_url (line 188) | def test_split_url(self):
    method test_authorization (line 221) | def test_authorization(self):
    method test_localization_header_default (line 241) | def test_localization_header_default(self):
    method test_localization_header_when_localized (line 251) | def test_localization_header_when_localized(self):
    method test_user_agent (line 263) | def test_user_agent(self):
    method test_connect_close (line 306) | def test_connect_close(self):
    method test_network_retry (line 314) | def test_network_retry(self):
    method test_set_retry_interval (line 325) | def test_set_retry_interval(self):
    method test_http_error (line 365) | def test_http_error(self):
    method test_rpc_error (line 371) | def test_rpc_error(self):
    method test_call_rpc (line 381) | def test_call_rpc(self):
    method test_upload_s3_503 (line 447) | def test_upload_s3_503(self):
    method test_upload_s3_500 (line 473) | def test_upload_s3_500(self):
    method test_upload_s3_urlerror__get_attachment_upload_info (line 500) | def test_upload_s3_urlerror__get_attachment_upload_info(self):
    method test_upload_s3_urlerror__upload_to_storage (line 528) | def test_upload_s3_urlerror__upload_to_storage(self):
    method test_transform_data (line 562) | def test_transform_data(self):
    method test_encode_payload (line 618) | def test_encode_payload(self):
    method test_decode_response_ascii (line 629) | def test_decode_response_ascii(self):
    method test_decode_response_unicode (line 632) | def test_decode_response_unicode(self):
    method _assert_decode_resonse (line 635) | def _assert_decode_resonse(self, ensure_ascii, data):
    method test_parse_records (line 657) | def test_parse_records(self):
    method test_thumb_url (line 688) | def test_thumb_url(self):
  class TestShotgunClientInterface (line 719) | class TestShotgunClientInterface(base.MockTestBase):
    method test_client_interface (line 722) | def test_client_interface(self):
    method test_module_interface (line 728) | def test_module_interface(self):

FILE: tests/test_mockgun.py
  class TestMockgunModuleInterface (line 53) | class TestMockgunModuleInterface(unittest.TestCase):
    method test_interface_intact (line 58) | def test_interface_intact(self):
  class TestValidateFilterSyntax (line 72) | class TestValidateFilterSyntax(unittest.TestCase):
    method setUp (line 77) | def setUp(self):
    method test_filter_array_or_dict (line 89) | def test_filter_array_or_dict(self):
  class TestEntityFieldComparison (line 110) | class TestEntityFieldComparison(unittest.TestCase):
    method setUp (line 115) | def setUp(self):
    method test_searching_for_none_entity_field (line 139) | def test_searching_for_none_entity_field(self):
    method test_searching_for_initialized_entity_field (line 152) | def test_searching_for_initialized_entity_field(self):
    method test_find_entity_with_none_link (line 166) | def test_find_entity_with_none_link(self):
  class TestTextFieldOperators (line 179) | class TestTextFieldOperators(unittest.TestCase):
    method setUp (line 184) | def setUp(self):
    method test_operator_is (line 194) | def test_operator_is(self):
    method test_operator_is_none (line 202) | def test_operator_is_none(self):
    method test_operator_is_case_sensitivity (line 210) | def test_operator_is_case_sensitivity(self):
    method test_operator_is_not (line 218) | def test_operator_is_not(self):
    method test_operator_is_not_none (line 226) | def test_operator_is_not_none(self):
    method test_operator_is_not_case_sensitivity (line 234) | def test_operator_is_not_case_sensitivity(self):
    method test_operator_in (line 242) | def test_operator_in(self):
    method test_operator_in_none (line 250) | def test_operator_in_none(self):
    method test_operator_in_case_sensitivity (line 258) | def test_operator_in_case_sensitivity(self):
    method test_operator_not_in (line 266) | def test_operator_not_in(self):
    method test_operator_not_in_none (line 277) | def test_operator_not_in_none(self):
    method test_operator_not_in_case_sensitivity (line 285) | def test_operator_not_in_case_sensitivity(self):
    method test_operator_contains (line 293) | def test_operator_contains(self):
    method test_operator_contains_case_sensitivity (line 301) | def test_operator_contains_case_sensitivity(self):
    method test_operator_not_contains (line 309) | def test_operator_not_contains(self):
    method test_operator_not_contains_case_sensitivity (line 317) | def test_operator_not_contains_case_sensitivity(self):
    method test_operator_starts_with (line 325) | def test_operator_starts_with(self):
    method test_operator_starts_with_case_sensitivity (line 333) | def test_operator_starts_with_case_sensitivity(self):
    method test_operator_ends_with (line 341) | def test_operator_ends_with(self):
    method test_operator_ends_with_case_sensitivity (line 349) | def test_operator_ends_with_case_sensitivity(self):
  class TestMultiEntityFieldComparison (line 358) | class TestMultiEntityFieldComparison(unittest.TestCase):
    method setUp (line 363) | def setUp(self):
    method test_find_by_sub_entity_field (line 394) | def test_find_by_sub_entity_field(self):
    method test_find_with_none (line 442) | def test_find_with_none(self):
  class TestMultiEntityFieldUpdate (line 460) | class TestMultiEntityFieldUpdate(unittest.TestCase):
    method setUp (line 465) | def setUp(self):
    method test_update_add (line 501) | def test_update_add(self):
    method test_update_remove (line 521) | def test_update_remove(self):
    method test_update_set (line 537) | def test_update_set(self):
    method test_batch_update (line 553) | def test_batch_update(self):
  class TestFilterOperator (line 571) | class TestFilterOperator(unittest.TestCase):
    method setUp (line 576) | def setUp(self):
    method test_simple_filter_operators (line 616) | def test_simple_filter_operators(self):
    method test_ordered_filter_operator (line 644) | def test_ordered_filter_operator(self):
    method test_nested_filter_operators (line 685) | def test_nested_filter_operators(self):
    method test_invalid_operator (line 717) | def test_invalid_operator(self):
  class TestConfig (line 734) | class TestConfig(unittest.TestCase):
    method test_set_server_params_with_regular_url (line 739) | def test_set_server_params_with_regular_url(self):
    method test_set_server_params_with_url_with_path (line 748) | def test_set_server_params_with_url_with_path(self):

FILE: tests/test_proxy.py
  class ServerConnectionTest (line 17) | class ServerConnectionTest(base.TestBase):
    method setUp (line 20) | def setUp(self):
    method test_connection (line 23) | def test_connection(self):
    method test_proxy_info (line 28) | def test_proxy_info(self):

FILE: tests/test_unit.py
  class TestShotgunInit (line 24) | class TestShotgunInit(unittest.TestCase):
    method setUp (line 27) | def setUp(self):
    method test_http_proxy_server (line 33) | def test_http_proxy_server(self):
    method test_http_proxy_server_and_port (line 57) | def test_http_proxy_server_and_port(self):
    method test_http_proxy_server_and_port_with_authentication (line 83) | def test_http_proxy_server_and_port_with_authentication(self):
    method test_http_proxy_with_at_in_password (line 117) | def test_http_proxy_with_at_in_password(self):
    method test_malformatted_proxy_info (line 135) | def test_malformatted_proxy_info(self):
  class TestShotgunSummarize (line 150) | class TestShotgunSummarize(unittest.TestCase):
    method setUp (line 156) | def setUp(self):
    method test_filter_operator_none (line 161) | def test_filter_operator_none(self):
    method _assert_filter_operator (line 166) | def _assert_filter_operator(self, expected_logical_operator, filter_op...
    method test_filter_operator_all (line 171) | def test_filter_operator_all(self):
    method test_filter_operator_or (line 176) | def test_filter_operator_or(self):
    method test_filters (line 181) | def test_filters(self):
    method get_call_rpc_params (line 192) | def get_call_rpc_params(self, args, kws, call_rpc):
    method test_grouping (line 199) | def test_grouping(self):
    method test_grouping_type (line 207) | def test_grouping_type(self):
  class TestShotgunBatch (line 214) | class TestShotgunBatch(unittest.TestCase):
    method setUp (line 215) | def setUp(self):
    method test_missing_required_key (line 220) | def test_missing_required_key(self):
  class TestServerCapabilities (line 244) | class TestServerCapabilities(unittest.TestCase):
    method test_no_server_version (line 245) | def test_no_server_version(self):
    method test_bad_version (line 248) | def test_bad_version(self):
    method test_dev_version (line 257) | def test_dev_version(self):
  class TestClientCapabilities (line 271) | class TestClientCapabilities(unittest.TestCase):
    method test_darwin (line 273) | def test_darwin(self):
    method test_windows (line 276) | def test_windows(self):
    method test_linux (line 279) | def test_linux(self):
    method assert_platform (line 282) | def assert_platform(self, sys_ret_val, expected):
    method test_no_platform (line 294) | def test_no_platform(self):
    method test_py_version (line 305) | def test_py_version(self, mock_sys):
  class TestFilters (line 315) | class TestFilters(unittest.TestCase):
    method test_empty (line 318) | def test_empty(self):
    method test_simple (line 324) | def test_simple(self):
    method test_arrays (line 339) | def test_arrays(self):
    method test_nested (line 361) | def test_nested(self):
    method test_invalid (line 412) | def test_invalid(self):
    method test_related_object (line 437) | def test_related_object(self):
    method test_related_object_entity_optimization_is (line 474) | def test_related_object_entity_optimization_is(self):
    method test_related_object_entity_optimization_in (line 530) | def test_related_object_entity_optimization_in(self):
    method test_related_object_update_entity (line 581) | def test_related_object_update_entity(self):
    method test_related_object_update_optimization_entity (line 618) | def test_related_object_update_optimization_entity(self):
    method test_related_object_update_optimization_entity_multi (line 671) | def test_related_object_update_optimization_entity_multi(self):
  class TestCerts (line 766) | class TestCerts(unittest.TestCase):
    method setUp (line 779) | def setUp(self):
    method _check_url_with_sg_api_httplib2 (line 787) | def _check_url_with_sg_api_httplib2(self, url, certs):
    method _check_url_with_urllib (line 795) | def _check_url_with_urllib(self, url):
    method test_found_correct_cert (line 805) | def test_found_correct_cert(self):
    method test_httplib (line 823) | def test_httplib(self):
    method test_urlib (line 841) | def test_urlib(self):

FILE: update_httplib2.py
  class Utilities (line 16) | class Utilities:
    method download_archive (line 17) | def download_archive(self, file_path, file_name):
    method unzip_archive (line 30) | def unzip_archive(self, file_path, file_name, temp_dir):
    method remove_folder (line 35) | def remove_folder(self, path):
    method git_remove (line 40) | def git_remove(self, target):
    method copy_folder (line 54) | def copy_folder(self, source, target):
    method sanitize_file (line 58) | def sanitize_file(self, file_path):
  function main (line 73) | def main(temp_path, repo_root, version):
Condensed preview — 84 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,539K chars).
[
  {
    "path": ".coveragerc",
    "chars": 645,
    "preview": "# Copyright (c) 2018 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": ".flake8",
    "chars": 538,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": ".gitattributes",
    "chars": 231,
    "preview": "# Handle line endings automatically for files detected as text\n# and leave all files detected as binary untouched.\n* tex"
  },
  {
    "path": ".gitignore",
    "chars": 687,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 2053,
    "preview": "# Copyright (c) 2024, Shotgun Software Inc.\n#\n# Redistribution and use in source and binary forms, with or without\n# mod"
  },
  {
    "path": "HISTORY.rst",
    "chars": 23216,
    "preview": "*********************************************\nFlow Production Tracking Python API Changelog\n****************************"
  },
  {
    "path": "LICENSE",
    "chars": 2824,
    "preview": "Copyright (c) 2009-2011, Shotgun Software Inc\nAll rights reserved.\n\nRedistribution and use in source and binary forms, w"
  },
  {
    "path": "README.md",
    "chars": 2918,
    "preview": "[![Supported Python versions: 3.9, 3.10, 3.11, 3.13](https://img.shields.io/badge/Python-3.9_|_3.10_|_3.11_|_3.13-blue?l"
  },
  {
    "path": "SECURITY.md",
    "chars": 1470,
    "preview": "# Security Policy\n\n## Security\n\nAt Autodesk, we know that the security of your data is critical to your studio’s\noperati"
  },
  {
    "path": "azure-pipelines-templates/run-tests.yml",
    "chars": 8424,
    "preview": "# -----------------------------------------------------------------------------\n# Copyright (c) 2009-2021, Shotgun Softw"
  },
  {
    "path": "azure-pipelines-templates/type_checking.yml",
    "chars": 2280,
    "preview": "# Copyright (c) 2025, Shotgun Software Inc.\n#\n# Redistribution and use in source and binary forms, with or without\n# mod"
  },
  {
    "path": "azure-pipelines.yml",
    "chars": 4356,
    "preview": "# -----------------------------------------------------------------------------\n# Copyright (c) 2009-2021, Shotgun Softw"
  },
  {
    "path": "developer/README.md",
    "chars": 3019,
    "preview": "\n# Updating HTTPLib2\n\nThe API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder. To update the co"
  },
  {
    "path": "docs/advanced/iron_python.rst",
    "chars": 1863,
    "preview": "**********\nIronPython\n**********\n\nWe do not test against IronPython and cannot be sure that we won't introduce breaking "
  },
  {
    "path": "docs/advanced/packaging.rst",
    "chars": 1854,
    "preview": ".. _packaging:\n\n################################################\nPackaging an application with py2app (or py2exe)\n######"
  },
  {
    "path": "docs/advanced.rst",
    "chars": 410,
    "preview": ".. _advanced_topics:\n\n###############\nAdvanced Topics\n###############\n\nBelow are some more advanced topics regarding usa"
  },
  {
    "path": "docs/authentication.rst",
    "chars": 5708,
    "preview": "##############\nAuthentication\n##############\n\nIn order to communicate with your server via the API, you must provide val"
  },
  {
    "path": "docs/changelog.rst",
    "chars": 77,
    "preview": ".. currentmodule:: shotgun_api3.shotgun.Shotgun\n\n.. include:: ../HISTORY.rst\n"
  },
  {
    "path": "docs/cookbook/attachments.rst",
    "chars": 15333,
    "preview": ".. _attachments:\n\n################################\nDetails About Working With Files\n################################\n\nTh"
  },
  {
    "path": "docs/cookbook/examples/ami_handler.rst",
    "chars": 9862,
    "preview": ".. _ami_handler:\n\n###############################\nHandling Action Menu Item Calls\n###############################\n\nThis "
  },
  {
    "path": "docs/cookbook/examples/ami_version_packager.rst",
    "chars": 10732,
    "preview": ".. _ami_version_packager:\n\n########################################################\nUsing an ActionMenuItem to Package V"
  },
  {
    "path": "docs/cookbook/examples/basic_create_shot.rst",
    "chars": 3712,
    "preview": ".. _example_create_shot:\n\nCreate A Shot\n=============\n\nBuilding the data and calling :meth:`~shotgun_api3.Shotgun.create"
  },
  {
    "path": "docs/cookbook/examples/basic_create_shot_task_template.rst",
    "chars": 2966,
    "preview": "Create a Shot with a Task Template\n==================================\nCreating a new Shot with a Task Template is just l"
  },
  {
    "path": "docs/cookbook/examples/basic_create_version_link_shot.rst",
    "chars": 4098,
    "preview": "Create a Version Linked to a Shot\n=================================\nYou've just created a sweet new Version of your shot"
  },
  {
    "path": "docs/cookbook/examples/basic_delete_shot.rst",
    "chars": 1460,
    "preview": "Delete A Shot\n=============\n\nCalling :meth:`~shotgun_api3.Shotgun.delete`\n--------------------------------------------\nD"
  },
  {
    "path": "docs/cookbook/examples/basic_find_shot.rst",
    "chars": 2682,
    "preview": ".. _example_find_shot:\n\nFind a Shot\n===========\n\nBuilding the Query\n------------------\nWe are going to assume we know th"
  },
  {
    "path": "docs/cookbook/examples/basic_sg_instance.rst",
    "chars": 1048,
    "preview": ".. _example_sg_instance:\n\nCreate a Flow Production Tracking API instance\n==============================================\n"
  },
  {
    "path": "docs/cookbook/examples/basic_update_shot.rst",
    "chars": 2985,
    "preview": "Update A Shot\n=============\n\nBuilding the data and calling :meth:`~shotgun_api3.Shotgun.update`\n------------------------"
  },
  {
    "path": "docs/cookbook/examples/basic_upload_thumbnail_version.rst",
    "chars": 1481,
    "preview": "Upload a Thumbnail for a Version\n================================\n\nSo you've created a new Version of a Shot, and you've"
  },
  {
    "path": "docs/cookbook/examples/svn_integration.rst",
    "chars": 8615,
    "preview": ".. _svn_integration:\n\n############################\nSubversion (SVN) Integration\n############################\n\nIntegratin"
  },
  {
    "path": "docs/cookbook/smart_cut_fields.rst",
    "chars": 1715,
    "preview": ".. _smart_cut_fields:\n\n################\nSmart Cut Fields\n################\n\n.. warning::\n    Smart Cut Fields should be c"
  },
  {
    "path": "docs/cookbook/tasks/split_tasks.rst",
    "chars": 7297,
    "preview": ".. _split_tasks:\n\n###########\nSplit Tasks\n###########\n\nSplit tasks can be created and edited via the API but must comply"
  },
  {
    "path": "docs/cookbook/tasks/task_dependencies.rst",
    "chars": 12837,
    "preview": ".. _task_dependencies:\n\n#################\nTask Dependencies\n#################\n\nTask dependencies work the same way in th"
  },
  {
    "path": "docs/cookbook/tasks/updating_tasks.rst",
    "chars": 11217,
    "preview": ".. _updating_tasks:\n\n########################################################\nUpdating Task Dates: How Flow Production T"
  },
  {
    "path": "docs/cookbook/tasks.rst",
    "chars": 323,
    "preview": "##################\nWorking With Tasks\n##################\n\nTasks have various special functionality available in the UI t"
  },
  {
    "path": "docs/cookbook/tutorials.rst",
    "chars": 702,
    "preview": "########\nExamples\n########\n\nHere's a list of various simple tutorials to walk through that should provide you with a goo"
  },
  {
    "path": "docs/cookbook/usage_tips.rst",
    "chars": 12929,
    "preview": "##############\nAPI Usage Tips\n##############\n\nBelow is a list of helpful tips when using the Flow Production Tracking AP"
  },
  {
    "path": "docs/cookbook.rst",
    "chars": 1909,
    "preview": "************\nAPI Cookbook\n************\n\nHere we have a collection of useful information you can use for reference when w"
  },
  {
    "path": "docs/index.rst",
    "chars": 2656,
    "preview": "###########################################\nFlow Production Tracking Python API library\n################################"
  },
  {
    "path": "docs/installation.rst",
    "chars": 2850,
    "preview": "############\nInstallation\n############\n\n********************\nMinimum Requirements\n********************\n\n.. note::\n    So"
  },
  {
    "path": "docs/reference.rst",
    "chars": 35683,
    "preview": ".. currentmodule:: shotgun_api3\n\n.. _apireference:\n\n#############\nAPI Reference\n#############\n\n\n************************"
  },
  {
    "path": "setup.py",
    "chars": 1604,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is pro"
  },
  {
    "path": "shotgun_api3/__init__.py",
    "chars": 2192,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": "shotgun_api3/lib/.gitignore",
    "chars": 11,
    "preview": "*.dist-info"
  },
  {
    "path": "shotgun_api3/lib/README.md",
    "chars": 1617,
    "preview": "# Lib Submodules\n\n## Third Party Modules\n\nSome third-party modules are bundled with `python-api` inside lib.\n\n### httpli"
  },
  {
    "path": "shotgun_api3/lib/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "shotgun_api3/lib/certifi/__init__.py",
    "chars": 94,
    "preview": "from .core import contents, where\n\n__all__ = [\"contents\", \"where\"]\n__version__ = \"2026.01.04\"\n"
  },
  {
    "path": "shotgun_api3/lib/certifi/__main__.py",
    "chars": 243,
    "preview": "import argparse\n\nfrom certifi import contents, where\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"-c\", \"--co"
  },
  {
    "path": "shotgun_api3/lib/certifi/cacert.pem",
    "chars": 270954,
    "preview": "\n# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited\n# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited\n# Label: \"QuoVad"
  },
  {
    "path": "shotgun_api3/lib/certifi/core.py",
    "chars": 3394,
    "preview": "\"\"\"\ncertifi.py\n~~~~~~~~~~\n\nThis module returns the installation location of cacert.pem or its contents.\n\"\"\"\nimport sys\ni"
  },
  {
    "path": "shotgun_api3/lib/certifi/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "shotgun_api3/lib/httplib2/__init__.py",
    "chars": 69381,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Small, fast HTTP client library for Python.\"\"\"\n\n__author__ = \"Joe Gregorio (joe@bitworking.or"
  },
  {
    "path": "shotgun_api3/lib/httplib2/auth.py",
    "chars": 2166,
    "preview": "import base64\nimport re\n\nfrom .. import pyparsing as pp\n\nfrom .error import *\n\n\ntry:  # pyparsing>=3.0.0\n    downcaseTok"
  },
  {
    "path": "shotgun_api3/lib/httplib2/cacerts.txt",
    "chars": 137365,
    "preview": "# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.\n# Subject: CN=GTE CyberTrust"
  },
  {
    "path": "shotgun_api3/lib/httplib2/certs.py",
    "chars": 971,
    "preview": "\"\"\"Utilities for certificate management.\"\"\"\n\nimport os\n\ncertifi_available = False\ncertifi_where = None\ntry:\n    from cer"
  },
  {
    "path": "shotgun_api3/lib/httplib2/error.py",
    "chars": 954,
    "preview": "# All exceptions raised here derive from HttpLib2Error\nclass HttpLib2Error(Exception):\n    pass\n\n\n# Some exceptions can "
  },
  {
    "path": "shotgun_api3/lib/httplib2/iri2uri.py",
    "chars": 4153,
    "preview": "# -*- coding: utf-8 -*-\n\"\"\"Converts an IRI to a URI.\"\"\"\n\n__author__ = \"Joe Gregorio (joe@bitworking.org)\"\n__copyright__ "
  },
  {
    "path": "shotgun_api3/lib/httplib2/socks.py",
    "chars": 19701,
    "preview": "\"\"\"SocksiPy - Python SOCKS module.\n\nVersion 1.00\n\nCopyright 2006 Dan-Haim. All rights reserved.\n\nRedistribution and use "
  },
  {
    "path": "shotgun_api3/lib/mockgun/__init__.py",
    "chars": 1800,
    "preview": "\"\"\"\n -----------------------------------------------------------------------------\n Copyright (c) 2009-2017, Shotgun Sof"
  },
  {
    "path": "shotgun_api3/lib/mockgun/errors.py",
    "chars": 2067,
    "preview": "\"\"\"\n -----------------------------------------------------------------------------\n Copyright (c) 2009-2017, Shotgun Sof"
  },
  {
    "path": "shotgun_api3/lib/mockgun/mockgun.py",
    "chars": 38226,
    "preview": "\"\"\"\n -----------------------------------------------------------------------------\n Copyright (c) 2009-2017, Shotgun Sof"
  },
  {
    "path": "shotgun_api3/lib/mockgun/schema.py",
    "chars": 4875,
    "preview": "\"\"\"\n -----------------------------------------------------------------------------\n Copyright (c) 2009-2017, Shotgun Sof"
  },
  {
    "path": "shotgun_api3/lib/pyparsing.py",
    "chars": 273233,
    "preview": "# -*- coding: utf-8 -*-\n# module pyparsing.py\n#\n# Copyright (c) 2003-2019  Paul T. McGuire\n#\n# Permission is hereby gran"
  },
  {
    "path": "shotgun_api3/lib/requirements.txt",
    "chars": 1704,
    "preview": "# Copyright (c) 2009-2019, Shotgun Software Inc.\n#\n# Redistribution and use in source and binary forms, with or without\n"
  },
  {
    "path": "shotgun_api3/lib/sgtimezone.py",
    "chars": 3390,
    "preview": "#! /opt/local/bin/python\n\n# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is p"
  },
  {
    "path": "shotgun_api3/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "shotgun_api3/shotgun.py",
    "chars": 199169,
    "preview": "#!/usr/bin/env python\n\"\"\"\n-----------------------------------------------------------------------------\nCopyright (c) 20"
  },
  {
    "path": "software_credits",
    "chars": 7052,
    "preview": "The Flow Production Tracking Python API uses the following software.\nThanks to their creators, license information below"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/base.py",
    "chars": 16650,
    "preview": "\"\"\"Base class for Flow Production Tracking API tests.\"\"\"\n\nimport configparser\nimport contextlib\nimport json\nimport os\nim"
  },
  {
    "path": "tests/empty.txt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/example_config",
    "chars": 1332,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": "tests/mockgun/schema.pickle",
    "chars": 870359,
    "preview": "(dp0\nS'Playlist'\np1\n(dp2\nS'open_notes_count'\np3\n(dp4\nS'mandatory'\np5\n(dp6\nS'editable'\np7\nI00\nsS'value'\np8\nI00\nssS'name'\n"
  },
  {
    "path": "tests/mockgun/schema_entity.pickle",
    "chars": 8854,
    "preview": "(dp0\nS'Playlist'\np1\n(dp2\nS'name'\np3\n(dp4\nS'editable'\np5\nI00\nsS'value'\np6\nS'Playlist'\np7\nsssS'AppWelcomeUserConnection'\np"
  },
  {
    "path": "tests/requirements.txt",
    "chars": 478,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": "tests/run_appveyor.bat",
    "chars": 702,
    "preview": ":: Copyright (c) 2018 Shotgun Software Inc.\n::\n:: CONFIDENTIAL AND PROPRIETARY\n::\n:: This work is provided \"AS IS\" and s"
  },
  {
    "path": "tests/test_api.py",
    "chars": 121303,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": "tests/test_api_long.py",
    "chars": 7704,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": "tests/test_client.py",
    "chars": 29299,
    "preview": "# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is provided \"AS IS\" and subjec"
  },
  {
    "path": "tests/test_config_file",
    "chars": 123,
    "preview": "[SERVER_INFO]\nserver_url     : https://url\nscript_name    : xyz\napi_key        : %%abce\n\n[TEST_DATA]\nproject_name   : hj"
  },
  {
    "path": "tests/test_mockgun.py",
    "chars": 25751,
    "preview": "#! /opt/local/bin/python\n\n#  -----------------------------------------------------------------------------\n#  Copyright "
  },
  {
    "path": "tests/test_proxy.py",
    "chars": 1285,
    "preview": "#! /opt/local/bin/python\n\n# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is p"
  },
  {
    "path": "tests/test_unit.py",
    "chars": 30565,
    "preview": "#! /opt/local/bin/python\n\n# Copyright (c) 2019 Shotgun Software Inc.\n#\n# CONFIDENTIAL AND PROPRIETARY\n#\n# This work is p"
  },
  {
    "path": "update_httplib2.py",
    "chars": 3431,
    "preview": "#!/usr/bin/env python3\n\n\"\"\"\nUpdates the httplib2 module.\n\nRun as \"./upgrade_httplib2.py vX.Y.Z\" to get a specific releas"
  }
]

About this extraction

This page contains the full source code of the shotgunsoftware/python-api GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 84 files (2.3 MB), approximately 599.3k tokens, and a symbol index with 1067 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!