From 694f7c3fa939d1c8f4fb78a7171ef376b7129404 Mon Sep 17 00:00:00 2001 From: Dan Bradham Date: Mon, 4 Mar 2019 16:04:12 -0500 Subject: [PATCH 001/140] Remove everything --- CHANGELOG | 12 + CONTRIBUTORS | 1 + README.rst | 38 +- casts/.config/asciinema/install-id | 1 - casts/.tmux.conf | 1 - casts/README.rst | 47 - casts/convert.Dockerfile | 22 - casts/convert.sh | 13 - casts/record.Dockerfile | 47 - casts/record.sh | 13 - casts/scripts/new_project.yaml | 8 - casts/tasks.py | 176 - construct/__init__.py | 13 - construct/action.py | 348 - construct/actioncontext.py | 29 - construct/actionparams.py | 162 - construct/actionrunner.py | 344 - construct/api.py | 347 - construct/builtins/__init__.py | 99 - construct/builtins/assets.py | 245 - construct/builtins/collections.py | 124 - construct/builtins/files.py | 212 - construct/builtins/projects.py | 92 - construct/builtins/sequences.py | 137 - construct/builtins/shots.py | 148 - construct/builtins/tasks.py | 126 - construct/builtins/templates.py | 105 - .../builtins/templates/vfx_asset/.data/data | 1 - .../templates/vfx_asset/.data/tag_asset | 0 .../templates/vfx_collection/.data/data | 0 .../vfx_collection/.data/tag_collection | 0 .../builtins/templates/vfx_project/.data/data | 2 - .../templates/vfx_project/.data/tag_project | 0 .../uuid_69c45613-75a9-4b90-aaa5-d40323214e58 | 0 .../templates/vfx_project/assets/.data/data | 0 .../vfx_project/assets/.data/tag_collection | 0 .../vfx_project/assets/character/.data/data | 0 .../assets/character/.data/tag_asset_type | 0 .../uuid_e71f7370-7e40-40e5-ab90-7c005677427e | 0 .../vfx_project/assets/environment/.data/data | 0 .../assets/environment/.data/tag_asset_type | 0 .../uuid_4ead44a5-3d8b-4b44-a918-fde9318de284 | 0 .../vfx_project/assets/prop/.data/data | 0 .../assets/prop/.data/tag_asset_type | 0 .../uuid_7cb5a9c8-4aaf-4ee4-a90b-61805478fc13 | 0 .../templates/vfx_sequence/.data/data | 1 - .../templates/vfx_sequence/.data/tag_sequence | 0 .../builtins/templates/vfx_shot/.data/data | 1 - .../templates/vfx_shot/.data/tag_shot | 0 .../builtins/templates/vfx_task/.data/data | 1 - .../templates/vfx_task/.data/tag_task | 0 .../templates/vfx_workspace/.data/data | 1 - .../vfx_workspace/.data/tag_workspace | 0 construct/builtins/time.py | 251 - construct/builtins/workspaces.py | 120 - construct/cli/__init__.py | 2 - construct/cli/__main__.py | 248 - construct/cli/commands.py | 533 - construct/cli/constants.py | 63 - construct/cli/formatters.py | 126 - construct/cli/stout.py | 468 - construct/cli/utils.py | 43 - construct/cli/widgets.py | 93 - construct/compat.py | 28 - construct/config.py | 61 - construct/constants.py | 56 - construct/context.py | 174 - construct/defaults/construct.yaml | 195 - construct/errors.py | 122 - construct/extension.py | 388 - construct/models.py | 277 - construct/signals.py | 146 - construct/stats.py | 48 - construct/tasks.py | 1021 -- construct/tests/__init__.py | 11 - construct/tests/test_actionparams.py | 57 - .../tests/test_data/extpath1/extensiona.py | 6 - .../tests/test_data/extpath2/extensionb.py | 6 - .../extpath3/extension_package/__init__.py | 6 - construct/tests/test_extensions.py | 30 - construct/tests/test_tasks.py | 273 - construct/tests/test_types.py | 54 - construct/tests/utils.py | 0 construct/types.py | 227 - construct/utils.py | 263 - construct/vendor/__init__.py | 10 - construct/vendor/lucidity/__init__.py | 128 - construct/vendor/lucidity/_version.py | 2 - construct/vendor/lucidity/error.py | 21 - construct/vendor/lucidity/template.py | 327 - docs/api.rst | 136 +- docs/cli.rst | 107 - docs/concepts.rst | 124 - docs/configuration.rst | 146 - docs/doctrees/api.doctree | Bin 738256 -> 0 bytes docs/doctrees/cli.doctree | Bin 17001 -> 0 bytes docs/doctrees/concepts.doctree | Bin 17533 -> 0 bytes docs/doctrees/configuration.doctree | Bin 10609 -> 0 bytes docs/doctrees/environment.doctree | Bin 4405 -> 0 bytes docs/doctrees/environment.pickle | Bin 61247 -> 0 bytes docs/doctrees/guide.doctree | Bin 14911 -> 0 bytes docs/doctrees/index.doctree | Bin 17247 -> 0 bytes docs/environment.rst | 8 - docs/guide.rst | 147 - docs/html/.buildinfo | 4 - docs/html/_sources/api.rst.txt | 137 - docs/html/_sources/cli.rst.txt | 107 - docs/html/_sources/concepts.rst.txt | 127 - docs/html/_sources/configuration.rst.txt | 149 - docs/html/_sources/environment.rst.txt | 11 - docs/html/_sources/guide.rst.txt | 150 - docs/html/_sources/index.rst.txt | 76 - docs/html/_static/ajax-loader.gif | Bin 673 -> 0 bytes docs/html/_static/basic.css | 665 - docs/html/_static/comment-bright.png | Bin 756 -> 0 bytes docs/html/_static/comment-close.png | Bin 829 -> 0 bytes docs/html/_static/comment.png | Bin 641 -> 0 bytes docs/html/_static/css/badge_only.css | 2 - docs/html/_static/css/theme.css | 5 - docs/html/_static/doctools.js | 311 - docs/html/_static/documentation_options.js | 9 - docs/html/_static/down-pressed.png | Bin 222 -> 0 bytes docs/html/_static/down.png | Bin 202 -> 0 bytes docs/html/_static/file.png | Bin 286 -> 0 bytes docs/html/_static/fonts/Inconsolata-Bold.ttf | Bin 109948 -> 0 bytes .../_static/fonts/Inconsolata-Regular.ttf | Bin 96964 -> 0 bytes docs/html/_static/fonts/Lato-Bold.ttf | Bin 656544 -> 0 bytes docs/html/_static/fonts/Lato-Regular.ttf | Bin 656568 -> 0 bytes docs/html/_static/fonts/RobotoSlab-Bold.ttf | Bin 170616 -> 0 bytes .../html/_static/fonts/RobotoSlab-Regular.ttf | Bin 169064 -> 0 bytes .../_static/fonts/fontawesome-webfont.eot | Bin 76518 -> 0 bytes .../_static/fonts/fontawesome-webfont.svg | 685 -- .../_static/fonts/fontawesome-webfont.ttf | Bin 152796 -> 0 bytes .../_static/fonts/fontawesome-webfont.woff | Bin 90412 -> 0 bytes docs/html/_static/jquery-3.2.1.js | 10253 ---------------- docs/html/_static/jquery.js | 4 - docs/html/_static/js/modernizr.min.js | 4 - docs/html/_static/js/theme.js | 169 - docs/html/_static/minus.png | Bin 90 -> 0 bytes docs/html/_static/plus.png | Bin 90 -> 0 bytes docs/html/_static/pygments.css | 69 - docs/html/_static/searchtools.js | 761 -- docs/html/_static/underscore-1.3.1.js | 999 -- docs/html/_static/underscore.js | 31 - docs/html/_static/up-pressed.png | Bin 214 -> 0 bytes docs/html/_static/up.png | Bin 203 -> 0 bytes docs/html/_static/websupport.js | 808 -- docs/html/api.html | 2477 ---- docs/html/cli.html | 339 - docs/html/concepts.html | 375 - docs/html/configuration.html | 383 - docs/html/environment.html | 247 - docs/html/genindex.html | 1166 -- docs/html/guide.html | 373 - docs/html/index.html | 343 - docs/html/objects.inv | Bin 2310 -> 0 bytes docs/html/py-modindex.html | 330 - docs/html/search.html | 242 - docs/html/searchindex.js | 1 - docs/index.rst | 45 +- setup.py | 8 - 161 files changed, 26 insertions(+), 31308 deletions(-) create mode 100644 CONTRIBUTORS delete mode 100644 casts/.config/asciinema/install-id delete mode 100644 casts/.tmux.conf delete mode 100644 casts/README.rst delete mode 100644 casts/convert.Dockerfile delete mode 100644 casts/convert.sh delete mode 100644 casts/record.Dockerfile delete mode 100644 casts/record.sh delete mode 100644 casts/scripts/new_project.yaml delete mode 100644 casts/tasks.py delete mode 100644 construct/__init__.py delete mode 100644 construct/action.py delete mode 100644 construct/actioncontext.py delete mode 100644 construct/actionparams.py delete mode 100644 construct/actionrunner.py delete mode 100644 construct/api.py delete mode 100644 construct/builtins/__init__.py delete mode 100644 construct/builtins/assets.py delete mode 100644 construct/builtins/collections.py delete mode 100644 construct/builtins/files.py delete mode 100644 construct/builtins/projects.py delete mode 100644 construct/builtins/sequences.py delete mode 100644 construct/builtins/shots.py delete mode 100644 construct/builtins/tasks.py delete mode 100644 construct/builtins/templates.py delete mode 100644 construct/builtins/templates/vfx_asset/.data/data delete mode 100644 construct/builtins/templates/vfx_asset/.data/tag_asset delete mode 100644 construct/builtins/templates/vfx_collection/.data/data delete mode 100644 construct/builtins/templates/vfx_collection/.data/tag_collection delete mode 100644 construct/builtins/templates/vfx_project/.data/data delete mode 100644 construct/builtins/templates/vfx_project/.data/tag_project delete mode 100644 construct/builtins/templates/vfx_project/.data/uuid_69c45613-75a9-4b90-aaa5-d40323214e58 delete mode 100644 construct/builtins/templates/vfx_project/assets/.data/data delete mode 100644 construct/builtins/templates/vfx_project/assets/.data/tag_collection delete mode 100644 construct/builtins/templates/vfx_project/assets/character/.data/data delete mode 100644 construct/builtins/templates/vfx_project/assets/character/.data/tag_asset_type delete mode 100644 construct/builtins/templates/vfx_project/assets/character/.data/uuid_e71f7370-7e40-40e5-ab90-7c005677427e delete mode 100644 construct/builtins/templates/vfx_project/assets/environment/.data/data delete mode 100644 construct/builtins/templates/vfx_project/assets/environment/.data/tag_asset_type delete mode 100644 construct/builtins/templates/vfx_project/assets/environment/.data/uuid_4ead44a5-3d8b-4b44-a918-fde9318de284 delete mode 100644 construct/builtins/templates/vfx_project/assets/prop/.data/data delete mode 100644 construct/builtins/templates/vfx_project/assets/prop/.data/tag_asset_type delete mode 100644 construct/builtins/templates/vfx_project/assets/prop/.data/uuid_7cb5a9c8-4aaf-4ee4-a90b-61805478fc13 delete mode 100644 construct/builtins/templates/vfx_sequence/.data/data delete mode 100644 construct/builtins/templates/vfx_sequence/.data/tag_sequence delete mode 100644 construct/builtins/templates/vfx_shot/.data/data delete mode 100644 construct/builtins/templates/vfx_shot/.data/tag_shot delete mode 100644 construct/builtins/templates/vfx_task/.data/data delete mode 100644 construct/builtins/templates/vfx_task/.data/tag_task delete mode 100644 construct/builtins/templates/vfx_workspace/.data/data delete mode 100644 construct/builtins/templates/vfx_workspace/.data/tag_workspace delete mode 100644 construct/builtins/time.py delete mode 100644 construct/builtins/workspaces.py delete mode 100644 construct/cli/__init__.py delete mode 100644 construct/cli/__main__.py delete mode 100644 construct/cli/commands.py delete mode 100644 construct/cli/constants.py delete mode 100644 construct/cli/formatters.py delete mode 100644 construct/cli/stout.py delete mode 100644 construct/cli/utils.py delete mode 100644 construct/cli/widgets.py delete mode 100644 construct/compat.py delete mode 100644 construct/config.py delete mode 100644 construct/constants.py delete mode 100644 construct/context.py delete mode 100644 construct/defaults/construct.yaml delete mode 100644 construct/errors.py delete mode 100644 construct/extension.py delete mode 100644 construct/models.py delete mode 100644 construct/signals.py delete mode 100644 construct/stats.py delete mode 100644 construct/tasks.py delete mode 100644 construct/tests/__init__.py delete mode 100644 construct/tests/test_actionparams.py delete mode 100644 construct/tests/test_data/extpath1/extensiona.py delete mode 100644 construct/tests/test_data/extpath2/extensionb.py delete mode 100644 construct/tests/test_data/extpath3/extension_package/__init__.py delete mode 100644 construct/tests/test_extensions.py delete mode 100644 construct/tests/test_tasks.py delete mode 100644 construct/tests/test_types.py delete mode 100644 construct/tests/utils.py delete mode 100644 construct/types.py delete mode 100644 construct/utils.py delete mode 100644 construct/vendor/__init__.py delete mode 100644 construct/vendor/lucidity/__init__.py delete mode 100644 construct/vendor/lucidity/_version.py delete mode 100644 construct/vendor/lucidity/error.py delete mode 100644 construct/vendor/lucidity/template.py delete mode 100644 docs/cli.rst delete mode 100644 docs/doctrees/api.doctree delete mode 100644 docs/doctrees/cli.doctree delete mode 100644 docs/doctrees/concepts.doctree delete mode 100644 docs/doctrees/configuration.doctree delete mode 100644 docs/doctrees/environment.doctree delete mode 100644 docs/doctrees/environment.pickle delete mode 100644 docs/doctrees/guide.doctree delete mode 100644 docs/doctrees/index.doctree delete mode 100644 docs/html/.buildinfo delete mode 100644 docs/html/_sources/api.rst.txt delete mode 100644 docs/html/_sources/cli.rst.txt delete mode 100644 docs/html/_sources/concepts.rst.txt delete mode 100644 docs/html/_sources/configuration.rst.txt delete mode 100644 docs/html/_sources/environment.rst.txt delete mode 100644 docs/html/_sources/guide.rst.txt delete mode 100644 docs/html/_sources/index.rst.txt delete mode 100644 docs/html/_static/ajax-loader.gif delete mode 100644 docs/html/_static/basic.css delete mode 100644 docs/html/_static/comment-bright.png delete mode 100644 docs/html/_static/comment-close.png delete mode 100644 docs/html/_static/comment.png delete mode 100644 docs/html/_static/css/badge_only.css delete mode 100644 docs/html/_static/css/theme.css delete mode 100644 docs/html/_static/doctools.js delete mode 100644 docs/html/_static/documentation_options.js delete mode 100644 docs/html/_static/down-pressed.png delete mode 100644 docs/html/_static/down.png delete mode 100644 docs/html/_static/file.png delete mode 100644 docs/html/_static/fonts/Inconsolata-Bold.ttf delete mode 100644 docs/html/_static/fonts/Inconsolata-Regular.ttf delete mode 100644 docs/html/_static/fonts/Lato-Bold.ttf delete mode 100644 docs/html/_static/fonts/Lato-Regular.ttf delete mode 100644 docs/html/_static/fonts/RobotoSlab-Bold.ttf delete mode 100644 docs/html/_static/fonts/RobotoSlab-Regular.ttf delete mode 100644 docs/html/_static/fonts/fontawesome-webfont.eot delete mode 100644 docs/html/_static/fonts/fontawesome-webfont.svg delete mode 100644 docs/html/_static/fonts/fontawesome-webfont.ttf delete mode 100644 docs/html/_static/fonts/fontawesome-webfont.woff delete mode 100644 docs/html/_static/jquery-3.2.1.js delete mode 100644 docs/html/_static/jquery.js delete mode 100644 docs/html/_static/js/modernizr.min.js delete mode 100644 docs/html/_static/js/theme.js delete mode 100644 docs/html/_static/minus.png delete mode 100644 docs/html/_static/plus.png delete mode 100644 docs/html/_static/pygments.css delete mode 100644 docs/html/_static/searchtools.js delete mode 100644 docs/html/_static/underscore-1.3.1.js delete mode 100644 docs/html/_static/underscore.js delete mode 100644 docs/html/_static/up-pressed.png delete mode 100644 docs/html/_static/up.png delete mode 100644 docs/html/_static/websupport.js delete mode 100644 docs/html/api.html delete mode 100644 docs/html/cli.html delete mode 100644 docs/html/concepts.html delete mode 100644 docs/html/configuration.html delete mode 100644 docs/html/environment.html delete mode 100644 docs/html/genindex.html delete mode 100644 docs/html/guide.html delete mode 100644 docs/html/index.html delete mode 100644 docs/html/objects.inv delete mode 100644 docs/html/py-modindex.html delete mode 100644 docs/html/search.html delete mode 100644 docs/html/searchindex.js diff --git a/CHANGELOG b/CHANGELOG index e94dc81..ffd989b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,13 +2,20 @@ Release Notes ============= +1.0.0 (Unreleased) +------------------ + +* Rewrite Construct with multisite studios in mind + 0.1.21 (2018-06-15) +------------------- * Context.from_path - Exclude all entry keys 0.1.20 (2018-05-22) +------------------- * Add support for Collection Entry type @@ -23,11 +30,13 @@ Release Notes 0.1.14 +------ * Adjust availability of new.template, file.* and publish.file 0.1.13 +------ * Add more info to STATUSES and TASK_TYPES config vars * Fixed mac os mangled some paths causes fsfs search to fail @@ -35,16 +44,19 @@ Release Notes 0.1.11 +------ * Fixed fsfs was not setup prior to pulling context from env vars 0.1.10 +------ * *construct push* now goes to the shortest path found 0.1.0 +----- * Include cli in main package * Improved Task api diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..f96e7eb --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1 @@ +Dan Bradham (danielbradham@gmail.com) diff --git a/README.rst b/README.rst index b58bf4e..9977b0a 100644 --- a/README.rst +++ b/README.rst @@ -9,39 +9,16 @@ ========= construct ========= -An extensible API for creative project and asset management. +An extensible API for creative projects and asset libraries. Features ======== - - Manage Projects on your file system + - Manage Projects and Asset Libraries + - Create your own Extensions and Actions - Integrate with your Creative Software - - Manage Workspaces and Publishes - - Folder and Path Templating (fsfs_ and Lucidity_) - - Contextual API - - Extensions, Actions, and Tasks - - Rock solid CLI - - + manage and navigate your projects - + automatically generate commands from Actions - -Core Packages -============= - -+----------------------+------------------------------------------------+ -| package | description | -+======================+================================================+ -| construct_cpenv_ | Cpenv Integration for env management | -+----------------------+------------------------------------------------+ -| construct_launcher_ | Application launcher | -+----------------------+------------------------------------------------+ -| construct_maya_ | Autodesk Maya integration | -+----------------------+------------------------------------------------+ -| construct_nuke_ | The Foundry Nuke integration | -+----------------------+------------------------------------------------+ -| construct_ui_ | Graphical user interface | -+----------------------+------------------------------------------------+ + - Manage Work files and Publish files Installation @@ -57,12 +34,5 @@ Documentation `Visit the Full Documentation for more info and API Documentation `_ -.. _construct_cli: https://github.com/construct-org/construct_cli -.. _construct_cpenv: https://github.com/construct-org/construct_cpenv -.. _construct_templates: https://github.com/construct-org/construct_templates -.. _construct_launcher: https://github.com/construct-org/construct_launcher -.. _construct_maya: https://github.com/construct-org/construct_maya -.. _construct_nuke: https://github.com/construct-org/construct_nuke .. _construct_ui: https://github.com/construct-org/construct_ui .. _fsfs: https://github.com/danbradham/fsfs -.. _Lucidity: https://gitlab.com/4degrees/lucidity diff --git a/casts/.config/asciinema/install-id b/casts/.config/asciinema/install-id deleted file mode 100644 index 3064ab2..0000000 --- a/casts/.config/asciinema/install-id +++ /dev/null @@ -1 +0,0 @@ -e74024c0-3f88-11e8-9c80-bc83850e38b2 diff --git a/casts/.tmux.conf b/casts/.tmux.conf deleted file mode 100644 index 65fe5f5..0000000 --- a/casts/.tmux.conf +++ /dev/null @@ -1 +0,0 @@ -set-option -g status off diff --git a/casts/README.rst b/casts/README.rst deleted file mode 100644 index bf6bc12..0000000 --- a/casts/README.rst +++ /dev/null @@ -1,47 +0,0 @@ -===== -casts -===== - - -Record CLI demos using spielbash/asciinema -========================================== - -Build it... - -.. code-block:: console - - docker build -f record.Dockerfile -t record_casts . - - -Run it... - -.. code-block:: console - - docker run --rm -it -v $PWD:/data record_casts --width 120 --height 40 - - -Convert asciicasts to gif using asciicast2gif -============================================= - -Build it... - -.. code-block:: console - - docker build -f convert.Dockerfile -t convert_casts . - - -Run it... - -.. code-block:: console - - docker run --rm -it -v %cd%:/data convert_casts - - -Run em back to back on Windows -============================== - -Build both the record and convert Dockerfiles. Then run... - -.. code-block:: console - - build diff --git a/casts/convert.Dockerfile b/casts/convert.Dockerfile deleted file mode 100644 index 0305265..0000000 --- a/casts/convert.Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ -FROM asciinema/asciicast2gif - - -# Install convert CMD - -ADD convert.sh /root/convert.sh -RUN chmod +x /root/convert.sh - - -# Install font - -RUN apt-get update -RUN apt-get -y install unifont -RUN fc-cache -f -v - - -# Add unifont to head of font-family for asciinema-player.css - -RUN sed -i 's/font-family: C/font-family: unifont, C/g' /app/page/asciinema-player.css - - -ENTRYPOINT ["/root/convert.sh"] diff --git a/casts/convert.sh b/casts/convert.sh deleted file mode 100644 index 9e2edb3..0000000 --- a/casts/convert.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -mkdir -p gifs - -for filename in casts/*.cast; do - - output="gifs/$(basename "$filename" .cast).gif" - - echo "asciicast2gif $@ $filename $output" - /app/asciicast2gif "$@" $filename $output - echo - -done diff --git a/casts/record.Dockerfile b/casts/record.Dockerfile deleted file mode 100644 index 2f57f9c..0000000 --- a/casts/record.Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -From asciinema/asciinema - - -# Install pip - -RUN apt-get update -RUN apt-get install -y python3-pip -RUN apt-get install -y git -RUN apt-get install -y tmux - -RUN python3 -m pip install pip --upgrade -RUN python3 -m pip install wheel - - -# Install construct - -RUN pip install git+git://github.com/construct-org/construct_setup.git -I --process-dependency-links --trusted-host github.com -RUN echo "source /usr/local/bin/construct.sh" >> /root/.bashrc - - -# Install spielbash - -RUN pip install git+git://github.com/danbradham/spielbash.git - - -# Configure tmux - -ADD .tmux.conf /root/.tmux.conf - - -# Install record CMD - -ADD record.sh /src/record.sh -RUN chmod +x /src/record.sh - - -# Setup data volume - -WORKDIR /data -VOLUME ["/data"] - - -# Set PS1 -RUN echo 'export PS1="\w> "' >> /root/.bashrc - - -ENTRYPOINT ["/src/record.sh"] diff --git a/casts/record.sh b/casts/record.sh deleted file mode 100644 index 1b71499..0000000 --- a/casts/record.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -mkdir -p casts - -for filename in scripts/*.yaml; do - - output="casts/$(basename "$filename" .yaml).cast" - - echo "spielbash --script $filename --output $output $@" - spielbash --script $filename --output $output "$@" - echo - -done diff --git a/casts/scripts/new_project.yaml b/casts/scripts/new_project.yaml deleted file mode 100644 index 5bef597..0000000 --- a/casts/scripts/new_project.yaml +++ /dev/null @@ -1,8 +0,0 @@ -title: Create a new project -scenes: - - name: go home note - action: "# first we'll navigate to our root" - - name: go home - action: construct home - - name: new project - action: construct new.project --root=my_project diff --git a/casts/tasks.py b/casts/tasks.py deleted file mode 100644 index a698ecd..0000000 --- a/casts/tasks.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import argparse -import os -import sys - - -class record(object): - '''Uses spielbash and asciinema to record cli demos stored in scripts/. - ''' - - def __call__(self, args): - build_cmd = ( - 'docker build -q -f record.Dockerfile -t record_casts .' - ) - os.system(build_cmd) - cmd = ( - 'docker run --rm -it -v %cd%:/data record_casts ' - '--width {width} ' - '--height {height} ' - '--speed {speed} ' - '--readtime {readtime}' - ).format(**args.__dict__) - os.system(cmd) - - def parser(self): - parser = argparse.ArgumentParser( - 'tasks record', - add_help=False, - description=self.__doc__ - ) - parser.add_argument( - '-w', '--width', - type=int, - help='Console width (default: 120)', - default=120, - required=False - ) - parser.add_argument( - '-h', '--height', - type=int, - help='Console height (default: 40)', - default=40, - required=False - ) - parser.add_argument( - '-s', '--speed', - type=float, - help='Typing Speed (default: 0.1)', - default=0.1, - required=False - ) - parser.add_argument( - '-r', '--readtime', - type=float, - help='Time to wait between scenes (default: 1.0)', - default=1.0, - required=False - ) - parser.add_argument( - '--help', - help='show this help message and exit', - action='store_true' - ) - return parser - - -class convert(object): - '''Uses asciicast2gif to convert all cast files in casts/ to gif files. - Run after record task. Accepts the same arguments as asciicast2gif.''' - - def __call__(self, args): - build_cmd = ( - 'docker build -q -f convert.Dockerfile -t convert_casts .' - ) - os.system(build_cmd) - cmd = ( - 'docker run --rm -it -v %cd%:/data convert_casts ' - '-w {width} ' - '-h {height} ' - '-s {speed} ' - '-t {theme} ' - '-S {scale}' - ).format(**args.__dict__) - os.system(cmd) - - def parser(self): - parser = argparse.ArgumentParser( - 'tasks convert', - add_help=False, - description=self.__doc__ - ) - parser.add_argument( - '-w', '--width', - type=int, - help='clip terminal to specified number of columns (width: 120)', - default=120, - required=False - ) - parser.add_argument( - '-h', '--height', - type=int, - help='clip terminal to specified number of rows (height: 40)', - default=40, - required=False - ) - parser.add_argument( - '-t', '--theme', - help='color theme, one of: asciinema, tango, solarized-dark, solarized-light, monokai (default: asciinema)', - default='asciinema', - required=False - ) - parser.add_argument( - '-S', '--scale', - help='image scale / pixel density (default: 2)', - type=int, - default=2, - required=False - ) - parser.add_argument( - '-s', '--speed', - type=int, - help='animation speed (default: 1)', - default=1, - required=False - ) - parser.add_argument( - '--help', - help='show this help message and exit', - action='store_true' - ) - return parser - - -class upload(object): - '''Uploads all casts in casts/ to asciinema.org''' - - def __call__(self, args): - cmd = ( - 'docker run --rm -it ' - '-v %cd%/casts:/root/casts -v %cd%/.config:/root/.config ' - 'asciinema/asciinema ' - 'asciinema upload casts/{file}' - ) - for f in os.listdir('casts'): - if f.endswith('.cast'): - os.system(cmd.format(file=f)) - - def parser(self): - parser = argparse.ArgumentParser( - 'tasks upload', - description=self.__doc__ - ) - return parser - - -def main(): - parser = argparse.ArgumentParser(add_help=False) - parser.add_argument( - 'command', - choices=['record', 'convert', 'upload'] - ) - args, extra_args = parser.parse_known_args() - - cmd = getattr(sys.modules[__name__], args.command)() - cmd_parser = cmd.parser() - args = cmd_parser.parse_args(extra_args) - if 'help' in args and args.help: - cmd_parser.print_help() - sys.exit(1) - cmd(args) - - -if __name__ == '__main__': - main() diff --git a/construct/__init__.py b/construct/__init__.py deleted file mode 100644 index 3928f7c..0000000 --- a/construct/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__title__ = 'construct' -__description__ = 'Creative project and asset management' -__version__ = '0.1.22' -__author__ = 'Dan Bradham' -__email__ = 'danielbradham@gmail.com' -__license__ = 'MIT' -__url__ = 'https://github.com/construct-org/construct' - - -from construct.api import * diff --git a/construct/action.py b/construct/action.py deleted file mode 100644 index 678c488..0000000 --- a/construct/action.py +++ /dev/null @@ -1,348 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = [ - 'Action', - 'ActionCollector', - 'ActionProxy', - 'get_action_identifier', - 'group_actions', - 'is_action', - 'is_action_type', - 'sort_actions', -] - -import abc -import logging -from operator import attrgetter -from collections import OrderedDict -from construct import types, actionparams, signals -from construct.compat import basestring -from construct.constants import ACTION_SIGNALS -from construct.utils import missing, classproperty -from construct.actionrunner import ActionRunner -from construct.tasks import sort_tasks, CtxAction -from construct.errors import ActionUnavailable - - -_log = logging.getLogger(__name__) - - -class Action(types.ABC): - '''Action Base Class - - Attributes: - label (str): Nice name used for labeling like: 'New Project' - identifier (str): Used to find and run the action like: 'new.project' - description (str): Description of what the action does - parameters (dict or staticmethod): Description of parameters for - validation like: - - { - 'str_param': { - 'label': 'Str Param', - 'type': str, - 'required': True, - }, - 'int_param': { - 'label': 'Int Param', - 'type': int, - 'required': False, - 'default': 1 - }, - ... - } - - ''' - - runner_cls = ActionRunner - parameters = None - suppress_signals = False - - @abc.abstractproperty - def label(self): - return NotImplemented - - @abc.abstractproperty - def identifier(self): - return NotImplemented - - @classproperty - def description(cls): - return cls.__doc__.split('\n')[0] - - @classproperty - def long_description(cls): - from textwrap import dedent - lines = cls.__doc__.split('\n') - first = lines[0] + '\n' - rest = '' - if len(lines) > 1: - rest = dedent('\n'.join(lines[1:])) - return first + rest - - def _returns(self): - '''Called by ActionProxy to extract a return value from the Action's - Context after run. This function calls Action.returns passing along - the ActionContext when available. If Action.returns is not set - all the artifacts from an Action run are returned. - - Examples: - - from construct import Action - from construct.tasks import artifact - - class NewProject(Action): - ... - returns = artifact('project') - ''' - - return_getter = getattr(self, 'returns', None) - if return_getter: - if isinstance(return_getter, CtxAction): - return return_getter.get(self.ctx) - else: - return return_getter(self.ctx) - else: - return self.ctx.artifacts - - @classmethod - def _available(cls, ctx=missing): - if ctx is missing: - return True - if callable(cls.available): - return cls.available(ctx) - return cls.available - - @abc.abstractmethod - def available(ctx): - ''' - Return True if this action is available in the provided Context. Must - be a classmethod or staticmethod. - - Arguments: - ctx (Context): Context class - ''' - - @classmethod - def params(cls, ctx): - if callable(cls.parameters): - return cls.parameters(ctx) - return cls.parameters or {} - - def __init__(self, *args, **kwargs): - from construct.api import get_context - from construct.actioncontext import ActionContext - - # Get base context to build ActionContext from - ctx = kwargs.pop('ctx', get_context()) - - # Validate params - params = self.params(ctx) - actionparams.validate(params) - - # Combine kwargs with action parameter defaults - action_kwargs = actionparams.get_defaults(params) - action_kwargs.update(kwargs) - - # Create action context - self.ctx = ActionContext(self, args, action_kwargs, ctx) - - # Validate kwargs against params - actionparams.validate_kwargs(params, self.ctx.kwargs) - - # Create action runner - self.runner = self.runner_cls(self, self.ctx) - - def __call__(self): - return self.runner.run() - - def __getattr__(self, attr): - return self.ctx.__dict__.get(attr) - - def retry_group(self, priority): - with signals.suppressed_when(self.suppress_signals, *ACTION_SIGNALS): - return self.runner.retry_group(priority) - - def run_group(self, priority): - with signals.suppressed_when(self.suppress_signals, *ACTION_SIGNALS): - return self.runner.run_group() - - def run(self): - with signals.suppressed_when(self.suppress_signals, *ACTION_SIGNALS): - return self.runner.run() - - -def is_action_type(obj): - return ( - obj is not Action and - isinstance(obj, type) and - issubclass(obj, Action) - ) - - -def is_action(obj): - return isinstance(obj, Action) - - -def get_action_identifier(action_or_identifier): - - identifier = getattr(action_or_identifier, 'identifier', None) - if identifier: - return identifier - - if isinstance(action_or_identifier, basestring): - return action_or_identifier - - raise RuntimeError( - 'Can not determine identifier of %s' % action_or_identifier - ) - - -class ActionCollector(object): - '''Collects Actions and their associated tasks from the currently - available Extensions. - ''' - - def __init__(self, extension_collector): - self._extensions = extension_collector - - def __iter__(self): - for action in sort_actions(self.collect().values()): - yield action - - def __getattr__(self, identifier): - return self.get(identifier) - - def get(self, identifier, ctx=None): - from construct.api import get_context - ctx = ctx or get_context() - try: - action = self.collect(missing)[identifier] - except KeyError: - _log.error('Action not found: %s', identifier) - return - else: - if not action._available(ctx): - raise ActionUnavailable('Action unavaibale in current context') - return action - - def collect(self, ctx=None): - '''Collect all actions in the given context. If no context is provided - return ALL actions. - - Arguments: - ctx: Context instance - used to determine action availability - - Returns: - dict - (Action.identifier, Action) key value pairs - ''' - - from construct.api import get_context - ctx = ctx or get_context() - actions = {} - for name, extension in self._extensions.by_name.items(): - if extension.enabled and extension._available(ctx): - ext_actions = extension.get_actions(ctx) - for identifier, action in ext_actions.items(): - if identifier in actions: - _log.warning( - 'Action identifier collision: %s <-> %s', - actions[identifier], action - ) - else: - actions[identifier] = action - - return actions - - def collect_tasks(self, action_or_identifier, ctx=None): - '''Collect all the tasks for a given Action or Action identifier. - - Arguments: - action_or_identifier: Action class or Action.identifier string - ctx: Context instance - - Returns: - [Task...] - List of Task instances in priority order - ''' - - from construct.api import get_context - ctx = ctx or get_context() - identifier = get_action_identifier(action_or_identifier) - - tasks = [] - for name, extension in self._extensions.by_name.items(): - if extension.enabled and extension._available(ctx): - more_tasks = extension.get_tasks(identifier, ctx) - tasks.extend(more_tasks) - - return sort_tasks(tasks) - - -class ActionProxy(object): - '''Proxy object that allows you to call Actions as if they were functions. - ''' - - def __init__(self, identifier): - self.identifier = identifier - - @property - def action(self): - from construct import actions - return actions.get(self.identifier) - - def instance(self, **kwargs): - return self.action(**kwargs) - - def __repr__(self): - return repr(self.action) - - def __str__(self): - return str(self.action) - - def __getattr__(self, attr): - return getattr(self.action, attr) - - def __call__(self, **kwargs): - a = self.action(**kwargs) - a.run() - return a._returns() - - -def sort_actions(actions): - '''Sort the given actions by identifier''' - - return sorted(actions, key=attrgetter('identifier')) - - -def group_actions(actions): - '''Group sorted actions in a tree structure. - - Returns: - OrderedDict suitable for creating a nested menu - ''' - - if isinstance(actions, dict): - actions = sort_actions(actions.values()) - else: - actions = sort_actions(actions) - - groups = OrderedDict() - for action in actions: - parts = action.identifier.split('.') - if len(parts) == 1: - groups.setdefault('', []) - groups[''].append(action) - else: - nodes = parts[:-1] - section = None - for node in nodes: - if section is None: - section = groups.setdefault(node, OrderedDict()) - section.setdefault('', []) - else: - section = section.setdefault(node, OrderedDict()) - section.setdefault('', []) - section[''].append(action) - - return groups diff --git a/construct/actioncontext.py b/construct/actioncontext.py deleted file mode 100644 index 81ddc76..0000000 --- a/construct/actioncontext.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from construct.context import Context -from construct.tasks import group_tasks -from construct import types, actionparams -from construct.constants import WAITING - - -class ActionContext(Context): - - def __init__(self, action, args, kwargs, ctx=None): - from construct.api import actions, get_context - - if ctx is None: - ctx = get_context() - super(ActionContext, self).__init__(**ctx.__dict__) - - self.action = action - self.status = WAITING - self.tasks = actions.collect_tasks(action, self) - self.task_groups = group_tasks(self.tasks) - self.priorities = list(self.task_groups.keys()) - self.results = {t.identifier: None for t in self.tasks} - self.requests = {} - self.artifacts = types.Namespace() - self.store = types.Namespace() - self.args = args - self.kwargs = actionparams.get_defaults(action.params(ctx)) - self.kwargs.update(kwargs) diff --git a/construct/actionparams.py b/construct/actionparams.py deleted file mode 100644 index f086089..0000000 --- a/construct/actionparams.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = ['validate', 'validate_kwargs', 'get_defaults'] - -import string -from construct.errors import ParameterError, ArgumentError -from fstrings import f - -REQUIRED_OPTIONS = ['type', 'label'] -OPTIONS = ['type', 'label', 'default', 'required', 'options'] -# TODO: Add an additional option to handle coercions? -# coercions=[(TYPE, CONVERTER)...] -VALID_CHARACTERS = string.ascii_lowercase + string.digits + '_' - - -def validate(parameters): - '''Validate Action parameters dict - - Arguments: - parameters (dict): describes parameters of a callable: - - { - 'str_param': { - 'label': 'Str Param', - 'type': str, - 'required': True, - 'validator': lambda x: 1 < len(x) < 36, - 'help': 'This is a required string parameter' - }, - 'int_param': { - 'label': 'Int Param', - 'type': int, - 'required': False, - 'default': 1, - 'help': 'This is an unrequired int parameter' - }, - ... - } - - Returns: - True if parameters are valid - - Raises: - ParameterError: describing invalid parameter - ''' - - for name, options in parameters.items(): - - chars = set(name) - invalid_chars = chars - set(VALID_CHARACTERS) - if invalid_chars: - raise ParameterError( - name, ' contains invalid chars: ', ''.join(invalid_chars) - ) - - opt_names = set(options.keys()) - missing_options = set(REQUIRED_OPTIONS) - opt_names - if missing_options: - raise ParameterError( - name, ' missing required options: ', list(missing_options) - ) - - if 'default' in options: - default = options['default'] - if callable(default): - pass - elif not isinstance(default, options['type']): - raise ParameterError( - '{} default value, {}, does not match type, {}'.format( - name, default, options['type'] - ) - ) - - validator = options.get('validator', None) - if validator and not callable(validator): - raise ParameterError( - 'Validator must be callable: ', name - ) - - return True - - -def validate_kwargs(parameters, kwargs): - '''Validate kwargs using Action parameters dict: - - >>> parameters = {'arg': {'name': 'Arg', 'default': 1.0, 'type': float}} - >>> kwargs = {'arg': 10.0} - >>> validate_kwargs(parameters, kwargs) - True - - Arguments: - parameters (dict): describes parameters of a callable: - kwargs (dict): function arguments as keyword arguments: - - Returns: - True - - Raises: - ArgumentError: describing why kwargs is invalid - ''' - - if not parameters and kwargs: - raise ArgumentError(f('Got additional arguments: {kwargs}')) - - for name, options in parameters.items(): - - validator = options.get('validator', None) - required = options.get('required', False) - type_ = options['type'] - value = kwargs.get(name, None) - valid_values = options.get('options', None) - - if value is None and required: - raise ArgumentError(f('Missing required argument: {name}')) - elif value is None and not required: - continue - - if not isinstance(value, options['type']): - raise ArgumentError(f('{name} must be {type_} not {value}.')) - - if valid_values: - exc_msg = f('{name} must be one of: {valid_values}') - if value not in valid_values: - raise ArgumentError(exc_msg) - - if validator: - exc_msg = f('{name} failed custom validator: {value}') - try: - if validator(value) is False: - raise ArgumentError(exc_msg) - except: - raise ArgumentError(exc_msg) - - return True - - -def get_defaults(parameters): - '''Extract default values from parameters dict. - - >>> parameters = {'arg': {'name': 'Arg', 'default': 1.0}} - >>> get_defaults(parameters) - {'arg': 1.0} - - Arguments: - parameters (dict): describes parameters of a callable: - - Returns: - dict: Containing name, default value pairs - ''' - - defaults = {} - for name, options in parameters.items(): - try: - default = options['default'] - if callable(default): - default = default() - defaults[name] = default - except KeyError: - continue - - return defaults diff --git a/construct/actionrunner.py b/construct/actionrunner.py deleted file mode 100644 index 644b623..0000000 --- a/construct/actionrunner.py +++ /dev/null @@ -1,344 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = ['TaskGroup', 'ActionRunner'] - -import logging -import six -from construct.types import Stack -from construct import signals -from construct.constants import * -from construct.errors import ( - TimeoutError, - ValidationError, - Abort, - Fail, - Pause, - Confirm, - Skip, - Disable -) -from construct.tasks import AsyncRequest -from fstrings import f - - -_log = logging.getLogger(__name__) - - -class TaskGroup(object): - - def __init__(self, priority, tasks, runner): - self.priority = priority - self.tasks = tasks - self.runner = runner - self._exc = None - self._status = WAITING - - def set_exception(self, exc): - self.set_status(FAILED) - self._exc = exc - - def get_exception(self): - return self._exc - - def raise_exception(self): - if not self._exc: - return - six.reraise(*self._exc) - - def set_status(self, status): - if self._status == status: - return - last_status = self._status - self._status = status - signals.send('group.status.changed', self, last_status, status) - - def get_status(self): - return self._status - - @property - def waiting(self): - return self._status == WAITING - - @property - def running(self): - return self._status == RUNNING - - @property - def success(self): - return self._status == SUCCESS - - @property - def failed(self): - return self._status == FAILED - - @property - def done(self): - return self._status in DONE_STATUSES - - def push(self): - for task in self.tasks: - request = self.runner._get_request(task) - self.runner._remove_from_stacks(request) - self.runner._waiting.push(request) - - self.set_status(PENDING) - - def reset(self): - for task in self.tasks: - request = self.runner._get_request(task) - self.runner._remove_from_stacks(request) - self.runner.ctx.requests.pop(request.task.identifier, None) - - self.set_status(WAITING) - - -class ActionRunner(object): - - def __init__(self, action, ctx): - self.action = action - self.ctx = ctx - self._groups = { - p: TaskGroup(p, t, self) - for p, t in self.ctx.task_groups.items() - } - self._waiting = Stack() - self._ready = Stack() - self._failed = Stack() - self._success = Stack() - self._skipped = Stack() - self._disabled = Stack() - self._stacks = [ - self._waiting, - self._ready, - self._failed, - self._success, - self._skipped - ] - - def _remove_from_stacks(self, request): - for stack in self._stacks: - try: - stack.remove(request) - except ValueError: - continue - - def _fail_request(self, request): - self._failed.push(request) - self.ctx.results[request.task.identifier] = request._exc - group = self._get_group(request.task.priority) - group.set_exception(request._exc) - - def _get_group(self, priority): - return self._groups[priority] - - def _get_request(self, task): - '''Create a request object for a Task''' - - if task.identifier not in self.ctx.requests: - self.ctx.requests[task.identifier] = task.request(ctx=self.ctx) - - return self.ctx.requests[task.identifier] - - def _process_waiting(self): - '''Process waiting tasks''' - - nwaiting = len(self._waiting) - for _ in range(nwaiting): - - request = self._waiting.pop() - task = request.task - - if not request.enabled: - self._disabled.push(request) - - elif task.ready(self.ctx): - self._ready.push(request) - - elif task.skip(self.ctx): - request.set_status(SKIPPED) - self._skipped.push(request) - - else: - self._waiting.push(request) - - def _process_ready(self): - '''Process ready tasks''' - - nready = len(self._ready) - for _ in range(nready): - - request = self._ready.pop() - task = request.task - - if task.skip(self.ctx): - request.set_status(SKIPPED) - self._skipped.push(request) - continue - - try: - - get_kwargs = {'propagate': True} - if isinstance(request, AsyncRequest): - get_kwargs['timeout'] = 0 - - result = request.get(**get_kwargs) - - except TimeoutError: - - # AsyncRequest timed out, push it back onto the ready stack - # and check it again next time around - self._ready.push(request) - - except ValidationError: - - # Task failed to validate, future tasks can handle this - # tasks validation errors by requiring failure of this task - # and receiving the result of this task as a parameter - self._fail_request(request) - - except Fail: - - # Task failed but does not want to stop execution - self._fail_request(request) - - except Abort: - - # Task explicitly sent Abort Error - self._fail_request(request) - raise - - except Pause: - - # Task wants to paused execution of the Action - self._success.push(request) - self.ctx.results[task.identifier] = result - raise - - except Confirm: - - # Task has requested user confirmation - self._fail_request(request) - - # TODO: Get user input on Confirm - raise NotImplementedError( - 'Confirm error handling not yet implemented' - ) - - except Skip: - - # Task wants to skip - self._skipped.push(request) - request.set_status(SKIPPED) - - except Disable: - - # Task wants to skip - self._waiting.push(request) - request.set_enabled(False) - request.set_status(DISABLED) - - except Exception: - - # Propagate unrecognized exceptions - self._fail_request(request) - raise - - else: - - # Maybe add support for generator tasks here - # The implementation would be a task that is a generator - # that yields instances of other tasks - # Each task yielded would be added to the _waiting stack - # for future processing - self._success.push(request) - self.ctx.results[task.identifier] = result - - finally: - - # Perform any teardown necessary - pass - - def _process_skipped(self): - - for _ in range(len(self._skipped)): - request = self._skipped.pop() - task = request.task - - if task.ready(self.ctx): - self._ready.push(request) - else: - self._skipped.push(request) - - def _skip_waiting(self): - - nwaiting = len(self._waiting) - for _ in range(nwaiting): - request = self._waiting.pop() - task = request.task - - if task.ready(self.ctx): - self._ready.push(request) - else: - request.set_status(SKIPPED) - self._skipped.push(request) - - def _run_once(self, propagate=True): - - while True: - - self._process_waiting() - - if not self._ready: - self._skip_waiting() - return - - self._process_ready() - self._process_skipped() - - def retry_group(self, priority): - '''Retry TaskGroup of priority''' - - group = self._get_group(priority) - group.reset() - self.run_group(priority) - - def run_group(self, priority): - '''Run TaskGroup of priority''' - - group = self._get_group(priority) - - if group.waiting: - - signals.send('group.before', group) - group.push() - group.set_status(RUNNING) - - try: - - self._run_once() - - finally: - - if not group.failed: - group.set_status(SUCCESS) - - signals.send('group.after', group) - - else: - - raise Exception(f('Task Group already ran: {priority}')) - - def run(self): - '''Run all TaskGroups of the action sequentially''' - - signals.send('action.before', self.ctx) - - try: - - for priority in self.ctx.priorities: - self.run_group(priority) - - finally: - - signals.send('action.after', self.ctx) diff --git a/construct/api.py b/construct/api.py deleted file mode 100644 index 249140b..0000000 --- a/construct/api.py +++ /dev/null @@ -1,347 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -import os -import fsfs -import logging -from logging.config import dictConfig -from itertools import chain -import yaml -from construct.vendor import lucidity -from construct.context import _ctx_stack, _req_stack, Context -from construct.config import Config -from construct.extension import ExtensionCollector, Extension, HostExtension -from construct.action import Action, ActionCollector, ActionProxy -from construct.constants import DEFAULT_LOGGING -from construct.actioncontext import ActionContext -from construct.utils import unipath, ensure_instance -from construct.stats import log_call -from construct.errors import TemplateError - -__all__ = [ - 'Context', - 'config', - 'Config', - 'config_file', - 'extensions', - 'Extension', - 'HostExtension', - 'actions', - 'Action', - 'ActionContext', - 'ActionProxy', - 'init', - 'uninit', - 'set_context', - 'set_context_from_entry', - 'set_context_from_path', - 'get_context', - 'get_request', - 'search', - 'quick_select', - 'get_path_template', - 'get_path_templates', - 'get_template_search_paths', - 'get_templates', - 'get_template', - 'get_host', - 'get_form', - 'new_project', - 'new_sequence', - 'new_shot', - 'new_asset', - 'new_task', - 'new_workspace', - 'new_template', - 'publish', - 'publish_file', - 'save', -] - - -logging.config.dictConfig(DEFAULT_LOGGING) -_log = logging.getLogger(__name__) -_initialized = False -_context = None -config = Config() -config_file = None -extensions = ExtensionCollector() -actions = ActionCollector(extensions) - - -@log_call -def init(root=None, host=None, extension_paths=None, logging=None): - '''Initialize Construct''' - - global _initialized - if _initialized: - raise RuntimeError('Construct has already been initialized.') - - # Load configuration - global config_file - config_file = os.environ.get('CONSTRUCT_CONFIG') - if config_file: - with open(config_file, 'r') as f: - config_data = yaml.load(f.read()) - config.update(config_data) - - # Configure logging - dictConfig(logging or config.get('LOGGING', DEFAULT_LOGGING)) - - if config_file: - _log.debug('Loaded config: %s' % config_file) - else: - _log.debug('Using default config') - - # Setup fsfs entry factory - _log.debug('Configuring fsfs...') - from construct.models import factory - from construct.constants import FSFS_DATA_ROOT, FSFS_DATA_FILE - fsfs.set_entry_factory(factory) - fsfs.set_data_root(FSFS_DATA_ROOT) - fsfs.set_data_file(FSFS_DATA_FILE) - - # Setup initial context - global _context - _log.debug('Setting initial context...') - _context = Context.from_env() - if root: - _log.debug('Setting root to %s' % root) - _context.root = unipath(root) - elif 'CONSTRUCT_ROOT' in os.environ: - _log.debug('Setting root to %s' % os.environ['CONSTRUCT_ROOT']) - _context.root = unipath(os.environ['CONSTRUCT_ROOT']) - else: - _log.debug('Setting root to %s' % config['ROOT']) - _context.root = unipath(config['ROOT']) - - if host: - _context.host = host - - # Discover extensions - _log.debug('Discovering extensions...') - extension_paths = extension_paths or [] - extensions.discover(*extension_paths) - - # Register builtins - from construct.builtins import Builtins - extensions.register(Builtins) - - _log.debug('Initialized!') - _initialized = True - - -@log_call -def uninit(): - '''Uninitialize Construct...''' - - global _initialized - if not _initialized: - raise RuntimeError('Construct has not been initialized yet...') - - global config - _log.debug('Setting default config...') - config = Config() - - global config_file - config_file = None - - global _context - _log.debug('Clearing context...') - _context = None - - _log.debug('Removing all extensions...') - extensions.clear() - - _log.debug('Restoring default fsfs policy...') - fsfs.set_default_policy() - - _log.debug('Uninitialized!') - _initialized = False - - -@log_call -def set_context(ctx): - '''Manually set the current context to the specified :class:`Context`''' - ensure_instance(ctx, Context) - - global _context - _context = ctx - - -@log_call -def set_context_from_entry(entry): - '''Extract and set the current context from the specified entry''' - - global _context - new_context = Context.from_path(entry.path) - _context.update(new_context, exclude=['host', 'root']) - - -@log_call -def set_context_from_path(path): - '''Extract and set the current context from the specified path''' - - global _context - new_context = Context.from_path(path) - _context.update(new_context, exclude=['host', 'root']) - - -@log_call -def get_context(): - '''Get current :class:`Context`''' - - return _ctx_stack.top or _context - - -@log_call -def get_request(): - '''Get current task :class:`Request`''' - - return _req_stack.top - - -@log_call -def get_host(name=None): - '''Get current host extension or get host by name''' - - ctx = get_context() - host = name or ctx.host - return extensions.get(host, None) - - -@log_call -def get_form(action_identifier): - '''Get form for action_identifier''' - - for ext in extensions: - form = ext.get_form(action_identifier) - if form: - return form - - -@log_call -def get_path_template(name): - '''Get one of the current projects templates by name''' - - return lucidity.Template(name, config['PATH_TEMPLATES'][name], anchor=None) - - -@log_call -def get_path_templates(): - '''Get a dict containining the current projects templates''' - - return {k: lucidity.Template(k, v, anchor=None) - for k, v in config['PATH_TEMPLATES']} - - -@log_call -def get_template_search_paths(): - - ctx = get_context() - - paths = set() - if ctx.project: - paths.add(unipath(ctx.project.data.path, 'templates')) - - for ext in extensions: - paths.update(ext._template_paths) - - return [p for p in paths if os.path.isdir(p)] - - -@log_call -def get_template(name, *tags): - '''Get a template by tag and name''' - - templates = get_templates(*tags) - - alt = None - for template in templates.values(): - if name == template.name: - return template - if name in template.name: - alt = template.name - - if alt: - msg = '"{}" not found, did you mean "{}"?'.format(name, alt) - else: - msg = '"{}" not found'.format(name) - raise TemplateError(msg) - - -@log_call -def get_templates(*tags): - '''Get all templates matching a set of tags.''' - - entries = chain(*( - fsfs.search(path, depth=1).tags(*tags) - for path in get_template_search_paths() - )) - - templates = {} - for template in entries: - if template.name in templates: - continue - templates[template.name] = template - - return templates - - -@log_call -def search(name=None, tags=None, **kwargs): - '''Search for Construct Entries by name or tag''' - - ctx = get_context() - root = kwargs.pop('root', None) - - if root is None: - entry = ctx.get_deepest_entry() - if entry: - root = entry.path - else: - root = ctx.root or os.getcwd() - - entries = fsfs.search(root, **kwargs) - if name: - entries = entries.name(name) - if tags: - tags = fsfs.util.tupilize(tags) - entries = entries.tags(*tags) - return entries - - -@log_call -def quick_select(selector, **kwargs): - '''Quickly find one Entry using a selector string like entry1/entry2''' - - ctx = get_context() - root = kwargs.pop('root', None) - - if root is None: - entry = ctx.get_deepest_entry() - if entry: - root = entry.path - else: - root = ctx.root or os.getcwd() - - first_depth = 5 if ctx.project else 3 - if ctx.project: - first_depth = 4 - - return fsfs.quick_select(root, selector, first_depth=first_depth) - - -# Builtin Action Aliases - -new_project = ActionProxy('new.project') -new_sequence = ActionProxy('new.sequence') -new_shot = ActionProxy('new.shot') -new_asset_type = ActionProxy('new.asset_type') -new_asset = ActionProxy('new.asset') -new_task = ActionProxy('new.task') -new_workspace = ActionProxy('new.workspace') -new_template = ActionProxy('new.template') -save = ActionProxy('save') -publish = ActionProxy('publish') -publish_file = ActionProxy('publish_file') diff --git a/construct/builtins/__init__.py b/construct/builtins/__init__.py deleted file mode 100644 index af19932..0000000 --- a/construct/builtins/__init__.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from construct.extension import Extension -from construct.utils import package_path -from construct.builtins import ( - assets, - collections, - files, - projects, - sequences, - shots, - tasks, - templates, - workspaces, - time -) - - -class Builtins(Extension): - '''Builtin Actions and Tasks''' - - name = 'Builtins' - attr_name = 'builtins' - - def available(self, ctx): - return True - - def load(self): - - self.add_template_path(package_path('builtins/templates')) - - self.add_action(projects.NewProject) - self.add_task(projects.NewProject, projects.stage_project) - self.add_task(projects.NewProject, projects.validate_project) - self.add_task(projects.NewProject, projects.commit_project) - - self.add_action(collections.NewCollection) - self.add_task(collections.NewCollection, collections.stage_collection) - self.add_task(collections.NewCollection, collections.validate_collection) - self.add_task(collections.NewCollection, collections.commit_collection) - - self.add_action(sequences.NewSequence) - self.add_task(sequences.NewSequence, sequences.stage_sequence) - self.add_task(sequences.NewSequence, sequences.validate_sequence) - self.add_task(sequences.NewSequence, sequences.commit_sequence) - - self.add_action(shots.NewShot) - self.add_task(shots.NewShot, shots.stage_shot) - self.add_task(shots.NewShot, shots.validate_shot) - self.add_task(shots.NewShot, shots.commit_shot) - - self.add_action(assets.NewAsset) - self.add_task(assets.NewAsset, assets.stage_asset) - self.add_task(assets.NewAsset, assets.validate_asset) - self.add_task(assets.NewAsset, assets.commit_asset) - - self.add_action(assets.NewAssetType) - self.add_task(assets.NewAssetType, assets.stage_asset_type) - self.add_task(assets.NewAssetType, assets.validate_asset_type) - self.add_task(assets.NewAssetType, assets.commit_asset_type) - - self.add_action(tasks.NewTask) - self.add_task(tasks.NewTask, tasks.stage_task) - self.add_task(tasks.NewTask, tasks.validate_task) - self.add_task(tasks.NewTask, tasks.commit_task) - - self.add_action(workspaces.NewWorkspace) - self.add_task(workspaces.NewWorkspace, workspaces.stage_workspace) - self.add_task(workspaces.NewWorkspace, workspaces.validate_workspace) - self.add_task(workspaces.NewWorkspace, workspaces.commit_workspace) - - self.add_action(templates.NewTemplate) - self.add_task(templates.NewTemplate, templates.stage_template) - self.add_task(templates.NewTemplate, templates.validate_template) - self.add_task(templates.NewTemplate, templates.commit_template) - - self.add_action(files.Publish) - self.add_action(files.PublishFile) - - self.add_action(files.Open) - self.add_task(files.Open, files.open_file) - - self.add_action(files.Save) - self.add_task(files.Save, files.build_filename) - self.add_task(files.Save, files.save_file) - - self.add_action(time.SaveFrameRange) - self.add_task(time.SaveFrameRange, time.store_frame_range) - - self.add_action(time.SyncFrameRange) - self.add_task(time.SyncFrameRange, time.get_frame_range) - self.add_task(time.SyncFrameRange, time.apply_frame_range) - - self.add_action(time.SaveFPS) - self.add_task(time.SaveFPS, time.store_frame_rate) - - self.add_action(time.SyncFPS) - self.add_task(time.SyncFPS, time.get_frame_rate) - self.add_task(time.SyncFPS, time.apply_frame_rate) diff --git a/construct/builtins/assets.py b/construct/builtins/assets.py deleted file mode 100644 index dfcff71..0000000 --- a/construct/builtins/assets.py +++ /dev/null @@ -1,245 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct import api -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct.errors import Abort -from construct import types -import fsfs - - -class NewAssetType(Action): - '''Create a new Asset Type''' - - label = 'New Asset Type' - identifier = 'new.asset_type' - returns = artifact('asset_type') - - @staticmethod - def parameters(ctx): - params = dict( - project={ - 'label': 'Project', - 'help': 'Project Entry', - 'required': True, - 'type': types.Entry, - }, - collection={ - 'label': 'Collection', - 'help': 'Collection Name', - 'required': True, - 'type': types.String - }, - name={ - 'label': 'Asset Type Name', - 'required': True, - 'type': types.String, - 'help': 'Name of Asset Type', - }, - ) - - if not ctx: - return params - - if ctx.project: - params['project']['default'] = ctx.project - params['project']['required'] = False - - if ctx.collection: - params['collection']['default'] = ctx.collection.name - params['collection']['required'] = False - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - ctx.collection and - not ctx.asset_type and - not ctx.sequence - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('asset_type')) -def stage_asset_type(project, collection, name): - '''Stage new asset_type Entry''' - - path_template = api.get_path_template('asset_type') - asset_type_path = path_template.format(dict( - project=project.path, - collection=collection, - asset_type=name, - )) - - return fsfs.get_entry(asset_type_path) - - -@task(priority=types.VALIDATE) -@requires(success('stage_asset_type')) -@params(store('asset_type')) -def validate_asset_type(asset_type): - '''Make sure new asset_type does not already exist''' - - if asset_type.exists: - raise Abort('Asset Type already exists: %s' % asset_type.name) - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_asset_type')) -@params(store('asset_type')) -@returns(artifact('asset_type')) -def commit_asset_type(asset_type): - '''Commit new asset_type Entry''' - - asset_type.tag('asset_type') - return asset_type - - -class NewAsset(Action): - '''Create a new Asset''' - - label = 'New Asset' - identifier = 'new.asset' - returns = artifact('asset') - - @staticmethod - def parameters(ctx): - params = dict( - project={ - 'label': 'Project', - 'help': 'Project Entry', - 'required': True, - 'type': types.Entry, - }, - collection={ - 'label': 'Collection', - 'help': 'Collection Name', - 'required': True, - 'type': types.String - }, - asset_type={ - 'label': 'Asset Type', - 'required': True, - 'type': types.String, - 'help': 'Type of asset', - }, - name={ - 'label': 'Asset Name', - 'required': True, - 'type': types.String, - 'help': 'Name of asset' - }, - template={ - 'label': 'Asset Template', - 'required': False, - 'type': types.String, - 'help': 'Name of asset template' - } - ) - - if not ctx: - return params - - if ctx.project: - params['project']['default'] = ctx.project - params['project']['required'] = False - - # TODO: fix search speed upstream... - # collection_types = [e.name for e in ctx.project.collections] - # params['collection']['options'] = collection_types - - # asset_types = [e.name for e in ctx.project.asset_types] - # params['asset_type']['options'] = asset_types - - if ctx.collection: - params['collection']['default'] = ctx.collection.name - params['collection']['required'] = False - - if ctx.asset_type: - params['asset_type']['default'] = ctx.asset_type.name - params['asset_type']['required'] = False - - templates = list(api.get_templates('asset').keys()) - if templates: - params['template']['options'] = templates - params['template']['default'] = sorted(templates)[0] - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - ctx.collection and - ctx.asset_type and - not ctx.asset - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('asset_item')) -def stage_asset(project, collection, asset_type, name, template=None): - '''Stage a new Asset''' - - path_template = api.get_path_template('asset') - asset_path = path_template.format(dict( - project=project.path, - collection=collection, - asset_type=asset_type, - asset=name - )) - - try: - template = api.get_template(template, 'asset') - except KeyError: - template = None - - return dict( - name=name, - path=asset_path, - tags=['asset'], - template=template, - ) - - -@task(priority=types.VALIDATE) -@requires(success('stage_asset')) -@params(store('asset_item')) -def validate_asset(asset_item): - '''Make sure new asset does not exist''' - - if os.path.exists(asset_item['path']): - raise Abort('Asset already exists: ' + asset_item['name']) - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_asset')) -@params(store('asset_item')) -@returns(artifact('asset')) -def commit_asset(asset_item): - '''Make new asset''' - - if asset_item['template']: - asset = asset_item['template'].copy(asset_item['path']) - else: - asset = fsfs.get_entry(asset_item['path']) - - asset.tag(*asset_item['tags']) - - return asset diff --git a/construct/builtins/collections.py b/construct/builtins/collections.py deleted file mode 100644 index 4557a11..0000000 --- a/construct/builtins/collections.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct import api -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct.errors import Abort -from construct import types -import fsfs - - -class NewCollection(Action): - '''Create a new Collection''' - - label = 'New Collection' - identifier = 'new.collection' - returns = artifact('collection') - - @staticmethod - def parameters(ctx): - params = dict( - project={ - 'label': 'Project', - 'help': 'Project Entry', - 'required': True, - 'type': types.Entry, - }, - name={ - 'label': 'Collection Name', - 'required': True, - 'type': types.String, - 'help': 'Name of Collection', - }, - template={ - 'label': 'Collection Template', - 'require': False, - 'type': types.String, - 'help': 'Name of template', - }, - ) - - if not ctx: - return params - - if ctx.project: - params['project']['default'] = ctx.project - params['project']['required'] = False - - templates = list(api.get_templates('collection').keys()) - if templates: - params['template']['options'] = templates - params['template']['default'] = templates[0] - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - not ctx.collection - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('collection_item')) -def stage_collection(project, name, template=None): - '''Stage a new collection''' - - path_template = api.get_path_template('collection') - collection_path = path_template.format(dict( - project=project.path, - collection=name - )) - - try: - template = api.get_template(template, 'collection') - except KeyError: - template = None - - return dict( - name=name, - path=collection_path, - tags=['collection'], - template=template, - ) - - -@task(priority=types.VALIDATE) -@requires(success('stage_collection')) -@params(store('collection_item')) -def validate_collection(collection_item): - '''Make sure new collection does not exist''' - - if os.path.exists(collection_item['path']): - raise Abort('collection already exists: ' + collection_item['name']) - - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_collection')) -@params(store('collection_item')) -@returns(artifact('collection')) -def commit_collection(collection_item): - '''Make new collection''' - - if collection_item['template']: - collection = collection_item['template'].copy(collection_item['path']) - else: - collection = fsfs.get_entry(collection_item['path']) - - collection.tag(*collection_item['tags']) - - return collection diff --git a/construct/builtins/files.py b/construct/builtins/files.py deleted file mode 100644 index 70757d2..0000000 --- a/construct/builtins/files.py +++ /dev/null @@ -1,212 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct import types, get_host, utils, get_path_template -from construct.vendor.lucidity.error import ParseError - - -class Publish(Action): - '''Publish the current open file''' - - label = 'Publish' - identifier = 'publish' - returns = artifact('file') - - @staticmethod - def parameters(ctx): - params = dict( - task={ - 'label': 'Task', - 'required': True, - 'type': types.Entry, - 'help': 'Task Entry', - }, - version={ - 'label': 'Version', - 'required': True, - 'type': types.Integer, - 'help': 'File Version' - }, - name={ - 'label': 'Name', - 'required': True, - 'type': types.String, - 'help': 'Publish Name' - } - ) - - # TODO: Implement contextual defaults and options - return params - - @staticmethod - def available(ctx): - return ctx.host != 'cli' and ctx.task - - -class PublishFile(Action): - '''Publish a file to the specified task''' - - label = 'Publish File' - identifier = 'publish.file' - returns = artifact('file') - - @staticmethod - def parameters(ctx): - params = dict( - task={ - 'label': 'Task', - 'required': True, - 'type': types.Entry, - 'help': 'Task', - }, - version={ - 'label': 'Version', - 'required': True, - 'type': types.Integer, - 'help': 'File Version' - }, - file={ - 'label': 'File path', - 'required': True, - 'type': types.String, - 'help': 'Path to file you want to publish' - } - ) - - # TODO: Implement contextual defaults and options - return params - - @staticmethod - def available(ctx): - return ctx.host == 'cli' and ctx.task - - -class Save(Action): - '''Save the current file''' - - label = 'Save' - identifier = 'file.save' - returns = artifact('file') - - @staticmethod - def parameters(ctx): - params = dict( - workspace={ - 'label': 'Workspace', - 'required': False, - 'type': types.Entry, - 'help': 'Workspace to save to' - }, - name={ - 'label': 'Name', - 'required': False, - 'type': types.String, - 'help': 'Filename', - }, - version={ - 'label': 'Version', - 'required': True, - 'type': types.Integer, - 'help': 'File Version' - }, - ext={ - 'label': 'Extension', - 'require': True, - 'type': types.String, - 'help': 'File Extension' - } - ) - - if not ctx: - return params - - params['workspace']['default'] = ctx.workspace - name = ctx.task.parent().name - if ctx.file: - path_template = get_path_template('workspace_file') - try: - data = path_template.parse(str(ctx.file)) - except ParseError: - pass - else: - name = data['name'] - - params['name']['default'] = name - - extensions = ctx.workspace.config['extensions'] - params['ext']['default'] = extensions[0] - params['ext']['options'] = extensions - params['ext']['required'] = False - next_version = ctx.workspace.get_next_version(name, extensions[0]) - params['version']['default'] = next_version - params['version']['required'] = False - return params - - @staticmethod - def available(ctx): - return ctx.host != 'cli' and ctx.workspace - - -@task -@pass_kwargs -@returns(store('file')) -def build_filename(workspace, name, version, ext): - '''Builds the full save path''' - - task = workspace.parent() - path_template = get_path_template('workspace_file') - filename = path_template.format(dict( - task=task.short, - name=name, - version='{:0>3d}'.format(version), - ext=ext - )) - return utils.unipath(workspace.path, filename) - - -@task -@requires(success('build_filename')) -@params(store('file')) -def save_file(file): - '''Save file in Host application''' - - host = get_host() - host.save_file(file) - - -class Open(Action): - '''Open a file''' - - label = 'Open' - identifier = 'file.open' - parameters = dict( - file={ - 'label': 'File path', - 'required': True, - 'type': types.String, - 'help': 'Path to file you want to publish' - } - ) - - @staticmethod - def available(ctx): - return ctx.host != 'cli' and ctx.task - - -@task -@pass_kwargs -def open_file(file): - '''Open file in Host application''' - - host = get_host() - host.open_file(file) diff --git a/construct/builtins/projects.py b/construct/builtins/projects.py deleted file mode 100644 index 8d97743..0000000 --- a/construct/builtins/projects.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct import api, types -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct.utils import unipath -from construct.errors import Abort - - -class NewProject(Action): - '''Create a new Project''' - - label = 'New Project' - identifier = 'new.project' - returns = artifact('project') - - @classmethod - def parameters(cls, ctx): - params = dict( - root={ - 'label': 'Project Root', - 'required': True, - 'type': types.String, - 'help': 'project root directory', - }, - template={ - 'label': 'Project Template', - 'required': True, - 'type': types.String, - 'help': 'name of a project template', - } - ) - - if not ctx: - return params - - templates = list(api.get_templates('project').keys()) - params['template']['options'] = templates - if templates: - params['template']['default'] = templates[0] - - return params - - @staticmethod - def available(ctx): - return not ctx.project - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('project_item')) -def stage_project(root, template): - '''Stage project data for validation''' - - return dict( - path=unipath(root), - name=os.path.basename(root), - template=api.get_template(template) - ) - - -@task(priority=types.VALIDATE) -@params(store('project_item')) -@requires(success('stage_project')) -def validate_project(project_item): - '''Make sure the project does not already exist.''' - - if os.path.exists(project_item['path']): - raise Abort('Project already exists: %s' % project_item['path']) - - return True - - -@task(priority=types.COMMIT) -@params(store('project_item')) -@requires(success('validate_project')) -@returns(artifact('project')) -def commit_project(project_item): - '''Copy the project template to project directory''' - - project = project_item['template'].copy(project_item['path']) - return project diff --git a/construct/builtins/sequences.py b/construct/builtins/sequences.py deleted file mode 100644 index 34bf89e..0000000 --- a/construct/builtins/sequences.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct import api -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct import types -from construct.errors import Abort -import fsfs - - -class NewSequence(Action): - '''Create a new Sequence''' - - label = 'New Sequence' - identifier = 'new.sequence' - returns = artifact('sequence') - - @staticmethod - def parameters(ctx): - params = dict( - project={ - 'label': 'Project', - 'help': 'Project Entry', - 'required': True, - 'type': types.Entry, - }, - collection={ - 'label': 'Collection', - 'help': 'Collection Name', - 'required': True, - 'type': types.String - }, - name={ - 'label': 'Sequence Name', - 'required': True, - 'type': types.String, - 'help': 'Name of sequence', - }, - template={ - 'label': 'Sequence Template', - 'required': False, - 'type': types.String, - 'help': 'Name of sequence template' - } - ) - - if not ctx: - return params - - if ctx.project: - params['project']['default'] = ctx.project - params['project']['required'] = False - - # TODO: fix search speed upstream... - # collection_types = [e.name for e in ctx.project.collections] - # params['collection']['options'] = collection_types - - if ctx.collection: - params['collection']['default'] = ctx.collection.name - params['collection']['required'] = False - - templates = list(api.get_templates('sequence').keys()) - if templates: - params['template']['default'] = templates[0] - params['template']['options'] = templates - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - ctx.collection and - not ctx.sequence and - not ctx.asset_type - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('sequence_item')) -def stage_sequence(project, collection, name, template=None): - - path_template = api.get_path_template('sequence') - sequence_path = path_template.format(dict( - project=project.path, - collection=collection, - sequence=name - )) - - try: - template = api.get_template(template, 'sequence') - except KeyError: - template = None - - return dict( - name=name, - path=sequence_path, - tags=['sequence'], - template=template, - ) - - -@task(priority=types.VALIDATE) -@requires(success('stage_sequence')) -@params(store('sequence_item')) -def validate_sequence(sequence_item): - if os.path.exists(sequence_item['path']): - raise Abort('Sequence already exists: ' + sequence_item['name']) - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_sequence')) -@params(store('sequence_item')) -@returns(artifact('sequence')) -def commit_sequence(sequence_item): - '''Make new task''' - - if sequence_item['template']: - sequence = sequence_item['template'].copy(sequence_item['path']) - else: - sequence = fsfs.get_entry(sequence_item['path']) - - sequence.tag(*sequence_item['tags']) - - return sequence diff --git a/construct/builtins/shots.py b/construct/builtins/shots.py deleted file mode 100644 index b50c63b..0000000 --- a/construct/builtins/shots.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct import api -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct import types -from construct.errors import Abort -import fsfs - - -class NewShot(Action): - '''Create a new Shot''' - - label = 'New Shot' - identifier = 'new.shot' - returns = artifact('shot') - - @staticmethod - def parameters(ctx): - params = dict( - project={ - 'label': 'Project', - 'help': 'Project Entry', - 'required': True, - 'type': types.Entry, - }, - collection={ - 'label': 'Collection', - 'help': 'Collection Name', - 'required': True, - 'type': types.String - }, - sequence={ - 'label': 'Sequence', - 'required': True, - 'type': types.Entry, - 'help': 'Name of sequence', - }, - name={ - 'label': 'Shot Name', - 'required': True, - 'type': types.String, - 'help': 'Name of shot' - }, - template={ - 'label': 'Shot Template', - 'required': False, - 'type': types.String, - 'help': 'Name of shot template' - } - ) - - if not ctx: - return params - - if ctx.project: - params['project']['default'] = ctx.project - params['project']['required'] = False - - # TODO: fix search speed upstream... - # collection_types = [e.name for e in ctx.project.collections] - # params['collection']['options'] = collection_types - - if ctx.collection: - params['collection']['default'] = ctx.collection.name - params['collection']['required'] = False - - if ctx.sequence: - params['sequence']['default'] = ctx.sequence - params['sequence']['required'] = False - - templates = list(api.get_templates('shot').keys()) - if templates: - params['template']['options'] = templates - params['template']['default'] = sorted(templates)[0] - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - ctx.collection and - ctx.sequence and - not ctx.shot - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('shot_item')) -def stage_shot(project, collection, sequence, name, template=None): - - path_template = api.get_path_template('shot') - shot_path = path_template.format(dict( - project=project.path, - collection=collection, - sequence=sequence.name, - shot=name - )) - - try: - template = api.get_template(template, 'shot') - except KeyError: - template = None - - return dict( - name=name, - path=shot_path, - tags=['shot'], - template=template, - ) - - -@task(priority=types.VALIDATE) -@requires(success('stage_shot')) -@params(store('shot_item')) -def validate_shot(shot_item): - if os.path.exists(shot_item['path']): - raise Abort('Shot already exists: ' + shot_item['name']) - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_shot')) -@params(store('shot_item')) -@returns(artifact('shot')) -def commit_shot(shot_item): - '''Make new task''' - - if shot_item['template']: - shot = shot_item['template'].copy(shot_item['path']) - else: - shot = fsfs.get_entry(shot_item['path']) - - shot.tag(*shot_item['tags']) - - return shot diff --git a/construct/builtins/tasks.py b/construct/builtins/tasks.py deleted file mode 100644 index 7376b5c..0000000 --- a/construct/builtins/tasks.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct import api, config, types -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct.utils import unipath -from construct.errors import Abort -import fsfs - - -class NewTask(Action): - '''Create a new Task''' - - label = 'New Task' - identifier = 'new.task' - returns = artifact('task') - - @staticmethod - def parameters(ctx): - params = dict( - parent={ - 'label': 'Parent Entry', - 'required': True, - 'type': types.Entry, - 'help': 'Parent entry of task', - }, - type={ - 'label': 'Task Type', - 'required': True, - 'type': types.String, - 'help': 'Type of task', - }, - name={ - 'label': 'Task Name', - 'required': False, - 'type': types.String, - 'help': 'Name of task' - }, - template={ - 'label': 'Task Template Name', - 'required': False, - 'type': types.String, - 'help': 'Name of task template' - } - ) - - if not ctx: - return params - - params['type']['options'] = list(config['TASK_TYPES'].keys()) - - if ctx.shot: - params['parent']['default'] = ctx.shot - params['parent']['required'] = False - - elif ctx.asset: - params['parent']['default'] = ctx.asset - params['parent']['required'] = False - - templates = list(api.get_templates('task').keys()) - if templates: - params['template']['options'] = templates - params['template']['default'] = templates[0] - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - (ctx.shot or ctx.asset) and - not ctx.task - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('task_item')) -def stage_task(parent, type, name=None, template=None): - '''Stage task for creation''' - - name = name or type - return dict( - name=name, - path=unipath(parent.path, name), - tags=['task', type], - template=api.get_template(template, 'task'), - ) - - -@task(priority=types.VALIDATE) -@requires(success('stage_task')) -@params(store('task_item')) -def validate_task(task_item): - '''Validate potential task''' - - if os.path.exists(task_item['path']): - raise Abort('Task already exists: ' + task_item['name']) - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_task')) -@params(store('task_item')) -@returns(artifact('task')) -def commit_task(task_item): - '''Create new task''' - - if task_item['template']: - task = task_item['template'].copy(task_item['path']) - else: - task = fsfs.get_entry(task_item['path']) - - task.tag(*task_item['tags']) - - return task diff --git a/construct/builtins/templates.py b/construct/builtins/templates.py deleted file mode 100644 index d5ee2f2..0000000 --- a/construct/builtins/templates.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -import construct -from construct.action import Action -from construct.errors import Abort -from construct.tasks import ( - task, - pass_kwargs, - requires, - success, - pass_context, - store, - kwarg, - artifact, - params, - returns -) -from construct import types -from construct.utils import unipath - - -class NewTemplate(Action): - '''Create a new Template from an Entry''' - - label = 'New Template' - identifier = 'new.template' - returns = artifact('template') - - @staticmethod - def parameters(ctx): - params = dict( - name={ - 'label': 'Template Name', - 'required': True, - 'type': types.String, - 'help': 'Name of new template', - }, - entry={ - 'label': 'Entry', - 'required': True, - 'type': types.Entry, - 'help': 'Entry to create template from', - }, - include_files={ - 'label': 'Include Files', - 'type': bool, - 'default': False, - 'help': 'Include all files in entry not just entry data', - } - ) - if not ctx: - return params - - if ctx.host == 'cli': - params['entry']['default'] = ctx.get_deepest_entry() - - return params - - @staticmethod - def available(ctx): - return False - - -@task(priority=types.STAGE) -@pass_context -@pass_kwargs -@returns(store('template')) -def stage_template(ctx, name, entry, include_files): - '''Stage template data''' - - templates_path = unipath(ctx.project.data.path, 'templates') - template_path = unipath(templates_path, name) - - template_item = types.Namespace( - entry=entry, - name=name, - path=template_path, - include_files=include_files, - ) - return template_item - - -@task(priority=types.VALIDATE) -@requires(success('stage_template_data')) -@params(store('template')) -def validate_template(template): - '''Make sure template does not already exist''' - - if ( - os.path.exists(template.path) or - template.name in construct.get_templates() - ): - raise Abort('Template already exists: ' + template.name) - - -@task(priority=types.COMMIT) -@requires(success('validate_template')) -@params(store('template')) -@returns(artifact('template')) -def commit_template(template): - '''Commit template''' - - new_template = entry.copy(template_path, only_data=not include_files) - return new_template diff --git a/construct/builtins/templates/vfx_asset/.data/data b/construct/builtins/templates/vfx_asset/.data/data deleted file mode 100644 index db8464e..0000000 --- a/construct/builtins/templates/vfx_asset/.data/data +++ /dev/null @@ -1 +0,0 @@ -status: waiting diff --git a/construct/builtins/templates/vfx_asset/.data/tag_asset b/construct/builtins/templates/vfx_asset/.data/tag_asset deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_collection/.data/data b/construct/builtins/templates/vfx_collection/.data/data deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_collection/.data/tag_collection b/construct/builtins/templates/vfx_collection/.data/tag_collection deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/.data/data b/construct/builtins/templates/vfx_project/.data/data deleted file mode 100644 index dae77cb..0000000 --- a/construct/builtins/templates/vfx_project/.data/data +++ /dev/null @@ -1,2 +0,0 @@ -status: in progress -fps: 24fps diff --git a/construct/builtins/templates/vfx_project/.data/tag_project b/construct/builtins/templates/vfx_project/.data/tag_project deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/.data/uuid_69c45613-75a9-4b90-aaa5-d40323214e58 b/construct/builtins/templates/vfx_project/.data/uuid_69c45613-75a9-4b90-aaa5-d40323214e58 deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/.data/data b/construct/builtins/templates/vfx_project/assets/.data/data deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/.data/tag_collection b/construct/builtins/templates/vfx_project/assets/.data/tag_collection deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/character/.data/data b/construct/builtins/templates/vfx_project/assets/character/.data/data deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/character/.data/tag_asset_type b/construct/builtins/templates/vfx_project/assets/character/.data/tag_asset_type deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/character/.data/uuid_e71f7370-7e40-40e5-ab90-7c005677427e b/construct/builtins/templates/vfx_project/assets/character/.data/uuid_e71f7370-7e40-40e5-ab90-7c005677427e deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/environment/.data/data b/construct/builtins/templates/vfx_project/assets/environment/.data/data deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/environment/.data/tag_asset_type b/construct/builtins/templates/vfx_project/assets/environment/.data/tag_asset_type deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/environment/.data/uuid_4ead44a5-3d8b-4b44-a918-fde9318de284 b/construct/builtins/templates/vfx_project/assets/environment/.data/uuid_4ead44a5-3d8b-4b44-a918-fde9318de284 deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/prop/.data/data b/construct/builtins/templates/vfx_project/assets/prop/.data/data deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/prop/.data/tag_asset_type b/construct/builtins/templates/vfx_project/assets/prop/.data/tag_asset_type deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_project/assets/prop/.data/uuid_7cb5a9c8-4aaf-4ee4-a90b-61805478fc13 b/construct/builtins/templates/vfx_project/assets/prop/.data/uuid_7cb5a9c8-4aaf-4ee4-a90b-61805478fc13 deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_sequence/.data/data b/construct/builtins/templates/vfx_sequence/.data/data deleted file mode 100644 index db8464e..0000000 --- a/construct/builtins/templates/vfx_sequence/.data/data +++ /dev/null @@ -1 +0,0 @@ -status: waiting diff --git a/construct/builtins/templates/vfx_sequence/.data/tag_sequence b/construct/builtins/templates/vfx_sequence/.data/tag_sequence deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_shot/.data/data b/construct/builtins/templates/vfx_shot/.data/data deleted file mode 100644 index db8464e..0000000 --- a/construct/builtins/templates/vfx_shot/.data/data +++ /dev/null @@ -1 +0,0 @@ -status: waiting diff --git a/construct/builtins/templates/vfx_shot/.data/tag_shot b/construct/builtins/templates/vfx_shot/.data/tag_shot deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_task/.data/data b/construct/builtins/templates/vfx_task/.data/data deleted file mode 100644 index db8464e..0000000 --- a/construct/builtins/templates/vfx_task/.data/data +++ /dev/null @@ -1 +0,0 @@ -status: waiting diff --git a/construct/builtins/templates/vfx_task/.data/tag_task b/construct/builtins/templates/vfx_task/.data/tag_task deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/templates/vfx_workspace/.data/data b/construct/builtins/templates/vfx_workspace/.data/data deleted file mode 100644 index db8464e..0000000 --- a/construct/builtins/templates/vfx_workspace/.data/data +++ /dev/null @@ -1 +0,0 @@ -status: waiting diff --git a/construct/builtins/templates/vfx_workspace/.data/tag_workspace b/construct/builtins/templates/vfx_workspace/.data/tag_workspace deleted file mode 100644 index e69de29..0000000 diff --git a/construct/builtins/time.py b/construct/builtins/time.py deleted file mode 100644 index 35c2fa2..0000000 --- a/construct/builtins/time.py +++ /dev/null @@ -1,251 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from construct.action import Action -from construct.tasks import ( - task, - pass_context, - pass_kwargs, - returns, - artifact, - store, - params, - success, - requires -) -from construct import types, get_host, utils, get_path_template -from construct.vendor.lucidity.error import ParseError - - -class SaveFrameRange(Action): - '''Save the frame range for this shot or asset''' - - label = 'Save Frame Range' - identifier = 'time.saveframerange' - - @staticmethod - def parameters(ctx): - params = dict( - asset_or_shot={ - 'label': 'Asset or Shot', - 'required': True, - 'type': types.Entry, - 'help': 'Asset or Shot Entry' - }, - min={ - 'label': 'Min Frame', - 'required': True, - 'type': types.Number, - 'help': 'Min Frame' - }, - start={ - 'label': 'Start Frame', - 'required': False, - 'type': types.Number, - 'help': 'Animation Start Frame', - }, - end={ - 'label': 'End Frame', - 'required': False, - 'type': types.Number, - 'help': 'Animation End Frame', - }, - max={ - 'label': 'Max Frame', - 'required': True, - 'type': types.Number, - 'help': 'Max Frame' - }, - ) - - if not ctx: - return params - - params['asset_or_shot']['default'] = ctx.asset or ctx.shot - - host = get_host() - if host: - frame_range = host.get_frame_range() - - if frame_range is NotImplemented: - return params - - params['min']['default'] = float(frame_range[0]) - params['start']['default'] = float(frame_range[1]) - params['end']['default'] = float(frame_range[2]) - params['max']['default'] = float(frame_range[3]) - - return params - - @staticmethod - def available(ctx): - return ctx.host and ctx.task - - -class SyncFrameRange(Action): - '''Sync your scenes frame range''' - - label = 'Sync Frame Range' - identifier = 'time.syncframerange' - - @staticmethod - def parameters(ctx): - params = dict( - asset_or_shot={ - 'label': 'Asset or Shot', - 'required': True, - 'type': types.Entry, - 'help': 'Asset or Shot Entry' - } - ) - - if not ctx: - return params - - params['asset_or_shot']['default'] = ctx.asset or ctx.shot - return params - - @staticmethod - def available(ctx): - return ctx.host != 'cli' and ctx.task - - -@task -@pass_kwargs -def store_frame_range(asset_or_shot, min, start, end, max): - '''Stores the frame range in the asset or shot''' - - asset_or_shot.write( - frame_range=[min, start, end, max] - ) - - -@task -@pass_kwargs -@pass_context -@returns(store('frame_range')) -def get_frame_range(ctx, asset_or_shot): - '''Get the Asset or Shot frame range''' - - frame_range = asset_or_shot.data.get( - 'frame_range', - ctx.project.data.get( - 'frame_range', - [101, 101, 580, 580] - ) - ) - return frame_range - - -@task -@requires(store('frame_range')) -@params(store('frame_range')) -@returns(artifact('frame_range')) -def apply_frame_range(frame_range): - '''Applies the frame range to your current scene''' - - host = get_host() - host.set_frame_range(*frame_range) - return {'frame_range': frame_range} - - -class SaveFPS(Action): - '''Save the frame rate for this shot or asset''' - - label = 'Save FPS' - identifier = 'time.savefps' - - @staticmethod - def parameters(ctx): - params = dict( - asset_or_shot={ - 'label': 'Asset or Shot', - 'required': True, - 'type': types.Entry, - 'help': 'Asset or Shot Entry' - }, - fps={ - 'label': 'Frame Rate', - 'required': True, - 'type': types.Number, - 'help': 'Frames per second' - } - ) - - if not ctx: - return params - - params['asset_or_shot']['default'] = ctx.asset or ctx.shot - - host = get_host() - if host: - frame_rate = host.get_frame_rate() - - if frame_rate is NotImplemented: - return params - - params['fps']['default'] = float(frame_rate) - - return params - - @staticmethod - def available(ctx): - return ctx.host and ctx.task - - -class SyncFPS(Action): - '''Sync your scenes frame rate''' - - label = 'Sync FPS' - identifier = 'time.syncfps' - - @staticmethod - def parameters(ctx): - params = dict( - asset_or_shot={ - 'label': 'Asset or Shot', - 'required': True, - 'type': types.Entry, - 'help': 'Asset or Shot Entry' - } - ) - - if not ctx: - return params - - params['asset_or_shot']['default'] = ctx.asset or ctx.shot - return params - - @staticmethod - def available(ctx): - return ctx.host != 'cli' and ctx.task - - -@task -@pass_kwargs -def store_frame_rate(asset_or_shot, fps): - '''Stores the frame rate in the asset or shot''' - - asset_or_shot.write(fps=fps) - - -@task -@pass_kwargs -@pass_context -@returns(store('fps')) -def get_frame_rate(ctx, asset_or_shot): - '''Get the Asset or Shot frame rate''' - - fps = asset_or_shot.data.get('fps', ctx.project.data.get('fps', 24)) - return fps - - -@task -@requires(store('fps')) -@params(store('fps')) -@returns(artifact('fps')) -def apply_frame_rate(fps): - '''Applies the frame rate to your current scene''' - - host = get_host() - host.set_frame_rate(fps) - return {'fps': fps} diff --git a/construct/builtins/workspaces.py b/construct/builtins/workspaces.py deleted file mode 100644 index fd4f2cc..0000000 --- a/construct/builtins/workspaces.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from construct.action import Action -from construct.tasks import ( - task, - pass_kwargs, - requires, - success, - pass_context, - store, - kwarg, - artifact, - returns, - params -) -from construct import types, api -from construct.errors import Abort -import fsfs - - -class NewWorkspace(Action): - '''Create a new Workspace''' - - label = 'New Workspace' - identifier = 'new.workspace' - returns = artifact('workspace') - - @staticmethod - def parameters(ctx): - params = dict( - task={ - 'label': 'Parent Entry', - 'required': True, - 'type': types.Entry, - 'help': 'Parent entry of workspace', - }, - name={ - 'label': 'Workspace Name', - 'required': True, - 'type': types.String, - 'help': 'Name of workspace' - }, - template={ - 'label': 'Workspace Template Name', - 'required': True, - 'type': types.String, - 'help': 'Name of workspace template' - } - ) - - if not ctx: - return params - - if ctx.task: - params['task']['default'] = ctx.task - params['task']['required'] = False - - templates = api.get_templates('workspace') - if templates: - params['template']['options'] = list(templates.keys()) - - return params - - @staticmethod - def available(ctx): - return ( - ctx.project and - (ctx.shot or ctx.asset) and - ctx.task and - not ctx.workspace - ) - - -@task(priority=types.STAGE) -@pass_kwargs -@returns(store('workspace_item')) -def stage_workspace(task, name=None, template=None): - '''Stage workspace for creation''' - - path_template = api.get_path_template('workspace') - path = path_template.format(dict( - task=task.path, - workspace=name - )) - name = name - return dict( - name=name, - path=path, - tags=['workspace'], - template=api.get_template(template, 'workspace'), - ) - - -@task(priority=types.VALIDATE) -@requires(success('stage_workspace')) -@params(store('workspace_item')) -def validate_workspace(workspace_item): - '''Validate potential workspace''' - - if os.path.exists(workspace_item['path']): - raise Abort('Task already exists: ' + workspace_item['name']) - return True - - -@task(priority=types.COMMIT) -@requires(success('validate_workspace')) -@params(store('workspace_item')) -@returns(artifact('workspace')) -def commit_workspace(workspace_item): - '''Create new workspace''' - - if workspace_item['template']: - workspace = workspace_item['template'].copy(workspace_item['path']) - else: - workspace = fsfs.get_entry(workspace_item['path']) - - workspace.tag(*workspace_item['tags']) - - return workspace diff --git a/construct/cli/__init__.py b/construct/cli/__init__.py deleted file mode 100644 index 139759b..0000000 --- a/construct/cli/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import diff --git a/construct/cli/__main__.py b/construct/cli/__main__.py deleted file mode 100644 index 84f445b..0000000 --- a/construct/cli/__main__.py +++ /dev/null @@ -1,248 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function -import os -import sys -import argparse -import colorama -import construct -from collections import OrderedDict -from construct import signals, get_context -from construct.constants import FAILED, SUCCESS, SKIPPED -from construct.cli.commands import commands, ActionCommand -from construct.cli.formatters import ( - Root, - format_section, -) -from construct.cli.utils import styled, style -from construct.cli.constants import ( - OPTIONS_TITLE, - ARTIFACTS_TITLE, - ACTION_CONTEXT_TITLE, - ICONS -) -from construct.cli import stout -from construct.cli.widgets import TaskLine, ProgressBar -import win_unicode_console - - -CONSOLE_WIDGETS = {} - - -@signals.route('action.before') -def on_action_before(ctx): - print() - print(ctx.action.description + '\n') - - if ctx.kwargs or ctx.args: - args_data = [(k, v) for k, v in ctx.kwargs.items()] - if ctx.args: - args_data.append(('extra_args', ctx.args)) - args_section = format_section( - styled('{bright}{fg.yellow}Options{reset}'), - data=args_data, - lcolor=style.bright - ) - print(args_section + '\n') - - ctx_keys = ['action'] + ctx.keys - ctx_data = [] - for k in ctx_keys: - v = ctx[k] - if not v: - continue - if k in ctx.entry_keys: - ctx_data.append((k, v.name)) - else: - ctx_data.append((k, v)) - - ctx_section = format_section( - ACTION_CONTEXT_TITLE, - data=ctx_data, - lcolor=style.bright - ) - print(ctx_section + '\n') - - print('Status Key') - key_tmpl = ' {} {:<8}' - for status, icon in list(ICONS.items())[:5]: - print(key_tmpl.format(icon, status), end='') - print() - for status, icon in list(ICONS.items())[5:]: - print(key_tmpl.format(icon, status), end='') - print('\n') - - console = stout.Console() - console.init() - stout.set_console(console) - - for group, tasks in ctx.task_groups.items(): - print(group.description) - for task in tasks: - w = TaskLine(task, console) - CONSOLE_WIDGETS[task.identifier] = w - console.add_widget(w) - - print() - progress = ProgressBar('Tasks', len(ctx.tasks), console) - CONSOLE_WIDGETS['progress'] = progress - console.add_widget(progress) - - -@signals.route('action.after') -def on_action_after(ctx): - - console = stout.get_console() - console.deinit() - stout.set_console(None) - - # TODO: console deinit turns off colorama and win_unicode_console - # Might want to check colorama and win_unicode_console before - # console.init, if they are already enabled, don't disable them - # in console.deinit, assume someone else is handling it. - win_unicode_console.enable() - colorama.init() - - artifacts = ctx.artifacts.items() - if not artifacts: - print() - return - - data = [(k, v) for k, v in artifacts if v] - if data: - artifacts = format_section( - ARTIFACTS_TITLE, - data=data, - lcolor=style.bright - ) - print('\n' + artifacts) - print() - - -@signals.route('request.status.changed') -def on_request_status_change(request, last_status, status): - - console = stout.get_console() - w = CONSOLE_WIDGETS[request.task.identifier] - w.set_status(status) - - if status == FAILED: - err = w.format_error(request.exception) - console.insert(w.row, err) - - ctx = get_context() - progress = CONSOLE_WIDGETS['progress'] - i = 0 - for request in ctx.requests.values(): - if request._status in [FAILED, SUCCESS, SKIPPED]: - i += 1 - progress.set_value(i) - - -def logging_config(level): - return dict( - version=1, - formatters={ - 'simple': { - 'format': '%(levelname).1s:%(name)s> %(message)s' - } - }, - handlers={ - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - } - }, - loggers={ - 'construct': { - 'level': level, - 'handlers': ['console'], - 'propagate': False - } - } - ) - - -def setup_parser(): - '''Build the root parser''' - - usage = styled( - '{bright}construct ' - '{fg.blue}{fg.reset} ' - '{fg.yellow}[options]{reset}' - ) - parser = argparse.ArgumentParser( - 'construct', - usage=usage, - formatter_class=Root, - add_help=False, - ) - parser._optionals.title = OPTIONS_TITLE - parser.add_argument( - '-h', - '--help', - help='show this help message and exit', - action='store_true', - dest='-h' - ) - parser.add_argument( - '-v', - '--verbose', - help='verbose output', - action='store_true', - dest='-v' - ) - parser.add_argument( - 'command', - help=argparse.SUPPRESS, - action='store', - nargs='?' - ) - return parser - - -def main(): - '''CLI Entry Point''' - - # Enable ansi console colors - win_unicode_console.enable() - colorama.init() - - parser = setup_parser() - args, extra_args = parser.parse_known_args() - root_flags = [f for f, v in args.__dict__.items() - if f.startswith('-') and v] - - # Configure Construct - logging_level = ('WARNING', 'DEBUG')[args.__dict__['-v']] - construct.init(host='cli', logging=logging_config(logging_level)) - construct.set_context_from_path(os.getcwd()) - - # Add all subcommands including contextual actions - subcommands = OrderedDict() - - for command in commands: - subcommands[command.name] = command(parser) - - for action in construct.actions: - subcommands[action.identifier] = ActionCommand(action, parser) - - # Print root help if we received no command argument - if not args.command: - parser.print_help() - sys.exit() - - command_name = args.command - command_args = root_flags + extra_args - - if command_name not in subcommands: - print('Command does not exist: ', command_name) - sys.exit(1) - - command = subcommands[command_name] - args, extra_args = command.parse(command_args) - args.__dict__.pop('verbose') - command.run(args, *extra_args) - - -if __name__ == '__main__': - main() diff --git a/construct/cli/commands.py b/construct/cli/commands.py deleted file mode 100644 index b561fb8..0000000 --- a/construct/cli/commands.py +++ /dev/null @@ -1,533 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function -import argparse -import os -import sys -import logging -import traceback -from textwrap import dedent -from scrim import get_scrim -import construct -from construct.errors import ActionControlFlowError, ArgumentError -from construct.cli.formatters import new_formatter, Contextual, format_section -from construct.cli.constants import OPTIONS_TITLE, ARGUMENTS_TITLE -from construct.cli.utils import styled, error, abort -from construct.utils import classproperty - - -_log = logging.getLogger(__name__) - - -class Command(object): - '''Base class for all CLI Commands. Override :meth:`setup_parser` to add - arguments to a command. Implement :meth:`run` to customize what happens - when the command is invoked. - ''' - - name = None - - def __init__(self, parent): - self.parent = parent - self.parser = argparse.ArgumentParser( - self.name, - formatter_class=new_formatter(self, Contextual) - ) - self.parser._optionals.title = OPTIONS_TITLE - self.parser._positionals.title = ARGUMENTS_TITLE - self.parser.add_argument( - '-v', - '--verbose', - help='verbose output', - action='store_true', - ) - self._parser_setup = False - - @classmethod - def _available(cls): - return cls.name and cls.available() - - @classmethod - def available(cls): - '''Implement this to set when a command is available.''' - return True - - @property - def arguments(self): - return self.parser._get_positional_actions() - - @property - def options(self): - return self.parser._get_optional_actions() - - @property - def usage(self): - usage = styled('{bright}{} {}', self.parent.prog, self.name) - for argument in self.arguments: - usage += styled(' {fg.blue}<{}>', argument.dest) - if self.options: - usage += styled(' {fg.yellow}[options]') - usage += styled('{reset}') - return usage - - @classproperty - def short_description(cls): - return cls.__doc__.split('\n')[0] - - @classproperty - def description(cls): - parts = cls.__doc__.split('\n') - short = parts[0] - body = dedent('\n'.join(parts[1:])) - text = '\n'.join([short, body]) - return text - - def _setup_parser(self, parser): - if not self._parser_setup: - self.setup_parser(self.parser) - self._parser_setup = True - - def setup_parser(self, parser): - return NotImplemented - - def parse(self, args): - self._setup_parser(self.parser) - args, extra_args = self.parser.parse_known_args(args) - return args, extra_args - - def run(self, args, *extra_args): - return NotImplemented - - -class Version(Command): - '''Version information''' - - name = 'version' - - def run(self, args, *extra_args): - section = format_section( - '', - [ - ('', ''), - ('name', construct.__title__), - ('version', construct.__version__), - ('url', construct.__url__), - ('package', os.path.dirname(construct.__file__)), - ('config', os.environ.get('CONSTRUCT_CONFIG', 'default')), - ], - lcolor=styled('{bright}') - ) - print(section) - - -def requires_scrim(command): - '''Command becomes available only when cli run by scrim script... - - See also: - https://github.com/danbradham/scrim - ''' - - command.available = staticmethod(lambda: get_scrim().shell) - return command - - -@requires_scrim -class Home(Command): - '''Go to root directory''' - - name = 'home' - - def run(self, args, *extra_args): - ctx = construct.get_context() - - if not os.path.exists(ctx.root): - os.makedirs(ctx.root) - - scrim = get_scrim() - scrim.pushd(ctx.root) - - -@requires_scrim -class Pop(Command): - '''Go back to last location''' - - name = 'pop' - - def run(self, args, *extra_args): - scrim = get_scrim() - scrim.popd() - - -@requires_scrim -class Push(Command): - '''Go to first search result''' - - name = 'push' - - def setup_parser(self, parser): - parser.add_argument( - 'name', - nargs='?', - help='Name of Entry like: my_project or my_project/my_asset', - ) - parser.add_argument( - '-r', '--root', - default=os.getcwd(), - help='Root directory of search', - type=str, - ) - parser.add_argument( - '--up', - action='store_true', - default=False, - help='Search up tree instead of down', - dest='direction' - ) - parser.add_argument( - '-t', '--tags', - nargs='*', - help='List of tags like: project' - ) - parser.add_argument( - '-d', '--depth', - type=int, - required=False, - help='Search depth' - ) - - def run(self, args, *extra_args): - import fsfs - - ctx = construct.get_context() - - if not args.tags and args.name: - query = dict( - selector=args.name, - root=args.root, - skip_root=True - ) - entry = construct.quick_select(**query) - else: - query = dict( - root=args.root, - name=args.name, - tags=args.tags, - direction=args.direction, - depth=args.depth or (3 if ctx.project else 2), - skip_root=True - ) - # Get a better match, not just the first hit - entries = list(construct.search(**query)) - - if not entries: - error('Could not find entry...') - sys.exit(1) - - if len(entries) == 1: - entry = entries[0] - else: - # The shortest entry has to be the closest to our query - entry = min(entries, key=lambda e: len(e.path)) - - if not entry: - error('Could not find entry...') - sys.exit(1) - - path = entry.path - if args.name: - parts = args.name.split('/') - for part in parts: - highlight = styled('{bright}{fg.yellow}{}{reset}', part) - path = path.replace(part, highlight) - print(path) - - scrim = get_scrim() - scrim.pushd(os.path.abspath(entry.path)) - - -class Search(Command): - '''Search for Entries - - Examples: - construct search my_project - construct search my_project/my_asset - construct search -t asset - construct search -t workspace maya - ''' - - name = 'search' - - def setup_parser(self, parser): - parser.add_argument( - 'name', - nargs='?', - help='Name of Entry', - ) - parser.add_argument( - '-r', '--root', - default=os.getcwd(), - help='Root directory of search', - type=str, - ) - parser.add_argument( - '--up', - action='store_true', - default=False, - help='Search up tree instead of down', - dest='direction' - ) - parser.add_argument( - '-t', '--tags', - nargs='*', - help='List of tags like: project' - ) - parser.add_argument( - '-d', '--depth', - type=int, - required=False, - help='Search depth' - ) - - def run(self, args, *extra_args): - ctx = construct.get_context() - query = dict( - root=args.root, - name=args.name, - tags=args.tags, - direction=args.direction, - depth=args.depth or (3 if ctx.project else 1), - ) - entries = construct.search(**query) - - i = 0 - for i, entry in enumerate(entries): - path = entry.path - if args.name: - parts = args.name.split('/') - for part in parts: - highlight = styled('{bright}{fg.yellow}{}{reset}', part) - path = path.replace(part, highlight) - print(path) - - if i == 0: - print(('Found 0 result.')) - - -class Read(Command): - '''Read metadata''' - - name = 'read' - - def setup_parser(self, parser): - parser.add_argument( - '--root', '-r', - default=os.getcwd(), - help='Directory to read from' - ) - parser.add_argument('keys', nargs=-1, help='keys to read') - - def run(self, args, *extra_args): - import fsfs - data = fsfs.read(args.root, *args.keys) - print(fsfs.encode_data(data)) - - -class Write(Command): - '''Write metadata''' - - name = 'write' - - def setup_parser(self, parser): - parser.add_argument( - '--root', '-r', - default=os.getcwd(), - help='Directory to read from' - ) - parser.add_argument( - '-k', '--key', - dest='data', - required=True, - action='append', - nargs=2, - help='key value pairs to write' - ) - parser.add_argument( - '-d', '--delete', - dest='delkeys', - action='append', - help='Keys to delete', - nargs=1 - ) - - def run(self, args, *extra_args): - - import fsfs - from fsfs.cli import safe_eval - - entry = fsfs.get_entry(args.root) - if args.delkeys: - entry.remove(*args.delkeys) - - data = {k: safe_eval(v) for k, v in args.data} - - try: - entry.write(**data) - except Exception as e: - print('Failed to write data: ') - print(dict(data)) - print(e.message) - else: - print('Wrote data to ' + args.root) - - -class Tag(Command): - '''Tag a directory - - Examples: - construct tag project - construct tag task model - - Unless you know what you're doing, don't add tags. - ''' - - name = 'tag' - - def setup_parser(self, parser): - parser.add_argument( - '--root', '-r', - default=os.getcwd(), - help='Directory to tag' - ) - parser.add_argument('tags', nargs='*', help='List of tags to add') - - def run(self, args, *extra_args): - import fsfs - fsfs.tag(args.root, *args.tags) - - -class Untag(Command): - '''Untag a directory - - Examples: - construct untag project - construct untag task model - - Unless you know what you're doing, don't remove tags. - ''' - - name = 'untag' - - def setup_parser(self, parser): - parser.add_argument( - '--root', '-r', - default=os.getcwd(), - help='Directory to tag' - ) - parser.add_argument('tags', nargs='*', help='List of tags to remove') - - def run(self, args, *extra_args): - import fsfs - fsfs.untag(args.root, *args.tags) - - -class ActionCommand(Command): - ''':class:`Action` CLI command class''' - - def __init__(self, action, parent): - _log.debug('Initializing ActionCommand for %s' % action.identifier) - self.action = action - self.name = action.identifier - super(ActionCommand, self).__init__(parent) - - @property - def short_description(self): - return self.action.short_description - - @property - def description(self): - return self.action.description - - def custom_validator(self, name, cast, *validators): - def check_value(value): - value = cast(value) - if not all([v(value) for v in validators]): - msg = '%s: invalid value: %r' % (name, value) - raise abort(msg) - return value - return check_value - - def add_option(self, parser, param_name, param_options): - '''Add an argument to parser from an action parameter''' - param_flag = '--' + param_name - - # Handle bool options - if param_options['type'] is bool: - arg_spec = dict( - action='store_true', - dest=param_name, - help=param_options.get('help', None), - default=param_options.get('default', False), - ) - return parser.add_argument(param_flag, **arg_spec) - - # Handle all other options - arg_spec = dict( - action=('store', 'store_true')[param_options['type'] is bool], - type=param_options.get('type', str), - dest=param_name, - help=param_options.get('help', None), - # required=param_options.get('required', False), - default=param_options.get('default', None), - choices=param_options.get('options', None) - ) - - # Build a validator if we get a tuple of types, argparse calls - # type(arg), so our validator casts the argument first, then - # validates it. - cast = arg_spec['type'] - validators = param_options.get('validator', []) - if isinstance(param_options['type'], tuple): - cast = param_options['type'][0] - validators.append(lambda x: isinstance(x, param_options['type'])) - - if validators: - arg_spec['type'] = self.custom_validator( - param_flag, - cast, - *validators - ) - - parser.add_argument(param_flag, **arg_spec) - - def setup_parser(self, parser): - ctx = construct.get_context() - params = self.action.params(ctx) - for param_name, param_options in params.items(): - self.add_option(parser, param_name, param_options) - - def run(self, args, *extra_args): - try: - action = self.action(*extra_args, **args.__dict__) - action.run() - except ActionControlFlowError as e: - msg = styled( - '{bright}{fg.red}Control Flow Error:{fg.reset} {}' - ' raised critical error {fg.red}{}{reset}', - self.action.identifier, - e.__class__.__name__ - ) - print(msg) - except ArgumentError as e: - msg = styled('{bright}{fg.red}Argument Error:{fg.reset} {}', e) - print(msg) - except Exception as e: - msg = styled( - '{bright}{fg.red}Action Error:{fg.reset} {}' - ' raised unexpected error {fg.red}{}{reset}\n', - self.action.identifier, - e.__class__.__name__ - ) - print(msg) - traceback.print_exc() - - -commands = [c for c in Command.__subclasses__() if c._available()] diff --git a/construct/cli/constants.py b/construct/cli/constants.py deleted file mode 100644 index 7626b2b..0000000 --- a/construct/cli/constants.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from platform import platform -from construct.cli.utils import styled -from collections import OrderedDict -from construct.constants import ( - WAITING, - PENDING, - RUNNING, - SUCCESS, - FAILED, - SKIPPED, - PAUSED, - DISABLED, - ENABLED, -) - - -ACTION_CONTEXT_TITLE = styled('{normal}Action Context{reset}') -ACTIONS_TITLE = styled('{bright}{fg.blue}Actions{reset}') -ARGUMENTS_TITLE = styled('{bright}{fg.blue}Arguments{reset}') -ARTIFACTS_TITLE = styled('{bright}{fg.green}Artifacts{reset}') -COMMANDS_TITLE = styled('{bright}{fg.blue}Commands{reset}') -CONTEXT_TITLE = styled('{normal}Current Context{reset}') -OPTIONS_TITLE = styled('{bright}{fg.yellow}Options{reset}') - -STATUS_LABELS = { - WAITING: styled('{dim}WAITING{reset}'), - PENDING: styled('{dim}PENDING{reset}'), - RUNNING: styled('{fg.green}RUNNING{reset}'), - SUCCESS: styled('{bright}{fg.green}SUCCESS{reset}'), - FAILED: styled('{bright}{fg.red}FAILED{reset}'), - SKIPPED: styled('{fg.cyan}SKIPPED{reset}'), - PAUSED: styled('{fg.red}PAUSED{reset}'), - DISABLED: styled('{dim}DISABLED{reset}'), - ENABLED: styled('{bright}ENABLED{reset}'), -} -ICONS = OrderedDict([ - ('ENABLED', styled('{dim}{fg.green}◌{reset}')), - ('DISABLED', styled('{dim}{fg.red}◌{reset}')), - ('WAITING', styled('{dim}◌{reset}')), - ('RUNNING', styled('{fg.green}◌{reset}')), - ('PENDING', styled('{bright}{fg.magenta}◌{reset}')), - ('PAUSED', styled('{bright}{fg.cyan}◌{reset}')), - ('SKIPPED', styled('{bright}{fg.blue}◌{reset}')), - ('SUCCESS', styled('{bright}{fg.green}●{reset}')), - ('FAILED', styled('{bright}{fg.red}●{reset}')), -]) - - -if platform().startswith('Windows-7'): - # Use cp437 compatible characters on Windows 7 - ICONS = OrderedDict([ - ('ENABLED', styled('{dim}{fg.green}○{reset}')), - ('DISABLED', styled('{bright}{fg.red}○{reset}')), - ('WAITING', styled('{dim}○{reset}')), - ('RUNNING', styled('{fg.green}○{reset}')), - ('PENDING', styled('{bright}{fg.magenta}○{reset}')), - ('PAUSED', styled('{bright}{fg.cyan}○{reset}')), - ('SKIPPED', styled('{bright}{fg.blue}○{reset}')), - ('SUCCESS', styled('{bright}{fg.green}•{reset}')), - ('FAILED', styled('{bright}{fg.red}•{reset}')), - ]) diff --git a/construct/cli/formatters.py b/construct/cli/formatters.py deleted file mode 100644 index 5f08ba5..0000000 --- a/construct/cli/formatters.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import argparse -import construct -from construct.cli.constants import ( - ACTIONS_TITLE, - COMMANDS_TITLE, - CONTEXT_TITLE -) -from construct.cli.utils import style, styled - - -reset = style.reset - - -def new_formatter(parent, formatter_type): - return type( - formatter_type.__name__, - (formatter_type,), - dict( - parent=parent - ) - ) - - -def format_context(): - ctx = construct.get_context() - ctx_data = [] - for k in ctx.keys: - v = ctx[k] - if not v: - continue - if k in ctx.entry_keys: - ctx_data.append((k, v.name)) - else: - ctx_data.append((k, v)) - return format_section( - CONTEXT_TITLE, - ctx_data, - lcolor=styled('{bright}') - ) - - -def format_commands(): - from construct.cli.commands import commands - return format_section( - COMMANDS_TITLE, - [(c.name, c.short_description) for c in commands], - lcolor=styled('{bright}') - ) - - -def format_actions(): - actions = construct.actions.collect() - return format_section( - ACTIONS_TITLE, - [(a.identifier, a.description) for a in construct.actions], - lcolor=styled('{bright}') - ) - - -def format_section(title, data, indent='', lcolor=reset, rcolor=reset): - '''Format a section''' - - if isinstance(data, dict): - data = list(data.items()) - - width = max(max([len(k) for k, v in data]), 12) - line = indent + ' {lc}{:<{w}}{reset} {rc}{}{reset}' - - lines = [] - if title: - lines.append(indent + title) - for key, value in data: - lines.append(styled(line, key, value, w=width, lc=lcolor, rc=rcolor)) - return '\n'.join(lines) - - -class Contextual( - argparse.RawDescriptionHelpFormatter, - argparse.ArgumentDefaultsHelpFormatter -): - - def _format_action(self, action): - help = super(Contextual, self)._format_action(action) - sep = ' ' * self._indent_increment - parts = help.split(sep) - left = styled('{bright}{}{reset}', sep.join(parts[:-1])) - right = parts[-1] - return sep.join([left, right]) - - def add_usage(self, usage, actions, groups, prefix=None): - - parent = getattr(self, 'parent', None) - if parent: - usage = parent.usage - description = parent.description - else: - description = None - - prefix = 'Usage: ' - super(Contextual, self).add_usage(usage, actions, groups, prefix) - if description: - self.add_text('\n' + description) - # self.add_text(format_context() + '\n') - - def add_section(self, header, lines): - self.start_section(header) - self.add_lines(lines) - self.end_section() - - def add_lines(self, lines): - def format_lines(*args): - cind = self._current_indent * ' ' - text = cind + (('\n' + cind).join(args)) - return text - self._add_item(format_lines, lines) - - -class Root(Contextual): - - def add_usage(self, usage, actions, groups, prefix=None): - super(Root, self).add_usage(usage, actions, groups, prefix) - self.add_text(format_context() + '\n') - self.add_text(format_commands()) - self.add_text(format_actions()) diff --git a/construct/cli/stout.py b/construct/cli/stout.py deleted file mode 100644 index c412902..0000000 --- a/construct/cli/stout.py +++ /dev/null @@ -1,468 +0,0 @@ -# -*- coding: utf-8 -*- -''' -Terminal UI Framework -===================== -''' - -from __future__ import absolute_import, print_function -import re -import sys -import colorama -from backports.shutil_get_terminal_size import get_terminal_size -import win_unicode_console -import os - -_win_hide = None -_win_show = None -if os.name == 'nt': - # Taken from stackoverflow - # https://stackoverflow.com/a/10455937 - import msvcrt - import ctypes - - class _CursorInfo(ctypes.Structure): - _fields_ = [("size", ctypes.c_int), - ("visible", ctypes.c_byte)] - - def _win_hide(): - ci = _CursorInfo() - handle = ctypes.windll.kernel32.GetStdHandle(-11) - ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) - ci.visible = False - ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) - - def _win_show(): - ci = _CursorInfo() - handle = ctypes.windll.kernel32.GetStdHandle(-11) - ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci)) - ci.visible = True - ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci)) - - -TAB_WIDTH = 4 -orig_stdout = None -orig_stderr = None -_console = None - - -def get_console(): - return _console - - -def set_console(console): - global _console - _console = console - - -def clean_text(text): - from functools import reduce - substitutions = [('\n', ''), ('\r', ''), ('\t', ' ' * TAB_WIDTH)] - return reduce(lambda s, sre: str.replace(s, *sre), substitutions, text) - - -class AnsiToken(str): - - def __new__(cls, value, pattern): - inst = str.__new__(cls, value) - inst.pattern = re.compile(pattern) - return inst - - def __call__(self, *args): - return self.format(*args) - - def search(self, text): - return self.pattern.search(text) - - -class Ansi(object): - '''Ansi cursor movement parsing''' - - pattern = re.compile('\\x1b\[([\d+];[\d+]H|[\d+]?[ABCDK]|\?25[lh])') - - # Ansi cursor movement tokens - move = AnsiToken('\x1b[{};{}H', '\\x1b\[(\d+)\;(\d+)H') - move_up = AnsiToken('\x1b[{}A', '\\x1b\[(\d+)?A') - move_down = AnsiToken('\x1b[{}B', '\\x1b\[(\d+)?B') - move_right = AnsiToken('\x1b[{}C', '\\x1b\[(\d+)?C') - move_left = AnsiToken('\x1b[{}D', '\\x1b\[(\d+)?D') - clear = AnsiToken('\x1b[K', '\\x1b\[K') - hide_cursor = AnsiToken('\x1b[?25l', '\\x1b\[?25l') - show_cursor = AnsiToken('\x1b[?25h', '\\x1b\[?25h') - home = AnsiToken('\r', '\\r') - tokens = [t for t in locals().items() if isinstance(t[1], AnsiToken)] - - @classmethod - def get_token_args(cls, text): - '''Parses text returning method name and arguments. - - Examples: - >>> Ansi.get_token_args('\x1b[10;2H') - ('move', [10, 2]) - >>> Ansi.get_token_args('\x1b[4A') - ('move_up', [4]) - ''' - - for name, token in cls.tokens: - if token == '\r': - continue - match = token.search(text) - if match: - args = [int(g) for g in match.groups()] - return name, args - - @classmethod - def match(cls, text): - '''Returns True if the text matches ANY token in Ansi.tokens''' - - return cls.pattern.match(text) - - -class Cursor(object): - '''Provides terminal console cursor interaction''' - - def __init__(self, console): - self.console = console - self.dx = 0 - self.dy = 0 - - def show_cursor(self): - if _win_show: - _win_show() - else: - self.console.stream.write(Ansi.show_cursor) - - def hide_cursor(self): - if _win_hide: - _win_hide() - else: - self.console.stream.write(Ansi.hide_cursor) - - def home(self): - self.console.stream.write(Ansi.home) - self.dx = 0 - - def clear(self): - self.console.stream.write(Ansi.clear) - - def reset(self): - self.move(-self.dx, -self.dy) - - def move_up(self, y=1): - self.console.stream.write(Ansi.move_up(y)) - self.dy += y - - def move_down(self, y=1): - self.console.stream.write(Ansi.move_down(y)) - self.dy -= y - - def move_right(self, x=1): - self.console.stream.write(Ansi.move_right(x)) - self.dx += min(self.console.width, self.dx + x) - - def move_left(self, x=1): - self.console.stream.write(Ansi.move_left(x)) - self.dx = max(0, self.dx - x) - - def move(self, x, y): - if x > 0: - self.move_right(x) - else: - self.move_left(abs(x)) - if y > 0: - self.move_up(y) - else: - self.move_down(abs(y)) - - -class ConsoleStream(object): - '''Parse and redirect stream.write to console.write. - - This stream wrapper parses text written to a stream and splits it line by - line and sends it to Console. This allows Console to maintain a buffered - list of Line widgets. - ''' - - def __init__(self, stream, console): - self.stream = stream - self.console = console - - def __getattr__(self, attr): - return self.stream.__getattr__(attr) - - def write(self, value): - buffer = list(value) - indent = 0 - string = '' - while buffer: - string += buffer.pop(0) - indent = len(string) - len(string.lstrip()) - if len(string) == self.console.width - 2: - self.console.write(string + '-\n') - string = ' ' * indent - elif string[-1] in ['\r', '\n']: - self.console.write(string) - string = '' - elif Ansi.match(string): - name, args = Ansi.get_token_args(string) - cursor_meth = getattr(self.console.cursor, name) - cursor_meth(*args) - string = '' - - if string: - if Ansi.match(string): - name, args = Ansi.get_token_args(string) - cursor_meth = getattr(self.console.cursor, name) - cursor_meth(*args) - else: - self.console.write(''.join(string)) - - -class Console(object): - '''Wraps stdout and stderr and wraps every line you write as a - Line widget. Making it easy to modify previously printed lines. You - can also subclass Line and manually add your own widgets to the output - stream. - ''' - - def __init__(self): - - self.lines = [] - self.wrapper = None - self.stream = None - self.cursor = None - - # Terminal size used to split lines longer than console width - t = get_terminal_size() - self.width = t.columns - self.height = t.lines # TODO: cap self.lines at height - 2? - - # State from previous write used - # to determine whether print modifies - # a line or creates a new one - self._n = True # \n - self._r = False # \r - self._e = False # eol - - def __enter__(self): - self.init() - return self - - def __exit__(self, type, value, traceback): - self.deinit() - - def init(self): - if self.stream: - raise RuntimeError('Console already initialized.') - - global orig_stdout - global orig_stderr - win_unicode_console.enable() - colorama.init() - orig_stderr = sys.stderr - orig_stdout = sys.stdout - self.stream = sys.stdout - self.wrapper = ConsoleStream(sys.stdout, self) - sys.stdout = self.wrapper - sys.stderr = self.wrapper - self.cursor = Cursor(self) - set_console(self) - self.cursor.hide_cursor() - - def deinit(self): - if self.stream is None: - raise RuntimeError('Console has not been initialized.') - - self.cursor.show_cursor() - global orig_stdout - global orig_stderr - sys.stdout = orig_stdout - sys.stderr = orig_stderr - orig_stdout = None - orig_stderr = None - self.stream = None - self.wrapper = None - self.cursor = None - colorama.deinit() - win_unicode_console.disable() - - def set_state(self, n, r, e): - self._n, self._r, self._e = n, r, e - - def add_widget(self, widget): - '''Manually add a widget''' - - self.lines.insert(0, widget) - self.set_state(True, False, False) - self.stream.write(widget.text.rstrip('\n') + '\n') - - def insert_widget(self, index, widget): - '''Manually insert a widget - - Examples: - import stout, time - with stout.Console() as con: - print(100) - w = stout.Spinner(con) - con.insert_widget(1, w) # insert spinner above '100' - for i in range(20): - w.tick() - time.sleep(0.05) - ''' - - # Insert a blank line - sys.__stdout__.write('\n') - - # Add widget to fill blank line - self.lines.insert(index, widget) - - # Update all widgets - for line in self.lines: - line.update() - - def insert(self, index, text): - '''Inserts text at the given index. Index starts at the bottom of your - console, like this: - - 3. a line - 2. another line - 1. yet another line - 0. last line - ''' - - # Store lines up to index and after index - before_lines = self.lines[:index] - after_lines = self.lines[index:] - - # Create new lines - num_lines = len(self.lines) - print(text) - new_lines = self.lines[:len(self.lines) - num_lines] - - # Rebuild line ordering - self.lines = before_lines + new_lines + after_lines - - # Update all lines - for line in self.lines: - line.update() - - def write(self, value): - '''Always returns a Line, either a new Line widget or the line that - was modified. - ''' - - length = len(value) - dy = self.cursor.dy - dx = self.cursor.dx - - if dy > 0: # Cursor is in another line - - line = self.lines[dy - 1] - if dx: - line._text = clean_text( - line._text[:dx] + - value + - line._text[dx + length:] - ) - else: - line._text = clean_text(value + line._text[length:]) - - elif self._n: # Cursor is on new line - - line = Line(value, self) - self.lines.insert(0, line) - - elif self._r: # Cursor is at the start of the last line - - line = self.lines[0] - line._text = clean_text(value + line._text[length:]) - - else: # Cursor is somewhere on the last line - - line = self.lines[0] - if dx: - line._text = clean_text( - line._text[:dx] + - value + - line._text[dx + length:] - ) - else: - line._text = clean_text(value + line._text[length:]) - - # Set state for next print - if value.endswith('\n'): - self.set_state(True, False, False) - self.cursor.dx = 0 - elif value.endswith('\r'): - self.set_state(False, True, False) - self.cursor.dx = 0 - else: - self.set_state(False, False, True) - self.cursor.dx += length - - # Finally...print to stream - self.stream.write(value) - return line - - -class Line(object): - '''Base widget class''' - - def __init__(self, text, console): - self._text = clean_text(text) - self.console = console - - def __repr__(self): - return '{}(row={}, text={!r})'.format( - self.__class__.__name__, - self.row, - self.text - ) - - @property - def text(self): - return self._text - - @text.setter - def text(self, value): - self._text = clean_text(value) - self.update() - - @property - def row(self): - return self.console.lines.index(self) - - def update(self): - row = self.row + 1 - if row > self.console.height - 2: # Skip updates outside view - return - - cursor = self.console.cursor - cursor.move_up(row) - cursor.home() - cursor.clear() - self.console.stream.write(self._text) - cursor.move_down(row) - cursor.home() - - -class Spinner(Line): - - chars = '|/-\\' - interval = 0.2 - - def __init__(self, console): - self.chars = self.default - self.index = 0 - super(Spinner, self).__init__(self.chars[0], console) - - def tick(self): - self.index = (self.index + 1) % len(self.chars) - self.text = self.chars[self.index] - - -class PingPong(Spinner): - - chars = [ - '. ', '.. ', '... ', ' ... ', ' ...', ' ..', - ' .', ' ..', ' ...', ' ... ', '... ', '.. ' - ] diff --git a/construct/cli/utils.py b/construct/cli/utils.py deleted file mode 100644 index 5139046..0000000 --- a/construct/cli/utils.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import colorama -import sys - - -def error(msg): - print(styled('{bright}{fg.red}Error! {reset}{}', msg)) - - -def abort(msg=''): - print(styled('{bright}{fg.yellow}Abort! {reset}{}', msg)) - sys.exit(1) - - -def extract_styles(obj): - '''Extract styles from a colorama ansi code obj''' - attrs = {k.lower(): v for k, v in obj.__dict__.items() if k.isupper()} - return type(obj.__class__.__name__, (), attrs) - - -class style(object): - fg = extract_styles(colorama.Fore) - bg = extract_styles(colorama.Back) - reset = colorama.Style.RESET_ALL - dim = colorama.Style.DIM - normal = colorama.Style.NORMAL - bright = colorama.Style.BRIGHT - - -def styled(string, *args, **kwargs): - '''Like string.format but includes ansi color styling keywords including - fg, bg, and style. These are aliases to colorama.Fore, colorama.Back and - colorama.Style. All codes should be referenced in lowercase. Finally - colorama.Style.RESET_ALL is aliased as reset. - - Examples: - >>> styled('{fg.red}WARNING:{reset}') # doctest: +SKIP - [WARNING] - ''' - - kwargs.update(vars(style)) - return string.format(*args, **kwargs) diff --git a/construct/cli/widgets.py b/construct/cli/widgets.py deleted file mode 100644 index 1a2bd71..0000000 --- a/construct/cli/widgets.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division -from construct.cli.stout import Line -from construct.cli.constants import ( - STATUS_LABELS, - ICONS -) -from construct.cli.utils import styled, style -from construct.constants import WAITING, FAILED -from construct import signals - - -class TaskLine(Line): - - template = ' {icon} {bright}{identifier}{sep}{reset}{description}' - error_template = ' {bright}{fg.red}{}: {reset}{}' - - def __init__(self, task, console): - self.task = task - self.status = WAITING - text = self.format_status(self.status) - super(TaskLine, self).__init__(text, console) - - def format_error(self, exc_info): - # Insert error message - exc_type, exc_value, exc_tb = exc_info - err = styled(self.error_template, exc_type.__name__, exc_value) - return err - - def format_status(self, status): - return styled( - self.template, - icon=ICONS[self.status], - identifier=self.task.identifier + style.reset, - description=self.task.description, - sep=' - ' - ) - - def set_status(self, status): - self.status = status - self.text = self.format_status(status) - - -class ProgressBar(Line): - - info_template = '{label} <{i} of {max}> ' - bar_template = '{bright}{color}{lfill}{dim}{rfill}{reset}' - lfill_char = '░' - rfill_char = '░' - color_map = [ - style.fg.red, - style.fg.yellow, - style.fg.green - ] - - def __init__(self, label, max, console): - self.label = label - self.max = max - self.width = min(76, console.width - 2) - self.i = 0 - text = self.format_bar() - super(ProgressBar, self).__init__(text, console) - - def get_color(self, percent): - from math import floor - index = int(floor(percent * (len(self.color_map) - 1))) - return self.color_map[index] - - def format_bar(self): - info = self.info_template.format( - label=self.label, - i=self.i, - max=self.max - ) - info_width = len(info) - bar_width = self.width - info_width - if self.i == 0: - percent = 0 - else: - percent = self.i / self.max - - lfill_width = int(bar_width * percent) - rfill_width = bar_width - lfill_width - return styled( - info + self.bar_template, - color=self.get_color(percent), - lfill=self.lfill_char * lfill_width, - rfill=self.rfill_char * rfill_width - ) - - def set_value(self, i): - self.i = i - self.text = self.format_bar() diff --git a/construct/compat.py b/construct/compat.py deleted file mode 100644 index 93e7703..0000000 --- a/construct/compat.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -try: - basestring = basestring -except NameError: - basestring = (str, bytes) - - -def pkg_resources(): - '''Make sure we get the pkg_resources that installed construct. - - Nuke ships with setuptools-0.6c11-py26.egg - an extremely archaic version - of setuptools. In setuptools prior to the introduction of python wheels - pkg_resources found entry_points by parsing .egg-info directories. Nowadays - we install wheels and package metadata is stored in .dist-info directories. - ''' - from os.path import exists - from construct.utils import package_path, import_file - - pkg_resources_path = package_path('../pkg_resources') - if not exists(pkg_resources_path): - # Fallback to standard import - import pkg_resources - return pkg_resources - - pkg_resources = import_file(pkg_resources_path) - return pkg_resources diff --git a/construct/config.py b/construct/config.py deleted file mode 100644 index be8e132..0000000 --- a/construct/config.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from construct.constants import DEFAULT_CONFIG -import yaml -try: - from collections import ChainMap -except ImportError: - from chainmap import ChainMap - - -class Config(object): - '''Config object that looks up values in the following order. - - 1. context.project.data - 2. config.dict - 3. config.defaults - ''' - - defaults = {} - - with open(DEFAULT_CONFIG, 'r') as f: - defaults.update(yaml.load(f.read())) - - def __init__(self, *args, **kwargs): - self.dict = dict(*args, **kwargs) - - def __str__(self): - return str(self.flatten()) - - def chain(self): - '''Build chainmap for current context''' - - dicts = [] - - from construct.api import get_context - ctx = get_context() - if ctx and ctx.project: - data = dict( - (k, v) for k, v in ctx.project.data.items() - if k.isupper() - ) - dicts.append(data) - - dicts.append(self.dict) - dicts.append(self.defaults) - return ChainMap(*dicts) - - def flatten(self): - return dict(self.chain()) - - def get(self, *args): - return self.chain().get(*args) - - def __getitem__(self, item): - return self.chain().__getitem__(item) - - def __setitem__(self, item, value): - self.dict.__setitem__(item, value) - - def update(self, *args, **kwargs): - self.dict.update(*args, **kwargs) diff --git a/construct/constants.py b/construct/constants.py deleted file mode 100644 index ba174b8..0000000 --- a/construct/constants.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -import timeit -import time -from construct.utils import package_path, unipath - - -WAITING = 'WAITING' -PENDING = 'PENDING' -RUNNING = 'RUNNING' -SUCCESS = 'SUCCESS' -FAILED = 'FAILED' -SKIPPED = 'SKIPPED' -PAUSED = 'PAUSED' -DISABLED = 'DISABLED' -ENABLED = 'ENABLED' -DONE_STATUSES = [SUCCESS, FAILED] -ACTION_SIGNALS = [ - 'action.*', - 'group.*', - 'request.*', -] - - -EXTENSIONS_ENTRY_POINT = 'construct.extensions' -USER_CONFIG = unipath('~/.construct/construct.yaml') -DEFAULT_CONFIG = package_path('defaults', 'construct.yaml') -DEFAULT_ROOT = unipath('~/projects') -DEFAULT_HOST = 'standalone' -DEFAULT_LOGGING = dict( - version=1, - formatters={ - 'simple': { - 'format': '%(levelname).1s:%(name)s> %(message)s' - } - }, - handlers={ - 'console': { - 'class': 'logging.StreamHandler', - 'formatter': 'simple', - } - }, - loggers={ - 'construct': { - 'level': os.environ.get('CONSTRUCT_LOGGING_LEVEL', 'DEBUG'), - 'handlers': ['console'], - } - } -) - -FSFS_DATA_ROOT = '.data' -FSFS_DATA_FILE = 'data' - -TIMER = timeit.default_timer -SLEEP = time.sleep diff --git a/construct/context.py b/construct/context.py deleted file mode 100644 index c3adc1f..0000000 --- a/construct/context.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = [ - 'Context', - '_ctx_stack', - '_req_stack', -] - -import os -import fsfs -from getpass import getuser -from collections import Mapping -from werkzeug.local import LocalStack -from construct.constants import DEFAULT_ROOT, DEFAULT_HOST -from construct.utils import platform, unipath -from construct.models import is_entry - - -_ctx_stack = LocalStack() -_req_stack = LocalStack() - - -class Context(object): - - keys = [ - 'host', - 'root', - 'user', - 'project', - 'collection', - 'sequence', - 'shot', - 'asset_type', - 'asset', - 'task', - 'workspace', - 'platform', - 'file' - ] - entry_keys = [ - 'project', - 'collection', - 'sequence', - 'shot', - 'asset_type', - 'asset', - 'task', - 'workspace', - 'file' - ] - defaults = {k: None for k in keys} - defaults['platform'] = platform - defaults['user'] = getuser() - - def __init__(self, **kwargs): - kwargs = dict(self.defaults, **kwargs) - self.__dict__.update(kwargs) - - def __repr__(self): - kwargs = ', '.join( - [('{}={}').format(k, v) for k, v in self.__dict__.items()] - ) - return '{}({})'.format(self.__class__.__name__, kwargs) - - def __enter__(self): - self.push() - return self - - def __exit__(self, exc_type, exc_value, tb): - self.pop() - - def __getitem__(self, key): - return self.__dict__.__getitem__(key) - - def __setitem__(self, key, value): - self.__dict__.__setitem__(key, value) - - def __contains__(self, key): - return self.__dict__.__contains__(key) - - def push(self): - _ctx_stack.push(self) - - def pop(self): - if _ctx_stack.top is self: - _ctx_stack.pop() - - def get_deepest_entry(self): - for key in reversed(self.entry_keys): - entry = self.__dict__.get(key, None) - if entry and is_entry(entry): - return entry - - def update(self, other, exclude=['host']): - if isinstance(other, Context): - other = other.__dict__ - - if isinstance(other, Mapping): - if exclude: - other = {k: v for k, v in other.items() if k not in exclude} - self.__dict__.update(other) - else: - raise TypeError('Not a Mapping or Context: ', other) - - def to_env_dict(self, exclude=None): - '''Push context to environment variables...''' - - exclude = exclude or ['host'] - data = {} - for key in self.keys: - - if key in exclude: - continue - - value = getattr(self, key) - if value is not None: - data['CONSTRUCT_' + key.upper()] = str(value) - return data - - def to_env(self, exclude=None): - '''Push context to environment variables...''' - - exclude = exclude or ['host'] - - for key in self.keys: - - if key in exclude: - continue - - value = getattr(self, key) - if value is not None: - os.environ['CONSTRUCT_' + key.upper()] = str(value) - - @classmethod - def from_env(cls, exclude=None): - '''Create new context from environment variables''' - - exclude = exclude or [] - data = dict( - root=os.environ.get('CONSTRUCT_ROOT', DEFAULT_ROOT), - host=os.environ.get('CONSTRUCT_HOST', DEFAULT_HOST), - ) - - for entry in cls.entry_keys: - - if entry in exclude: - continue - - env_var = 'CONSTRUCT_' + entry.upper() - value = os.environ.get(env_var, None) - if value: - value = fsfs.get_entry(value) - data[entry] = value - - return cls(**data) - - @classmethod - def from_path(cls, path): - '''Extract context from file path''' - - ctx = Context.from_env(exclude=cls.entry_keys) - - if os.path.isfile(path): - ctx.file = unipath(path) - path = os.path.dirname(path) - - for entry in fsfs.search(path, direction=fsfs.UP): - tags = entry.tags - for key in Context.entry_keys: - if key in tags: - setattr(ctx, key, entry) - - return ctx diff --git a/construct/defaults/construct.yaml b/construct/defaults/construct.yaml deleted file mode 100644 index 0f56601..0000000 --- a/construct/defaults/construct.yaml +++ /dev/null @@ -1,195 +0,0 @@ - -ROOT: '~/projects' -EXTENSION_PATHS: - - '~/.construct' -EVENT_STREAM_DB: '~/.construct/eventstream.sqlite' -DISABLE_EXTENSIONS: [] - -LOGGING: - version: 1 - formatters: - simple: - format: '%(levelname).1s:%(name)s> %(message)s' - handlers: - console: - formatter: simple - class: logging.StreamHandler - loggers: - construct: - level: DEBUG - handlers: [console] - propagate: False - - -STATUSES: - waiting: - color: [0.0, 0.0, 0.0] - icon: 'default' - order: 0 - in progress: - color: [0.5, 0.5, 0.0] - icon: 'default' - order: 1 - rendering: - color: [0.0, 1.0, 0.0] - icon: 'default' - order: 2 - pending: - color: [0.5, 0.0, 0.5] - icon: 'default' - order: 3 - rejected: - color: [1.0, 0.0, 0.0] - icon: 'default' - order: 4 - complete: - color: [0.0, 0.0, 1.0] - icon: 'default' - order: 5 - - -TASK_TYPES: - model: - short: mdl - color: [0, 0, 0] - icon: 'default' - rig: - short: rig - color: [0, 0, 0] - icon: 'default' - shade: - short: shd - color: [0, 0, 0] - icon: 'default' - texture: - short: txt - color: [0, 0, 0] - icon: 'default' - comp: - short: comp - color: [0, 0, 0] - icon: 'default' - light: - short: lgt - color: [0, 0, 0] - icon: 'default' - layout: - short: lo - color: [0, 0, 0] - icon: 'default' - anim: - short: anim - color: [0, 0, 0] - icon: 'default' - fx: - short: fx - color: [0, 0, 0] - icon: 'default' - - -PATH_TEMPLATES: - collection: '{project}/{collection}' - asset_type: '{project}/{collection}/{asset_type}' - asset: '{project}/{collection}/{asset_type}/{asset}' - sequence: '{project}/{collection}/{sequence}' - shot: '{project}/{collection}/{sequence}/{shot}' - task: '{parent}/{task}' - publish: '{task}/publish/{file_type}/{task:[^_/]+}_{name}/v{versio:\d+}' - publish_file: '{task:[^_/]+}_{name}_v{version:\d+}{ext:\.\w+}' - workspace: '{task}/work/{workspace}' - workspace_file: '{task:[^_/]+}_{name}_v{version:\d+}{ext:\.\w+}' - - -SOFTWARE: - maya2018: - host: 'maya' - identifier: 'launch.maya' - description: 'Launch Autodesk Maya2018' - default_workspace: 'maya' - icon: './icons/maya.png' - extensions: ['.mb', '.ma'] - cmd: - win: - path: C:/Program Files/Autodesk/Maya2018/bin/maya.exe - args: [] - linux: - path: /usr/autodesk/maya2018/bin/maya - args: [] - mac: - path: /Applications/Autodesk/maya2018/bin/maya - args: [] - nuke11.1v3: - host: 'nuke' - identifier: 'launch.nuke' - description: 'Launch The Foundry Nuke11.1v3' - default_workspace: 'nuke' - icon: './icons/nuke.png' - extensions: ['.nk'] - cmd: - win: - path: C:/Program Files/Nuke11.1v3/Nuke11.1.exe - args: [] - linux: - path: /usr/local/Nuke11.1v3/Nuke11.1 - args: [] - mac: - path: /Applications/Nuke11.1v3/Nuke11.1v3.app/Nuke11.1 - args: [] - houdini16.5: - host: 'houdini' - identifier: 'launch.houdini' - description: 'Launch SideFX Houdini 16.5' - default_workspace: 'houdini' - icon: './icons/houdini.png' - extensions: ['.hip', '.hipnc'] - cmd: - win: - path: C:/Program Files/Side Effects Software/Houdini 16.5.496/bin/houdinifx.exe - args: [] - linux: - path: houdini - args: [] - mac: - path: houdini - args: [] - sublime3: - host: 'sublime' - identifier: 'launch.sublime' - description: 'Launch Sublimt Text 3' - default_workspace: null - icon: './icons/sublime.png' - extensions: ['.txt', '.py', '.mel'] - cmd: - win: - path: C:/Program Files/Sublime Text 3//subl.exe - args: [] - linux: - path: /usr/bin/subl - args: [] - mac: - path: /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl - args: [] - - -FILE_TYPES: - alembic: - extensions: ['.abc'] - icon: 'default' - maya: - extensions: [ '.mb', '.ma'] - icon: 'default' - nuke: - extensions: ['.nk'] - icon: 'default' - c4d: - extensions: ['.c4d'] - icon: 'default' - photoshop: - extensions: ['.psd', '.psb'] - icon: 'default' - illustrator: - extensions: ['.ai'] - icon: 'default' - sequence: - extensions: ['.png', '.tif', '.exr', '.dpx'] - icon: 'default' diff --git a/construct/errors.py b/construct/errors.py deleted file mode 100644 index cbc7ff2..0000000 --- a/construct/errors.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -''' -errors and exceptions -===================== -All errors and exceptions in Construct are subclasses of ConstructError. -Errors that modify Action execution are subclasses of ActionControlFlowError. -''' - - -class ConstructError(Exception): - '''Base class for all Construct exceptions''' - - -class InvalidPluginPath(ConstructError): - '''Raised when a plugin path does not exist''' - - -class ParameterError(ConstructError): - '''Raise when a :attr:`Action.parameters` is defined incorrectly''' - - -class RegistrationError(ConstructError): - '''Raised when a plugin raises an exception during registration''' - - -class ContextError(ConstructError): - '''Raised when an error in Context is encountered''' - - -class ConfigurationError(ConstructError): - '''Raised when an Entry or Construct is misconfigured''' - - -class ActionUnavailable(ConstructError): - '''Raised when an Action is sent even though it is unavailable in the - current context''' - - -class ActionError(ConstructError): - '''Raised when an action fails''' - - -class TaskError(ConstructError): - '''Raised when a task is misconfigured''' - - -class TemplateError(ConstructError): - '''Raised when a folder template, or path template error occurs''' - - -class ArgumentError(ConstructError): - '''Raised when an :class:`Action` is instantiated with arguments that do - not match the parameters specified in :attr:`Action.parameters`''' - - -class ActionControlFlowError(ConstructError): - ''' - Raised by Tasks to control Action execution... - ''' - - def __init__(self, *args, **kwargs): - import construct - self.ctx = construct.get_context() - self.request = construct.get_request() - super(ActionControlFlowError, self).__init__(*args, **kwargs) - - -class ValidationError(ActionControlFlowError): - '''Raised by a Task when validation of a staged item fails.''' - - def __init__(self, *args, **kwargs): - self.selection = kwargs.pop('selection', []) - super(ValidationError, self).__init__(*args, **kwargs) - - -class TimeoutError(ActionControlFlowError): - '''Raised when a request times out''' - - -class Skip(ActionControlFlowError): - ''' - Raise by a Task to notify the Action runner that the task should be - skipped. - ''' - - -class Disable(ActionControlFlowError): - ''' - Raise by a Task to notify the Action runner that the task should be - disabled. - ''' - - -class Abort(ActionControlFlowError): - ''' - Raised by a Task to notify the ActionRunner that a critical error has - occured. The ActionRunner will stop executing tasks and raise the error. - ''' - - -class Fail(ActionControlFlowError): - ''' - Raised by a Task to notify the ActionRunner that the Task has failed, but, - the ActionRunner should continue. - ''' - - -class Pause(ActionControlFlowError): - ''' - Raised by a Task to pause the ActionRunner. - ''' - - -class Confirm(ActionControlFlowError): - ''' - Raised by a Task to request confirmation of specific items from user. - ''' - - def __init__(self, items, *args, **kwargs): - self.items = items - super(Confirm, self).__init__(*args, **kwargs) diff --git a/construct/extension.py b/construct/extension.py deleted file mode 100644 index 08f0ef9..0000000 --- a/construct/extension.py +++ /dev/null @@ -1,388 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import abc -import os -import inspect -import logging -from collections import defaultdict -from fnmatch import fnmatch - -from construct.constants import EXTENSIONS_ENTRY_POINT -from construct.types import ABC -from construct.utils import iter_modules, ensure_type, missing, unipath -from construct.action import get_action_identifier, Action -from construct.compat import pkg_resources -pkg_resources = pkg_resources() - - -_extensions = {} -_log = logging.getLogger(__name__) - - -class Config(object): - - def __init__(self, key, default=missing): - self.key = key - self.default = default - - def __get__(self, obj, type=None): - from construct.api import config - - if obj is None: - return self - if self.default is missing: - return config[self.key] - else: - return config.get(self.key, self.default) - - -class Extension(ABC): - - name = None - attr_name = None - - @abc.abstractproperty - def name(self): - '''Name of this extension like: "Builtins"''' - - @abc.abstractproperty - def attr_name(self): - '''Nice name used for attribute access like: "builtins"''' - - def __init__(self): - self.enabled = True - self._actions = {} - self._tasks = defaultdict(list) - self._template_paths = [] - self._forms = {} - - def _available(self, ctx=missing): - if ctx is not missing: - return self.available(ctx) - return True - - def available(self, ctx): - return True - - def _load(self): - '''Load extension and call user implemented load''' - self.load() - - def _unload(self): - '''Unload extension and call user implemented unload''' - self.unload() - - def load(self): - '''Called by Construct when loading the extension. - - This method should register actions and tasks and perform any - necessary setup. - ''' - - def unload(self): - '''Called by Construct when unloading the extension. - - This method should unregister actions and tasks and perform any - necessary teardown. - ''' - - def add_template_path(self, path): - self._template_paths.append(unipath(path)) - - def remove_template_path(self, path): - p = unipath(path) - if p in self._template_paths: - self._template_paths.remove(p) - - def get_template_paths(self): - return list(self._template_paths) - - def add_form(self, action_or_identifier, form): - identifier = get_action_identifier(action_or_identifier) - self._forms[identifier] = form - - def remove_form(self, action_or_identifier, form): - identifier = get_action_identifier(action_or_identifier) - if self._forms[identifier] == form: - self._forms.pop(identifier) - - def get_form(self, action_or_identifier): - identifier = get_action_identifier(action_or_identifier) - return self._forms.get(identifier, None) - - def add_action(self, action): - '''Add an action to this extension''' - ensure_type(action, Action) - - if action.identifier not in self._actions: - self._actions[action.identifier] = action - - def remove_action(self, action): - '''Remove an action from this extensions''' - ensure_type(action, Action) - - if action.identifier in self._actions: - self._actions.pop(action.identifier) - - def get_actions(self, ctx=missing): - '''Get all actions for the specified ctx''' - - if ctx is missing: - return dict(self._actions) - - return { - identifier: action - for identifier, action in self._actions.items() - if action._available(ctx) - } - - def add_task(self, action_or_identifier, task, **task_overrides): - '''Add a task to the specified action''' - - identifier = get_action_identifier(action_or_identifier) - if task_overrides: - task = task.clone(**task_overrides) - self._tasks[identifier].append(task) - - def remove_task(self, action_or_identifier, task, **task_overrides): - '''Remove a task from the specified action''' - - identifier = get_action_identifier(action_or_identifier) - if task in self._tasks[identifier]: - self._tasks.remove(task) - - def get_tasks(self, identifier, ctx=missing): - '''Get all tasks for the spcified action ''' - - all_tasks = list(self._tasks[identifier]) - - for key, tasks in self._tasks.items(): - if key == identifier: - continue - if fnmatch(identifier, key): - all_tasks.extend(tasks) - - if ctx is missing: - return list(all_tasks) - - return [t for t in all_tasks if t.available(ctx)] - - -class HostExtension(Extension): - - name = 'Nice Name' - host_name = 'name' - - def modified(self): - '''Check if current file is modified''' - return NotImplemented - - def save_file(self, file): - '''Save a file''' - return NotImplemented - - def open_file(self, file): - '''Open a file''' - return NotImplemented - - def get_selection(self): - '''Get the host's current selection''' - return NotImplemented - - def set_selection(self, selection): - '''Set the host's current selection''' - return NotImplemented - - def get_workspace(self): - '''Get the host's workspace''' - return NotImplemented - - def set_workspace(self, directory): - '''Set the host's workspace''' - return NotImplemented - - def get_filepath(self): - '''Get the path to the host's current file''' - return NotImplemented - - def get_filename(self): - '''Get the name of the host's current file''' - return NotImplemented - - def get_frame_rate(self): - '''Get the frame rate of the host - - Returns: - float - fps - ''' - return NotImplemented - - def set_frame_rate(self, fps): - '''Set the frame rate of the host - - Arguments: - fps (float) - Frames per second - ''' - return NotImplemented - - def get_frame_range(self): - '''Get the frame range of the host - - Returns: - [min_frame, start_frame, end_frame, max_frame] - ''' - return NotImplemented - - def set_frame_range(self, min, start, end, max): - '''Set the frame range of the host - - Arguments: - min_frame (int): Min frame of timeline - start_frame (int): Start frame of animation - end_frame (int): End frame of animation - max_frame (int): Max frame of timeline - ''' - return NotImplemented - - def get_qt_parent(self): - '''Get the host's main QT widget''' - return NotImplemented - - def get_qt_app(self): - from Qt import QtWidgets - return QtWidgets.QApplication.instance() - - -class ExtensionCollector(object): - '''Handles Extension discovery and registration. Also allows looking - up extensions via attribute access.''' - - def __init__(self): - self.by_name = {} - self.by_attr = {} - - def __getattr__(self, name): - if name in self.by_name: - return self.by_name[name] - - if name in self.by_attr: - return self.by_attr[name] - - return self.__getattribute__(name) - - def __getitem__(self, name): - return getattr(self, name) - - def __iter__(self): - for ext in self.by_name.values(): - yield ext - - def get(self, name, default=missing): - if name in self.by_name: - return self.by_name[name] - - if name in self.by_attr: - return self.by_attr[name] - - if default is not missing: - return default - - def collect(self): - return dict(self.by_name) - - def register(self, extension): - '''Register an extension''' - - if not is_extension_type(extension): - raise RuntimeError('Not an extension: %s' % extension) - - if extension.name in _extensions: - _log.debug('Extension already loaded: %s' % extension) - return - - try: - instance = extension() - instance._load() - except Exception as e: - _log.debug( - 'Failed to load extension: %s: %s' % (extension, e.message) - ) - return - - self.by_name[extension.name] = instance - self.by_attr[extension.attr_name] = instance - _log.debug('Registered extension: %s', extension) - - def unregister(self, extension): - '''Unregister an extension''' - - if not is_extension_type(extension): - raise RuntimeError('Not an extension: %s' % extension) - - registered_extension = self.by_name.get(extension.name, None) - is_same_extension = ( - registered_extension is extension or - isinstance(registered_extension, extension) or - registered_extension.name == extension.name - ) - if is_same_extension: - self.by_name.pop(extension.name) - self.by_attr.pop(extension.attr_name) - registered_extension._unload() - _log.debug('Unregistered extension: %s', registered_extension) - - def clear(self): - '''Unloads and deletes all extensions''' - - while self.by_name: - name, ext = self.by_name.popitem() - ext.unload() - _log.debug('Unregistered extension: %s' % ext) - del ext - - self.by_attr = {} - - def discover(self, *paths): - '''Discover extensions - - 1. Search construct.extensions entry_point - 2. Search paths passed to discover_extensions - 3. Search configuration EXTENSION_PATHS - 4. Search CONSTRUCT_EXTENSION_PATHS environment variable - ''' - - from construct.api import config - - search_paths = list(paths) - - entry_points = pkg_resources.iter_entry_points(EXTENSIONS_ENTRY_POINT) - for entry_point in entry_points: - obj = entry_point.load() - for _, extension in inspect.getmembers(obj, is_extension_type): - self.register(extension) - - cfg_search_paths = config.get('EXTENSION_PATHS', []) - search_paths.extend(cfg_search_paths) - - env_search_paths = os.environ.get('CONSTRUCT_EXTENSION_PATHS') - if env_search_paths: - search_paths.extend(env_search_paths.split(os.pathsep)) - - for mod in iter_modules(*search_paths): - for _, extension in inspect.getmembers(mod, is_extension_type): - self.register(extension) - - -EXTENSION_TYPES = (Extension, HostExtension) - - -def is_extension_type(obj): - return ( - obj not in EXTENSION_TYPES and - isinstance(obj, type) and - issubclass(obj, EXTENSION_TYPES) - ) - - -def is_extension(obj): - return isinstance(obj, EXTENSION_TYPES) diff --git a/construct/models.py b/construct/models.py deleted file mode 100644 index b336175..0000000 --- a/construct/models.py +++ /dev/null @@ -1,277 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import getpass -import datetime -import fsfs -from scandir import scandir -from construct.vendor.lucidity.error import ParseError -from construct.errors import ConfigurationError -from construct.utils import cached_property - - -factory = fsfs.EntryFactory() - - -class Entry(factory.Entry): - '''Base class for all Construct Entry types''' - - default_thumbnail = '' - - def get_comments(self): - '''Get Entry comments''' - - try: - return self.read('comments') - except KeyError: - return [] - - def new_comment(self, body): - '''Add a new comment to Entry''' - - comment = dict( - user=getpass.getuser(), - time=datetime.datetime.utcnow(), - body=body - ) - comments = self.get_comments() - comments.append(comment) - self.write(comments=comments) - - def get_thumbnail(self): - '''Get Entry thumbnail''' - - try: - return self.read_file('thumbnail') - except KeyError: - return fsfs.File(self.default_thumbnail, 'rb') - - def set_thumbnail(self, file): - '''Set Entry thumbnail to file''' - - self.write_file('thumbnail', file) - - def get_status(self): - '''Get Entry status''' - - try: - return self.read('status') - except KeyError: - self.set_status('waiting') - return 'waiting' - - def set_status(self, status): - '''Set status of Entry''' - - from construct import config - statuses = config['STATUSES'].keys() - - if status not in statuses: - raise ValueError('Status must be one of: ' + str(statuses)) - - self.write(status=status) - - -factory.Entry = Entry - - -class Project(Entry): - - @property - def collections(self): - return self.children(levels=1).tags('collection') - - @property - def asset_types(self): - return self.children(levels=2).tags('asset_type') - - @property - def assets(self): - return self.children(levels=3).tags('asset') - - @property - def sequences(self): - return self.children(levels=2).tags('sequence') - - @property - def shots(self): - return self.children(levels=3).tags('shot') - - -class Collection(Entry): - - @property - def sequences(self): - return self.children(levels=1).tags('sequence') - - @property - def asset_types(self): - return self.children(levels=1).tags('asset_type') - - @property - def assets(self): - return self.children(levels=2).tags('asset') - - @property - def shots(self): - return self.children(levels=2).tags('shot') - - -class Sequence(Entry): - - @property - def shots(self): - return self.children(levels=1).tags('shot') - - -class Shot(Entry): - - @property - def tasks(self, *tags): - return self.children(levels=1).tags('task', *tags) - - -class AssetType(Entry): - - type_for_tag = 'asset_type' - - @property - def assets(self): - return self.children(levels=1).tags('asset') - - -class Asset(Entry): - - @property - def tasks(self, *tags): - return self.children(levels=1).tags('task', *tags) - - @cached_property - def type(self): - return self.parent().name - - -class Task(Entry): - - @cached_property - def config(self): - from construct import config - tasks_cfg = config['TASK_TYPES'] - if self.name in tasks_cfg: - return tasks_cfg[self.name] - else: - for tag in self.tags: - if tag in tasks_cfg: - return tasks_cfg[tag] - - @property - def short(self): - return self.config['short'] - - @property - def color(self): - return self.config['color'] - - @property - def icon(self): - return self.config['icon'] - - @property - def workspaces(self, *tags): - return self.children(levels=1).tags('workspace', *tags) - - @property - def publishes(self, *tags): - return self.children(levels=1).tags('publish', *tags) - - def get_latest_publish(self): - publishes = list(self.publishes) - try: - return publishes[-1] - except IndexError: - pass - - -class Workspace(Entry): - - @cached_property - def config(self): - from construct import config - - software_cfg = config['SOFTWARE'] - for app_name, app_cfg in software_cfg.items(): - if app_cfg['host'] == self.name: - return app_cfg - else: - for tag in self.tags: - if app_cfg['host'] == tag: - return app_cfg - - @property - def icon(self): - return self.config['icon'] - - def get_work_files(self): - import construct - path_template = construct.get_path_template('workspace_file') - versions = {} - for f in scandir(self.path): - try: - data = path_template.parse(f.name) - data['version'] = int(data['version']) - except ParseError: - continue - versions[f.name] = data - return versions - - def get_next_version(self, name, ext): - task = self.parent().short - versions = self.get_work_files() - version = 0 - - for data in versions.values(): - is_match = ( - data['name'] == name and - data['task'] == task and - data['ext'] == ext - ) - if is_match: - version = max(version, data['version']) - - return version + 1 - - def new_version(self, user, name, version, file_type, file): - import os - relative_path = os.path.relpath(file, self.path) - version = Version( - user=user, - file_type=file_type, - file=relative_path, - name=name, - version=version, - ) - data = self.read() - data.setdefault('versions', []) - data['versions'].append(version) - self.write(**data) - - -class EmbeddedModel(dict): - - __fields__ = [] - - def __init__(self, *args, **kwargs): - super(Version, self).__init__(*args, **kwargs) - self.__dict__ = self - for field in self.__fields__: - if field not in self: - raise ValueError('Missing keyword argument: %s' % field) - - -class Version(EmbeddedModel): - - __field__ = ['user', 'name', 'version', 'file_type', 'file'] - - -def is_entry(obj): - '''Returns True if obj is an instance of Entry or EntryProxy''' - return isinstance(obj, (factory.Entry, factory.EntryProxy)) diff --git a/construct/signals.py b/construct/signals.py deleted file mode 100644 index 85269fe..0000000 --- a/construct/signals.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = [ - 'route', - 'chain', - 'send', - 'connect', - 'disconnect', -] - -import logging -from collections import defaultdict -from fnmatch import fnmatch -from contextlib import contextmanager -from construct.utils import dummy_ctxmanager -from construct.types import weakset - - -ALL = '*' -_subscribers = defaultdict(weakset) -_suppress = None -_log = logging.getLogger(__name__) - - -def is_suppressed(identifier): - if _suppress is None: - return False - - if _suppress == ALL or any([fnmatch(identifier, k) for k in _suppress]): - _log.debug('suppressed %s' % identifier) - return True - - -def suppressed_when(condition, *identifiers): - if condition: - return suppressed(*identifiers) - return dummy_ctxmanager() - - -@contextmanager -def suppressed(*identifiers): - global _suppress - _suppress = list(identifiers) or ALL - _log.debug('suppressing %s' % _suppress) - try: - yield - finally: - _suppress = None - - -def get_subscribers(identifier): - '''Yield all subscribers for identifier, including fuzzy matches. - - Arguments: - identifier (str): Signal identifier - - Yields: - callable subscribers - ''' - - for subscriber in _subscribers[identifier]: - yield subscriber - - for key in _subscribers.keys(): - if key == identifier: - continue - - # Fuzzy Subscription - if fnmatch(identifier, key): - for subscriber in _subscribers[key]: - yield subscriber - - -def send(identifier, *args, **kwargs): - '''Send signal - - Arguments: - identifier (str): signal identifier - ''' - - if is_suppressed(identifier): - return - - results = [] - for subscriber in get_subscribers(identifier): - results.append(subscriber(*args, **kwargs)) - return results - - -def chain(identifier, *args, **kwargs): - '''Chain signal subscribers - - Arguments: - identifier (str): signal identifier - ''' - if is_suppressed(identifier): - return - - subscribers = list(get_subscribers(identifier)) - if not subscribers: - return - - result = subscribers[0](*args, **kwargs) - if len(subscribers) == 1: - return result - - for subscriber in subscribers[1:]: - result = subscriber(result) - - return result - - -def route(identifier): - '''Connect function to signal via decorator, only works with functions. - - Arguments: - identifier (str): signal identifier - ''' - - def connect_to_signal(obj): - connect(identifier, obj) - return obj - return connect_to_signal - - -def connect(identifier, obj): - '''Connect function to signal - - Arguments: - identifier (str): signal identifier - obj (callable): signal subscriber - ''' - - _subscribers[identifier].add(obj) - - -def disconnect(identifier, obj): - '''Disconnect function to signal - - Arguments: - identifier (str): signal identifier - obj (callable): signal subscriber - ''' - - _subscribers[identifier].discard(obj) diff --git a/construct/stats.py b/construct/stats.py deleted file mode 100644 index b34d040..0000000 --- a/construct/stats.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from functools import wraps -from construct.constants import TIMER -from construct.utils import get_qualname -from contextlib import contextmanager -import logging - - -_log = logging.getLogger(__name__) - - -class Stats(object): - '''Collection of :class:`Action` execution stats - - Attributes: - number_of_failures - number_of_successes - number_of_skips - total_execution_time - group_times - average_group_time - task_times - average_task_time - ''' - # TODO: Implement stats - - -@contextmanager -def log_time(msg): - - start_time = TIMER() - try: - yield - finally: - _log.info('%s: %.8fs', msg, TIMER() - start_time) - - -def log_call(fn): - '''Log execution time of a wrapped function''' - @wraps(fn) - def _logged_call(*args, **kwargs): - start_time = TIMER() - try: - return fn(*args, **kwargs) - finally: - _log.info('%s(): %.8fs', get_qualname(fn), TIMER() - start_time) - return _logged_call diff --git a/construct/tasks.py b/construct/tasks.py deleted file mode 100644 index 6166b23..0000000 --- a/construct/tasks.py +++ /dev/null @@ -1,1021 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = [ - 'RequestThread', - 'AsyncRequest', - 'AsyncTask', - 'Request', - 'Task', - 'TaskCollection', - 'task', - 'async_task', - 'sort_tasks', - 'group_tasks', - 'requires', - 'skips', - 'pass_context', - 'pass_args', - 'pass_kwargs', - 'params', - 'available', - 'done', - 'failure', - 'success', - 'store', - 'artifact', - 'returns', - 'kwarg', -] - -import threading -import sys -import inspect -import logging -import six -from operator import attrgetter -from collections import OrderedDict -from construct.compat import basestring -from construct.constants import * -from construct.context import _ctx_stack, _req_stack -from construct.utils import ( - get_callable_name, - pop_attr, - ensure_callable, -) -from construct.types import Priority -from construct.errors import TimeoutError, TaskError -from construct import signals - - -_log = logging.getLogger(__name__) -DEFAULT_PRIORITY = Priority(0) - - -class RequestThread(threading.Thread): - - def __init__(self, request, retries=0, interval=1): - super(RequestThread, self).__init__() - self.retries = retries - self.interval = interval - self.request = request - self._shutdown = threading.Event() - self._stopped = threading.Event() - self._started = threading.Event() - self.request.set_status(WAITING) - - def start(self): - '''Pass context through to thread''' - self.request.set_status(PENDING) - super(RequestThread, self).start() - - def run(self): - - try: - - self._started.set() - retries = 0 - self.request.push() - - while True: - - try: - # Setup task - self.request.set_status(RUNNING) - self.request._before_task() - - # Run task - result = self.request.task( - *self.request.args, - **self.request.kwargs - ) - - # Finish task - self.request.set_value(result) - self.request._after_task() - break - - except: - self.request.set_exception(*sys.exc_info()) - if retries >= self.retries: - break - - retries += 1 - if self._shutdown.wait(self.interval): - break - - finally: - - self.request.pop() - self._stopped.set() - - def started(self): - return self._started.is_set() - - def stopped(self): - return self._stopped.is_set() - - def stop(self): - self._shutdown.set() - self._stopped.wait() - if self.isAlive(): - self.join() - - def ready(self): - return self.request._status in DONE_STATUSES - - def _get_ready(self, propagate=True): - - if self.request.success: - return self.request._value - - if self.request.failed: - if propagate: - six.reraise(*self.request._exc) - return self.request._exc - - def get(self, timeout=None, interval=0.2, propagate=True): - - st = TIMER() - while not self.ready(): - if timeout is not None and TIMER() - st > timeout: - raise TimeoutError('Result not ready yet...') - SLEEP(interval) - - return self._get_ready(propagate) - - -class Request(object): - - def __init__(self, task, ctx=None, args=None, kwargs=None): - self.task = task - self.ctx = ctx - self.args = args or () - self.kwargs = kwargs or {} - self.retries = 0 - self._enabled = True - self._status = None - self._exc = None - self._value = None - self.push() - self.set_status(WAITING) - self.pop() - - def push(self): - if _ctx_stack.top is not self.ctx: - _ctx_stack.push(self.ctx) - if _req_stack.top is not self: - _req_stack.push(self) - - def pop(self): - if _req_stack.top is self: - _req_stack.pop() - if _ctx_stack.top is self.ctx: - _ctx_stack.pop() - - @property - def value(self, value): - return self._value - - def set_value(self, value): - self._value = value - self.set_status(SUCCESS) - - @property - def exception(self): - return self._exc - - def set_exception(self, *exc_info): - self._exc = exc_info - self.set_status(FAILED) - - @property - def status(self): - - return self._status - - def set_status(self, status): - - if self._status == status: - return - - self.push() - try: - last_status = status - self._status = status - signals.send('request.status.changed', self, last_status, status) - finally: - self.pop() - - @property - def enabled(self): - return self._enabled - - def set_enabled(self, value): - self._enabled = value - signals.send('request.enabled', self, self._enabled) - - @property - def success(self): - return self._status == SUCCESS - - @property - def failed(self): - return self._status == FAILED - - @property - def done(self): - return self._status in DONE_STATUSES - - @property - def ready(self): - return self._status in DONE_STATUSES - - def _before_task(self): - if self.ctx is not None: - self.args, self.kwargs = self.task.get_params(self.ctx) - - def _after_task(self): - if self.ctx is not None: - self.task.run_callbacks(self.ctx, self._value) - - def get(self, propagate=True): - - if self.success: - return self._value - - if self.failed: - if propagate: - six.reraise(*self._exc) - return self._exc - - try: - - # Setup task - self.set_status(RUNNING) - self._before_task() - self.push() - - # Run task - result = self.task(*self.args, **self.kwargs) - - # Finish task - self.set_value(result) - self._after_task() - return result - - except: - - self.retries += 1 - exc_info = sys.exc_info() - self.set_exception(*exc_info) - if propagate: - six.reraise(*exc_info) - return exc_info - - finally: - - self.pop() - - -class AsyncRequest(Request): - - def __init__(self, task, ctx=None, args=None, kwargs=None): - super(AsyncRequest, self).__init__(task, ctx, args, kwargs) - self._thread = RequestThread(self) - - def get(self, timeout=None, interval=0.2, propagate=True): - - try: - - self.push() - - if not self._thread.started(): - self._thread.start() - - return self._thread.get(timeout, interval, propagate) - - finally: - - self.pop() - - -class Task(object): - - request_cls = Request - - def __init__(self, fn, identifier=None, description=None, - priority=DEFAULT_PRIORITY, requires=None, skips=None, - arg_getters=None, kwarg_getters=None, callbacks=None, - available=True, parent=None): - self.fn = fn - if not isinstance(priority, Priority): - priority = Priority(priority) - self.priority = priority - self.identifier = identifier - self.description = description - self.requires = requires or [] - self.skips = skips or [] - self.arg_getters = arg_getters or [] - self.kwarg_getters = kwarg_getters or {} - self.callbacks = callbacks or [] - self._available = available - self.parent = parent - - def __call__(self, *args, **kwargs): - if self.bound_method: - return self.fn(self.parent, *args, **kwargs) - else: - return self.fn(*args, **kwargs) - - def __repr__(self): - return '<{} {} at 0x{}>(identifier={!r})'.format( - ('unbound', 'bound')[self.bound], - self.__class__.__name__, - id(self), - self.identifier - ) - - def __get__(self, obj, type): - if obj is None: - return self - - bind_task = self.clone(parent=obj) - - for name, member in inspect.getmembers(type): - if member is self: - setattr(obj, name, bind_task) - - return bind_task - - @property - def bound(self): - return self.parent is not None - - @property - def bound_method(self): - spec = inspect.getargspec(self.fn) - return self.bound and 'self' in spec.args - - @property - def func_name(self): - return get_callable_name(self.fn) - - def clone(self, **kwargs): - '''Create a new Task instance, overriding any kwargs you need to''' - - kwargs.setdefault('fn', self.fn) - kwargs.setdefault('identifier', self.identifier) - kwargs.setdefault('description', self.description) - kwargs.setdefault('priority', self.priority) - kwargs.setdefault('requires', self.requires) - kwargs.setdefault('skips', self.skips) - kwargs.setdefault('arg_getters', self.arg_getters) - kwargs.setdefault('kwarg_getters', self.kwarg_getters) - kwargs.setdefault('callbacks', self.callbacks) - kwargs.setdefault('available', self._available) - kwargs.setdefault('parent', self.parent) - return self.__class__(**kwargs) - - def ready(self, ctx): - - if self.requires is None: - return True - - for fn in self.requires: - if not fn(ctx): - return False - - return True - - def skip(self, ctx): - - if self.skips is None: - return False - - for fn in self.skips: - if fn(ctx): - return True - - return False - - def get_params(self, ctx): - - args, kwargs = [], {} - - for getter in self.arg_getters: - args.append(getter(ctx)) - - for name, getter in self.kwarg_getters.items(): - if name == 'kwargs': - more = getter(ctx) - kwargs.update(more) - else: - kwargs[name] = getter(ctx) - - return tuple(args), kwargs - - def run_callbacks(self, ctx, result): - - for callback in self.callbacks: - callback(ctx, result) - - def available(self, ctx): - - if callable(self._available): - return self._available(ctx) - - return self._available - - def request(self, ctx=None, args=None, kwargs=None): - return self.request_cls(self, ctx, args, kwargs) - - -class AsyncTask(Task): - - request_cls = AsyncRequest - - -class TaskCollection(object): - - identifier = None - - def __init__(self, identifier=None, tasks=None): - self.identifier = identifier or self.identifier - self.tasks = [] - - # Bind tasks - if tasks: - if isinstance(tasks, (list, tuple, set, self.__class__)): - for task in tasks: - self._bind_task(task) - else: - raise ValueError('Tasks must be a list of task objects') - - # Bind classmethod tasks - for name, member in inspect.getmembers(self.__class__): - if isinstance(member, Task): - bound_task = getattr(self, name) - self.tasks.append(bound_task) - - def __iter__(self): - return iter(self.tasks) - - def __len__(self): - return len(self.tasks) - - def __getitem__(self, index): - return self.tasks.__getitem__(index) - - def __contains__(self, task): - if task in self.tasks: - return True - - for t in self.tasks: - if t.identifier == task.identifier: - return True - - return False - - def index(self, task): - for i, bound_task in enumerate(self.tasks): - if bound_task.identifier == task.identifier: - return i - raise ValueError('%s is not in TaskCollection' % task.identifier) - - def sort(self): - '''Sort tasks by priority''' - - self.tasks.sort(key=lambda t: t.priority) - - def _bind_task(self, task): - '''Bind and add task to this collection''' - - if task in self: - return - - bind_name = task.func_name - if hasattr(self, bind_name): - raise NameError( - 'TaskCollection already has a method %s' % bind_name - ) - - if task.parent is not self: - task = task.clone(parent=self) - setattr(self, bind_name, task) - - self.tasks.append(task) - - def _unbind_task(self, task): - '''Unbind and remove a task from this collection''' - - try: - index = self.index(task) - task = self.tasks.pop(index) - bind_name = task.func_name - if getattr(self, bind_name, None) is task: - delattr(self, bind_name) - except ValueError: - pass - - def append(self, task): - '''Bind and add a task to this collection''' - - self._bind_task(task) - - def remove(self, task): - '''Remove a task from this collection''' - - self._unbind_task(task) - - def extend(self, tasks): - '''Bind and add a list of tasks to this collection''' - - for task in tasks: - self._bind_task(task) - - -# Decorator interface - - -def task(identifier=None, priority=None, description=None, cls=Task): - '''Decorator that converts a function to a Task object. The function - remains callable by normal means, but also receives the additional - functionality of a Task object. - - Arguments: - identifier (str or callable): When a string is passed, return - a decorator which passes the string to the Task object as it's - identifier. If a callable is passed, use the callables name as - the Task's identifier. - priority (Priority or int): Priority used to order tasks in an action - description (str): Defaults to the decorated functions docstring - cls (Object): Task type to create, defaults to Task - - Returns: - Task or instance of cls - ''' - - def make_task(fn, identifier): - return cls( - fn, - identifier=identifier or get_callable_name(fn), - description=description or fn.__doc__, - requires=pop_attr(fn, '__task_requires__', None), - skips=pop_attr(fn, '__task_skips__', None), - arg_getters=pop_attr(fn, '__task_arg_getters__', None), - kwarg_getters=pop_attr(fn, '__task_kwarg_getters__', None), - callbacks=pop_attr(fn, '__task_callbacks__', None), - available=pop_attr(fn, '__task_available__', True), - priority=priority if priority is not None else DEFAULT_PRIORITY - ) - - if callable(identifier): - fn = identifier - identifier = get_callable_name(fn) - return make_task(fn, identifier) - - if isinstance(identifier, basestring) or identifier is None: - def wrapper(fn): - return make_task(fn, identifier) - return wrapper - - raise TypeError("Argument must be: str or callable") - - -def async_task(identifier, priority=None, description=None, cls=AsyncTask): - '''Like :func:`task` decorator. This returns an AsyncTask which will be run - in a separate thread when an Action is evaluated. Can still be called - normally like a standard function. - - Arguments: - identifier (str or callable): When a string is passed, return - a decorator which passes the string to the Task object as it's - identifier. If a callable is passed, use the callables name as - the Task's identifier. - priority (Priority or int): Priority used to order tasks in an action - description (str): Defaults to the decorated functions docstring - cls (Object): Task type to create, defaults to AsyncTask - - Returns: - AsyncTask or instance of cls - ''' - - return task(identifier, priority, description, cls) - - -def requires(*funcs): - '''Task decorator describing a tasks's requirements. - - Each requirement must be a callable accepting ctx as an argument. Once all - requirements are met, the task is ready to run. The ActionRunner will move - the task from the waiting queue to the ready queue and execute it in the - next loop. - - Require the success of another task: - >>> @task - ... @requires(success('other.task')) - ... def my_task(): - ... pass - - Require failure of another task: - >>> @task - ... @requires(failure('other.task')) - ... def my_task(): - ... pass - ''' - - def requires(fn): - if not hasattr(fn, '__task_requires__'): - fn.__task_requires__ = [] - fn.__task_requires__.extend([_requires_for('requires', f) for f in funcs]) - return fn - - return requires - - -def skips(*funcs): - - def skips(fn): - if not hasattr(fn, '__task_skips__'): - fn.__task_skips__ = [] - fn.__task_skips__.extend([_getter_for('skips', f) for f in funcs]) - return fn - - return skips - - -def pass_context(fn): - '''Task decorator that passes Context object as first argument to task''' - - def pass_ctx(ctx): - return ctx - - if not hasattr(fn, '__task_arg_getters__'): - fn.__task_arg_getters__ = [] - - if len(fn.__task_arg_getters__): - for getter in fn.__task_arg_getters__: - if getattr(getter, '__name__', None) == 'pass_ctx': - return fn - - fn.__task_arg_getters__.insert(0, pass_ctx) - return fn - - -def pass_args(fn): - '''Task decorator that passes Context object as first argument to task''' - - def pass_args(ctx): - return ctx.args - - if not hasattr(fn, '__task_arg_getters__'): - fn.__task_arg_getters__ = [] - - if len(fn.__task_arg_getters__): - for getter in fn.__task_arg_getters__: - if getattr(getter, '__name__', None) == 'pass_args': - return fn - - fn.__task_arg_getters__.insert(0, pass_args) - return fn - - -def pass_kwargs(fn): - '''Task decorator that passes all action kwargs to the task''' - - def pass_kwargs(ctx): - return ctx.kwargs - - if not hasattr(fn, '__task_kwarg_getters__'): - fn.__task_kwarg_getters__ = {} - fn.__task_kwarg_getters__['kwargs'] = pass_kwargs - return fn - - -def params(*args, **kwargs): - '''Task decorator used to describe how to get args and kwargs to pass to - the task. This should have the same signature as the task itself, but, - each argument and keyword argument should be a function accepting one - argument, ctx, that returns a value to pass to the task. - - Examples: - - >>> @task - ... @params(store('a'), b=store('b')) - ... def plus(a, b): - ... return a + b - - # pass_context provides ctx, so we describe the remaining args - >>> @task - ... @pass_context - ... @params(store('a'), store('b')) - ... def plus2(ctx, a, b): - ... value = a + b - ... ctx.store['plus_result'] = value - ... return value - - ''' - - def describe_params(fn): - if not hasattr(fn, '__task_arg_getters__'): - fn.__task_arg_getters__ = [] - - for arg in args: - fn.__task_arg_getters__.append(_getter_for('params', arg)) - - if not hasattr(fn, '__task_kwarg_getters__'): - fn.__task_kwarg_getters__ = {} - - for name, arg in kwargs.items(): - fn.__task_kwarg_getters__[name] = _getter_for('params', arg) - - return fn - - return describe_params - - -def returns(*callbacks): - '''Task decorator that adds callbacks to run when the task successfully - returns. Also accepts CtxAction objects like artifact(key) and store(key) - ''' - - def returns(fn): - if not hasattr(fn, '__task_callbacks__'): - fn.__task_callbacks__ = [] - - for callback in callbacks: - fn.__task_callbacks__.append(_setter_for('returns', callback)) - - return fn - - return returns - - -def available(value): - '''Task available when value is True or value() is True''' - - def available(fn): - fn.__task_available__ = value - return fn - - return available - - -def success(identifier): - '''Check if a task succeeded''' - - def success(ctx): - '''Returns True if the task has succeeded''' - try: - request = ctx.requests[identifier] - return request.success - except KeyError: - raise TaskError('success(%r): Request not found..' % identifier) - - return success - - -def done(identifier): - '''Check if a task is done''' - - def done(ctx): - '''Returns True if the task is done''' - try: - request = ctx.requests[identifier] - return request.done - except KeyError: - raise TaskError('done(%r): Request not found..' % identifier) - - return done - - -def failure(identifier): - '''Check if a task failed''' - - def failure(ctx): - '''Returns True if the task has failed''' - try: - request = ctx.requests[identifier] - return request.failed - except KeyError: - raise TaskError('failure(%r): Request not found..' % identifier) - - return failure - - -class CtxAction(object): - - section = '' - sep = ':' - actions = 'store', 'append', 'count' - gets = True - sets = True - requires = True - - def __init__(self, key, action='store'): - self.key = key - self.action = action - - def split_key(self): - if self.sep in self.key: - keys = self.key.split() - return keys[:-1], keys[-1] - return [], self.key - - def get_section_key(self, ctx): - keys, key = self.split_key() - section = getattr(ctx, self.section) - if keys: - for key in keys: - section = section.get(key) - return section, key - - def exists(self, ctx): - try: - section, key = self.get_section_key(ctx) - except KeyError: - return False - return key in section - - def get(self, ctx): - section, key = self.get_section_key(ctx) - return section[key] - - def set(self, ctx, value): - section, key = self.get_section_key(ctx) - if self.action == 'store': - section[key] = value - elif self.action == 'append': - if key not in section: - section[key] = [] - section[key].append(value) - else: - if key in section and type(section[key], (float, int)): - section[key] += value - - -class artifact(CtxAction): - '''Use as an argument for :func:`params` or :func:`returns`. - When used with :func:`params`, artifact will get the value of the key - from the context's "artifacts" dict and supply it as an arg or kwarg for - the task. If used with :func:`returns`, artifact will inject the return - value of the task into the specified key of the context's "artifacts" dict. - You can specify a nested key using : as a separator. - - Examples: - - # Set ctx.artifacts['funky'] to return value of task - >>> @task - ... @returns(artifact('funky')) - ... def funky_func(): - ... return {} - - # Set ctx.artifacts['funky']['monkey'] to return value of task - >>> @task - ... @returns(artifact('funky:monkey')) - ... def funky_monkey_func(): - ... return 'Super Funky Monkey' - ''' - section = 'artifacts' - - -class store(CtxAction): - '''Use as an argument for :func:`params` or :func:`returns`. - When used with :func:`params`, store will get the value of the key - from the context's "store" dict and supply it as an arg or kwarg for - the task. If used with :func:`returns`, store will inject the return - value of the task into the specified key of the context's "store" dict. - You can specify a nested key using : as a separator. - - Examples: - - # Pass ctx.store['x'] as an arg and ctx.store['y'] as kwarg to add - # Set ctx.store['sum'] to the return value of add - >>> @task - ... @params(store('x'), b=store('y')) - ... @returns(store('sum')) - ... def add(a, b): - ... return a + b - ''' - section = 'store' - - -class kwarg(CtxAction): - '''Use as an argument for :func:`params`. When used with :func:`params`, - kwarg will get the value of the key from the context's "kwargs" dict and - supply it as an arg or kwarg for the task. This action can not be used with - :func:`returns`. - - You can specify a nested key using : as a separator. - - Examples: - - # Pass ctx.store['x'] as an arg and ctx.store['y'] as kwarg to add - # Set ctx.store['sum'] to the return value of add - >>> @task - ... @params(store('x'), b=store('y')) - ... @returns(store('sum')) - ... def add(a, b): - ... return a + b - ''' - section = 'kwargs' - - def set(self, ctx, value): - raise NotImplementedError('Can not use kwarg with the returns deco') - - -def _requires_for(name, obj): - '''Returns a callable object for use with task decorators. Requirement - must accept one argument ctx.''' - - if callable(obj): - spec = inspect.getargspec(obj) - if spec.args and len(spec.args) == 1: - return obj - - raise TypeError('%s must accept ctx as an argument' % name) - - if isinstance(obj, CtxAction): - if obj.requires: - return obj.exists - - raise TypeError( - '%s can not be used as an argument to %s()' % - (type(obj).__name__, name) - ) - - raise TypeError( - '%s received unsupported type: %s\nMust be a callable or CtxAction' % - (type(obj).__name__, name) - ) - - -def _getter_for(name, obj): - '''Returns a callable object for use with task decorators. Getters - must accept one argument ctx.''' - - if callable(obj): - spec = inspect.getargspec(obj) - if spec.args and len(spec.args) == 1: - return obj - - raise TypeError('%s must accept ctx as an argument' % name) - - if isinstance(obj, CtxAction): - if obj.gets: - return obj.get - - raise TypeError( - '%s can not be used as an argument to %s()...' % - (type(obj).__name__, name) - ) - - raise TypeError( - '%s received unsupported type: %s\nMust be a callable or CtxAction' % - (type(obj).__name__, name) - ) - - -def _setter_for(name, obj): - '''Returns a callable object for use with task decorators. Setters - must accept two arguments ctx and value.''' - - if callable(obj): - spec = inspect.getargspec(obj) - if spec.args and len(spec.args) == 2: - return obj - - raise TypeError('%s must accept two args: ctx and value' % name) - - if isinstance(obj, CtxAction): - if obj.sets: - return obj.set - - raise TypeError( - '%s can not be used as an argument to %s()...' % - (type(obj).__name__, name) - ) - - raise TypeError( - '%s received unsupported type: %s\nMust be a callable or CtxAction' % - (type(obj).__name__, name) - ) - - -# Sort methods - -def sort_tasks(tasks): - '''Sort the given tasks by priority''' - - return sorted(tasks, key=attrgetter('priority')) - - -def group_tasks(tasks): - '''Group sorted tasks by priority''' - - groups = OrderedDict() - for task in tasks: - groups.setdefault(task.priority, []) - groups[task.priority].append(task) - return groups diff --git a/construct/tests/__init__.py b/construct/tests/__init__.py deleted file mode 100644 index 8ea9a79..0000000 --- a/construct/tests/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -import os -from functools import partial - - -# Convenient attributes and functions -root = os.path.dirname(__file__) -data = os.path.join(root, 'test_data') -test_path = partial(os.path.join, root) -data_path = partial(os.path.join, data) diff --git a/construct/tests/test_actionparams.py b/construct/tests/test_actionparams.py deleted file mode 100644 index f73eb30..0000000 --- a/construct/tests/test_actionparams.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function -from nose.tools import raises -from construct import actionparams -from construct.errors import ArgumentError - - -params_0 = dict() -params_1 = dict( - str_arg={ - 'label': 'String Argument', - 'help': 'A String Argument', - 'required': True, - 'type': str - }, - int_arg={ - 'label': 'Integer Argument', - 'help': 'An Integer Argument', - 'required': True, - 'default': 1, - 'type': int - }, - float_arg={ - 'label': 'Float Argument', - 'help': 'A Float Argument', - 'required': False, - 'default': 10.0, - 'type': float - }, - dict_arg={ - 'label': 'Dict Argument', - 'help': 'A Dict Argument', - 'required': True, - 'type': dict - } -) - - -def test_validate_nada(): - '''Validate empty params dict''' - actionparams.validate(params_0) - actionparams.validate(params_1) - - -@raises(ArgumentError) -def test_pass_args_to_empty_params(): - '''Validate kwargs against empty params''' - actionparams.validate_kwargs(params_0, {'invalid': 'kwargs'}) - - -@raises(ArgumentError) -def test_missing_required(): - '''Validate kwargs with missing required argument''' - actionparams.validate_kwargs( - params_1, - {'str_arg': 'str', 'dict_arg': {}} - ) diff --git a/construct/tests/test_data/extpath1/extensiona.py b/construct/tests/test_data/extpath1/extensiona.py deleted file mode 100644 index 4a8a6d8..0000000 --- a/construct/tests/test_data/extpath1/extensiona.py +++ /dev/null @@ -1,6 +0,0 @@ -from construct import Extension - - -class ExtensionA(Extension): - name = 'ExtensionA' - attr_name = 'extension_a' diff --git a/construct/tests/test_data/extpath2/extensionb.py b/construct/tests/test_data/extpath2/extensionb.py deleted file mode 100644 index e465a9f..0000000 --- a/construct/tests/test_data/extpath2/extensionb.py +++ /dev/null @@ -1,6 +0,0 @@ -from construct import Extension - - -class ExtensionB(Extension): - name = 'ExtensionB' - attr_name = 'extension_b' diff --git a/construct/tests/test_data/extpath3/extension_package/__init__.py b/construct/tests/test_data/extpath3/extension_package/__init__.py deleted file mode 100644 index 249d546..0000000 --- a/construct/tests/test_data/extpath3/extension_package/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from construct import Extension - - -class ExtensionC(Extension): - name = 'ExtensionC' - attr_name = 'extension_c' diff --git a/construct/tests/test_extensions.py b/construct/tests/test_extensions.py deleted file mode 100644 index 70fd34c..0000000 --- a/construct/tests/test_extensions.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, print_function -from construct.tests import data_path -import construct - - -def teardown(): - construct.extensions.clear() - - -def test_discover_single_path(): - '''Discover Extensions on single path''' - - construct.extensions.discover(data_path('extpath1')) - exts = construct.extensions.collect() - assert 'ExtensionA' in exts - - -def test_discover_multiple_paths(): - '''Discover Extensions on multiple paths''' - - construct.extensions.discover( - data_path('extpath1'), - data_path('extpath2'), - data_path('extpath3'), - ) - exts = construct.extensions.collect() - assert 'ExtensionA' in exts - assert 'ExtensionB' in exts - assert 'ExtensionC' in exts diff --git a/construct/tests/test_tasks.py b/construct/tests/test_tasks.py deleted file mode 100644 index a1c699e..0000000 --- a/construct/tests/test_tasks.py +++ /dev/null @@ -1,273 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, division, print_function -import time -from timeit import default_timer -from construct.tasks import Task, AsyncTask, task, TaskCollection -from construct.errors import TimeoutError -from construct.constants import * -from nose.tools import raises - - -def func_no_args(): - return 'SUCCESS' - - -def func_w_args(*args, **kwargs): - return args, kwargs - - -def func_poll(interval): - time.sleep(interval) - return True - - -def func_raises(): - raise ValueError - - -def test_call_Task(): - '''Call a Task''' - - t = Task(func_no_args, 'func.no.args', 'function without arguments') - assert t() == 'SUCCESS' - - t = Task(func_w_args, 'func.w.args', 'function with arguments') - args, kwargs = ('arg',), {'kwarg': None} - assert t(*args, **kwargs) == (args, kwargs) - - -def test_call_AsyncTask(): - '''Call an AsyncTask''' - - t = AsyncTask(func_no_args, 'func.no.args', 'function without arguments') - assert t() == 'SUCCESS' - - t = AsyncTask(func_w_args, 'func.w.args', 'function with arguments') - args, kwargs = ('arg',), {'kwarg': None} - assert t(*args, **kwargs) == (args, kwargs) - - -def test_Task_request(): - '''Use a task request''' - - t = Task(func_no_args, 'func.no.args', 'function without arguments') - r = t.request() - - assert r.task is t - assert r.args == () - assert r.kwargs == {} - assert r.status == WAITING - assert not r.ready - assert r.get() == 'SUCCESS' - assert r.ready - assert r.status == SUCCESS - - -@raises(ValueError) -def test_Request_propagate(): - '''Request exception propagation''' - - t = Task(func_raises, 'func.raises', 'Task raises ValueError') - - # Do not propagate - r = t.request() - exc = r.get(propagate=False) - assert r.status == FAILED - assert exc[0] == ValueError - - # Propagate - r = t.request() - r.get() - - -def test_AsyncTask_request(): - '''Use an async task request''' - - t = AsyncTask(func_poll, 'func.poll', 'Polling task') - r = t.request(args=(0,)) - - assert r.task is t - assert r.args == (0,) - assert r.kwargs == {} - assert r.status == WAITING - assert not r.ready - assert r.get() - assert r.ready - assert r.status == SUCCESS - - -@raises(TimeoutError) -def test_AsyncRequest_timeout(): - '''AsyncRequest raises TimeoutError''' - - t = AsyncTask(func_poll, 'func.poll', 'Polling task') - r = t.request(args=(1,)) - r.get(0) - - -@raises(ValueError) -def test_AsyncRequest_propagate(): - '''AsyncRequest exception propagation''' - - t = AsyncTask(func_raises, 'func.raises', 'Task raises ValueError') - - # Do not propagate - r = t.request() - exc = r.get(propagate=False) - assert r.status == FAILED - assert exc[0] == ValueError - - # Propagate - r = t.request() - r.get() - - -def test_AsyncRequest_returns_asap(): - '''AsyncRequest get returns ASAP''' - - t = AsyncTask(func_poll, 'func.poll', 'Polling task') - r = t.request(args=(0.1,)) - - st = default_timer() - value = r.get(1) - - assert default_timer() - st < 1 - assert value - - -def test_task_decorator(): - '''Task function decorator''' - - @task - def null_task(): - return True - - assert not null_task.bound - assert null_task.identifier == 'null_task' - assert null_task.priority == 0 - assert null_task() - assert null_task.ready(None) - - r = null_task.request() - assert r.get() - - -def test_task_method(): - '''Task method decorator''' - - class ObjectWithTasks(object): - - @task(priority=0) - def task_a(self): - return True - - tc = ObjectWithTasks() - - assert tc.task_a is tc.task_a - assert tc.task_a.bound - assert tc.task_a.identifier == 'task_a' - assert tc.task_a() - - tc2 = ObjectWithTasks() - - assert tc.task_a is not tc2.task_a - - -@task -def null_task(): - return True - - -class TestCollection(TaskCollection): - - identifier = 'test_collection' - - @task(priority=0) - def task_a(self): - return True - - @task(priority=1) - def task_b(self): - return False - - @task(priority=2) - def task_c(self): - return False - - -def test_task_collection_methods(): - '''TaskCollection automagical task registration''' - - tc = TestCollection() - - # Membership tests use task identifiers - assert TestCollection.task_a in tc - assert TestCollection.task_b in tc - assert TestCollection.task_c in tc - assert tc.task_a() - assert not tc.task_b() - assert not tc.task_c() - - # All task methods are automagically bound in __init__ - assert tc.task_a.bound and tc.task_a.parent is tc - assert tc.task_b.bound and tc.task_b.parent is tc - assert tc.task_c.bound and tc.task_c.parent is tc - assert len(tc) == 3 - - -def test_task_collection_add_remove(): - '''TaskCollection append and remove''' - - tc = TestCollection() - - # Add a task - tc.append(null_task) - assert null_task in tc - assert len(tc) == 4 - assert hasattr(tc, null_task.func_name) - assert tc.null_task() - - # Does nothing - tc.append(null_task) - assert len(tc) == 4 - - # Remove a task - tc.remove(null_task) - assert null_task not in tc - assert len(tc) == 3 - assert not hasattr(tc, null_task.func_name) - - # Does nothing - tc.remove(null_task) - assert len(tc) == 3 - - -def test_task_collection_init_args(): - '''TaskCollection init args''' - - @task(priority=0) - def task_a(): - return True - - @task(priority=1) - def task_b(): - return False - - @task(priority=2) - def task_c(): - return False - - tc = TaskCollection('identifier', [task_a, task_b, task_c]) - assert tc.identifier == 'identifier' - assert len(tc) == 3 - assert tc.task_a() - assert not tc.task_b() - assert not tc.task_c() - - tc.remove(task_a) - tc.remove(task_b) - tc.remove(task_c) - assert len(tc) == 0 - assert not hasattr(tc, task_a.func_name) - assert not hasattr(tc, task_b.func_name) - assert not hasattr(tc, task_c.func_name) diff --git a/construct/tests/test_types.py b/construct/tests/test_types.py deleted file mode 100644 index 87ebb1c..0000000 --- a/construct/tests/test_types.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from construct import types - - -def test_weakset_bound_methods(): - '''Weakset with bound methods''' - - class Object(object): - - def method_a(self): - pass - - def method_b(self): - pass - - weakset = types.weakset() - - a = Object() - b = Object() - - weakset.add(a.method_a) - weakset.add(a.method_b) - weakset.add(b.method_a) - weakset.add(b.method_b) - assert len(weakset) == 4 - - del(b) - assert len(weakset) == 2 - - weakset.add(a.method_a) - weakset.add(a.method_b) - assert len(weakset) == 2 - - del(a) - assert len(weakset) == 0 - - -def test_weakset_functions(): - '''Weakset with functions''' - - weakset = types.weakset() - - def func(): - pass - - weakset.add(func) - assert len(weakset) == 1 - - weakset.add(func) - assert len(weakset) == 1 - - del(func) - assert len(weakset) == 0 diff --git a/construct/tests/utils.py b/construct/tests/utils.py deleted file mode 100644 index e69de29..0000000 diff --git a/construct/types.py b/construct/types.py deleted file mode 100644 index fd60986..0000000 --- a/construct/types.py +++ /dev/null @@ -1,227 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = [ - 'ABC', - 'Namespace', - 'Stack', - 'Priority', - 'STAGE0', - 'STAGE1', - 'STAGE2', - 'STAGE3', - 'STAGE4', - 'STAGE', - 'VALIDATE', - 'REPAIR', - 'COMMIT', - 'INTEGRATE', - 'weakset', - 'weakmeth', -] - -import abc -import inspect -import weakref -from collections import Mapping, deque -from construct.utils import update_dict - -# Use for type checking with isinstance -# Bring this in for api convenience -from construct.models import Entry -Number = (int, float) - -# Alias some six types -import six -Binary = six.binary_type -Integer = six.integer_types -Object = six.class_types -Text = six.text_type -if six.PY2: - String = (str, unicode) -else: - String = str - -# py2-3 compatible ABCMeta -ABC = abc.ABCMeta('ABC', (object,), {}) - - -class Namespace(object): - - def __init__(self, **kwargs): - self.__dict__.update(**kwargs) - - def __repr__(self): - return '{}({})'.format( - self.__class__.__name__, - ', '.join( - [('{}={}').format(k, v) for k, v in self.__dict__.items()] - ) - ) - - def __contains__(self, key): - return key in self.__dict__ - - def __getitem__(self, key): - return self.__dict__.__getitem__(key) - - def __setitem__(self, key, value): - return self.__dict__.__setitem__(key, value) - - def items(self): - return self.__dict__.items() - - def values(self): - return self.__dict__.values() - - def keys(self): - return self.__dict__.keys() - - def update(self, other): - if isinstance(other, Namespace): - update_dict(self.__dict__, other.__dict__) - elif isinstance(other, Mapping): - update_dict(self.__dict__, other) - else: - raise TypeError('Argument must be a Namespace or Mapping instance') - - -class Stack(deque): - - def push(self, item): - self.appendleft(item) - - def size(self): - return len(self) - - def empty(self): - return not self - - def top(self): - return self[-1] - - -# Priority Type and Defaults - - -class Priority(int): - - _instances = {} - - def __new__(cls, value, label=None, description=None): - - key = (value, label, description) - if key in cls._instances: - return cls._instances[key] - - if label is None: - label = 'Priority-' + str(value) - - if description is None: - description = 'Priority ' + str(value) - - key = (value, label, description) - if key in cls._instances: - return cls._instances[key] - - obj = super(Priority, cls).__new__(cls, value) - obj.label = label - obj.description = description - - return cls._instances.setdefault(key, obj) - - -STAGE0 = Priority(0, 'Stage-0', 'First stage') -STAGE1 = Priority(1, 'Stage-1', 'Second stage') -STAGE2 = Priority(2, 'Stage-2', 'Third stage') -STAGE3 = Priority(3, 'Stage-3', 'Fourth stage') -STAGE4 = Priority(4, 'Stage-4', 'Fifth stage') - -STAGE = Priority(0, 'Stage', 'Stage') -VALIDATE = Priority(1, 'Validate', 'Validate') -REPAIR = Priority(2, 'Repair', 'Repair') -COMMIT = Priority(3, 'Commit', 'Commit') -INTEGRATE = Priority(4, 'Integrate', 'Integrate artifacts') - -DEFAULTS = { - (0, None, None): STAGE0, - (1, None, None): STAGE1, - (2, None, None): STAGE2, - (3, None, None): STAGE3, - (4, None, None): STAGE4, -} -Priority._instances.update(DEFAULTS) - - -class weakset(object): - '''Like WeakSet but works with bound methods''' - - def __init__(self): - self._ids = [] - self._refs = [] - - def __repr__(self): - return ( - '<{}>(_ids={}, _refs={})' - ).format( - self.__class__.__name__, - self._ids, - self._refs - ) - - def __len__(self): - return len(self._refs) - - def __iter__(self): - for i in range(len(self._refs)): - id_ = self._ids.pop(0) - ref = self._refs.pop(0) - obj = ref() - if obj is not None: - yield obj - self._refs.append(ref) - self._ids.append(id_) - - def discard(self, obj): - if obj not in self._refs: - return - - index = self._refs.index(obj) - self._ids.pop(index) - self._refs.pop(index) - - def add(self, obj): - id_ = weak_id(obj) - - if id_ in self._ids: - return - - self._ids.append(id_) - - if inspect.ismethod(obj): - self._refs.append(weakmeth(obj, self.discard)) - else: - self._refs.append(weakref.ref(obj, self.discard)) - - -def weak_id(obj): - '''Returns a unique and constant id for an object including bound meths.''' - - if inspect.ismethod(obj): - return id(obj.__self__) + id(obj.__func__) - else: - return id(obj) - - -class weakmeth(object): - '''Like weakref but for bound methods''' - - def __init__(self, meth, callback=None): - self.name = meth.__name__ - self.ref = weakref.ref(meth.__self__, lambda _: callback(self)) - - def __call__(self): - obj = self.ref() - if obj is None: - return - return getattr(obj, self.name, None) diff --git a/construct/utils.py b/construct/utils.py deleted file mode 100644 index 778d374..0000000 --- a/construct/utils.py +++ /dev/null @@ -1,263 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -__all__ = [ - 'ensure_callable', - 'ensure_instance', - 'ensure_type', - 'env_with_default', - 'get_qualname', - 'get_callable_name', - 'path_split', - 'pop_attr', - 'update_dict', - 'unipath', - 'package_path', - 'isolated_imports', - 'import_file', - 'iter_modules', - 'missing', - 'classproperty', - 'dummy_ctxmanager', - 'cached_property' -] - -import inspect -import os -import sys -from collections import Mapping -from fstrings import f -from glob import glob -from contextlib import contextmanager -from construct.compat import basestring - - -platform = sys.platform.rstrip('0123456789').lower() -if platform == 'darwin': - platform = 'mac' -missing = object() - - -def package_path(*paths): - '''Return a path relative to the construct package''' - - return unipath(os.path.dirname(__file__), *paths) - - -def unipath(*paths): - '''os.path.join with forward slashes only.''' - - return os.path.abspath( - os.path.expanduser(os.path.join(*paths)) - ).replace('\\', '/') - - -def get_qualname(obj): - '''Get the qualified name (dotted path) of the object''' - - try: - return obj.__qualname__ - except AttributeError: - pass - - if inspect.ismethod(obj): - if obj.__self__: - cls = obj.__self__.__class__ - else: - cls = obj.im_class - return cls.__name__ + '.' + obj.__name__ - - return get_callable_name(obj) - - -def pop_attr(obj, attr, default): - '''Get the value of an attr then delete it else return default''' - - try: - value = getattr(obj, attr) - delattr(obj, attr) - except AttributeError: - value = default - return value - - -def get_callable_name(obj): - '''Get name of obj object or callable''' - - try: - return obj.func_name - except AttributeError: - return obj.__name__ - except AttributeError: - return obj.__class__.__name__ - except AttributeError: - return obj.__func__.__name__ - - -def path_split(value): - - if isinstance(value, basestring): - return [value] - elif ';' in value or ':' in value: - return value.split(os.pathsep) - - raise EnvironmentError('Can not split: ' + value) - - -def env_with_default(var, default, converter=None): - '''Get an environment variable or a default value.''' - - if var in os.environ: - value = os.environ[var] - else: - value = default - - if converter: - value = converter(value) - - return value - - -def ensure_type(obj, type_, exc_type=TypeError): - if not issubclass(obj, type_): - raise exc_type(f('{obj} must be subclass of {type_}')) - - -def ensure_instance(obj, type_, exc_type=TypeError): - if not isinstance(obj, type_): - raise exc_type(f('{obj} must be instance of {type_}')) - - -def ensure_callable(obj, exc_type=TypeError): - '''Ensures that an obj is callable''' - - if not callable(obj): - raise exc_type('{obj} must be callable') - - -def update_dict(d, u): - '''Update a dict recursively. - - See also: - https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth - ''' - - for k, v in u.items(): - if isinstance(v, Mapping): - dv = d.get(k, {}) - if isinstance(dv, Mapping): - d[k] = update_dict(dv, v) - else: - d[k] = v - else: - d[k] = v - return d - - -@contextmanager -def isolated_imports(path=None, restore=True): - '''Contextmanager that isolates all import''' - - old_path_cache = {k: v for k, v in sys.path_importer_cache.items()} - old_modules = {k: v for k, v in sys.modules.items()} - old_path = sys.path[:] - - try: - - if path: - sys.path.insert(0, path) - yield - - finally: - - sys.path[:] = old_path - - if restore: - for k, v in list(sys.modules.items()): - if k in old_modules: - sys.modules[k] = old_modules[k] - else: - del(sys.modules[k]) - - for k, v in list(sys.path_importer_cache.items()): - if k in old_path_cache: - sys.path_importer_cache[k] = old_path_cache[k] - else: - del(sys.path_importer_cache[k]) - - -def unload_modules(*names): - '''Removes modules and their children from sys.modules - - This is intended for use within the isolated_imports contextmanager. - ''' - - for name in names: - for k, v in list(sys.modules.items()): - if k.startswith(name): - del(sys.modules[k]) - - -def import_file(path, isolated=True): - '''Import python module by absolute path. The imported module is removed - from sys.modules before being returned. This means the same module - imported using import_file multiple times will return different module - objects. I haven't decided on whether this is desirable for our use - case yet, Extension loading. There have been no apparent downsides yet. - ''' - - root, basename = os.path.split(path) - name, _ = os.path.splitext(basename) - - with isolated_imports(root, restore=isolated): - unload_modules(name) - mod = __import__(name, globals(), locals(), []) - - return mod - - -def iter_modules(*paths): - '''Iterate over paths yielding all contained python modules and packages. - - The modules are removed from sys.modules after importing and before - they are yielded. - ''' - - for path in paths: - for py_file in glob(path + '/*.py'): - mod = import_file(py_file, isolated=False) - yield mod - - for py_pkg in glob(path + '/*/__init__.py'): - mod = import_file(os.path.dirname(py_pkg), isolated=False) - yield mod - - -class classproperty(object): - - def __init__(self, fn): - self.fn = fn - - def __get__(self, obj, type): - return self.fn(type) - - -@contextmanager -def dummy_ctxmanager(): - yield None - - -class cached_property(object): - '''Like property but replaces the instance method with the result of the - first call''' - - def __init__(self, method): - self.method = method - - def __get__(self, obj, type): - if obj is None: - return self - - value = self.method(obj) - setattr(obj, self.method.__name__, value) - return value diff --git a/construct/vendor/__init__.py b/construct/vendor/__init__.py deleted file mode 100644 index af1d81c..0000000 --- a/construct/vendor/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -''' -Included 3rd Party packages -=========================== - -Lucidity --------- -Temporarily included until a Python 3 support is added upstream. - - Changed absolute imports to relative - - Fixed all Python 3 errors -''' diff --git a/construct/vendor/lucidity/__init__.py b/construct/vendor/lucidity/__init__.py deleted file mode 100644 index 74d1cfe..0000000 --- a/construct/vendor/lucidity/__init__.py +++ /dev/null @@ -1,128 +0,0 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips -# :license: See LICENSE.txt. - -import os -import uuid -import imp - -from ._version import __version__ -from .template import Template, Resolver -from .error import ParseError, FormatError, NotFound - - -def discover_templates(paths=None, recursive=True): - '''Search *paths* for mount points and load templates from them. - - *paths* should be a list of filesystem paths to search for mount points. - If not specified will try to use value from environment variable - :envvar:`LUCIDITY_TEMPLATE_PATH`. - - A mount point is a Python file that defines a 'register' function. The - function should return a list of instantiated - :py:class:`~lucidity.template.Template` objects. - - If *recursive* is True (the default) then all directories under a path - will also be searched. - - ''' - templates = [] - - if paths is None: - paths = os.environ.get('LUCIDITY_TEMPLATE_PATH', '').split(os.pathsep) - - for path in paths: - for base, directories, filenames in os.walk(path): - for filename in filenames: - _, extension = os.path.splitext(filename) - if extension != '.py': - continue - - module_path = os.path.join(base, filename) - module_name = uuid.uuid4().hex - module = imp.load_source(module_name, module_path) - try: - registered = module.register() - except AttributeError: - pass - else: - if registered: - templates.extend(registered) - - if not recursive: - del directories[:] - - return templates - - -def parse(path, templates): - '''Parse *path* against *templates*. - - *path* should be a string to parse. - - *templates* should be a list of :py:class:`~lucidity.template.Template` - instances in the order that they should be tried. - - Return ``(data, template)`` from first successful parse. - - Raise :py:class:`~lucidity.error.ParseError` if *path* is not - parseable by any of the supplied *templates*. - - ''' - for template in templates: - try: - data = template.parse(path) - except ParseError: - continue - else: - return (data, template) - - raise ParseError( - 'Path {0!r} did not match any of the supplied template patterns.' - .format(path) - ) - - -def format(data, templates): # @ReservedAssignment - '''Format *data* using *templates*. - - *data* should be a dictionary of data to format into a path. - - *templates* should be a list of :py:class:`~lucidity.template.Template` - instances in the order that they should be tried. - - Return ``(path, template)`` from first successful format. - - Raise :py:class:`~lucidity.error.FormatError` if *data* is not - formattable by any of the supplied *templates*. - - - ''' - for template in templates: - try: - path = template.format(data) - except FormatError: - continue - else: - return (path, template) - - raise FormatError( - 'Data {0!r} was not formattable by any of the supplied templates.' - .format(data) - ) - - -def get_template(name, templates): - '''Retrieve a template from *templates* by *name*. - - Raise :py:exc:`~lucidity.error.NotFound` if no matching template with - *name* found in *templates*. - - ''' - for template in templates: - if template.name == name: - return template - - raise NotFound( - '{0} template not found in specified templates.'.format(name) - ) diff --git a/construct/vendor/lucidity/_version.py b/construct/vendor/lucidity/_version.py deleted file mode 100644 index 86d9c8b..0000000 --- a/construct/vendor/lucidity/_version.py +++ /dev/null @@ -1,2 +0,0 @@ -__version__ = '1.5.0' - diff --git a/construct/vendor/lucidity/error.py b/construct/vendor/lucidity/error.py deleted file mode 100644 index 94b575e..0000000 --- a/construct/vendor/lucidity/error.py +++ /dev/null @@ -1,21 +0,0 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips -# :license: See LICENSE.txt. - -'''Custom error classes.''' - - -class ParseError(Exception): - '''Raise when a template is unable to parse a path.''' - - -class FormatError(Exception): - '''Raise when a template is unable to format data into a path.''' - - -class NotFound(Exception): - '''Raise when an item cannot be found.''' - - -class ResolveError(Exception): - '''Raise when a template reference can not be resolved.''' diff --git a/construct/vendor/lucidity/template.py b/construct/vendor/lucidity/template.py deleted file mode 100644 index c2cb759..0000000 --- a/construct/vendor/lucidity/template.py +++ /dev/null @@ -1,327 +0,0 @@ -# :coding: utf-8 -# :copyright: Copyright (c) 2013 Martin Pengelly-Phillips -# :license: See LICENSE.txt. - -import abc -import six -import re -import functools -from collections import defaultdict - -from . import error - -# Type of a RegexObject for isinstance check. -_RegexType = type(re.compile('')) - - -class Template(object): - '''A template.''' - - _STRIP_EXPRESSION_REGEX = re.compile(r'{(.+?)(:(\\}|.)+?)}') - _PLAIN_PLACEHOLDER_REGEX = re.compile(r'{(.+?)}') - _TEMPLATE_REFERENCE_REGEX = re.compile(r'{@(?P.+?)}') - - ANCHOR_START, ANCHOR_END, ANCHOR_BOTH = (1, 2, 3) - - RELAXED, STRICT = (1, 2) - - def __init__(self, name, pattern, anchor=ANCHOR_START, - default_placeholder_expression='[\w_.\-]+', - duplicate_placeholder_mode=RELAXED, - template_resolver=None): - '''Initialise with *name* and *pattern*. - - *anchor* determines how the pattern is anchored during a parse. A - value of :attr:`~Template.ANCHOR_START` (the default) will match the - pattern against the start of a path. :attr:`~Template.ANCHOR_END` will - match against the end of a path. To anchor at both the start and end - (a full path match) use :attr:`~Template.ANCHOR_BOTH`. Finally, - ``None`` will try to match the pattern once anywhere in the path. - - *duplicate_placeholder_mode* determines how duplicate placeholders will - be handled during parsing. :attr:`~Template.RELAXED` mode extracts the - last matching value without checking the other values. - :attr:`~Template.STRICT` mode ensures that all duplicate placeholders - extract the same value and raises :exc:`~lucidity.error.ParseError` if - they do not. - - If *template_resolver* is supplied, use it to resolve any template - references in the *pattern* during operations. It should conform to the - :class:`Resolver` interface. It can be changed at any time on the - instance to affect future operations. - - ''' - super(Template, self).__init__() - self.duplicate_placeholder_mode = duplicate_placeholder_mode - self.template_resolver = template_resolver - - self._default_placeholder_expression = default_placeholder_expression - self._period_code = '_LPD_' - self._at_code = '_WXV_' - self._name = name - self._pattern = pattern - self._anchor = anchor - - # Check that supplied pattern is valid and able to be compiled. - self._construct_regular_expression(self.pattern) - - def __repr__(self): - '''Return unambiguous representation of template.''' - return '{0}(name={1!r}, pattern={2!r})'.format( - self.__class__.__name__, self.name, self.pattern - ) - - @property - def name(self): - '''Return name of template.''' - return self._name - - @property - def pattern(self): - '''Return template pattern.''' - return self._pattern - - def expanded_pattern(self): - '''Return pattern with all referenced templates expanded recursively. - - Raise :exc:`lucidity.error.ResolveError` if pattern contains a reference - that cannot be resolved by currently set template_resolver. - - ''' - return self._TEMPLATE_REFERENCE_REGEX.sub( - self._expand_reference, self.pattern - ) - - def _expand_reference(self, match): - '''Expand reference represented by *match*.''' - reference = match.group('reference') - - if self.template_resolver is None: - raise lucidity.error.ResolveError( - 'Failed to resolve reference {0!r} as no template resolver set.' - .format(reference) - ) - - template = self.template_resolver.get(reference) - if template is None: - raise lucidity.error.ResolveError( - 'Failed to resolve reference {0!r} using template resolver.' - .format(reference) - ) - - return template.expanded_pattern() - - def parse(self, path): - '''Return dictionary of data extracted from *path* using this template. - - Raise :py:class:`~lucidity.error.ParseError` if *path* is not - parsable by this template. - - ''' - # Construct regular expression for expanded pattern. - regex = self._construct_regular_expression(self.expanded_pattern()) - - # Parse. - parsed = {} - - match = regex.search(path) - if match: - data = {} - for key, value in sorted(match.groupdict().items()): - # Strip number that was added to make group name unique. - key = key[:-3] - - # If strict mode enabled for duplicate placeholders, ensure that - # all duplicate placeholders extract the same value. - if self.duplicate_placeholder_mode == self.STRICT: - if key in parsed: - if parsed[key] != value: - raise error.ParseError( - 'Different extracted values for placeholder ' - '{0!r} detected. Values were {1!r} and {2!r}.' - .format(key, parsed[key], value) - ) - else: - parsed[key] = value - - # Expand dot notation keys into nested dictionaries. - target = data - - parts = key.split(self._period_code) - for part in parts[:-1]: - target = target.setdefault(part, {}) - - target[parts[-1]] = value - - return data - - else: - raise error.ParseError( - 'Path {0!r} did not match template pattern.'.format(path) - ) - - def format(self, data): - '''Return a path formatted by applying *data* to this template. - - Raise :py:class:`~lucidity.error.FormatError` if *data* does not - supply enough information to fill the template fields. - - ''' - - format_specification = self._construct_format_specification( - self.expanded_pattern() - ) - - return self._PLAIN_PLACEHOLDER_REGEX.sub( - functools.partial(self._format, data=data), - format_specification - ) - - def _format(self, match, data): - '''Return value from data for *match*.''' - placeholder = match.group(1) - parts = placeholder.split('.') - - try: - value = data - for part in parts: - value = value[part] - - except (TypeError, KeyError): - raise error.FormatError( - 'Could not format data {0!r} due to missing key {1!r}.' - .format(data, placeholder) - ) - - else: - return value - - def keys(self): - '''Return unique set of placeholders in pattern.''' - format_specification = self._construct_format_specification( - self.expanded_pattern() - ) - return set(self._PLAIN_PLACEHOLDER_REGEX.findall(format_specification)) - - def references(self): - '''Return unique set of referenced templates in pattern.''' - format_specification = self._construct_format_specification( - self.pattern - ) - return set(self._TEMPLATE_REFERENCE_REGEX.findall(format_specification)) - - def _construct_format_specification(self, pattern): - '''Return format specification from *pattern*.''' - return self._STRIP_EXPRESSION_REGEX.sub('{\g<1>}', pattern) - - def _construct_regular_expression(self, pattern): - '''Return a regular expression to represent *pattern*.''' - # Escape non-placeholder components. - expression = re.sub( - r'(?P{(.+?)(:(\\}|.)+?)?})|(?P.+?)', - self._escape, - pattern - ) - - # Replace placeholders with regex pattern. - expression = re.sub( - r'{(?P.+?)(:(?P(\\}|.)+?))?}', - functools.partial( - self._convert, placeholder_count=defaultdict(int) - ), - expression - ) - - if self._anchor is not None: - if bool(self._anchor & self.ANCHOR_START): - expression = '^{0}'.format(expression) - - if bool(self._anchor & self.ANCHOR_END): - expression = '{0}$'.format(expression) - - # Compile expression. - try: - compiled = re.compile(expression) - except re.error as error: - if any([ - 'bad group name' in str(error), - 'bad character in group name' in str(error) - ]): - message = 'Placeholder name contains invalid characters.' - else: - message = 'Invalid pattern: ' + str(error) - - six.raise_from(ValueError(message), error) - - return compiled - - def _convert(self, match, placeholder_count): - '''Return a regular expression to represent *match*. - - *placeholder_count* should be a `defaultdict(int)` that will be used to - store counts of unique placeholder names. - - ''' - placeholder_name = match.group('placeholder') - - # Support at symbol (@) as referenced template indicator. Currently, - # this symbol not a valid character for a group name in the standard - # Python regex library. Rather than rewrite or monkey patch the library - # work around the restriction with a unique identifier. - placeholder_name = placeholder_name.replace('@', self._at_code) - - # Support period (.) as nested key indicator. Currently, a period is - # not a valid character for a group name in the standard Python regex - # library. Rather than rewrite or monkey patch the library work around - # the restriction with a unique identifier. - placeholder_name = placeholder_name.replace('.', self._period_code) - - # The re module does not support duplicate group names. To support - # duplicate placeholder names in templates add a unique count to the - # regular expression group name and strip it later during parse. - placeholder_count[placeholder_name] += 1 - placeholder_name += '{0:03d}'.format( - placeholder_count[placeholder_name] - ) - - expression = match.group('expression') - if expression is None: - expression = self._default_placeholder_expression - - # Un-escape potentially escaped characters in expression. - expression = expression.replace('\{', '{').replace('\}', '}') - - return r'(?P<{0}>{1})'.format(placeholder_name, expression) - - def _escape(self, match): - '''Escape matched 'other' group value.''' - groups = match.groupdict() - if groups['other'] is not None: - return re.escape(groups['other']) - - return groups['placeholder'] - - -# Python 2 and 3 compatible ABCMeta -ABC = abc.ABCMeta('ABC', (object,), {}) - - -class Resolver(ABC): - '''Template resolver interface.''' - - @abc.abstractmethod - def get(self, template_name, default=None): - '''Return template that matches *template_name*. - - If no template matches then return *default*. - - ''' - return default - - @classmethod - def __subclasshook__(cls, subclass): - '''Return whether *subclass* fulfils this interface.''' - if cls is Resolver: - return callable(getattr(subclass, 'get', None)) - - return NotImplemented diff --git a/docs/api.rst b/docs/api.rst index 3f5a21e..a07433b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,137 +1,3 @@ +================= API Documentation ================= - -.. automodule:: construct.api - :members: - :undoc-members: - :show-inheritance: - - -construct\.action module ------------------------- - -.. automodule:: construct.action - :members: - :undoc-members: - :show-inheritance: - -construct\.actioncontext module ---------------------------- - -.. automodule:: construct.actioncontext - :members: - :undoc-members: - :show-inheritance: - -construct\.actionparams module ------------------------------- - -.. automodule:: construct.actionparams - :members: - :undoc-members: - :show-inheritance: - -construct\.actionrunner module ----------------------------- - -.. automodule:: construct.actionrunner - :members: - :undoc-members: - :show-inheritance: - - -construct\.compat module ------------------------- - -.. automodule:: construct.compat - :members: - :undoc-members: - :show-inheritance: - -construct\.config module ---------------------------- - -.. automodule:: construct.config - :members: - :undoc-members: - :show-inheritance: - -construct\.constants module ---------------------------- - -.. automodule:: construct.constants - :members: - :undoc-members: - :show-inheritance: - -construct\.context module -------------------------- - -.. automodule:: construct.context - :members: - :undoc-members: - :show-inheritance: - -construct\.errors module ---------------------- - -.. automodule:: construct.errors - :members: - :undoc-members: - :show-inheritance: - -construct\.extension module -------------------------- - -.. automodule:: construct.extension - :members: - :undoc-members: - :show-inheritance: - -construct\.models module ------------------------- - -.. automodule:: construct.models - :members: - :undoc-members: - :show-inheritance: - -construct\.signals module ------------------------- - -.. automodule:: construct.signals - :members: - :undoc-members: - :show-inheritance: - -construct\.stats module ------------------------- - -.. automodule:: construct.stats - :members: - :undoc-members: - :show-inheritance: - -construct\.tasks module ------------------------ - -.. automodule:: construct.tasks - :members: - :undoc-members: - :show-inheritance: - -construct\.types module ------------------------ - -.. automodule:: construct.types - :members: - :undoc-members: - :show-inheritance: - -construct\.utils module ----------------------- - -.. automodule:: construct.utils - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/cli.rst b/docs/cli.rst deleted file mode 100644 index 7456a44..0000000 --- a/docs/cli.rst +++ /dev/null @@ -1,107 +0,0 @@ -====================== -Command Line Interface -====================== - -.. code-block:: console - - $ construct - Usage: construct [options] - - Current Context - host cli - root ~/projects - user dbradham - platform win - - Commands - version Version information - home Go to root directory - pop Go back to last location - push Go to first search result - search Search for Entries - read Read metadata - write Write metadata - tag Tag a directory - untag Untag a directory - - Actions - new.project Create a new Project - - Options: - -h, --help show this help message and exit (default: False) - -v, --verbose verbose output (default: False) - - -Anatomy of the CLI -================== - -Current Context - Describes the context of your current working directory. - -Commands - These are universal commands that are always present. They provide core functionality like navigation, searching, reading and writing data, tagging and untagging directories, and version info about Construct. - -Actions - Actions are invoked the same way commands are, but, they differ in a few ways. Actions are contextual, so different Actions will be available depending on your current context. For example, the new.project Action is available when you are not in a project. When you are in a project, you'll have actions for creating assets, shots, tasks and launching application. - - Actions are also typically composed of several tasks that can have a complicated flow of execution. In turn, when called from the CLI detailed info on the execution of the Actions tasks is provided including any artifacts that were created. An artifact is something produced by an Action. For example, the new.project action creates a new folder on your file system containing some metadata. - -Options - Options are data you provide to a command or action. Actions frequently require options to produce the results you want. For example, the new.project Action requires a --root option that specifies the directory where the project should be created. The --help option is universal, you can provide it whenever you need help using the CLI. - -Navigation Commands -=================== -The construct cli has 4 main commands to help you navigate your projects. - -home - Navigates to your context's root directory. Usually where your projects - are located. -push - Navigates to the first result in a search by name or tag -pop - Navigates -search - Finds Entries by name or tag. - - -Create a new Project -==================== -Construct has several builtin actions that can be used to create and manage your projects. Let's use the new.project, new.asset, and new.task Actions to build a new project. By the end of this section we will have created a new project named "my_project" with an asset named "coffee_cup" that contains a model task. - -First we'll start by going to our construct root directory. - -.. code-block:: console - - $ construct home - ~/projects$ - -Now we can use new.project to create our project. - -.. code-block:: console - - ~/projects$ construct new.project --root my_project - ... - Atrifacts - project ~/projects/my_project - -You should see a nice and detailed report ending with an Artifacts section showing us the the new project that was created. Now let's dive into my_project and create our asset and tasks. - -.. code-block:: console - - ~/projects$ construct search my_project - ~/projects/my_project - - ~/projects$ construct push my_project - ~/projects/my_project$ construct new.asset --name coffee_cup --asset_type prop - ... - Artifacts - asset ~/projects/my_project/assets/prop/coffee_cup - - ~/projects/my_project$ construct push cup - ~/projects/my_project/assets/prop/coffee_cup$ construct new.task --type model - ... - Artifacts - task ~/projects/my_project/assets/prop/coffee_cup/model - - ~/projects/my_project/assets/prop/coffee_cup$ construct push model - ~/projects/my_project/assets/prop/coffee_cup/model$ diff --git a/docs/concepts.rst b/docs/concepts.rst index 5e66e3a..64c3b93 100644 --- a/docs/concepts.rst +++ b/docs/concepts.rst @@ -1,127 +1,3 @@ ======== Concepts ======== - -Context -======= -Construct has a strong sense of Context. The current Context is used to -determine which Extensions, Actions, Tasks and Templates are available. - -:host: Host application cli, maya, nuke, etc... -:platform: win, linux, or mac -:root: Directory containing your projects -:user: Username -:project: Project Entry -:sequence: Sequence Entry - contains shots -:shot: Shot Entry - contains tasks -:asset_type: AssetType Entry - contains assets -:asset: Asset Entry - contains tasks -:task: Task Entry - where publishes and workspaces are stored -:workspace: Workspace Entry - where work files are stored -:file: Current workfile file - - -All Extensions, Actions, and Tasks implement an :meth:`available` method that -takes a Context object and returns True if available in that Context. - - -Extensions -========== -Extensions are the basic building blocks of Construct. They provide Actions, -Tasks, and Templates. All builtin actions and templates are provided by the -Builtins Extension. Extensions can provide Tasks to extend existing Actions or -provide their own Actions. - - -.. code-block:: python - - from construct import Extension - - class MyExtension(Extension): - - name = 'My Extension' - attr_name = 'my_extension' - - def load(self): - # setup extension - # add Actions, Tasks, and template paths - pass - - def available(self, ctx): - # Return True if Extension is available in ctx - return True - - -Actions -======= -Actions are interfaces to run a set of Tasks ordered by their respective -priorities and requirements. Let's take a look at the :class:`NewProject` Action provided by the Builtins Extension. - - -.. code-block:: python - - import construct - from construct import Action - - class NewProject(Action): - '''Create a new Project''' - - label = 'New Project' - identifier = 'new.project' - - @staticmethod - def parameters(ctx): - params = dict( - root={ - 'label': 'Project Root', - 'required': True, - 'type': types.String, - 'help': 'project root directory', - }, - template={ - 'label': 'Project Template', - 'required': True, - 'type': types.String, - 'help': 'name of a project template', - } - ) - - if not ctx: - return params - - templates = list(construct.get_templates('project').keys()) - params['template']['options'] = templates - if templates: - params['template']['default'] = templates[0] - - return params - - @staticmethod - def available(ctx): - return not ctx.project - - -Tasks -===== -Tasks are decorated python functions that are added to an Action and ordered by -their respective priorities and requirements. - -.. code-block:: python - - from construct.tasks import task, requires, success, returns, artifact - - @task - @requires(success('other_task')) - @returns(artifact('message')) - def some_task(): - return 'other_task completed successfully' - -This task is useless, but, it shows us a few neat things about Tasks. Our task -'some_task' requires the success of 'other_task' and returns an artifact named -'message'. Construct provides a rich set of decorators and functions to help -you setup the flow of your Actions. Tasks also remain callable after being -decorated, so you can run them on their own which is nice for testing. - -.. code-block:: python - - assert some_task() == 'other_task completed successfully' diff --git a/docs/configuration.rst b/docs/configuration.rst index bd85fa6..e5cda69 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1,149 +1,3 @@ ============= Configuration ============= - -Construct uses yaml files for configuration. Here is the default configuration, -construct/defaults/construct.yaml. You can specify your own configuration by -creating your own yaml file, and pointing your CONSTRUCT_CONFIG environment -variable to it. - - -.. code-block:: yaml - - ROOT: '~/projects' - EXTENSION_PATHS: - - '~/.construct' - EVENT_STREAM_DB: '~/.construct/eventstream.sqlite' - DISABLE_EXTENSIONS: [] - - LOGGING: - version: 1 - formatters: - simple: - format: '%(levelname).1s:%(name)s> %(message)s' - handlers: - console: - formatter: simple - class: logging.StreamHandler - loggers: - construct: - level: DEBUG - handlers: [console] - propagate: False - - - STATUSES: - - 'waiting' - - 'in progress' - - 'rendering' - - 'pending' - - 'rejected' - - 'complete' - - - TASK_TYPES: - - model - - rig - - shade - - texture - - comp - - light - - layout - - - PATH_TEMPLATES: - asset_type: '{project}/assets/{asset_type}' - asset: '{project}/assets/{asset_type}/{asset}' - sequence: '{project}/sequences/{sequence}' - shot: '{project}/sequences/{sequence}/{shot}' - task: '{parent}/{task}' - publish: '{task}/publish/{file_type}/{task}_{name}/v{:0>3d}' - publish_file: '{task}_{name}_v{:0>3d}.{ext}' - workspace: '{task}/work/{workspace}' - workspace_file: '{task}_{name}_v{:0>3d}.{ext}' - - - SOFTWARE: - maya2017: - host: 'maya' - workspace: 'maya' - identifier: 'launch.maya2017' - description: 'Launch Autodesk Maya2017' - default_workspace: 'maya' - icon: './icons/maya.png' - cmd: - win: - path: C:/Program Files/Autodesk/Maya2017/bin/maya.exe - args: [] - linux: - path: /usr/autodesk/maya2017/bin/maya - args: [] - mac: - path: /Applications/Autodesk/maya2017/bin/maya - args: [] - maya2018: - host: 'maya' - identifier: 'launch.maya2018' - description: 'Launch Autodesk Maya2018' - default_workspace: 'maya' - icon: './icons/maya.png' - extensions: ['.ma', '.mb', '.fbx', '.obj', '.abc'] - cmd: - win: - path: C:/Program Files/Autodesk/Maya2018/bin/maya.exe - args: [] - linux: - path: /usr/autodesk/maya2018/bin/maya - args: [] - mac: - path: /Applications/Autodesk/maya2018/bin/maya - args: [] - nuke11.1v2: - host: 'nuke' - identifier: 'launch.nuke11.1v2' - description: 'Launch The Foundry Nuke11.1v2' - default_workspace: 'nuke' - icon: './icons/nuke.png' - extensions: ['.nk'] - cmd: - win: - path: C:/Program Files/Nuke11.1v2/Nuke11.1.exe - args: [] - linux: - path: /usr/local/Nuke11.1v2/Nuke11.1 - args: [] - mac: - path: /Applications/Nuke11.1v2/Nuke11.1v2.app/Nuke11.1 - args: [] - sublime: - host: 'sublime' - identifier: 'launch.sublimetext' - description: 'Launch Sublimt Text 3' - default_workspace: null - icon: './icons/sublime.png' - extensions: ['.txt', '.py', '.mel'] - cmd: - win: - path: C:/Program Files/Sublime Text 3//subl.exe - args: [] - linux: - path: /usr/bin/subl - args: [] - mac: - path: /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl - args: [] - - - FILE_TYPES: - alembic: - extensions: ['.abc'] - maya: - extensions: ['.ma', '.mb'] - nuke: - extensions: ['.nk'] - c4d: - extensions: ['.c4d'] - photoshop: - extensions: ['.psd', '.psb'] - illustrator: - extensions: ['.ai'] diff --git a/docs/doctrees/api.doctree b/docs/doctrees/api.doctree deleted file mode 100644 index 4b9eb22bb3ceb7a54eaee13022759db0d0056484..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 738256 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
    2`6|5KUT0&JY=3!9EmQ_*Pj8EP7eKus5j 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

    yOtM@MmG(~imdE%&2!>lFQG;E42J5yuh9 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 diff --git a/docs/doctrees/cli.doctree b/docs/doctrees/cli.doctree deleted file mode 100644 index 79dd9158e4b6a28d0eee06aeeacbeb0544ec9539..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17001 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 diff --git a/docs/doctrees/concepts.doctree b/docs/doctrees/concepts.doctree deleted file mode 100644 index 76b9e5aaade7a0d1cf8becf7f12f30f09fee3928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17533 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 UIYWp)B0&$q2aYo0w=tFfI|dw9F8}}l diff --git a/docs/doctrees/configuration.doctree b/docs/doctrees/configuration.doctree deleted file mode 100644 index 530144c8d1bf407cdac816c20bf0c7ea3a7c9257..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10609 zcmeGi>wnb7(SaD-2N;u(HzZ0^YSX|815N0)r8UL|YB*DTn~;X9x+m?O6ze37cI8Li zOOv)~ao_Lv`~7~me_KCxW+k2O4nz61&WHYLK49JM?9A-Ec4u~8+hseUohZ+jcH+PHWu)9vxWD=!RSPgSymGHFQV zDZ~~AmLr-HMmjLc#v>j}VYP*nyoD&n>QG|ib`r-(#*$$Y+XD7hS;-N0nBm)3We@N~ zh0QQjL8yc!6^|9{U$~Er5Hp@5VY~we+&yf6KPMBCjoPl~JBS?)*5QG{4N74XgC%$n z_jjlR58?h|zMApzFdJffkYQvE9&vZEJ=yr9H)e3tvz43I;_wy3Aa6$N$_;~?NL{Kp zv6U6Zt>V{Bl%u4dg^K?6XERVRy zk_Jy2gP2fNycP4vEx~Ez{7eVlL7e}Sl$C47NKkUdI&b?%)ARAO8OBDxYK%)F7KSGc z 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

    @@ -680,6 +681,189 @@

    EventManager +

    Settings

    +
    +
    +class construct.settings.Settings(path)
    +

    Bases: dict

    +

    Provides dict access to your settings file.

    +
    +
    +path
    +

    construct.path.Path used to locate settings

    +
    + +
    +
    +file
    +

    Path to construct.yaml file

    +
    + +
    +
    +folder
    +

    Path containing construct.yaml file

    +
    + +
    +
    +delete_software(name)
    +

    Delete a software configuration

    + +++ + + + +
    Parameters:name (str) – The name of the software like “maya2019”
    +
    + +
    +
    +save()
    +

    Save these settings.

    +
    + +
    +
    +save_software(name, **data)
    +

    Save a new software configuration.

    + +++ + + + +
    Parameters:
      +
    • name (str) – The name of the software like “maya2019”
    • +
    • **data – Software settings must match the settings.yaml schema
    • +
    +
    +
    + +
    +
    +yaml(exclude=None)
    +

    Serialize these settings as yaml.

    +
    + +
    + +
    +
    +construct.settings.find_in_paths(paths, resource)
    +

    Find a resource in a list of paths

    +
    + +
    +
    +construct.settings.restore_default_settings(where)
    +

    Create a default configuration in the provided folder.

    +
    + + +
    +

    Path

    +
    +
    +class construct.path.Path(path=None)
    +

    Bases: list

    +

    A list of folders used to lookup resources like settings, icons, and +extensions.

    +
    +
    +find(resource)
    +

    Find a resource in one of this objects paths.

    +

    Examples

    +
    >>> path = Path(['~/.construct'])
    +>>> path.find('construct.yaml')
    +'~/.construct/construct.yaml'
    +
    +
    +
    + +
    + +
    +
    +

    Utils

    +
    +
    +construct.utils.import_file(path, isolated=True)
    +

    Import python module by absolute path. The imported module is removed +from sys.modules before being returned. This means the same module +imported using import_file multiple times will return different module +objects. I haven’t decided on whether this is desirable for our use +case yet, Extension loading. There have been no apparent downsides yet.

    +
    + +
    +
    +construct.utils.isolated_imports(path=None, restore=True)
    +

    Contextmanager that isolates all import

    +
    + +
    +
    +construct.utils.iter_modules(*paths)
    +

    Iterate over paths yielding all contained python modules and packages.

    +

    The modules are removed from sys.modules after importing and before +they are yielded.

    +
    + +
    +
    +construct.utils.unload_modules(*names)
    +

    Removes modules and their children from sys.modules

    +

    This is intended for use within the isolated_imports contextmanager.

    +
    + +
    +
    +construct.utils.update_env(d, **values)
    +

    Updates an environment dict with the specified values. List values +are combined with the existing values in d. String values override +values in d.

    +

    Example

    +
    >>> import os
    +>>> env = os.environ.copy()
    +>>> update_env(env, PATH=['some_path'], VAR='value')
    +
    +
    + +++ + + + +
    Parameters:
      +
    • d (dict) – Environment dict to update
    • +
    • **values – Values to update d with
    • +
    +
    +
    + +
    +
    +construct.utils.update_envvar(d, k, v)
    +

    Used by update_env to update a single key with the specified value.

    +
    + +
    +
    +

    Errors

    +
    +
    +exception construct.errors.InvalidSettings
    +

    Bases: Exception

    +

    Raise when your settings are invalid.

    +
    +

    Extension Types

    @@ -911,188 +1095,8 @@

    ExtensionManager -

    Settings

    -
    -
    -class construct.settings.Settings(path)
    -

    Bases: dict

    -

    Provides dict access to your settings file.

    -
    -
    -path
    -

    construct.path.Path used to locate settings

    -
    - -
    -
    -file
    -

    Path to construct.yaml file

    -
    - -
    -
    -folder
    -

    Path containing construct.yaml file

    -
    - -
    -
    -delete_software(name)
    -

    Delete a software configuration

    - --- - - - -
    Parameters:name (str) – The name of the software like “maya2019”
    -
    - -
    -
    -save()
    -

    Save these settings.

    -
    - -
    -
    -save_software(name, **data)
    -

    Save a new software configuration.

    - --- - - - -
    Parameters:
      -
    • name (str) – The name of the software like “maya2019”
    • -
    • **data – Software settings must match the settings.yaml schema
    • -
    -
    -
    - -
    -
    -yaml(exclude=None)
    -

    Serialize these settings as yaml.

    -
    - -
    - -
    -
    -construct.settings.find_in_paths(paths, resource)
    -

    Find a resource in a list of paths

    -
    - -
    -
    -construct.settings.restore_default_settings(where)
    -

    Create a default configuration in the provided folder.

    -
    - -

    -
    -

    Path

    -
    -
    -class construct.path.Path(path=None)
    -

    Bases: list

    -

    A list of folders used to lookup resources like settings, icons, and -extensions.

    -
    -
    -find(resource)
    -

    Find a resource in one of this objects paths.

    -

    Examples

    -
    >>> path = Path(['~/.construct'])
    ->>> path.find('construct.yaml')
    -'~/.construct/construct.yaml'
    -
    -
    -
    - -
    - -
    -
    -

    utils

    -
    -
    -construct.utils.import_file(path, isolated=True)
    -

    Import python module by absolute path. The imported module is removed -from sys.modules before being returned. This means the same module -imported using import_file multiple times will return different module -objects. I haven’t decided on whether this is desirable for our use -case yet, Extension loading. There have been no apparent downsides yet.

    -
    - -
    -
    -construct.utils.isolated_imports(path=None, restore=True)
    -

    Contextmanager that isolates all import

    -
    - -
    -
    -construct.utils.iter_modules(*paths)
    -

    Iterate over paths yielding all contained python modules and packages.

    -

    The modules are removed from sys.modules after importing and before -they are yielded.

    -
    - -
    -
    -construct.utils.unload_modules(*names)
    -

    Removes modules and their children from sys.modules

    -

    This is intended for use within the isolated_imports contextmanager.

    -
    - -
    -
    -construct.utils.update_env(d, **values)
    -

    Updates an environment dict with the specified values. List values -are combined with the existing values in d. String values override -values in d.

    -

    Example

    -
    >>> import os
    ->>> env = os.environ.copy()
    ->>> update_env(env, PATH=['some_path'], VAR='value')
    -
    -
    - --- - - - -
    Parameters:
      -
    • d (dict) – Environment dict to update
    • -
    • **values – Values to update d with
    • -
    -
    -
    - -
    -
    -construct.utils.update_envvar(d, k, v)
    -

    Used by update_env to update a single key with the specified value.

    -
    - -
    -
    -

    errors

    -
    -
    -exception construct.errors.InvalidSettings
    -

    Bases: Exception

    -

    Raise when your settings are invalid.

    -
    - +
    +

    Builtin Extensions

    diff --git a/docs/html/configuration.html b/docs/html/configuration.html index 97a4f55..d8cc582 100644 --- a/docs/html/configuration.html +++ b/docs/html/configuration.html @@ -88,7 +88,37 @@
  1. Concepts
  2. API Guide
  3. Environment Variables
  4. -
  5. Configuration
  6. +
  7. Configuration +
  8. API Documentation
  9. @@ -156,6 +186,193 @@

    Configuration

    +

    When starting up Construct will look for a construct.yaml file in the following locations:

    +
      +
    1. Paths stored in the CONSTRUCT_PATH environment variable
    2. +
    3. Your USER_PATH ~/.construct
    4. +
    +

    The first construct.yaml that is found becomes your settings folder. If no construct.yaml file is found a default settings structure is created in the last path checked (default: ~/.construct). The settings folder contains user icons, extensions, templates and software configurations.

    +

    To share a configuration accross your stuiod set CONSTRUCT_PATH on your workstations to a network folder. You can also override the default lookup behavior by providing your own list of paths when initializing the Construct API.

    +
    import construct
    +api = construct.API(path=['~/.custom_path'])
    +
    +
    +
    +

    Note

    +

    Settings files are validated prior to being loaded. This prevents Construct from running with faulty settings.

    +
    +
    +

    Settings folder structure

    +
    extensions/
    +icons/
    +templates/
    +software/
    +construct.yaml
    +
    +
    +
    +

    extensions folder

    +

    Python modules and packages containing extensions folders. This is the location where Construct discovers and loads Extensions.

    +
    +
    +

    icons folder

    +

    One of the locations where Construct will look for icons. When configuring software you can place custom +icons here and reference them using icons/myicon.png.

    +
    +
    +

    templates folder

    +

    Template files like a maya workspace.mel. These templates can be used in software configurations.

    +
    +
    +

    software folder

    +

    Individual software configuration files.

    +
    +
    +

    construct.yaml

    +

    The main settings file. By default it looks like this.

    +
    locations:
    +    local:
    +        lib: ~/lib
    +        projects: ~/projects
    +my_location: local
    +my_mount: projects
    +extensions: []
    +logging:
    +    version: 1
    +    formatters:
    +        simple:
    +        format: '%(levelname).1s:%(name)s> %(message)s'
    +    handlers:
    +        console:
    +            class: logging.StreamHandler
    +            formatter: simple
    +    loggers:
    +        construct:
    +            handlers:
    +                - console
    +            level: DEBUG
    +
    +
    +
    +
    +
    +

    Builtin Settings

    +

    The builtin settings that must be included in your construct.yaml file.

    +
    +

    locations

    +

    A dictionary containing locations and file system mounts. These are used to lookup file system paths to projects, folders, assets, and files. The default locations value supports a simple local setup.

    +
    locations:
    +    local:
    +        lib: ~/lib
    +        projects: ~/projects
    +
    +
    +

    For a multisite solution you can define multiple locations and OS specific mounts.

    +
    locations:
    +    NY:
    +        lib:
    +            win: //ny-server/lib
    +            mac: /Volumes/ny-server/lib
    +            linux: /mnt/ny-server/lib
    +        projects:
    +            win: //ny-server/projects
    +            mac: /Volumes/ny-server/projects
    +            linux: /mnt/ny-server/projects
    +    LA:
    +        lib:
    +            win: //la-server/lib
    +            mac: /Volumes/la-server/lib
    +            linux: /mnt/la-server/lib
    +        projects:
    +            win: //la-server/projects
    +            mac: /Volumes/la-server/projects
    +            linux: /mnt/la-server/projects
    +
    +
    +
    +
    +

    my_location

    +

    The default location that Construct will use.

    +

    Default: local

    +
    +
    +

    my_mount

    +

    The default mount that Construct will use.

    +

    Default: projects

    +
    +
    +

    extensions

    +

    A list of extensions to import and load when Construct starts. This is where you would list extensions +installed using pip.

    +
    +

    Note

    +

    Builtin Extensions and Extensions located in your settings extensions folder will be loaded automatically, they do not need to be included here.

    +
    +

    default: []

    +
    +
    +

    logging

    +

    A dictConfig used to configure logging.

    +
    +
    +
    +

    Software Settings

    +

    Software is configured in individual yaml files stored in the software settings folder. Let’s take a look at how we would configure Autodesk Maya2019.

    +

    software/maya2019.yaml:

    +
    label: Autodesk Maya 2019
    +icon: icons/maya.png
    +host: maya
    +cmd:
    +    linux: /usr/Autodesk/Maya2019/bin/maya
    +    mac: /applications/Autodesk/Maya2019.app/bin/maya.exe
    +    win: C:/Program Files/Autodesk/Maya2019/bin/maya.exe
    +args: []
    +files:
    +    templates/workspace.mel: workspace.mel
    +folders:
    +    - data
    +    - cache
    +extensions: ['.ma', '.mb', '.fbx', '.abc', '.obj']
    +env: {}
    +
    +
    +
    +

    label

    +

    The nice name for the software.

    +
    +
    +

    icon

    +

    The path to an icon relative to your settings folder.

    +
    +
    +

    host

    +

    The identifier of a Host extension that handles this software. Usually you will have to write your own Host extensions to support any meaningful integration with a piece of software.

    +
    +
    +

    cmd

    +

    OS specific paths to the software executables.

    +
    +
    +

    args

    +

    Args to pass to the software executable.

    +
    +
    +

    files

    +

    Files to copy to the workspace root when created. Each key is the path to a template file and it’s accompanying value is the path relative to the workspace root where it will be copied to.

    +
    +
    +

    folders

    +

    A list of folders to create in the workspace root.

    +
    +
    +

    extensions

    +

    A list of extensions that this software is able to open.

    +
    +
    +

    env

    +

    Environment variables to set when launching this software. When values are lists they will be prepended to your current Environment variables value. When values are strings they will override an existing Environment variable value.

    +
    +
    diff --git a/docs/html/index.html b/docs/html/index.html index e7f15af..60d91cd 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -189,19 +189,25 @@

    ContributeConcepts
  10. API Guide
  11. Environment Variables
  12. -
  13. Configuration
  14. +
  15. Configuration +
  16. API Documentation
  17. diff --git a/docs/html/searchindex.js b/docs/html/searchindex.js index 3b32fe0..9b55b07 100644 --- a/docs/html/searchindex.js +++ b/docs/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["api","concepts","configuration","environment","guide","index"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,sphinx:55},filenames:["api.rst","concepts.rst","configuration.rst","environment.rst","guide.rst","index.rst"],objects:{"construct.api":{API:[0,1,1,""]},"construct.api.API":{context:[0,2,1,""],define:[0,3,1,""],extend:[0,3,1,""],extensions:[0,2,1,""],get_mount:[0,3,1,""],host:[0,2,1,""],name:[0,2,1,""],off:[0,3,1,""],on:[0,3,1,""],path:[0,2,1,""],schemas:[0,2,1,""],send:[0,3,1,""],settings:[0,2,1,""],undefine:[0,3,1,""],unextend:[0,3,1,""]},"construct.context":{Context:[0,1,1,""]},"construct.context.Context":{copy:[0,3,1,""],load:[0,3,1,""],store:[0,3,1,""],to_envvars:[0,3,1,""],unload:[0,3,1,""]},"construct.errors":{InvalidSettings:[0,5,1,""]},"construct.events":{EventManager:[0,1,1,""]},"construct.events.EventManager":{clear:[0,3,1,""],define:[0,3,1,""],handlers:[0,3,1,""],off:[0,3,1,""],on:[0,3,1,""],send:[0,3,1,""],undefine:[0,3,1,""]},"construct.extensions":{Extension:[0,1,1,""],ExtensionManager:[0,1,1,""],Host:[0,1,1,""]},"construct.extensions.Extension":{is_available:[0,3,1,""],load:[0,3,1,""],setup:[0,3,1,""],unload:[0,3,1,""]},"construct.extensions.ExtensionManager":{discover:[0,3,1,""],get_available:[0,3,1,""],load:[0,3,1,""],loaded:[0,3,1,""],ls:[0,3,1,""],register:[0,3,1,""],unload:[0,3,1,""],unregister:[0,3,1,""]},"construct.extensions.Host":{get_filename:[0,3,1,""],get_filepath:[0,3,1,""],get_frame_range:[0,3,1,""],get_frame_rate:[0,3,1,""],get_qt_parent:[0,3,1,""],get_selection:[0,3,1,""],get_workspace:[0,3,1,""],modified:[0,3,1,""],open_file:[0,3,1,""],save_file:[0,3,1,""],set_frame_range:[0,3,1,""],set_frame_rate:[0,3,1,""],set_selection:[0,3,1,""],set_workspace:[0,3,1,""]},"construct.path":{Path:[0,1,1,""]},"construct.path.Path":{find:[0,3,1,""]},"construct.settings":{Settings:[0,1,1,""],find_in_paths:[0,0,1,""],restore_default_settings:[0,0,1,""]},"construct.settings.Settings":{delete_software:[0,3,1,""],file:[0,2,1,""],folder:[0,2,1,""],path:[0,2,1,""],save:[0,3,1,""],save_software:[0,3,1,""],yaml:[0,3,1,""]},"construct.utils":{import_file:[0,0,1,""],isolated_imports:[0,0,1,""],iter_modules:[0,0,1,""],unload_modules:[0,0,1,""],update_env:[0,0,1,""],update_envvar:[0,0,1,""]},construct:{API:[0,0,1,""],errors:[0,4,0,"-"],settings:[0,4,0,"-"],utils:[0,4,0,"-"]}},objnames:{"0":["py","function","Python function"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"],"3":["py","method","Python method"],"4":["py","module","Python module"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:function","1":"py:class","2":"py:attribute","3":"py:method","4":"py:module","5":"py:exception"},terms:{"case":0,"class":0,"default":0,"float":0,"function":5,"import":0,"int":0,"new":[0,5],"return":0,"true":0,"var":0,The:0,There:0,Used:0,__init__:0,abov:0,absolut:0,access:0,action:5,activ:0,add:0,after:0,after_clos:0,after_setup:0,all:0,allow:0,also:0,ani:0,anim:0,api:5,appar:0,applic:0,arg:0,argument:0,asset:[0,5],attribut:0,autoclass:[],autodesk:0,avail:0,base:0,becaus:0,been:0,befor:0,before_clos:0,before_setup:0,behavior:0,being:0,both:0,bound:0,branch:5,builtin:5,cach:0,call:0,callabl:0,can:0,central:0,check:0,children:0,clear:0,com:5,combin:0,commonli:0,concept:5,configur:[0,5],connect:0,constant:0,construct:0,construct_:0,construct_path:0,contain:0,context:5,contextmanag:0,conveni:0,copi:0,core:0,creat:[0,5],creativ:5,ctx:0,current:0,custom:0,data:0,databas:0,decid:0,decor:0,def:0,default_log:0,defin:0,delet:0,delete_softwar:0,depend:0,descript:0,desir:0,dict:0,dictionari:0,differ:0,directori:0,discov:0,doc:0,document:5,doe:0,done:0,downsid:0,each:0,emit:0,end:0,end_fram:0,entiti:0,env:0,environ:[0,5],error:5,etc:0,event:5,eventhandl:0,eventmanag:5,everyth:0,exampl:0,except:0,exclud:0,execut:0,exist:0,ext:0,extend:0,extens:5,extensionmanag:5,factori:[],file:[0,5],find:0,find_in_path:0,first:0,folder:0,follow:0,fork:5,found:0,fps:0,frame:0,from:0,get:0,get_avail:0,get_filenam:0,get_filepath:0,get_frame_r:0,get_frame_rang:0,get_mount:0,get_qt_par:0,get_select:0,get_softwar:[],get_workspac:0,git:5,github:5,given:0,global:0,good:0,greet:0,greeter:0,guid:5,handl:[],handler:0,has:0,have:0,haven:0,hello:0,host:0,houdini:0,icon:0,import_fil:0,index:5,inherit:[],init:[],initi:0,instead:0,integr:5,intend:0,interact:0,invalid:0,invalidset:0,is_avail:0,isol:0,isolated_import:0,item:0,iter:0,iter_modul:0,kei:0,keyword:0,kwarg:0,lambda:0,librari:5,like:0,linux:0,list:0,load:0,locat:0,log:0,look:0,lookup:0,mac:0,mai:0,main:0,maintain:0,make:5,manag:5,manipul:0,match:0,max:0,max_fram:0,maya2019:0,maya:0,mean:0,member:0,messag:0,method:0,min:0,min_fram:0,modifi:0,modul:[0,5],mount:0,msg:0,multipl:0,must:0,my_ev:0,my_loc:0,my_mount:0,my_project:0,name:0,necessari:[],none:0,normal:0,nuke:0,obj:0,object:5,off:0,one:0,onli:0,open:0,open_fil:0,oper:0,option:0,order:0,org:5,our:0,over:0,overrid:0,own:[0,5],packag:0,page:5,param:0,paramet:0,pass:0,path:5,perform:0,person:0,pip:5,place:0,platform:0,prefix:0,print:0,prior:0,prioriti:0,project:[0,5],provid:0,publish:5,pull:5,python:0,rais:0,rang:0,rate:0,rather:0,regist:0,remov:0,repositori:5,repres:0,request:5,requir:0,resourc:0,restor:0,restore_default_set:0,result:0,sai:0,same:0,save:0,save_fil:0,save_softwar:0,schema:0,search:[0,5],see:0,select:0,send:0,sent:0,serial:0,set:5,set_frame_r:0,set_frame_rang:0,set_select:0,set_workspac:0,setup:0,should:0,show:[],sidefx:0,simpl:0,singl:0,singleton:[],softwar:[0,5],some_path:0,someth:0,specif:0,specifi:0,ssh:5,standalon:0,standard:0,start:0,start_fram:0,state:0,storabl:0,store:0,str:0,string:0,subclass:0,sys:0,system:0,task:0,teardown:[],than:0,thei:0,thi:[0,5],time:0,timelin:0,to_envvar:0,typ:0,type:5,undefin:0,undo:0,unextend:0,unifi:0,uninit:[],uniniti:0,unload:0,unload_modul:0,unregist:0,updat:0,update_env:0,update_envvar:0,upper:0,use:0,used:0,useful:0,user:0,usernam:[],using:0,usual:0,util:5,valid:0,valu:0,variabl:[0,5],version:0,wai:0,when:0,where:0,whether:0,widget:0,win:0,within:0,work:5,workspac:0,world:0,wrap:0,write:0,yaml:0,yet:0,yield:0,you:0,your:[0,5]},titles:["API Documentation","Concepts","Configuration","Environment Variables","API Guide","construct"],titleterms:{"class":[],"function":0,api:[0,4],builtin:0,concept:1,configur:2,construct:5,context:0,contribut:5,document:0,environ:3,error:0,event:0,eventmanag:0,extens:0,extensionmanag:0,factori:[],featur:5,get:[],guid:4,instal:5,object:0,path:0,set:0,type:0,util:0,variabl:3}}) \ No newline at end of file +Search.setIndex({docnames:["api","concepts","configuration","environment","guide","index"],envversion:{"sphinx.domains.c":1,"sphinx.domains.changeset":1,"sphinx.domains.cpp":1,"sphinx.domains.javascript":1,"sphinx.domains.math":2,"sphinx.domains.python":1,"sphinx.domains.rst":1,"sphinx.domains.std":1,sphinx:55},filenames:["api.rst","concepts.rst","configuration.rst","environment.rst","guide.rst","index.rst"],objects:{"construct.api":{API:[0,1,1,""]},"construct.api.API":{context:[0,2,1,""],define:[0,3,1,""],extend:[0,3,1,""],extensions:[0,2,1,""],get_mount:[0,3,1,""],host:[0,2,1,""],name:[0,2,1,""],off:[0,3,1,""],on:[0,3,1,""],path:[0,2,1,""],schemas:[0,2,1,""],send:[0,3,1,""],settings:[0,2,1,""],undefine:[0,3,1,""],unextend:[0,3,1,""]},"construct.context":{Context:[0,1,1,""]},"construct.context.Context":{copy:[0,3,1,""],load:[0,3,1,""],store:[0,3,1,""],to_envvars:[0,3,1,""],unload:[0,3,1,""]},"construct.errors":{InvalidSettings:[0,5,1,""]},"construct.events":{EventManager:[0,1,1,""]},"construct.events.EventManager":{clear:[0,3,1,""],define:[0,3,1,""],handlers:[0,3,1,""],off:[0,3,1,""],on:[0,3,1,""],send:[0,3,1,""],undefine:[0,3,1,""]},"construct.extensions":{Extension:[0,1,1,""],ExtensionManager:[0,1,1,""],Host:[0,1,1,""]},"construct.extensions.Extension":{is_available:[0,3,1,""],load:[0,3,1,""],setup:[0,3,1,""],unload:[0,3,1,""]},"construct.extensions.ExtensionManager":{discover:[0,3,1,""],get_available:[0,3,1,""],load:[0,3,1,""],loaded:[0,3,1,""],ls:[0,3,1,""],register:[0,3,1,""],unload:[0,3,1,""],unregister:[0,3,1,""]},"construct.extensions.Host":{get_filename:[0,3,1,""],get_filepath:[0,3,1,""],get_frame_range:[0,3,1,""],get_frame_rate:[0,3,1,""],get_qt_parent:[0,3,1,""],get_selection:[0,3,1,""],get_workspace:[0,3,1,""],modified:[0,3,1,""],open_file:[0,3,1,""],save_file:[0,3,1,""],set_frame_range:[0,3,1,""],set_frame_rate:[0,3,1,""],set_selection:[0,3,1,""],set_workspace:[0,3,1,""]},"construct.path":{Path:[0,1,1,""]},"construct.path.Path":{find:[0,3,1,""]},"construct.settings":{Settings:[0,1,1,""],find_in_paths:[0,0,1,""],restore_default_settings:[0,0,1,""]},"construct.settings.Settings":{delete_software:[0,3,1,""],file:[0,2,1,""],folder:[0,2,1,""],path:[0,2,1,""],save:[0,3,1,""],save_software:[0,3,1,""],yaml:[0,3,1,""]},"construct.utils":{import_file:[0,0,1,""],isolated_imports:[0,0,1,""],iter_modules:[0,0,1,""],unload_modules:[0,0,1,""],update_env:[0,0,1,""],update_envvar:[0,0,1,""]},construct:{API:[0,0,1,""],errors:[0,4,0,"-"],settings:[0,4,0,"-"],utils:[0,4,0,"-"]}},objnames:{"0":["py","function","Python function"],"1":["py","class","Python class"],"2":["py","attribute","Python attribute"],"3":["py","method","Python method"],"4":["py","module","Python module"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:function","1":"py:class","2":"py:attribute","3":"py:method","4":"py:module","5":"py:exception"},terms:{"break":[],"case":0,"class":[0,2],"default":[0,2],"float":0,"function":5,"import":[0,2],"int":0,"new":[0,5],"return":0,"true":0,"var":0,For:2,One:2,The:[0,2],There:0,These:2,Used:0,__init__:0,abc:2,abl:2,abov:0,absolut:0,access:0,accompani:2,accross:2,action:5,activ:0,add:0,after:0,after_clos:0,after_setup:0,all:0,allow:0,also:[0,2],ani:[0,2],anim:0,api:[2,5],app:2,appar:0,applic:[0,2],arg:0,argument:0,asset:[0,2,5],attribut:0,autodesk:[0,2],automat:2,avail:0,base:0,basic:[],becaus:0,becom:2,been:0,befor:0,before_clos:0,before_setup:0,behavior:[0,2],being:[0,2],bin:2,both:0,bound:0,branch:5,builtin:5,cach:[0,2],call:0,callabl:0,can:[0,2],central:0,check:[0,2],children:0,clear:0,com:5,combin:0,commonli:0,concept:5,configur:[0,5],connect:0,consol:2,constant:0,construct:0,construct_:0,construct_path:[0,2],contain:[0,2],context:5,contextmanag:0,conveni:0,copi:[0,2],core:0,creat:[0,2,5],creativ:5,ctx:0,current:[0,2],custom:[0,2],custom_path:2,dani:[],data:[0,2],databas:0,debug:2,decid:0,decor:0,def:0,default_log:0,defin:[0,2],delet:0,delete_softwar:0,depend:0,descript:0,desir:0,dict:0,dictconfig:2,dictionari:[0,2],differ:0,directori:0,discov:[0,2],doc:0,document:5,doe:0,done:0,down:[],downsid:0,each:[0,2],emit:0,end:0,end_fram:0,entiti:0,env:0,environ:[0,2,5],error:5,etc:0,event:5,eventhandl:0,eventmanag:5,everyth:0,exampl:0,except:0,exclud:0,exe:2,execut:[0,2],exist:[0,2],ext:0,extend:0,extens:5,extensionmanag:5,faulti:2,fbx:2,file:[0,5],find:0,find_in_path:0,first:[0,2],folder:[0,5],follow:[0,2],fork:5,format:2,formatt:2,found:[0,2],fps:0,frame:0,from:[0,2],get:0,get_avail:0,get_filenam:0,get_filepath:0,get_frame_r:0,get_frame_rang:0,get_mount:0,get_qt_par:0,get_select:0,get_workspac:0,git:5,github:5,given:0,global:0,good:0,greet:0,greeter:0,guid:5,handl:2,handler:[0,2],has:0,have:[0,2],haven:0,hello:0,here:2,host:0,houdini:0,how:2,icon:0,identifi:2,import_fil:0,includ:2,index:5,individu:2,initi:[0,2],instal:2,instead:0,integr:[2,5],intend:0,interact:0,invalid:0,invalidset:0,is_avail:0,isol:0,isolated_import:0,item:0,iter:0,iter_modul:0,kei:[0,2],keyword:0,kwarg:0,lambda:0,last:2,launch:2,layout:[],let:2,level:2,levelnam:2,lib:2,librari:5,like:[0,2],linux:[0,2],list:[0,2],load:[0,2],local:2,locat:0,log:0,logger:2,look:[0,2],lookup:[0,2],mac:[0,2],mai:0,main:[0,2],maintain:0,make:5,manag:5,manipul:0,match:0,max:0,max_fram:0,maya2019:[0,2],maya:[0,2],mean:0,meaning:2,mel:2,member:0,messag:[0,2],method:0,min:0,min_fram:0,mnt:2,modifi:0,modul:[0,2,5],mount:[0,2],msg:0,multipl:[0,2],multisit:2,must:[0,2],my_ev:0,my_loc:0,my_mount:0,my_project:0,myicon:2,name:[0,2],need:2,network:2,nice:2,none:0,normal:0,nuke:0,obj:[0,2],object:5,off:0,one:0,onli:0,open:[0,2],open_fil:0,oper:0,option:0,order:0,org:5,our:0,over:0,overrid:[0,2],own:[0,2,5],packag:[0,2],page:5,param:0,paramet:0,pass:[0,2],path:[2,5],perform:0,person:0,piec:2,pip:[2,5],place:[0,2],platform:0,png:2,point:[],prefix:0,prepend:2,prevent:2,print:0,prior:[0,2],prioriti:0,program:2,project:[0,2,5],provid:[0,2],publish:5,pull:5,put:[],python:[0,2],rais:0,rang:0,rate:0,rather:0,refer:2,regist:0,rel:2,remov:0,repositori:5,repres:0,request:5,requir:0,resourc:0,restor:0,restore_default_set:0,result:0,root:2,run:2,sai:0,same:0,save:0,save_fil:0,save_softwar:0,schema:0,search:[0,5],section:[],see:0,select:0,send:0,sent:0,serial:0,server:2,set:5,set_frame_r:0,set_frame_rang:0,set_select:0,set_workspac:0,settings_fold:[],setup:[0,2],share:2,should:0,sidefx:0,simpl:[0,2],singl:0,softwar:[0,5],software_nam:[],solut:2,some_path:0,some_templ:[],someth:0,specif:[0,2],specifi:0,ssh:5,standalon:0,standard:0,start:[0,2],start_fram:0,state:0,storabl:0,store:[0,2],str:0,streamhandl:2,string:[0,2],structur:5,studio:[],stuiod:2,subclass:0,support:2,sys:0,system:[0,2],take:2,task:0,templat:[],than:0,thei:[0,2],them:2,thi:[0,2,5],thing:[],time:0,timelin:0,to_envvar:0,togeth:[],typ:0,type:5,undefin:0,undo:0,unextend:0,unifi:0,uniniti:0,unload:0,unload_modul:0,unregist:0,updat:0,update_env:0,update_envvar:0,upper:0,use:[0,2],used:[0,2],useful:0,user:[0,2],user_path:2,using:[0,2],usr:2,usual:[0,2],util:5,valid:[0,2],valu:[0,2],variabl:[0,2,5],version:[0,2],volum:2,wai:0,when:[0,2],where:[0,2],whether:0,widget:0,win:[0,2],within:0,work:5,workspac:[0,2],workstat:2,world:0,would:2,wrap:0,write:[0,2],yaml:0,yet:0,yield:0,you:[0,2],your:[0,2,5]},titles:["API Documentation","Concepts","Configuration","Environment Variables","API Guide","construct"],titleterms:{"function":0,api:[0,4],arg:2,builtin:[0,2],cmd:2,concept:1,configur:2,construct:[2,5],context:0,contribut:5,document:0,env:2,environ:3,error:0,event:0,eventmanag:0,extens:[0,2],extensionmanag:0,featur:5,file:2,folder:2,guid:4,host:2,icon:2,instal:5,label:2,locat:2,log:2,my_loc:2,my_mount:2,object:0,path:0,set:[0,2],softwar:2,structur:2,templat:2,type:0,util:0,variabl:3,yaml:2}}) \ No newline at end of file From e8a12b779bc1e7f5085c0e40b3082f6a1b579f6b Mon Sep 17 00:00:00 2001 From: Dan Bradham Date: Thu, 14 Mar 2019 14:33:19 -0400 Subject: [PATCH 031/140] Update config docs --- docs/doctrees/api.doctree | Bin 165543 -> 165821 bytes docs/doctrees/configuration.doctree | Bin 2250 -> 24207 bytes docs/doctrees/environment.pickle | Bin 29001 -> 38447 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/doctrees/api.doctree b/docs/doctrees/api.doctree index b978cf973fb9072cb173609e9fbe231ba1717603..0f40917359f0757311afd860a6bbd99b5c5491f5 100644 GIT binary patch delta 16148 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

    &AG=BnC!9`0M-(H~%Y7wQb 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 diff --git a/docs/doctrees/configuration.doctree b/docs/doctrees/configuration.doctree index 18c84e9167c79211e82a6eb91f5537ed0ddad5f1..3675f4b197a4cba6a23763e1d15d61ae1614b853 100644 GIT binary patch literal 24207 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 Q@|mx2$MNmBuXUyJe;Z>i00000 literal 29001 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 From b5a76a988c9cffb4b5ffe46463d308950008363a Mon Sep 17 00:00:00 2001 From: Dan Bradham Date: Thu, 14 Mar 2019 15:23:00 -0400 Subject: [PATCH 032/140] Stub out docs --- construct/events.py | 12 +++++------ docs/concepts.rst | 13 ++++++++++++ docs/doctrees/api.doctree | Bin 165821 -> 165409 bytes docs/doctrees/concepts.doctree | Bin 2225 -> 3452 bytes docs/doctrees/environment.pickle | Bin 38447 -> 41464 bytes docs/doctrees/guide.doctree | Bin 2226 -> 3749 bytes docs/doctrees/index.doctree | Bin 8105 -> 8083 bytes docs/guide.rst | 17 ++++++++++++++++ docs/html/_sources/concepts.rst.txt | 13 ++++++++++++ docs/html/_sources/guide.rst.txt | 17 ++++++++++++++++ docs/html/_sources/index.rst.txt | 1 - docs/html/api.html | 18 +++++++++-------- docs/html/concepts.html | 21 +++++++++++++++++-- docs/html/genindex.html | 1 - docs/html/guide.html | 30 ++++++++++++++++++++++++---- docs/html/index.html | 17 ++++++++++++---- docs/html/py-modindex.html | 1 - docs/html/search.html | 1 - docs/html/searchindex.js | 2 +- docs/index.rst | 1 - 20 files changed, 135 insertions(+), 30 deletions(-) diff --git a/construct/events.py b/construct/events.py index e0e3e94..20acae4 100644 --- a/construct/events.py +++ b/construct/events.py @@ -39,7 +39,7 @@ def unload(self): self.undefine('after_close') def define(self, event, doc): - '''Define a new event + '''Define a new event. Arguments: event (str): Name of the event @@ -64,12 +64,12 @@ def on(self, *args): '''Adds a handler to the specified event. Can be used as a decorator. Examples: - events = EventHandler() - events.on('greet', lambda person: print('Hello %s' % person)) + >>> events = EventHandler() + >>> events.on('greet', lambda person: print('Hello %s' % person)) - @events.on('greet') - def greeter(person): - print('Hello %s' % person) + >>> @events.on('greet') + ... def greeter(person): + ... print('Hello %s' % person) Arguments: event (str): Name of event diff --git a/docs/concepts.rst b/docs/concepts.rst index 64c3b93..6a56836 100644 --- a/docs/concepts.rst +++ b/docs/concepts.rst @@ -1,3 +1,16 @@ ======== Concepts ======== +# TODO + +Context +======= +# TODO + +Shots are Assets +================ +# TODO + +Asset Libraries Are Projects Too +================================ +# TODO diff --git a/docs/doctrees/api.doctree b/docs/doctrees/api.doctree index 0f40917359f0757311afd860a6bbd99b5c5491f5..595c53f78b56df086f89d1c4344874ce909f3d39 100644 GIT binary patch delta 17231 zcma)k33OD&(m&N?yJ3b90zxMHLYB!g3E4n|1Y`*UVO0cyBtRfsmIMhRYZ5j`7I`>< zRz!j4##00$VBFYTKJ~9CxFd^zg35;o`ta!!eP31eOzypbcg~-4()Zr#uIm0(RadXW z{&m6I)&%?2`EBrP^gH+OtgyZnQ>M+Th%JwuRWUEN;=YPmHJJ^Sf#Vx0JFdA|-kuJB z{M!#&?F)g#&a(4LXzM?|SFc{NvamCB_T#tFouR*av(S3UKRSaJSQ$|1(62i@q5Zpf z4n+mIPC8n(s)Wkcl@a=n^cp!RVD3b{BeZIjTHmm^A*IrP%}5ek#7&#yam*WLYe5+3 z`D0#N2$m2HF`lxEL7p>h0y^k73J&(bhRG03R_Lxib>EN|;q%iG;w7(Gcqy7TdkUFqEPHELyd4&#g?Yd;`Jd zMR9%ANd}`EuRMmWx zYVISq#zBA#h(=WrRGE-qt@nR#sQ0L<7avqFKK@K+07k+KkI1wC$N{jZO3yXj# zSse-C^lY32y=9>b0z4hZcM*|Mp0fi-cZiE>>AL|@?7R3N-wlvOF1Q`pJ&k{5iPj|8 zDbKoKj=0;XWE7Vb`1opVK0T=H@aD1!a$Gi)i=_s`5+!_zw0DI80>6l1Au~V}nT(u7 z{K*z$C%Ht1Ocg&w%3}$TN*d@bAghI@%4a8w+$d$)_o9?#^K;=x@ij_L$rUG12Yyf^ zKgxp<;^!zf=Vf{%dFrnDxlW)ed|`-QTNvzfq!|YLJc!XQ?L-v%?qcK+xfH{S+NXfG*9s+eOrMzXDX9b1jEENg!w4s+Ru?x)ouP6E#EAH9?)9W z7eTh?%nH92xxITsF?5u@iy=my838s3Rqu7v!Io(ebfY;>VLgU9PsxxzkP+@5gWVZp z_$UPPwYedlv;7l1sZaG3IWZnv`*3-*4@8SzF`jKtML`i&8s@3XPJ!F#wHMqW>#$v+ zE|gYzUMHcI7uNVqp5{69V==2GxB&+_z)DbDeOQuxEEQ3dtQa}_NeGh*`@-~~Nkn(( z<)naA&#*s&WK2J}S74c{KBBk$v>%k(vZ#zwSd}d758cJG7|(TQ6s%}gTW!6mjH(Jp z*m5HnEkdH94E5CoLsK&Tz3^b=PMx9bR!|z(*hf~>)J&T-bxvk=c}-=5Ph(4NR!ilc z)OtDfUg#tz4S+fDclpHt$bhE|oo!V*+sQf$^65-IUJTBTRjRT6sDsV;ijlVCn$EIr z0EEkH#gHsE$Dn&>hD7M5f{(^{)~7mc=ElhN1EH_@Jcc~EgT@LY7I}84D3ZB%v{d4j zJ5Y%iW8_zNKonduwD|oE_<}83{08$4Evm2oFD*`)Hlw0hi|21v3u|%5AegB6$FfVb zj>TS=W%uINP(iI?8Mm2Tt{RNEqhoK1J7Wk;y1{u8<)Ilw$G=wKOh&4fWGiId3Xe*;c$?3aPV0y4+r?5;ovhlWi-Zb{KDY?K4wN))*fs!`)+W;0VU~gNq#f|qlWVEYK2C_ zYwI{QympIYpQ1=~c_!R0OUJ+jbdUpM;FK5|$D{3_IC*dk)XMDvBG)x24sj|k7*DS) z#@2pFGc?>Uu35vU?|~?Lv6)$3od@yKISzViHAHvl_r`Ho7{GEls#e8OSFrMV;9rGe zt!j3VHv|ljy~jhN&tIY^z%=osYW$WsS(h((4C_Z;)7JCXut0ftDRhIED_E6VeQvn-osX7glePF%#exd9VyL`)0Es_Nw2?V2pO2 z$PWD*g6xtHmP5Tb6Q{iDH)@IUs%um#LT`+t8KLs1Fj-!Kx#UqH@$69$B6m;1nf{^4 zILVt*;hX7~Rd`o5&iIxt6%x-bg%5J65P5hqx)gqqOW8=iQ)uzLs@YWm^P$|Ray+vH zjYp9b!{wnV&`mZ?!Qg~B#6H$w_pT5Eh?2llmTPe|Wr3d+K%bK$Zgx_urpQTq#w#b? z9&dHhQ>K6NDJ(qZ2OrXFn(H-ewbyr#&}$3d z-G?;8ru*XAchh|~HHOOhC5pP-OYTm zd?v(u%5vJt+62*7*b*46&$pW>+iADXV&GrdGPZ}X3x_HUjq=Bt@H9@)NCVSlQ8je% zy!%Up%&dYck)6O(GLezcoI6|;C6LZ+841lwDU_d7!)O>G*UyHufD+7@J5laj2Dwni zBvF!}I)|Bu*jp$v8EJ+hlM_gh>7DzKJnxjuNn9$56v`{}aDlw!UhEWgM0V&)5?E=e z^rhxf3lmsr7PfZtp^tb}!PgpTju)Y)Ca9^vKj*+a(nVGY>f*~9cuws0s$fS#GciW? zy$?o;gGvuOj3ynB$M1u|aEw925qhZM#axCPXAKI^AkD!2j2HJ|r226G!(0jCeoDJa z6o>wkg1nu+6y!?^vW0^o2PZ1`@Iu}$tLMYBT6iMM>6D17Q*-zZa`*yx2s+3!3$TaW znn<&C$d?Z-gxz9bB0GdFpSYAAie=p*$Q7d#*()p=zb=Bi#k53TW>wIGmsy$SG9;&k z7i1&N&_=ebTn>rRxMS3)kHH+DMsiFoNaXO?eF@GA9!T`f&NaDd33P;qq4Moz88IEuV972iojrOV}sYAT*p2m7=pqC50` zq$!u@qqzn0!-bG0YwN*ZU_qXbzf%t-uw34<6h0GQu}0u?`l^u|m%(81qte9>hAuug zV0_Q@T0P?^(hOZ3C0%sE-HL~nL%97DGb?N0i{((Iwe7;qv~}UW@G%SJ0HSsAw0pdV z7OCvsV;KcP{_8?m-4Bvy71J* z${0Ey*Z049^*5FZN9gV@Dkd#_7`INwc9C%nut<*ez&adw&o)4|tX>3u&_>2Kz#$yF zk(6g^lZ33wl2I~#15SUllBoOPcH2uEu!>&%CDM}^ex|uNakhZ(k2C|mzifOAoZ+!Z z^q~(jRhOg&v2z}I*ik~`1b zWEFh8&f}k~I!~{s;Z9N#?LHlR8YYU>N-ek)y!#p0DK;r(JYY2RQCa*f41yhoGPcV@ z&q8;2wV5KeC#(KrW@8jqhgym>LqAKEel{b~r=R7fYMW3$<3an1NDlo=!tRoHJ%_Wf z&yq24-y%;v2MO8_D*HRiR?$1`GegJ)dd0&N&(UW)%E`~eQMg8p=0I*Umy=aHU^-~B zywQ6K5|DzN5&EO(EVy8}=LPIoJLU9!5P>$sLTcR*o1#{t_{5cHyo}!oaX5`9A$&-H z2OM0M)}yzgKheukCI>P2YdK@%<}1QvAI?r0b&o}$kmj}v(Ci_AOYTx+vxEY znNaNsAEMV@;=@!m>D)sy&SMM5AA}9WN|VmhL{vd=Z>kzGZaoC0LEjPGp`Rxl*|tTd zQu6d6G>)}FG`SjZ7(zuWr!r6pF+}K#P$U^>)nRy23vseYyR&(w6)dN|0Z)lwCx^*( zC-E4y_nUa;lZ6DF`=`qTZ^98#=u~t6bmCWeSl}?`VH#k}O+I-?XuMc%T92P0ReFs;2$b|;5Jw+MH01KMdy$jz7cwWwXAJ=&= zA-eGRpuJSUp+7HweIE+jKi}Mf62_qKlLJ100`Xpp!q0sG@7%2Lvmb#=oNQ6}^bu&f zY2gncNBq>HaKJ~f?xuxFvPh#gYrZx+J&n&ri0AR~SEh_j$Nraf5(D?aqYxw(IF-Ad zJPM^+R2qYfNTZS2?x3&8K|u%`OW7Te;;EV*C-)zNM97injzNl+qZsnAzMV-vhG1mK zrWT~)oTB<;xE0Ssp8pvADEMP=1$m{mk-a~LOf1mz6F90>C^?f@j<1MKw*3S$MP?dW zC|W-JDReg1kj5B;QWfU{dG=HIMSDPft*`)~lPLtiE3|t)hZ7fv5>CK2D=(Po+)<`oDwU%+;GNQWE5N!`I zvvLId@HEWPdK1~97pAl0__I(3bX&S|uT5XWW7<$vYKWy2P3*7~&wb6(W#btrf=XF_ z0Xk}4*#7AX_OP=sg*^B8IXvX*c@8FvS_Tgb<(KE+P4TdT{Ez{8q0#QO+&b%2Z7$Lb z$a8&=hkOe$_WRAOY{aI=z6A}mJ(Pimo(j%97R#V_rt=cV%&5?OU_mp_i{{`3s3bHc z-@{6|>pNVveWTEPEr)y$Z;4;iW&KoK$rxz9Hkx#mtF@vzjx+g z*Hp}!Gp%Y?=G<8`s>&xfZ%Ljs3uV)WU4(gBrwm5fF+(1!6xlYej#0MHP^SInA{=O+ zK&7~oGzUbAaoFj%UBT%i?%DdHQ9=esBYN=3-3cX;+yEuA8<<#*!_dNesF@Q z@f(Lhkli{KG;c4IDh3?X)ZFdJk9frF9pMIE#$)KY8GH;qI|G+TMRMyO7|{xUf>C05 z1`l)g+0B6TWf`)55e}a}{DkAm!x_}eT4a~J4WsZ#6QQQjoa2gj-_Ifg_0yuC&A=)Q zGtI~_jdl%~V#Z2TEV=Hu9M z-00E{kcjhoLx$(4ZaMPM?RYz&-4}RVbH*5BY%#s$uY;GH=uMU*JK| zex?kEekB7txJyR&yFmhNt^5i*@KO2)ESC5D3okr$lxKfe7ivN?W!WFNf3f;ED0GEp zqAV3q6X>;tTG}AZP)nOkQi~HBWa94-XKQQXmBW7rchIdwap>Ksu56o~1+stQt*zD~ z&2Hw%xk^o*Q%yYq%WoGRjR3GHr3Sa)}~JMHg)cQpjx1T zaG$!c*^=6ZQel(3K?F!GLqu%YARO9;Rx4ZazYV)YKLqEq$7`AYh4)t1Yew6Ta0{)m zWgpTE-20kw_Z1@1_OgkWa3@G9#M?pN64{}jQ@ER0Dx2tQh1<-gaQ|t=-Q>kR&ri%k z6t(GMuG|WUc1wWJWVOGTCpx%z^lRsmb?Ks}Ik#tKI~P{2ye*zyTX;GpicD-GO4U)}81My}-qzosFe3o?I6Pelwq}4-!RUgv;V`HkWd_SWV0z zJo$*HS`#7i$q11o3xdT+F^%!yC7i>-Vt;0Z(UN&woz*C%NHai`GKe6GAsF!6%j`BH ztfMcQOf*YUm{M(Gxb_gy9r_xVtiKz>JXgY)9#ojjdsB}~DL5P38x|VY9xddTA@CGGOpFm>S>$oJ8@nw`?01D^ zVTH=g;^?)7n`uZhFlbpE)C?1ab}*U9W){_qshte(CQ)Max<#}G&o$0*47f;p|4h0%2+C6aVsnqKF0El6-${H%K?X&OjvN@ zTSbGKzdRI){zhR?z8N8gi~S1KD_Pbe!|;M?XcTt%59px|>s~P$`#!hGigORr44iuu z&U9~IYg=43rzW$ix?)zTW|>L>A<-gDyGnFC#ZwB{$JMa{E-3|A_*enK*%o8& z^D5xZn3f7~#fn8DE}Mg5RJI8=X0)TFPO$tcQsj%=Y+fB?(L=4#qq4D4%9CC6+QO4N zAD7B(wpcnXe;OH8~?sF)g|eM2M+QA#O`St2Xt zv{H(hMY2+^Ta~idtCV)>G%7aXsOSeGtSg&5q$`bs56ZroqNhme$~KOt2iw>d(zV&f zczYyQgtgON9JO3kO1*sd!iAzbjl~wKjn04!vJj6^!UhO=$XbRl&&2$NpQ^ zmHju!JNIfMkL8K}GAr9RT+ElX*`ke%>?)Ruc?!xLgpx1rR~fH{h_-TSj_5B|DNrj7 zP;-phRwz(2kz#Q7l)2&Q*ke--MKh)JWq6o{tpGJioO)6XA~$4AA@?!3aW}g zy+9Vp7r4uCLc#cgVYp?#ZepmotQvpOX#5vOYkp9TKZX>e@yD7Q-@iLH{!>$NqwzdO zw3n}R7hSZF9EM@fVcUFSzzEJ^+gKzpjFcQpZ26?CI>5EAKbluq`KmO=k-j~|9<8sc zt`AkG4sZ))_HE)tQJBNqiXlA_b_KrU-QV&YTI=2~uT+RY*}0b(C2Dfi=3+TXQL$%^ z+}TSE7E5!;pKX?m+X_X0?7xS*QOL%7I(VV4n@Dq+%aEKFfomMn41JEvArJR%E-uF` z-vnubsR=o}xro<(@wQiEk?1D&=kSf+gH)kfX}w0TF|rJ=AdB~M)E4hf5^-K%F1Pg- zkBS#_c!%*g5tSqDlC%1V*TpWSnY(TmcZq8ZGw7{mYorvuyRp{4^HjJlp> z9aw|@I;0uuUzf{aGl2Z5vQfrO5n(cRh-lk9`+nFgO$E|*C1Rd-gy;_aeX>ZFeLYvi zW5K(*+*vJr+*vOuWI?;AstA3(JTp}6A!z%Di`6oAxTp|+GBojfF3}FUemClFlSf*! zBKZZg4J5xPBtZ(vnvSymPSLSr^Ip-fCRy_AIMLa2rbip;94YS9;z_nckIiE!*9<68 zc?`wOn#WLji3*{O?=`A;n68FI_8G5IpjMJ6@6kn_tQ&?aiudlqAW$J; z9NMd#9?D)NRK0S`vYVhRMw)@LI8P08%jMzGBH1>`#A~4HbhjwiR#KKjU#?Ky!IIfP z^$L}lRiWCVP-$LNcjT!VjAx9PMP|a$xk?s&frqdd1%u?3F|?|SBQ7AKt_B% zPu(}YPBp9hroU5eguXMc`4+Q2LEMkO#t5B&CFstnJzt$2$nj&O3LR`XnQ_iVYSQE z<*04^q9f#Bmc=(tf6wCs<6jj-?~PO2c|%D%W#dHALtBRxppw?+qqBQ&Q&|e-P7*I@ zkE=qDQK2lmSCe$Af|{iLPPKg_vNc0+3Q3*y-sv}vRI&cK6*Nh6$@l( zg$VY%`Ae*fpMrDT5AvzQ1iec&TZ+h@Q$-*6nl+_Ak?-p~*_AlfyzQ-vc~xk8uu>FitqNF0e<}2Du~_z_Kk_*qSok;|gcn#qzlFLO zioetNW|}V`)XM`iaOUuj?;uXLo-T%otO5?=>2lR{@rEm%YE&Vh7rnMXk%Tk@MN)we z1%`mmBsj#LY8IfNT%UnZ?j$-cV+z=iDJ+|z3@czL7CweD%L*l>KtZ`{mRLeTAiP?v z=PNr)t3;_-p-|Mz&eh^0@kjwr+8gMh7H0KEvpih4wSO-}nt^L!Gpz0=r?i_iJjuP}m?~Ctl3O!`8$_qNtXBDak*V z)afU9Dhb6)hI(5{=yzNyp`R`3oX}5ibWYOfcM+V_&>JW8V*{NNx{j}NLYKI8PUsf0 z&I#Si)t^bI#dI{@$73BT4V{xfJfPA2D52X3D!C0u_gcL>raGOCtAtJ*bx!D{PUnOUuyju7@JHu_&KGn} zXiHt^q>SD;p^aRf6WRjRIiY<-ofFz~(>c*tCG{ zT1)Dj&;m^7gjO>;Cl}Ba^lvDk8M#Vm7OQhY6Dpk(nvm$23`N~y;8h6?Vmc=jjC4+@ zL+hMS7tlHR54~N-WbPVmR{6||n)%fgbLh9^dZ663L_CF8az@pPRQ4@5yOdiepDJNj zayA=m%~#zgs8!m$2F0I@!I1t?gz@BLkY%%@%gEb=_=PH(S-s7R3gkN!@HoH(Sxo z7Id@m+-x{E8_mrIbEC0JiGeKTW-GbbLT;T*RKk{VvsK(|5jR-_OOiF*Yy>wOz|G1> zameM?N%JLhD`QC4eobH1i8g>&DK6BDelgsU>1WZZ57X~$RUf81>AGFsu~ei8d!j5` zCSq%6VK~4r!PnJQFh5F{^>GD-|2F`4disH<>hJXPM%CZxCwi*CXEpbC`gND;@ANAq z)!*rNI;y|ZuRBzKr=KUVA$7d4pX2TC9h&<)-CI}voo;Qb{!Z5o^#RmftFXDA?5&-I zK0uZ(6LB-V?dJ=Ls{Qk^u($mjC+POE3K4V>SA__=qpCs#UA|Nyg02~=5J8u~REVIP zJ$h>j3BHP3&s9{R zvu71m=y+2_6*|#VQ3cP*YQ15F4w6(@p(7X-R_Gi3! kRcH>aq6*E0R8*n)go-LO0&`Ts(MzscAuh_L4~QQB2M{)rJ^%m! delta 17412 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 J^a|1I{{X~$C&~Z- diff --git a/docs/doctrees/concepts.doctree b/docs/doctrees/concepts.doctree index 1b677764dba4eff2907260a3ae78cbc0a051bd1c..9d1e2459d2d25f5143289184529376c28ce54725 100644 GIT binary patch delta 1153 zcmb7^K~K~`7>2FOQdbaG6ECoaL1J9g#veeU61f;jG0Kt6W=baeLX@tZHqiqCLL?z+ zG-k((`U^-nz=n&zz#rhvzv97frrUPs0^vB{_s#px`*fx!H~&plcSpXxzwxyiIL(vJ zxbp&6(pr~Cyc6**R8P(CscqFnH65K}t1C+@3T{i`cCq}V1_OAEDR^*D!L*Nt-}~h> zM1pxd#55kD@p6RvW4fONxSq>mRO>Y{nHN&`dAJpe4{>f(tV^e7r1Zpgdaa!%sJ@WZ zjiXCWj|$-n1NgLk*_PcI4sb1(PXd8En#xQ|rSAIRyd1McX3j3%qtgAB(lRyLZt@R! z=+m9Uq5cM8{8Ta?3AQMuh-FU1bgV{+rb8ye&QPEUhR}>%4oyO`(w4Q*j8ospTWIQ% zB1n%Ccsxr`GRk@;UC}!SmOifyYsb1Z86vj45kx%N5RxqtwpUTODb{1jR>Lrdcky5e z^E0JN%hXB0i_ebP-fw53KzlfZcJgv)f0D`f(nLQS3vHeHM&3fp3^ea;p4UL`4)S_2 zD_pb9F9G!=G delta 69 zcmew(wNa3@fo1A4j)|;E8#8zq851{4v&AzphHb9qn9Zz^VUVF)+{2ldn44NsS&&*h YrFKe&fnRZkJdoDS;4a=A#iPUs03QGr{Qv*} diff --git a/docs/doctrees/environment.pickle b/docs/doctrees/environment.pickle index ff9e02aada6e52818e14c59476ab57bb944c4d5c..afbb4571cb721fb83257c7e7636ea5bdbccb6fea 100644 GIT binary patch literal 41464 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 Q@|mx2$MNmBuXUyJe;Z>i00000 diff --git a/docs/doctrees/guide.doctree b/docs/doctrees/guide.doctree index 4ad8e02ae1391e0dead25bd7f4a5468ae276a5e8..52f55065d7da4133b1b51ec582deaf679605fbeb 100644 GIT binary patch delta 1410 zcmb7^Pixdr6vZcfi?uMARZh$)LEiLz_&)ob*o|Cwb5=ib}B{ z1W_h#s#`aOLUnNEO8fv)sEa~BjQ6EYUY=7&W|2kic{%soU*3CP&wU#Xtazbf_5xjFG6|=N~b|>r`nAYArUK?h~P=@Pq%m0q1)8`LGtFFn%2}p(hAThFaWn1b0pCLW8mmrOr$5T`+F@=B?shXtrfW|WmXivL#S22XOXgdw< zE&=metR8Q(Q#FC@E#>#+`C@Eu64*xi!}f`6w`E%i+gZ{>dkI@H1j6U2)Z;k>Hz8|D zxd%9&Is$iJ?LL;-nKfgzxnjk8OcVs7Z pe)}BhnAfqTV^_z4&M?7?hjg&#=mOfL1F%DXb02%G^Dc8)`2z=P<1PRI delta 70 zcmZ1~yGf9>fo1A)j)|;E8#A~W851{~uqQAvWrS^>$vK-@A;Ta;x44HhFEKZ@q_QBj ZcuMV*3P!;6TSix)6CGy(DpV2%3TCVGkds;Qqxi^3UV@&GfSp)W_<;!?%~MIPsz+nNv)WY^&QC1 zS`Q+A0Ev#$4D~D|*Bb#H%Lz13n-#=joxD+09qeA|$zMd}bj`8487_+AX7kDRVhW7* zINWRlbF-@_E;q9krzRF9XAp9+G0>fsK-2UYO`)!>7PnzE+`L144-@13&81Qcm@_MZ zc4U=-2qvJPGyZ28f&8xzA__sGc|fAHhchoRH?^d)Ahj428Cf7reqarLVD)}rwSJ`; b;viX&X&C_-|1(M<=CFbc%iCNhr_BfeVq~gy delta 636 zcmbPiztWzifo1BvjVzvmJlv^yWtm0!dAX^1C6he_-I%g=P3{)_!FX|Ui;xZL_tc$W2aB;v* z<#a8vdIK(s;tk8mnqms#j)c8o2lj@WH!g3m6{jW^C1()wgbC0S)-YFtqk;`2WDXMo zdn8rdhS6yABJn*;j0-o%OD$kltOi<}RRJQHfFYCdKg$RdO8Ow87$jOSSwT+31f)8Kn@7tRUS5n=|CJ83CGhwPpYS diff --git a/docs/guide.rst b/docs/guide.rst index 66b51c5..b2caeff 100644 --- a/docs/guide.rst +++ b/docs/guide.rst @@ -1,3 +1,20 @@ ========= API Guide ========= +# TODO + +Quickstart +========== +# TODO + +Builtin Extensions +================== +# TODO + +Extending Construct +=================== +# TODO + +Examples +======== +# TODO diff --git a/docs/html/_sources/concepts.rst.txt b/docs/html/_sources/concepts.rst.txt index 64c3b93..6a56836 100644 --- a/docs/html/_sources/concepts.rst.txt +++ b/docs/html/_sources/concepts.rst.txt @@ -1,3 +1,16 @@ ======== Concepts ======== +# TODO + +Context +======= +# TODO + +Shots are Assets +================ +# TODO + +Asset Libraries Are Projects Too +================================ +# TODO diff --git a/docs/html/_sources/guide.rst.txt b/docs/html/_sources/guide.rst.txt index 66b51c5..b2caeff 100644 --- a/docs/html/_sources/guide.rst.txt +++ b/docs/html/_sources/guide.rst.txt @@ -1,3 +1,20 @@ ========= API Guide ========= +# TODO + +Quickstart +========== +# TODO + +Builtin Extensions +================== +# TODO + +Extending Construct +=================== +# TODO + +Examples +======== +# TODO diff --git a/docs/html/_sources/index.rst.txt b/docs/html/_sources/index.rst.txt index 7697604..ceb32bb 100644 --- a/docs/html/_sources/index.rst.txt +++ b/docs/html/_sources/index.rst.txt @@ -42,7 +42,6 @@ Contribute concepts guide - environment configuration api diff --git a/docs/html/api.html b/docs/html/api.html index 07e15e7..48ccce4 100644 --- a/docs/html/api.html +++ b/docs/html/api.html @@ -86,7 +86,6 @@