├── .coveragerc
├── .gitignore
├── .landscape.yaml
├── .noserc
├── .travis.yml
├── CHANGES
├── CONTRIBUTING.md
├── CONTRIBUTORS.txt
├── LICENSE.txt
├── README.md
├── README.txt
├── appveyor.yml
├── docs
├── Makefile
├── conf.py
├── index.rst
├── logo_small.png
├── make.bat
├── make_html.bat
└── requirements.txt
├── pyblish
├── __init__.py
├── __main__.py
├── api.py
├── cli.py
├── compat.py
├── error.py
├── icons
│ ├── logo-32x32.svg
│ └── logo-64x64.svg
├── lib.py
├── logic.py
├── main.py
├── plugin.py
├── plugins
│ ├── collect_current_date.py
│ ├── collect_current_user.py
│ └── collect_current_working_directory.py
├── util.py
├── vendor
│ ├── __init__.py
│ ├── click
│ │ ├── __init__.py
│ │ ├── _bashcomplete.py
│ │ ├── _compat.py
│ │ ├── _termui_impl.py
│ │ ├── _textwrap.py
│ │ ├── core.py
│ │ ├── decorators.py
│ │ ├── exceptions.py
│ │ ├── formatting.py
│ │ ├── parser.py
│ │ ├── termui.py
│ │ ├── testing.py
│ │ ├── types.py
│ │ └── utils.py
│ ├── iscompatible.py
│ ├── mock.py
│ └── six.py
└── version.py
├── run_coverage.py
├── run_testsuite.py
├── setup.cfg
├── setup.py
└── tests
├── __init__.py
├── lib.py
├── plugins.py
├── plugins
├── missing_extension
│ └── myCollector
├── missing_host
│ └── missing_host.py
└── private
│ └── _start_with_underscore.py
├── pre11
├── __init__.py
├── lib.py
├── plugins
│ ├── conform_instances.py
│ ├── custom
│ │ └── validate_custom_instance.py
│ ├── duplicate
│ │ ├── copy1
│ │ │ └── select_duplicate_instances.py
│ │ └── copy2
│ │ │ └── select_duplicate_instances.py
│ ├── echo
│ │ └── select_echo.py
│ ├── extract_documents.py
│ ├── extract_instances.py
│ ├── failing
│ │ ├── conform_instances_fail.py
│ │ ├── extract_instances_fail.py
│ │ ├── select_instances_fail.py
│ │ └── validate_instances_fail.py
│ ├── failing_cli
│ │ ├── extract_cli_instances.py
│ │ └── select_cli_instances.py
│ ├── full
│ │ ├── conform_instances.py
│ │ ├── extract_instances.py
│ │ ├── select_instances.py
│ │ └── validate_instances.py
│ ├── invalid
│ │ ├── select_missing_hosts.py
│ │ └── validate_missing_families.py
│ ├── select_instances.py
│ ├── validate_instances.py
│ ├── validate_other_instance.py
│ └── wildcards
│ │ ├── select_instances.py
│ │ └── validate_instances.py
├── test_cli.py
├── test_logic.py
└── test_plugins.py
├── pre13
├── __init__.py
├── test_di.py
├── test_logic.py
└── test_plugin.py
├── test_cli.py
├── test_context.py
├── test_events.py
├── test_logic.py
├── test_misc.py
├── test_plugin.py
├── test_simple.py
└── test_util.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | source = pyblish
3 |
4 | [report]
5 | include = *pyblish*
6 | omit =
7 | */vendor*
8 | */tests*
9 | */run_*
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swatches
2 | .idea
3 | **/published
4 | *.mb
5 | *.egg-info
6 | dist
7 | *.pyc
8 | cover
9 | .coverage
10 | docs/_*
11 | build
12 | playground.py
13 | *.sublime-*
14 | __pycache__
15 | *.p4env
16 | *pyrightconfig.json
17 |
--------------------------------------------------------------------------------
/.landscape.yaml:
--------------------------------------------------------------------------------
1 | doc-warnings: true
2 | max-line-length: 80
3 | ignore-paths:
4 | - docs
5 | - pyblish/vendor
--------------------------------------------------------------------------------
/.noserc:
--------------------------------------------------------------------------------
1 | [nosetests]
2 | verbosity=2
3 | with-doctest=1
4 | with-coverage=1
5 | cover-html=1
6 | cover-erase=1
7 | exclude=vendor
8 | cover-tests=1
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - 2.7
4 | - 3.5
5 | - 3.7
6 | - 3.8
7 | install:
8 | - pip install coveralls
9 | script:
10 | - nosetests -c .noserc
11 | - pip install git+git://github.com/pyblish/pyblish-base.git
12 | after_success:
13 | - coveralls
14 | deploy:
15 | provider: pypi
16 | user: mottosso
17 | distributions: "sdist bdist_wheel"
18 | password:
19 | secure: fwXIOGKn38gJFNkzlpvholQBhSBzNorOnMvs04JT3+Fdq6ys2TiUV6tlXHDwo6DkMY++USI19oD9NWlvg8gQaRJq4g2V/taazn8XDv1XnyKLzb6DnAT1ALilJtbIgotH/0QNOvkDP40a8UDwbelE0aR4AptNO1Ts7n1eERygENA=
20 | on:
21 | tags: true
22 | all_branches: true
23 | python: 2.7
24 | sudo: false
25 |
--------------------------------------------------------------------------------
/CHANGES:
--------------------------------------------------------------------------------
1 | pyblish Changelog
2 | =================
3 |
4 | This contains all major version changes between pyblish releases.
5 |
6 | Version 1.4.2
7 | -------------
8 |
9 | - Bugfix: `plugins` argument of CVEI convenience functions did not have any effect (#286)
10 | - Feature: CVEI-friendly convenience functions (#288)
11 | - BACKWARD INCOMPATIBLE: Due to #288, the behavior of util.collect() and friends are different and will need refactoring in your code if you depend on them.
12 |
13 | Version 1.4.1
14 | -------------
15 |
16 | - Feature: register_gui (#279)
17 |
18 | Version 1.4.0 - Steel
19 | ---------------------
20 |
21 | - Enhancement: All major objects now boasts unique IDs for increased stability (#266)
22 | - Enhancement: Stability improvements; Context and Instance are now much closer related (88478e9)
23 | - Bugfix: InstancePlugin collector will error out util.publish() with multiple instances (#268)
24 | - Enhancement: Simplified Iterator (#264)
25 |
26 | Version 1.3.1
27 | -------------
28 |
29 | - Enhancement: pyblish.api.deregister_callback now throws an error on missing callback
30 |
31 | Version 1.3.0
32 | -------------
33 |
34 | - Explicit plug-ins (#242)
35 | - Simplified CLI
36 | - CLI --data now handles inproperly formatted data (by treating it as strings)
37 | - DEPRECATED: Services
38 | - DEPRECATED: pyblish.logic.process
39 | - BACKWARD INCOMPATIBLE: Vendored packages nose, yaml and coverage no longer included
40 | - BACKWARD INCOMPATIBLE: CLI side-car configuration files no longer picked up.
41 | - BACKWARD INCOMPATIBLE: CLI side-car data files no longer picked up.
42 |
43 | Version 1.2.3
44 | -------------
45 |
46 | - Callbacks (#238)
47 | - Iterator (#239)
48 |
49 | Version 1.2.2
50 | -------------
51 |
52 | - Added icons for integrations under /icons
53 | - Added support for instances with multiple families (#231)
54 |
55 | Version 1.2.1
56 | -------------
57 |
58 | - Feature: Actions (see #142)
59 | - Enhancement: All instances are now given a default family, where none is provided, called "default".
60 | - Enhancement: Added deprecation warnings; quiet by default, run Python with -Wdefault to see them.
61 | - Enhancement: In favour of maintainability, the dict-like property of Context was simplified; no behavioural difference.
62 | - Enhancement: Multiple instances of the same name now officially supported.
63 | - DEPRECATED: Context/Instance `.data` is now a pure dictionary.
64 | - DEPRECATED: Context/Instance `.add` and `.remove`
65 |
66 | Version 1.2.0
67 | -------------
68 |
69 | - BACKWARD INCOMPATIBLE: Directly registered plug-ins are now also checked for duplicates; before, they overwrite those discovered on disk.
70 | - BACKWARD INCOMPATIBLE: `register_plugin` now checks the proposed registered plug-in for correctness, and throws an exception otherwise.
71 | - BACKWARD INCOMPATIBLE: `regex` has been deprecated from `discover()` and does nothing.
72 | - BACKWARD INCOMPATIBLE: `type` has been deprecated from `discover()` and does nothing.
73 | - Enhancement: Implementation of discover() has been greatly simplified, made more robust and reusable.
74 |
75 | Version 1.1.6
76 | -------------
77 |
78 | - Bugfix: Duplicate instances was allowed (#219)
79 | - Added pyblish.plugin.plugins_from_module
80 |
81 | Version 1.1.5
82 | -------------
83 |
84 | - Bugfix: Deprecated function pyblish.api.current_host fixed (#215)
85 |
86 | Version 1.1.4
87 | -------------
88 |
89 | - Feature: Added support for `"MyInstance" in context`
90 | - Enhancement: Removing the need for @pyblish.api.log (#213)
91 | - Bugfix: Negative collectors (#210)
92 |
93 | Version 1.1.3
94 | -------------
95 |
96 | - Bugfix: Decrementing order actually works now. (#206)
97 | - Feature: Dict-like getting of instances from Context (#208)
98 | - Feature: Host registration (#177)
99 | - `collect` and `integrate` now available via pyblish.util
100 | - "asset" available in context.data("result") for forwards compatibility
101 |
102 | Version 1.1.2
103 | -------------
104 |
105 | - Logic: Excluding SimplePlugin and Selectors from Default test (See #198)
106 | - BACKWARDS INCOMPATIBLE order now supports being decremented (see #199)
107 |
108 | Version 1.1.1
109 | -------------
110 |
111 | - Enhancement: Hosts limit, not allow (see #194)
112 | - Enhancement: CLI outputs less, more concise information
113 | - Enhancement: Lowering logging level for plug-ins skipped during discovery to DEBUG
114 | - Enhancement: Underscore-prefixed plug-ins are hidden from discovery (see #196)
115 | - Bugfix: Discover ignores non-Python files (see #192)
116 |
117 | Version 1.1.0
118 | -------------
119 |
120 | - Feature: Dependency Injection (see #127)
121 | - Feature: SimplePlugin (see #186)
122 | - Feature: In-memory plug-ins (see #140)
123 | - Feature: Custom test (see #183)
124 | - Feature: create_instance(name, **kwargs) (see #187)
125 | - Preview: Asset (see #188)
126 | - Enhancement: Logic unified between pyblish.util and pyblish.cli
127 | - Bugfix: Order now works with pyblish.util and pyblish.cli (see #178)
128 | - Standardised time format (see #181)
129 | - pyblish.util minified. For data visualisation, refer to pyblish-qml
130 | - Bugfix: True singleton configuration (see #182)
131 | - Added pyblish.lib.ItemList
132 | - API: Added Plugin.label
133 | - API: Added Plugin.active
134 | - API: Added pyblish.api.register_test()
135 | - API: Added pyblish.api.deregister_test()
136 | - API: Added pyblish.api.registered_test()
137 | - API: Added pyblish.api.plugins_by_instance()
138 | - API: New defaults for `hosts` and `families` of plug-ins. (see #176)
139 | - API: Added pyblish.api.register_plugin()
140 | - API: Added pyblish.api.deregister_plugin()
141 | - API: Added pyblish.api.registered_plugins()
142 | - API: Added pyblish.api.deregister_all_plugins()
143 | - API: Added pyblish.api.register_service()
144 | - API: Added pyblish.api.deregister_service()
145 | - API: Added pyblish.api.registered_services()
146 | - API: Added pyblish.api.deregister_all_services()
147 | - API: Renamed pyblish.api.sort() to pyblish.api.sort_plugins(), original deprecated
148 | - API: Renamed pyblish.api.deregister_all -> deregister_all_paths, original deprecated
149 | - BACKWARD INCOMPATIBLE You can no longer use both process_instance and process_context in the same plug-in; process_instance takes precedence.
150 | - BACKWARD INCOMPATIBLE Removed Extractor.compute_commit_dir
151 | - BACKWARD INCOMPATIBLE Removed Extractor.commit
152 | - BACKWARD INCOMPATIBLE Removed validate_naming_convention plug-in
153 | - Known issue: pyblish.logic.process.next_plugin yields wrong result
154 |
155 | Version 1.0.16
156 | --------------
157 |
158 | - Feature: The Pyblish CLI is back!
159 | - API: Added pyblish.api.sort()
160 | - API: Added pyblish.api.current_host()
161 | - API: Plug-in paths can no longer be modified by altering
162 | the results returned from pyblish.api.plugin_paths()
163 | - API: Paths are no longer made absolute during registration.
164 |
165 | Version 1.0.15
166 | --------------
167 |
168 | - API: Plugin.repair_* documented and implemented by default
169 | - API: Added lib.where()
170 | - API: Added `.id` attribute to instances and plug-ins
171 |
172 | Version 1.0.14
173 | --------------
174 |
175 | - Added pyblish.api.version
176 | - Matched verbosity level from processing context as the processing of instances.
177 | - Processing logic change; processing of plug-in *without* compatible instances will now *not* yield anything; previously it yielded a pair of (None, None).
178 |
179 | Version 1.0.13
180 | --------------
181 |
182 | - Added pyblish.api.sort_plugins
183 | - Added ordered output to pyblish.api.discover
184 | - pyblish.api.plugins_by_family now yields correct results
185 | for plug-ins with wildcard families.
186 | - Refactored main.py into util.py
187 |
188 | Version 1.0.12
189 | --------------
190 |
191 | - plugin.py reloadable without loosing currently loaded plug-ins
192 | - Basic plug-in manager "Manager"
193 | - Simplified configuration (no more user or custom configuration)
194 | - Simplifying discover() (no more indirection)
195 | - Adding default logger (good bye "No logger found..")
196 | - Temporarily removig CLI
197 | - Context is no longer a Singleton
198 | - Added forwards compatibility for Collector plug-in
199 |
200 | Version 1.0.11
201 | --------------
202 |
203 | - Added ability to process individual instances from within context
204 |
205 | Version 1.0.10
206 | --------------
207 |
208 | - Fixing PyPI installation
209 |
210 | Version 1.0.9
211 | -------------
212 |
213 | - Requires. Plug-ins may now specify a version with which they
214 | are compatible with, using [iscompatible][] which is a
215 | requirements.txt-like syntax for dependency specifications.
216 | - Improved logging, including visualisation of which instance
217 | is currently being processed by each plug-in.
218 | - iscompatible is now included in /vendor
219 |
220 | [iscompatible]: https://github.com/mottosso/iscompatible
221 |
222 |
223 | Version 1.0.8
224 | -------------
225 |
226 | - Nice name for Plug-ins. A plug-in can now carry an
227 | optional `name` attribute that will replace the
228 | default class-name used when visualising a plug-in
229 | name.
230 | - Configurable configuration location. Users can now specify
231 | where Pyblish will look for configuration files, via the
232 | PYBLISHCONFIGPATH variable.
233 | - Registered path no longer have to exist. To align better with
234 | paths added via environment variables or configuration, the
235 | registration of paths is now okay even though the path may
236 | not exist. A non-existing path will simply be discarded upon
237 | discovery (and log a warning message).
238 | - Auto-repair. pyblish.main.publish() now takes a auto_repair flag
239 | with which the Plugin.repair_instance method is called. If a
240 | repair fails, a message is logged, otherwise the validation is
241 | considered a success and publishing resumes.
242 | - Plug-ins for other hosts than the currently running host are
243 | discarded upon discovery. This means that they will be invisible
244 | to any incompatible host.
245 | - Optional plug-ins. A plug-in may now be marked "optional" and thus
246 | be ignored during processing by user-request using
247 | pyblish.main.publish(include_optional=False)
248 |
249 | Version 1.0.7
250 | -------------
251 |
252 | - Improved logging of pyblish.main.publish()
253 | - Refactored backend/frontend; frontends now in their own
254 | individual repositories.
255 | - Added landscape.io code-quality badge
256 |
257 | Version 1.0.6
258 | -------------
259 |
260 | - New API members: plugins_by_family, plugins_by_host and
261 | instances_by_plugin
262 | - Improved print of Context and Instance
263 | Context now prints members, like a regular list
264 | and Instance prints its name and class relation.
265 | - Implemented Context.delete() to simplify working with a Singleton.
266 |
267 | Version 1.0.5
268 | -------------
269 |
270 | - Redefined and simplified configuration
271 | - Moving API from root package to namespace "api"
272 | - Initial version of command-line interface (cli)
273 | - Initial version of package-control using cli
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ### Contributing to this project
2 |
3 | To contribute, fork this project and submit a pull-request.
4 |
5 | Your code will be reviewed and merged once it:
6 |
7 | 1. Does something useful
8 | 1. Is up to par with surrounding code
9 |
10 | Development for this project takes place in individual forks. The parent project ever only contains a single branch, a branch containing the latest working version of the project.
11 |
12 | Bugreports must include:
13 |
14 | 1. Description
15 | 2. Expected results
16 | 3. Short reproducible
17 |
18 | Feature requests must include:
19 |
20 | 1. Goal (what the feature aims to solve)
21 | 2. Motivation (why *you* think this is necessary)
22 | 3. Suggested implementation (psuedocode)
23 |
24 | Questions may also be submitted as issues.
25 |
--------------------------------------------------------------------------------
/CONTRIBUTORS.txt:
--------------------------------------------------------------------------------
1 | https://github.com/mottosso
2 | https://github.com/ljkart
3 | https://github.com/Byron
4 | https://github.com/madoodia
5 | danielottosson2@gmail.com
6 | https://github.com/davidmartinezanim
7 | https://github.com/tokejepsen
8 | https://github.com/bigroy
9 | https://github.com/mkolar
10 | https://github.com/pscadding
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 | --------------------------------------------------------------------------------
168 |
169 | SPECIAL EXCEPTION GRANTED BY COPYRIGHT HOLDERS
170 |
171 | As a special exception, copyright holders give you permission to link this
172 | library with independent modules to produce an executable, regardless of
173 | the license terms of these independent modules, and to copy and distribute
174 | the resulting executable under terms of your choice, provided that you also
175 | meet, for each linked independent module, the terms and conditions of
176 | the license of that module. An independent module is a module which is not
177 | derived from or based on this library. If you modify this library, you must
178 | extend this exception to your version of the library.
179 |
180 | Note: this exception relieves you of any obligations under sections 4 and 5
181 | of this license, and section 6 of the GNU General Public License.
182 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [![Build Status][travis-image]][travis-link]
2 | [![Build status][appveyor-image]](https://ci.appveyor.com/project/mottosso/pyblish)
3 | [![Coverage Status][cover-image]][cover-link]
4 | [![PyPI version][pypi-image]][pypi-link]
5 | [![Code Health][landscape-image]][landscape-repo]
6 | [![Gitter][gitter-image]](https://gitter.im/pyblish/pyblish?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7 |
8 | [](https://www.youtube.com/watch?v=j5uUTW702-U)
9 |
10 | Test-driven content creation for collaborative, creative projects.
11 |
12 | - [Wiki](../../wiki)
13 | - [Learn by Example](http://learn.pyblish.com)
14 |
15 |
16 |
17 |
18 |
19 | ### Introduction
20 |
21 | Pyblish is a modular framework, consisting of many sub-projects. This project contains the primary API upon which all other projects build.
22 |
23 | You may use this project as-is, or in conjunction with surrounding projects - such as [pyblish-maya][] for integration with Autodesk Maya, [pyblish-qml][] for a visual front-end and [pyblish-starter][] for a starting point your publishing pipeline.
24 |
25 | [pyblish-maya]: https://github.com/pyblish/pyblish-maya
26 | [pyblish-qml]: https://github.com/pyblish/pyblish-qml
27 | [pyblish-starter]: http://pyblish.com/pyblish-starter
28 |
29 | - [Browse All Projects](https://github.com/pyblish)
30 |
31 |
32 |
33 |
34 |
35 | ### Installation
36 |
37 | pyblish-base is available on PyPI.
38 |
39 | ```bash
40 | $ pip install pyblish-base
41 | ```
42 |
43 | Like all other Pyblish projects, it may also be cloned as-is via Git and added to your PYTHONPATH.
44 |
45 | ```bash
46 | $ git clone https://github.com/pyblish/pyblish-base.git
47 | $ # Windows
48 | $ set PYTHONPATH=%cd%\pyblish-base
49 | $ # Unix
50 | $ export PYTHONPATH=$(pwd)/pyblish-base
51 | ```
52 |
53 |
54 |
55 |
56 |
57 | ### Usage
58 |
59 | Refer to the [getting started guide](http://learn.pyblish.com) for a gentle introduction to the framework and [the forums](http://forums.pyblish.com) for tips and tricks.
60 |
61 | - [Learn Pyblish By Example](http://learn.pyblish.com)
62 | - [Pyblish Starter - an example pipeline](http://pyblish.com/pyblish-starter)
63 | - [Forums](http://forums.pyblish.com)
64 |
65 | [travis-image]: https://travis-ci.org/pyblish/pyblish-base.svg?branch=master
66 | [travis-link]: https://travis-ci.org/pyblish/pyblish-base
67 |
68 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/github/pyblish/pyblish-base?svg=true
69 |
70 | [cover-image]: https://coveralls.io/repos/pyblish/pyblish-base/badge.svg
71 | [cover-link]: https://coveralls.io/r/pyblish/pyblish-base
72 | [pypi-image]: https://badge.fury.io/py/pyblish-base.svg
73 | [pypi-link]: http://badge.fury.io/py/pyblish
74 | [landscape-image]: https://landscape.io/github/pyblish/pyblish-base/master/landscape.png
75 | [landscape-repo]: https://landscape.io/github/pyblish/pyblish-base/master
76 | [gitter-image]: https://badges.gitter.im/Join%20Chat.svg
77 |
78 |
79 |
80 | ### Upload to PyPI
81 |
82 | To make a new release onto PyPI, you'll need to be mottosso and type this.
83 |
84 | ```bash
85 | cd pyblish-base
86 | python .\setup.py sdist bdist_wheel
87 | python -m twine upload .\dist\*
88 | ```
89 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | Pyblish
2 | =======
3 |
4 | Plug-in driven automation framework for content.
5 |
6 | Built for platform, software and language agnosticism. Free, community-driven and licensed under LGPLv3.
7 |
8 | `See our website`_ for more information
9 |
10 | .. _`See our website`: http://pyblish.com
11 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - PYTHON: "C:\\Python27"
4 | PYTHON_VERSION: "2.7.8"
5 | PYTHON_ARCH: "32"
6 |
7 | - PYTHON: "C:\\Python27-x64"
8 | PYTHON_VERSION: "2.7.8"
9 | PYTHON_ARCH: "64"
10 |
11 | - PYTHON: "C:\\Python33"
12 | PYTHON_VERSION: "3.3.5"
13 | PYTHON_ARCH: "32"
14 |
15 | - PYTHON: "C:\\Python33-x64"
16 | PYTHON_VERSION: "3.3.5"
17 | PYTHON_ARCH: "64"
18 |
19 | - PYTHON: "C:\\Python34"
20 | PYTHON_VERSION: "3.4.1"
21 | PYTHON_ARCH: "32"
22 |
23 | - PYTHON: "C:\\Python34-x64"
24 | PYTHON_VERSION: "3.4.1"
25 | PYTHON_ARCH: "64"
26 |
27 | - PYTHON: "C:\\Python35"
28 | PYTHON_VERSION: "3.5.1"
29 | PYTHON_ARCH: "32"
30 |
31 | - PYTHON: "C:\\Python35-x64"
32 | PYTHON_VERSION: "3.5.1"
33 | PYTHON_ARCH: "64"
34 |
35 | install:
36 | - SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%
37 | - pip install nose
38 | - pip install coverage
39 |
40 | build_script:
41 | - python run_coverage.py
42 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Publish.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Publish.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Publish"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Publish"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import sys
4 | import os
5 | import sphinx
6 |
7 | src_path = os.path.abspath('..')
8 | if not src_path in sys.path:
9 | sys.path.insert(0, src_path)
10 |
11 | import pyblish
12 |
13 | extensions = [
14 | 'sphinx.ext.autodoc',
15 | 'sphinx.ext.autosummary',
16 | 'sphinx.ext.viewcode',
17 | 'sphinx.ext.autodoc',
18 | ]
19 |
20 | if sphinx.version_info >= (1, 3):
21 | extensions.append('sphinx.ext.napoleon')
22 | else:
23 | extensions.append('sphinxcontrib.napoleon')
24 |
25 | templates_path = ['_templates']
26 | source_suffix = '.rst'
27 | master_doc = 'index'
28 |
29 | project = u'Pyblish'
30 | copyright = u'2014, Marcus Ottosson'
31 | version = pyblish.__version__
32 | release = version
33 |
34 | exclude_patterns = []
35 | pygments_style = 'sphinx'
36 |
37 |
38 | # -- Options for HTML output ----------------------------------------------
39 |
40 | if os.environ.get('READTHEDOCS', None) != 'True':
41 | try:
42 | import sphinx_rtd_theme
43 | html_theme = 'sphinx_rtd_theme'
44 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
45 | except ImportError:
46 | pass
47 |
48 | html_static_path = ['_static']
49 | htmlhelp_basename = 'Pyblishdoc'
50 |
51 |
52 | # -- Options for LaTeX output ---------------------------------------------
53 |
54 | latex_elements = {}
55 |
56 | latex_documents = [
57 | ('index', 'Pyblish.tex', u'Pyblish Documentation',
58 | u'Marcus Ottosson', 'manual'),
59 | ]
60 |
61 | man_pages = [
62 | ('index', 'pyblish', u'Pyblish Documentation',
63 | [u'Marcus Ottosson'], 1)
64 | ]
65 |
66 | texinfo_documents = [
67 | ('index', 'Pyblish', u'Pyblish Documentation',
68 | u'Marcus Ottosson', 'Pyblish', 'Quality Assurance for Content',
69 | 'Miscellaneous'),
70 | ]
71 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 |
2 | .. image:: logo_small.png
3 |
4 | API documentation for Pyblish v\ |version|.
5 |
6 | .. module:: pyblish.plugin
7 |
8 | Objects
9 | =======
10 |
11 | Central objects used throughout Pyblish.
12 |
13 | .. autosummary::
14 | :nosignatures:
15 |
16 | AbstractEntity
17 | Context
18 | Instance
19 | Plugin
20 | Selector
21 | Validator
22 | Extractor
23 | Conformer
24 |
25 | Functions
26 | =========
27 |
28 | Helper utilities.
29 |
30 | .. autosummary::
31 | :nosignatures:
32 |
33 | discover
34 | plugin_paths
35 | registered_paths
36 | configured_paths
37 | environment_paths
38 | register_plugin_path
39 | deregister_plugin_path
40 | deregister_all
41 | plugins_by_family
42 | plugins_by_host
43 | instances_by_plugin
44 |
45 | .. module:: pyblish
46 |
47 | Configuration
48 | =============
49 |
50 | .. autosummary::
51 | :nosignatures:
52 |
53 | Config
54 |
55 | .. module:: pyblish.lib
56 |
57 | Library
58 | =======
59 |
60 | .. autosummary::
61 | :nosignatures:
62 |
63 | log
64 | format_filename
65 |
66 | .. module:: pyblish.error
67 |
68 | Exceptions
69 | ==========
70 |
71 | Exceptions raised that are specific to Pyblish.
72 |
73 | .. autosummary::
74 | :nosignatures:
75 |
76 | PyblishError
77 | SelectionError
78 | ValidationError
79 | ExtractionError
80 | ConformError
81 |
82 | .. module:: pyblish.plugin
83 |
84 |
85 | AbstractEntity
86 | --------------
87 |
88 | Superclass to Context and Instance, providing the data plug-in to plug-in
89 | API via the data member.
90 |
91 | .. autoclass:: AbstractEntity
92 | :members:
93 | :undoc-members:
94 |
95 | Context
96 | -------
97 |
98 | The context is a container of one or more objects of type :class:`Instance` along with metadata to describe them all; such as the current working directory or logged on user.
99 |
100 | .. autoclass:: Context
101 | :members:
102 | :undoc-members:
103 |
104 | Instance
105 | --------
106 |
107 | An instance describes one or more items in a working scene; you can think of it as the counter-part of a file on disk - once the file has been loaded, it's an `instance`.
108 |
109 | .. autoclass:: Instance
110 | :members:
111 | :undoc-members:
112 |
113 |
114 | Plugin
115 | ------
116 |
117 | As a plug-in driven framework, any action is implemented as a plug-in and this is the superclass from which all plug-ins are derived. The superclass defines behaviour common across all plug-ins, such as its internally executed method :meth:`Plugin.process` or it's virtual members :meth:`Plugin.process_instance` and :meth:`Plugin.process_context`.
118 |
119 | Each plug-in MAY define one or more of the following attributes prior to being useful to Pyblish.
120 |
121 | - :attr:`Plugin.hosts`
122 | - :attr:`Plugin.optional`
123 | - :attr:`Plugin.version`
124 |
125 | Some of which are MANDATORY, others which are OPTIONAL. See each corresponding subclass for details.
126 |
127 | - :class:`Selector`
128 | - :class:`Validator`
129 | - :class:`Extractor`
130 | - :class:`Conformer`
131 |
132 |
133 | .. autoclass:: Plugin
134 | :members:
135 | :undoc-members:
136 |
137 | Selector
138 | --------
139 |
140 | A selector finds instances within a working file.
141 |
142 | .. note:: The following attributes must be present when implementing this plug-in.
143 |
144 | - :attr:`Selector.hosts`
145 | - :attr:`Selector.version`
146 |
147 | .. autoclass:: Selector
148 | :members:
149 | :undoc-members:
150 |
151 | Validator
152 | ---------
153 |
154 | A validator validates selected instances.
155 |
156 | .. note:: The following attributes must be present when implementing this plug-in.
157 |
158 | - :attr:`Plugin.hosts`
159 | - :attr:`Plugin.version`
160 | - :attr:`Validator.families`
161 |
162 | .. autoclass:: Validator
163 | :members:
164 | :undoc-members:
165 |
166 | Extractor
167 | ---------
168 |
169 | Extractors are responsible for serialising selected data into a format suited for persistence on disk. Keep in mind that although an extractor does place file on disk, it isn't responsible for the final destination of files. See :class:`Conformer` for more information.
170 |
171 | .. note:: The following attributes must be present when implementing this plug-in.
172 |
173 | - :attr:`Plugin.hosts`
174 | - :attr:`Plugin.version`
175 | - :attr:`Extractor.families`
176 |
177 | .. autoclass:: Extractor
178 | :members:
179 | :undoc-members:
180 |
181 | Conformer
182 | ---------
183 |
184 | The conformer, also known as `integrator`, integrates data produced by extraction.
185 |
186 | Its responsibilities include:
187 |
188 | 1. Placing files into their final destination
189 | 2. To manage and increment versions, typically involving a third-party versioning library.
190 | 3. To notify artists of events
191 | 4. To provide hooks for out-of-band processes
192 |
193 | .. note:: The following attributes must be present when implementing this plug-in.
194 |
195 | - :attr:`Plugin.hosts`
196 | - :attr:`Plugin.version`
197 | - :attr:`Conformer.families`
198 |
199 | .. autoclass:: Conformer
200 | :members:
201 | :undoc-members:
202 |
203 |
204 | discover
205 | --------
206 |
207 | .. autofunction:: discover
208 |
209 | plugin_paths
210 | ------------
211 |
212 | .. autofunction:: plugin_paths
213 |
214 | registered_paths
215 | ----------------
216 |
217 | .. autofunction:: registered_paths
218 |
219 | configured_paths
220 | ----------------
221 |
222 | .. autofunction:: configured_paths
223 |
224 | environment_paths
225 | -----------------
226 |
227 | .. autofunction:: environment_paths
228 |
229 | register_plugin_path
230 | --------------------
231 |
232 | .. autofunction:: register_plugin_path
233 |
234 | deregister_plugin_path
235 | ----------------------
236 |
237 | .. autofunction:: deregister_plugin_path
238 |
239 | deregister_all
240 | --------------
241 |
242 | .. autofunction:: deregister_all
243 |
244 | plugins_by_family
245 | -----------------
246 |
247 | .. autofunction:: plugins_by_family
248 |
249 | plugins_by_host
250 | ----------------
251 |
252 | .. autofunction:: plugins_by_host
253 |
254 | instances_by_plugin
255 | -------------------
256 |
257 | .. autofunction:: instances_by_plugin
258 |
259 | .. module:: pyblish
260 |
261 | Config
262 | ------
263 |
264 | .. autoclass:: Config
265 | :members:
266 |
267 | .. module:: pyblish.lib
268 |
269 | log
270 | ---
271 |
272 | .. autofunction:: log
273 |
274 |
275 | format_filename
276 | ---------------
277 |
278 | .. autofunction:: format_filename
279 |
280 |
281 | .. module:: pyblish.error
282 |
283 |
284 | PyblishError
285 | ------------
286 |
287 | .. autoclass:: PyblishError
288 | :members:
289 | :undoc-members:
290 |
291 | SelectionError
292 | --------------------
293 |
294 | .. autoclass:: SelectionError
295 | :members:
296 | :undoc-members:
297 |
298 | ValidationError
299 | ---------------
300 |
301 | .. autoclass:: ValidationError
302 | :members:
303 | :undoc-members:
304 |
305 | ExtractionError
306 | ---------------
307 |
308 | .. autoclass:: ExtractionError
309 | :members:
310 | :undoc-members:
311 |
312 | ConformError
313 | ------------
314 |
315 | .. autoclass:: ConformError
316 | :members:
317 | :undoc-members:
318 |
--------------------------------------------------------------------------------
/docs/logo_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pyblish/pyblish-base/03cda36b26010642bcbdc8dbf2f256f298742f9f/docs/logo_small.png
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Publish.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Publish.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/docs/make_html.bat:
--------------------------------------------------------------------------------
1 | make html
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | sphinxcontrib-napoleon
--------------------------------------------------------------------------------
/pyblish/__init__.py:
--------------------------------------------------------------------------------
1 | """Pyblish initialisation
2 |
3 | Attributes:
4 | _registered_paths: Currently registered plug-in paths.
5 | _registered_plugins: Currently registered plug-ins.
6 |
7 | """
8 |
9 | from .version import version, version_info, __version__
10 |
11 |
12 | _registered_paths = list()
13 | _registered_callbacks = dict()
14 | _registered_plugins = dict()
15 | _registered_services = dict()
16 | _registered_test = dict()
17 | _registered_hosts = list()
18 | _registered_targets = list()
19 | _registered_gui = list()
20 | _registered_plugin_filters = list()
21 |
22 |
23 | __all__ = [
24 | "version",
25 | "version_info",
26 | "__version__",
27 | "_registered_paths",
28 | "_registered_callbacks",
29 | "_registered_plugins",
30 | "_registered_services",
31 | "_registered_test",
32 | "_registered_hosts",
33 | "_registered_targets",
34 | "_registered_gui",
35 | "_registered_plugin_filters"
36 | ]
37 |
--------------------------------------------------------------------------------
/pyblish/__main__.py:
--------------------------------------------------------------------------------
1 | """Pyblish package interface
2 |
3 | This makes the Pyblish package into an executable, via cli.py
4 |
5 | """
6 |
7 | from . import cli
8 |
9 | if __name__ == '__main__':
10 | cli.main(obj={}, prog_name="pyblish")
11 |
--------------------------------------------------------------------------------
/pyblish/api.py:
--------------------------------------------------------------------------------
1 | """Expose common functionality
2 |
3 | Use this in plugins, integrations and extensions of Pyblish.
4 | This part of the library is what will change the least and
5 | attempt to maintain backwards- and forwards-compatibility.
6 |
7 | This way, we as developers are free to refactor the library
8 | without breaking any of your tools.
9 |
10 | .. note:: Contributors, don't use this in any other module internal
11 | to Pyblish or a cyclic dependency will be created. This is only
12 | to be used by end-users of the library and from
13 | integrations/extensions of Pyblish.
14 |
15 | """
16 |
17 | from __future__ import absolute_import
18 |
19 | from . import version
20 | import getpass
21 | import os
22 |
23 | from .plugin import (
24 | Context,
25 | Instance,
26 |
27 | Action,
28 | Category,
29 | Separator,
30 |
31 | # Matching algorithms
32 | Subset,
33 | Intersection,
34 | Exact,
35 |
36 | Asset,
37 | Plugin,
38 | Validator,
39 | Extractor,
40 | Integrator,
41 | Collector,
42 | discover,
43 |
44 | ContextPlugin,
45 | InstancePlugin,
46 |
47 | CollectorOrder,
48 | ValidatorOrder,
49 | ExtractorOrder,
50 | IntegratorOrder,
51 |
52 | register_host,
53 | registered_hosts,
54 | deregister_host,
55 | deregister_all_hosts,
56 |
57 | current_target,
58 | register_target,
59 | registered_targets,
60 | deregister_target,
61 | deregister_all_targets,
62 |
63 | register_plugin,
64 | deregister_plugin,
65 | deregister_all_plugins,
66 | registered_plugins,
67 |
68 | plugin_paths,
69 | register_plugin_path,
70 | deregister_plugin_path,
71 | deregister_all_paths,
72 |
73 | register_service,
74 | deregister_service,
75 | deregister_all_services,
76 | registered_services,
77 |
78 | register_callback,
79 | deregister_callback,
80 | deregister_all_callbacks,
81 | registered_callbacks,
82 |
83 | register_discovery_filter,
84 | deregister_discovery_filter,
85 | deregister_all_discovery_filters,
86 | registered_discovery_filters,
87 |
88 | sort as sort_plugins,
89 |
90 | registered_paths,
91 | environment_paths,
92 | current_host,
93 | )
94 |
95 | from .lib import (
96 | log,
97 | time as __time,
98 | emit,
99 | main_package_path as __main_package_path
100 | )
101 |
102 | from .logic import (
103 | plugins_by_family,
104 | plugins_by_host,
105 | plugins_by_instance,
106 | plugins_by_targets,
107 | instances_by_plugin,
108 | register_test,
109 | deregister_test,
110 | registered_test,
111 |
112 | register_gui,
113 | registered_guis,
114 | deregister_gui,
115 |
116 | default_test as __default_test,
117 | )
118 |
119 | from .error import (
120 | PyblishError,
121 | SelectionError,
122 | ValidationError,
123 | ExtractionError,
124 | ConformError,
125 | NoInstancesError
126 | )
127 |
128 | from .compat import (
129 | deregister_all,
130 | sort,
131 | Selector,
132 | Conformer,
133 | format_filename,
134 | )
135 |
136 |
137 | def __init__():
138 | """Initialise Pyblish
139 |
140 | This function registered default services,
141 | hosts and tests. It is idempotent and thread-safe.
142 |
143 | """
144 |
145 | # Register default services
146 | register_service("time", __time)
147 | register_service("user", getpass.getuser())
148 | register_service("context", None)
149 | register_service("instance", None)
150 |
151 | # Register default host
152 | register_host("python")
153 |
154 | # Register hosts from environment "PYBLISHHOSTS"
155 | for host in os.environ.get("PYBLISH_HOSTS", "").split(os.pathsep):
156 | if not host:
157 | continue
158 |
159 | register_host(host)
160 |
161 | # Register targets for current session
162 | for target in os.environ.get("PYBLISH_TARGETS", "").split(os.pathsep):
163 | if not target:
164 | continue
165 |
166 | register_target(target)
167 |
168 | # Register default path
169 | register_plugin_path(os.path.join(__main_package_path(), "plugins"))
170 |
171 | # Register default test
172 | register_test(__default_test)
173 |
174 |
175 | __init__()
176 |
177 |
178 | __all__ = [
179 | # Base objects
180 | "Context",
181 | "Instance",
182 | "Asset",
183 |
184 | # Matching algorithms
185 | "Subset",
186 | "Intersection",
187 | "Exact",
188 |
189 | "Plugin",
190 | "Action",
191 | "Category",
192 | "Separator",
193 |
194 | # SVEC plug-ins
195 | "Collector",
196 | "Selector",
197 | "Validator",
198 | "Extractor",
199 | "Conformer",
200 | "Integrator",
201 |
202 | "ContextPlugin",
203 | "InstancePlugin",
204 |
205 | "CollectorOrder",
206 | "ValidatorOrder",
207 | "ExtractorOrder",
208 | "IntegratorOrder",
209 |
210 | # Plug-in utilities
211 | "discover",
212 |
213 | "plugin_paths",
214 | "registered_paths",
215 | "environment_paths",
216 |
217 | "register_host",
218 | "registered_hosts",
219 | "deregister_host",
220 | "deregister_all_hosts",
221 |
222 | "register_plugin",
223 | "deregister_plugin",
224 | "deregister_all_plugins",
225 | "registered_plugins",
226 |
227 | "register_service",
228 | "deregister_service",
229 | "deregister_all_services",
230 | "registered_services",
231 |
232 | "register_callback",
233 | "deregister_callback",
234 | "deregister_all_callbacks",
235 | "registered_callbacks",
236 |
237 | "register_plugin_path",
238 | "deregister_plugin_path",
239 | "deregister_all_paths",
240 |
241 | "register_test",
242 | "deregister_test",
243 | "registered_test",
244 |
245 | "register_gui",
246 | "registered_guis",
247 | "deregister_gui",
248 |
249 | "plugins_by_family",
250 | "plugins_by_host",
251 | "plugins_by_instance",
252 | "instances_by_plugin",
253 |
254 | "current_target",
255 | "register_target",
256 | "registered_targets",
257 | "deregister_target",
258 | "deregister_all_targets",
259 |
260 | "sort_plugins",
261 | "format_filename",
262 | "current_host",
263 | "sort_plugins",
264 |
265 | "version",
266 |
267 | # Utilities
268 | "log",
269 | "emit",
270 |
271 | # Exceptions
272 | "PyblishError",
273 | "SelectionError",
274 | "ValidationError",
275 | "ExtractionError",
276 | "ConformError",
277 | "NoInstancesError",
278 |
279 | # Compatibility
280 | "deregister_all",
281 | "sort",
282 | ]
283 |
--------------------------------------------------------------------------------
/pyblish/compat.py:
--------------------------------------------------------------------------------
1 | """Compatibility module"""
2 |
3 | import re
4 | import inspect
5 | import warnings
6 | from . import plugin, lib, logic
7 | from .vendor import six
8 |
9 | if six.PY2:
10 | get_arg_spec = inspect.getargspec
11 | else:
12 | get_arg_spec = inspect.getfullargspec
13 |
14 | # Aliases
15 | Selector = plugin.Collector
16 | Conformer = plugin.Integrator
17 |
18 | _filename_ascii_strip_re = re.compile(r'[^-\w.]')
19 | _windows_device_files = ('CON', 'AUX', 'COM1', 'COM2', 'COM3', 'COM4',
20 | 'LPT1', 'LPT2', 'LPT3', 'PRN', 'NUL')
21 |
22 |
23 | def sort(*args, **kwargs):
24 | warnings.warn("pyblish.api.sort has been deprecated; "
25 | "use pyblish.api.sort_plugins")
26 | return plugin.sort(*args, **kwargs)
27 |
28 |
29 | def deregister_all(*args, **kwargs):
30 | warnings.warn("pyblish.api.deregister_all has been deprecated; "
31 | "use pyblish.api.deregister_all_paths")
32 | return plugin.deregister_all_paths(*args, **kwargs)
33 |
34 |
35 | # AbstractEntity
36 | #
37 | # The below members represent backwards compatibility
38 | # features, kept separate for maintainability as they
39 | # will no longer be updated and eventually discarded.
40 |
41 |
42 | @lib.deprecated
43 | def set_data(self, key, value):
44 | """DEPRECATED - USE .data DICTIONARY DIRECTLY
45 |
46 | Modify/insert data into entity
47 |
48 | Arguments:
49 | key (str): Name of data to add
50 | value (object): Value of data to add
51 |
52 | """
53 |
54 | self.data[key] = value
55 |
56 |
57 | @lib.deprecated
58 | def remove_data(self, key):
59 | """DEPRECATED - USE .data DICTIONARY DIRECTLY
60 |
61 | Remove data from entity
62 |
63 | Arguments;
64 | key (str): Name of data to remove
65 |
66 | """
67 |
68 | self.data.pop(key)
69 |
70 |
71 | @lib.deprecated
72 | def has_data(self, key):
73 | """DEPRECATED - USE .data DICTIONARY DIRECTLY
74 |
75 | Check if entity has key
76 |
77 | Arguments:
78 | key (str): Key to check
79 |
80 | Return:
81 | True if it exists, False otherwise
82 |
83 | """
84 |
85 | return key in self.data
86 |
87 |
88 | @lib.deprecated
89 | def add(self, other):
90 | """DEPRECATED - USE .append
91 |
92 | Add member to self
93 |
94 | This is to mimic the interface of set()
95 |
96 | """
97 |
98 | return self.append(other)
99 |
100 |
101 | @lib.deprecated
102 | def remove(self, other):
103 | """DEPRECATED - USE .pop
104 |
105 | Remove member from self
106 |
107 | This is to mimic the interface of set()
108 |
109 | """
110 |
111 | index = self.index(other)
112 | return self.pop(index)
113 |
114 |
115 | plugin.AbstractEntity.add = add
116 | plugin.AbstractEntity.remove = remove
117 | plugin.AbstractEntity.set_data = set_data
118 | plugin.AbstractEntity.remove_data = remove_data
119 | plugin.AbstractEntity.has_data = has_data
120 |
121 |
122 | # Context
123 |
124 | @lib.deprecated
125 | def create_asset(self, *args, **kwargs):
126 | return self.create_instance(*args, **kwargs)
127 |
128 |
129 | @lib.deprecated
130 | def add(self, other):
131 | return super(plugin.Context, self).append(other)
132 |
133 |
134 | plugin.Context.create_asset = create_asset
135 | plugin.Context.add = add
136 |
137 |
138 | @lib.deprecated
139 | def format_filename(filename):
140 | return filename
141 |
142 |
143 | @lib.deprecated
144 | def format_filename2(filename):
145 | return filename
146 |
147 |
148 | lib.format_filename = format_filename
149 | lib.format_filename2 = format_filename2
150 |
151 |
152 | @lib.deprecated
153 | def process(func, plugins, context, test=None):
154 | r"""Primary processing logic
155 |
156 | Takes callables and data as input, and performs
157 | logical operations on them until the currently
158 | registered test fails.
159 |
160 | If `plugins` is a callable, it is called early, before
161 | processing begins. If `context` is a callable, it will
162 | be called once per plug-in.
163 |
164 | Arguments:
165 | func (callable): Callable taking three arguments;
166 | plugin(Plugin), context(Context) and optional
167 | instance(Instance). Each must provide a matching
168 | interface to their corresponding objects.
169 | plugins (list, callable): Plug-ins to process. If a
170 | callable is provided, the return value is used
171 | as plug-ins. It is called with no arguments.
172 | context (Context, callable): Context whose instances
173 | are to be processed. If a callable is provided,
174 | the return value is used as context. It is called
175 | with no arguments.
176 | test (callable, optional): Provide custom test, defaults
177 | to the currently registered test.
178 |
179 | Yields:
180 | A result per complete process. If test fails,
181 | a TestFailed exception is returned, containing the
182 | variables used in the test. Finally, any exception
183 | thrown by `func` is yielded. Note that this is
184 | considered a bug in *your* code as you are the one
185 | supplying it.
186 |
187 | """
188 |
189 | __plugins = plugins
190 | __context = context
191 |
192 | if test is None:
193 | test = logic.registered_test()
194 |
195 | if hasattr(__plugins, "__call__"):
196 | plugins = __plugins()
197 |
198 | def gen(plugin, instances):
199 | if plugin.__instanceEnabled__ and len(instances) > 0:
200 | for instance in instances:
201 | yield instance
202 | else:
203 | yield None
204 |
205 | vars = {
206 | "nextOrder": None,
207 | "ordersWithError": list()
208 | }
209 |
210 | # Clear introspection values
211 | # TODO(marcus): Return *next* pair, this currently
212 | # returns the current pair.
213 | self = process
214 | self.next_plugin = None
215 | self.next_instance = None
216 |
217 | for Plugin in plugins:
218 | self.next_plugin = Plugin
219 | vars["nextOrder"] = Plugin.order
220 |
221 | if not test(**vars):
222 | if hasattr(__context, "__call__"):
223 | context = __context()
224 |
225 | args = get_arg_spec(Plugin.process).args
226 |
227 | # Backwards compatibility with `asset`
228 | if "asset" in args:
229 | args.append("instance")
230 |
231 | instances = logic.instances_by_plugin(context, Plugin)
232 |
233 | # Limit processing to plug-ins with an available instance
234 | if not instances and "*" not in Plugin.families:
235 | continue
236 |
237 | for instance in gen(Plugin, instances):
238 | if instance is None and "instance" in args:
239 | continue
240 |
241 | # Provide introspection
242 | self.next_instance = instance
243 |
244 | try:
245 | result = func(Plugin, context, instance)
246 |
247 | except Exception as exc:
248 | # Any exception occuring within the function
249 | # you pass is yielded, you are expected to
250 | # handle it.
251 | yield exc
252 |
253 | else:
254 | # Make note of the order at which
255 | # the potential error error occured.
256 | if result["error"]:
257 | if Plugin.order not in vars["ordersWithError"]:
258 | vars["ordersWithError"].append(Plugin.order)
259 | yield result
260 |
261 | # Clear current
262 | self.next_instance = None
263 |
264 | else:
265 | yield logic.TestFailed(test(**vars), vars)
266 | break
267 |
268 |
269 | process.next_plugin = None
270 | process.next_instance = None
271 |
272 | logic.process = process
273 |
--------------------------------------------------------------------------------
/pyblish/error.py:
--------------------------------------------------------------------------------
1 | class PyblishError(Exception):
2 | """Baseclass for all Pyblish exceptions"""
3 |
4 |
5 | class ValidationError(PyblishError):
6 | """Baseclass for validation errors"""
7 |
8 |
9 | class SelectionError(PyblishError):
10 | """Baseclass for selection errors"""
11 |
12 |
13 | class ExtractionError(PyblishError):
14 | """Baseclass for extraction errors"""
15 |
16 |
17 | class ConformError(PyblishError):
18 | """Baseclass for conforming errors"""
19 |
20 |
21 | class NoInstancesError(Exception):
22 | """Raised if no instances could be found"""
23 |
--------------------------------------------------------------------------------
/pyblish/icons/logo-32x32.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
--------------------------------------------------------------------------------
/pyblish/icons/logo-64x64.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
--------------------------------------------------------------------------------
/pyblish/lib.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import logging
4 | import datetime
5 | import warnings
6 | import traceback
7 | import functools
8 |
9 | from . import _registered_callbacks
10 | from .vendor import six
11 |
12 |
13 | def inrange(number, base, offset=0.5):
14 | r"""Evaluate whether `number` is within `base` +- `offset`
15 |
16 | Lower bound is *included* whereas upper bound is *excluded*
17 | so as to allow for ranges to be stacked up against each other.
18 | For example, an offset of 0.5 and a base of 1 evenly stacks
19 | up against a base of 2 with identical offset.
20 |
21 | Arguments:
22 | number (float): Number to consider
23 | base (float): Center of range
24 | offset (float, optional): Amount of offset from base
25 |
26 | Usage:
27 | >>> inrange(0, base=1, offset=0.5)
28 | False
29 | >>> inrange(0.4, base=1, offset=0.5)
30 | False
31 | >>> inrange(1.4, base=1, offset=0.5)
32 | True
33 | >>> # Lower bound is included
34 | >>> inrange(0.5, base=1, offset=0.5)
35 | True
36 | >>> # Upper bound is excluded
37 | >>> inrange(1.5, base=1, offset=0.5)
38 | False
39 |
40 | """
41 |
42 | return (base - offset) <= number < (base + offset)
43 |
44 |
45 | class MessageHandler(logging.Handler):
46 | def __init__(self, records, *args, **kwargs):
47 | # Not using super(), for compatibility with Python 2.6
48 | logging.Handler.__init__(self, *args, **kwargs)
49 | self.records = records
50 |
51 | def emit(self, record):
52 | if record.name.startswith("pyblish"):
53 | self.records.append(record)
54 |
55 |
56 | def extract_traceback(exception, fname=None):
57 | """Inject current traceback and store in exception.traceback.
58 |
59 | Also storing the formatted traceback on exception.formtatted_traceback.
60 |
61 | Arguments:
62 | exception (Exception): Exception object
63 | fname (str): Optionally provide a file name for the exception.
64 | This is necessary to inject the correct file path in the traceback.
65 | If plugins are registered through `api.plugin.discover`, they only
66 | show "" instead of the actual source file.
67 | """
68 | exc_type, exc_value, exc_traceback = sys.exc_info()
69 | exception.traceback = traceback.extract_tb(exc_traceback)[-1]
70 |
71 | formatted_traceback = ''.join(traceback.format_exception(
72 | exc_type, exc_value, exc_traceback))
73 | if 'File "", line' in formatted_traceback and fname is not None:
74 | _, lineno, func, msg = exception.traceback
75 | fname = os.path.abspath(fname)
76 | exception.traceback = (fname, lineno, func, msg)
77 | formatted_traceback = formatted_traceback.replace(
78 | 'File "", line',
79 | 'File "{0}", line'.format(fname))
80 | exception.formatted_traceback = formatted_traceback
81 |
82 | del(exc_type, exc_value, exc_traceback)
83 |
84 |
85 | def time():
86 | """Return ISO formatted string representation of current UTC time."""
87 | return '%sZ' % datetime.datetime.utcnow().isoformat()
88 |
89 |
90 | class ItemList(list):
91 | """List with keys
92 |
93 | Raises:
94 | KeyError is item is not in list
95 |
96 | Example:
97 | >>> Obj = type("Object", (object,), {})
98 | >>> obj = Obj()
99 | >>> obj.name = "Test"
100 | >>> l = ItemList(key="name")
101 | >>> l.append(obj)
102 | >>> l[0] == obj
103 | True
104 | >>> l["Test"] == obj
105 | True
106 | >>> try:
107 | ... l["NotInList"]
108 | ... except KeyError:
109 | ... print(True)
110 | True
111 | >>> obj == l.get("Test")
112 | True
113 | >>> l.get("NotInList") == None
114 | True
115 |
116 | """
117 |
118 | def __init__(self, key, object=list()):
119 | super(ItemList, self).__init__(object)
120 | self.key = key
121 |
122 | def __getitem__(self, index):
123 | if isinstance(index, int):
124 | return super(ItemList, self).__getitem__(index)
125 |
126 | for item in self:
127 | if getattr(item, self.key) == index:
128 | return item
129 |
130 | raise KeyError("%s not in list" % index)
131 |
132 | def get(self, key, default=None):
133 | try:
134 | return self.__getitem__(key)
135 | except KeyError:
136 | return default
137 |
138 |
139 | class classproperty(object):
140 | def __init__(self, getter):
141 | self.getter = getter
142 |
143 | def __get__(self, instance, owner):
144 | return self.getter(owner)
145 |
146 |
147 | def log(cls):
148 | """Decorator for attaching a logger to the class `cls`
149 |
150 | Loggers inherit the syntax {module}.{submodule}
151 |
152 | Example
153 | >>> @log
154 | ... class MyClass(object):
155 | ... pass
156 | >>>
157 | >>> myclass = MyClass()
158 | >>> myclass.log.info('Hello World')
159 |
160 | """
161 |
162 | module = cls.__module__
163 | name = cls.__name__
164 |
165 | # Package name appended, for filtering of LogRecord instances
166 | logname = "pyblish.%s.%s" % (module, name)
167 | cls.log = logging.getLogger(logname)
168 |
169 | # All messages are handled by root-logger
170 | cls.log.propagate = True
171 |
172 | return cls
173 |
174 |
175 | def parse_environment_paths(paths):
176 | """Given a (semi-)colon separated string of paths, return a list
177 |
178 | Example:
179 | >>> import os
180 | >>> parse_environment_paths("path1" + os.pathsep + "path2")
181 | ['path1', 'path2']
182 | >>> parse_environment_paths("path1" + os.pathsep)
183 | ['path1', '']
184 |
185 | Arguments:
186 | paths (str): Colon or semi-colon (depending on platform)
187 | separated string of paths.
188 |
189 | Returns:
190 | list of paths as string.
191 |
192 | """
193 |
194 | paths_list = list()
195 |
196 | for path in paths.split(os.pathsep):
197 | paths_list.append(path)
198 |
199 | return paths_list
200 |
201 |
202 | def get_formatter():
203 | """Return a default Pyblish formatter for logging
204 |
205 | Example:
206 | >>> import logging
207 | >>> log = logging.getLogger("myLogger")
208 | >>> handler = logging.StreamHandler()
209 | >>> handler.setFormatter(get_formatter())
210 |
211 | """
212 |
213 | formatter = logging.Formatter(
214 | '%(asctime)s - '
215 | '%(levelname)s - '
216 | '%(name)s - '
217 | '%(message)s',
218 | '%H:%M:%S')
219 | return formatter
220 |
221 |
222 | def setup_log(root='pyblish', level=logging.DEBUG):
223 | """Setup a default logger for Pyblish
224 |
225 | Example:
226 | >>> log = setup_log()
227 | >>> log.info("Hello, World")
228 |
229 | """
230 |
231 | formatter = logging.Formatter("%(levelname)s - %(message)s")
232 | handler = logging.StreamHandler()
233 | handler.setFormatter(formatter)
234 |
235 | log = logging.getLogger(root)
236 | log.propagate = True
237 | log.handlers[:] = []
238 | log.addHandler(handler)
239 |
240 | log.setLevel(level)
241 |
242 | return log
243 |
244 |
245 | def main_package_path():
246 | """Return path of main pyblish package"""
247 | lib_py_path = sys.modules[__name__].__file__
248 | package_path = os.path.dirname(lib_py_path)
249 | return package_path
250 |
251 |
252 | def emit(signal, **kwargs):
253 | """Trigger registered callbacks
254 |
255 | Keyword arguments are passed from caller to callee.
256 |
257 | Arguments:
258 | signal (string): Name of signal emitted
259 |
260 | Example:
261 | >>> import sys
262 | >>> from . import plugin
263 | >>> plugin.register_callback(
264 | ... "mysignal", lambda data: sys.stdout.write(str(data)))
265 | ...
266 | >>> emit("mysignal", data={"something": "cool"})
267 | {'something': 'cool'}
268 |
269 | """
270 |
271 | for callback in _registered_callbacks.get(signal, []):
272 | try:
273 | callback(**kwargs)
274 | except Exception:
275 | file = six.StringIO()
276 | traceback.print_exc(file=file)
277 | sys.stderr.write(file.getvalue())
278 | # Why the roundabout through StringIO?
279 | #
280 | # tests.lib.captured_stderr attempts to capture stderr
281 | # but doing so with plain print_exc() results in a type
282 | # error in Python 3. I'm not confident in Python 3 unicode
283 | # handling so there is likely a better way to solve this.
284 | #
285 | # TODO(marcus): Make it prettier
286 |
287 |
288 | def deprecated(func):
289 | """Deprecation decorator
290 |
291 | Attach this to deprecated functions or methods.
292 |
293 | """
294 |
295 | @functools.wraps(func)
296 | def wrapper(*args, **kwargs):
297 | return func(*args, **kwargs)
298 | return wrapper
299 |
--------------------------------------------------------------------------------
/pyblish/main.py:
--------------------------------------------------------------------------------
1 | import warnings
2 | from .util import *
3 |
4 | warnings.warn("main.py deprecated; use util.py")
5 |
--------------------------------------------------------------------------------
/pyblish/plugins/collect_current_date.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 | import pyblish.lib
4 |
5 |
6 | class CollectCurrentDate(pyblish.api.ContextPlugin):
7 | """Inject the current time into the Context"""
8 |
9 | order = pyblish.api.CollectorOrder
10 | label = "Current date"
11 |
12 | def process(self, context):
13 | context.data['date'] = pyblish.lib.time()
14 |
--------------------------------------------------------------------------------
/pyblish/plugins/collect_current_user.py:
--------------------------------------------------------------------------------
1 |
2 | import getpass
3 | import pyblish.api
4 |
5 |
6 | class CollectCurrentUser(pyblish.api.ContextPlugin):
7 | """Inject the currently logged on user into the Context"""
8 |
9 | order = pyblish.api.CollectorOrder
10 | label = "Current user"
11 |
12 | def process(self, context):
13 | context.data['user'] = getpass.getuser()
14 |
--------------------------------------------------------------------------------
/pyblish/plugins/collect_current_working_directory.py:
--------------------------------------------------------------------------------
1 |
2 | import os
3 | import pyblish.api
4 |
5 |
6 | class CollectCurrentWorkingDirectory(pyblish.api.ContextPlugin):
7 | """Inject the current working directory into Context"""
8 |
9 | order = pyblish.api.CollectorOrder
10 | label = "Current working directory"
11 |
12 | def process(self, context):
13 | context.data['cwd'] = os.getcwd()
14 |
--------------------------------------------------------------------------------
/pyblish/util.py:
--------------------------------------------------------------------------------
1 | """Convenience functions for general publishing"""
2 |
3 | from __future__ import absolute_import
4 |
5 | # Standard library
6 | import logging
7 | import warnings
8 |
9 | # Local library
10 | from . import api, logic, plugin, lib
11 |
12 | log = logging.getLogger("pyblish.util")
13 |
14 | __all__ = [
15 | "publish",
16 | "collect",
17 | "validate",
18 | "extract",
19 | "integrate",
20 |
21 | # Iterator counterparts
22 | "publish_iter",
23 | "collect_iter",
24 | "validate_iter",
25 | "extract_iter",
26 | "integrate_iter",
27 | ]
28 |
29 |
30 | def publish(context=None, plugins=None, targets=None):
31 | """Publish everything
32 |
33 | This function will process all available plugins of the
34 | currently running host, publishing anything picked up
35 | during collection.
36 |
37 | Arguments:
38 | context (Context, optional): Context, defaults to
39 | creating a new context
40 | plugins (list, optional): Plug-ins to include,
41 | defaults to results of discover()
42 | targets (list, optional): Targets to include for publish session.
43 |
44 | Returns:
45 | Context: The context processed by the plugins.
46 |
47 | Usage:
48 | >> context = plugin.Context()
49 | >> publish(context) # Pass..
50 | >> context = publish() # ..or receive a new
51 |
52 | """
53 |
54 | context = context if context is not None else api.Context()
55 |
56 | for _ in publish_iter(context, plugins, targets):
57 | pass
58 |
59 | return context
60 |
61 |
62 | def publish_iter(context=None, plugins=None, targets=None):
63 | """Publish iterator
64 |
65 | This function will process all available plugins of the
66 | currently running host, publishing anything picked up
67 | during collection.
68 |
69 | Arguments:
70 | context (Context, optional): Context, defaults to
71 | creating a new context
72 | plugins (list, optional): Plug-ins to include,
73 | defaults to results of discover()
74 | targets (list, optional): Targets to include for publish session.
75 |
76 | Yields:
77 | tuple of dict and Context: A tuple is returned with a dictionary and
78 | the Context object. The dictionary contains all the result
79 | information of a plugin process, and the Context is the Context
80 | after the plugin has been processed.
81 |
82 | Usage:
83 | >> context = plugin.Context()
84 | >> for result in util.publish_iter(context):
85 | print result
86 | >> for result in util.publish_iter():
87 | print result
88 |
89 | """
90 | for result in _convenience_iter(context, plugins, targets):
91 | yield result
92 |
93 | api.emit("published", context=context)
94 |
95 |
96 | def _convenience_iter(context=None, plugins=None, targets=None, order=None):
97 | # Must check against None, as objects be emptys
98 | context = api.Context() if context is None else context
99 | plugins = api.discover() if plugins is None else plugins
100 |
101 | if order is not None:
102 | plugins = list(
103 | Plugin for Plugin in plugins
104 | if lib.inrange(Plugin.order, order)
105 | )
106 |
107 | # Do not consider inactive plug-ins
108 | plugins = list(p for p in plugins if p.active)
109 | collectors = list(p for p in plugins if lib.inrange(
110 | number=p.order,
111 | base=api.CollectorOrder)
112 | )
113 |
114 | # Compute an approximation of all future tasks
115 | # NOTE: It's an approximation, because tasks are
116 | # dynamically determined at run-time by contents of
117 | # the context and families of contained instances;
118 | # each of which may differ between task.
119 | task_count = len(list(logic.Iterator(plugins, context, targets=targets)))
120 |
121 | # First pass, collection
122 | tasks_processed_count = 1
123 | for Plugin, instance in logic.Iterator(collectors,
124 | context,
125 | targets=targets):
126 | result = plugin.process(Plugin, context, instance)
127 |
128 | # Inject additional member for results here.
129 | result["progress"] = float(tasks_processed_count) / task_count
130 |
131 | tasks_processed_count += 1
132 | yield result
133 |
134 | # Exclude collectors from further processing
135 | plugins = list(p for p in plugins if p not in collectors)
136 |
137 | # Exclude plug-ins that do not have at
138 | # least one compatible instance.
139 | for Plugin in list(plugins):
140 | if Plugin.__instanceEnabled__:
141 | if not logic.instances_by_plugin(context, Plugin):
142 | plugins.remove(Plugin)
143 |
144 | # Mutable state, used in Iterator
145 | state = {
146 | "nextOrder": None,
147 | "ordersWithError": set()
148 | }
149 |
150 | # Second pass, the remainder
151 | for Plugin, instance in logic.Iterator(plugins,
152 | context,
153 | state,
154 | targets=targets):
155 | try:
156 | result = plugin.process(Plugin, context, instance)
157 | result["progress"] = (
158 | float(tasks_processed_count) / task_count
159 | )
160 |
161 | tasks_processed_count += 1
162 | except StopIteration: # End of items
163 | raise
164 |
165 | except Exception: # This is unexpected, most likely a bug
166 | log.error("An expected exception occurred.\n")
167 | raise
168 |
169 | else:
170 | # Make note of the order at which the
171 | # potential error error occured.
172 | if result["error"]:
173 | state["ordersWithError"].add(Plugin.order)
174 |
175 | if isinstance(result, Exception):
176 | log.error("An unexpected error happened: %s" % result)
177 | break
178 |
179 | error = result["error"]
180 | if error is not None:
181 | print(error)
182 |
183 | yield result
184 |
185 |
186 | def collect(context=None, plugins=None, targets=None):
187 | """Convenience function for collection-only
188 |
189 | _________ . . . . . . . . . . . . . . . . . . .
190 | | | . . . . . .
191 | | Collect |-->. Validate .-->. Extract .-->. Integrate .
192 | |_________| . . . . . . . . . . . . . . . . . . .
193 |
194 | """
195 |
196 | context = context if context is not None else api.Context()
197 | for result in collect_iter(context, plugins, targets):
198 | pass
199 |
200 | return context
201 |
202 |
203 | def validate(context=None, plugins=None, targets=None):
204 | """Convenience function for validation-only
205 |
206 | . . . . . . __________ . . . . . . . . . . . . .
207 | . . | | . . . .
208 | . Collect .-->| Validate |-->. Extract .-->. Integrate .
209 | . . . . . . |__________| . . . . . . . . . . . . .
210 |
211 | """
212 |
213 | context = context if context is not None else api.Context()
214 | for result in validate_iter(context, plugins, targets):
215 | pass
216 |
217 | return context
218 |
219 |
220 | def extract(context=None, plugins=None, targets=None):
221 | """Convenience function for extraction-only
222 |
223 | . . . . . . . . . . . . _________ . . . . . . .
224 | . . . . | | . .
225 | . Collect .-->. Validate .-->| Extract |-->. Integrate .
226 | . . . . . . . . . . . . |_________| . . . . . . .
227 |
228 | """
229 |
230 | context = context if context is not None else api.Context()
231 | for result in extract_iter(context, plugins, targets):
232 | pass
233 |
234 | return context
235 |
236 |
237 | def integrate(context=None, plugins=None, targets=None):
238 | """Convenience function for integration-only
239 |
240 | . . . . . . . . . . . . . . . . . . ___________
241 | . . . . . . | |
242 | . Collect .-->. Validate .-->. Extract .-->| Integrate |
243 | . . . . . . . . . . . . . . . . . . |___________|
244 |
245 | """
246 |
247 | context = context if context is not None else api.Context()
248 | for result in integrate_iter(context, plugins, targets):
249 | pass
250 |
251 | return context
252 |
253 |
254 | def collect_iter(context=None, plugins=None, targets=None):
255 | for result in _convenience_iter(context, plugins, targets,
256 | order=api.CollectorOrder):
257 | yield result
258 |
259 | api.emit("collected", context=context)
260 |
261 |
262 | def validate_iter(context=None, plugins=None, targets=None):
263 | for result in _convenience_iter(context, plugins, targets,
264 | order=api.ValidatorOrder):
265 | yield result
266 |
267 | api.emit("validated", context=context)
268 |
269 |
270 | def extract_iter(context=None, plugins=None, targets=None):
271 | for result in _convenience_iter(context, plugins, targets,
272 | order=api.ExtractorOrder):
273 | yield result
274 |
275 | api.emit("extracted", context=context)
276 |
277 |
278 | def integrate_iter(context=None, plugins=None, targets=None):
279 | for result in _convenience_iter(context, plugins, targets,
280 | order=api.IntegratorOrder):
281 | yield result
282 |
283 | api.emit("integrated", context=context)
284 |
285 |
286 | def _convenience(context=None, plugins=None, targets=None, order=None):
287 | context = context if context is not None else api.Context()
288 |
289 | for result in _convenience_iter(context, plugins, targets, order):
290 | pass
291 |
292 | return context
293 |
294 |
295 | # Backwards compatibility
296 | select = collect
297 | conform = integrate
298 | run = publish # Alias
299 |
300 |
301 | def publish_all(context=None, plugins=None):
302 | warnings.warn("pyblish.util.publish_all has been "
303 | "deprecated; use publish()")
304 | return publish(context, plugins)
305 |
306 |
307 | def validate_all(context=None, plugins=None):
308 | warnings.warn("pyblish.util.validate_all has been "
309 | "deprecated; use collect() followed by validate()")
310 | context = collect(context, plugins)
311 | return validate(context, plugins)
312 |
--------------------------------------------------------------------------------
/pyblish/vendor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pyblish/pyblish-base/03cda36b26010642bcbdc8dbf2f256f298742f9f/pyblish/vendor/__init__.py
--------------------------------------------------------------------------------
/pyblish/vendor/click/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | click
4 | ~~~~~
5 |
6 | Click is a simple Python module that wraps the stdlib's optparse to make
7 | writing command line scripts fun. Unlike other modules, it's based around
8 | a simple API that does not come with too much magic and is composable.
9 |
10 | In case optparse ever gets removed from the stdlib, it will be shipped by
11 | this module.
12 |
13 | :copyright: (c) 2014 by Armin Ronacher.
14 | :license: BSD, see LICENSE for more details.
15 | """
16 |
17 | # Core classes
18 | from .core import Context, BaseCommand, Command, MultiCommand, Group, \
19 | CommandCollection, Parameter, Option, Argument
20 |
21 | # Decorators
22 | from .decorators import pass_context, pass_obj, make_pass_decorator, \
23 | command, group, argument, option, confirmation_option, \
24 | password_option, version_option, help_option
25 |
26 | # Types
27 | from .types import ParamType, File, Path, Choice, IntRange, STRING, INT, \
28 | FLOAT, BOOL, UUID
29 |
30 | # Utilities
31 | from .utils import echo, get_binary_stream, get_text_stream, \
32 | format_filename, get_app_dir
33 |
34 | # Terminal functions
35 | from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
36 | progressbar, clear, style, unstyle, secho, edit, launch, getchar, \
37 | pause
38 |
39 | # Exceptions
40 | from .exceptions import ClickException, UsageError, BadParameter, \
41 | FileError, Abort
42 |
43 | # Formatting
44 | from .formatting import HelpFormatter, wrap_text
45 |
46 | # Parsing
47 | from .parser import OptionParser
48 |
49 |
50 | __all__ = [
51 | # Core classes
52 | 'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
53 | 'CommandCollection', 'Parameter', 'Option', 'Argument',
54 |
55 | # Decorators
56 | 'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
57 | 'argument', 'option', 'confirmation_option', 'password_option',
58 | 'version_option', 'help_option',
59 |
60 | # Types
61 | 'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'STRING', 'INT',
62 | 'FLOAT', 'BOOL', 'UUID',
63 |
64 | # Utilities
65 | 'echo', 'get_binary_stream', 'get_text_stream', 'format_filename',
66 | 'get_app_dir',
67 |
68 | # Terminal functions
69 | 'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
70 | 'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch',
71 | 'getchar', 'pause',
72 |
73 | # Exceptions
74 | 'ClickException', 'UsageError', 'BadParameter', 'FileError',
75 | 'Abort',
76 |
77 | # Formatting
78 | 'HelpFormatter', 'wrap_text',
79 |
80 | # Parsing
81 | 'OptionParser',
82 | ]
83 |
84 |
85 | __version__ = '3.0-dev'
86 |
--------------------------------------------------------------------------------
/pyblish/vendor/click/_bashcomplete.py:
--------------------------------------------------------------------------------
1 | import os
2 | from click.utils import echo
3 | from click.parser import split_arg_string
4 | from click.core import MultiCommand, Option
5 |
6 |
7 | COMPLETION_SCRIPT = '''
8 | %(complete_func)s() {
9 | COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
10 | COMP_CWORD=$COMP_CWORD \\
11 | %(autocomplete_var)s=complete $1 ) )
12 | return 0
13 | }
14 |
15 | complete -F %(complete_func)s -o default %(script_names)s
16 | '''
17 |
18 |
19 | def get_completion_script(prog_name, complete_var):
20 | return (COMPLETION_SCRIPT % {
21 | 'complete_func': '_%s_completion' % prog_name,
22 | 'script_names': prog_name,
23 | 'autocomplete_var': complete_var,
24 | }).strip() + ';'
25 |
26 |
27 | def resolve_ctx(cli, prog_name, args):
28 | ctx = cli.make_context(prog_name, args, resilient_parsing=True)
29 | while ctx.args and isinstance(ctx.command, MultiCommand):
30 | cmd = ctx.command.get_command(ctx, ctx.args[0])
31 | if cmd is None:
32 | return None
33 | ctx = cmd.make_context(ctx.args[0], ctx.args[1:], parent=ctx,
34 | resilient_parsing=True)
35 | return ctx
36 |
37 |
38 | def do_complete(cli, prog_name):
39 | cwords = split_arg_string(os.environ['COMP_WORDS'])
40 | cword = int(os.environ['COMP_CWORD'])
41 | args = cwords[1:cword]
42 | try:
43 | incomplete = cwords[cword]
44 | except IndexError:
45 | incomplete = ''
46 |
47 | ctx = resolve_ctx(cli, prog_name, args)
48 | if ctx is None:
49 | return True
50 |
51 | choices = []
52 | if incomplete and not incomplete[:1].isalnum():
53 | for param in ctx.command.params:
54 | if not isinstance(param, Option):
55 | continue
56 | choices.extend(param.opts)
57 | choices.extend(param.secondary_opts)
58 | elif isinstance(ctx.command, MultiCommand):
59 | choices.extend(ctx.command.list_commands(ctx))
60 |
61 | for item in choices:
62 | if item.startswith(incomplete):
63 | echo(item)
64 |
65 | return True
66 |
67 |
68 | def bashcomplete(cli, prog_name, complete_var, complete_instr):
69 | if complete_instr == 'source':
70 | echo(get_completion_script(prog_name, complete_var))
71 | return True
72 | elif complete_instr == 'complete':
73 | return do_complete(cli, prog_name)
74 | return False
75 |
--------------------------------------------------------------------------------
/pyblish/vendor/click/_textwrap.py:
--------------------------------------------------------------------------------
1 | import textwrap
2 | from contextlib import contextmanager
3 |
4 |
5 | class TextWrapper(textwrap.TextWrapper):
6 |
7 | def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
8 | space_left = max(width - cur_len, 1)
9 |
10 | if self.break_long_words:
11 | last = reversed_chunks[-1]
12 | cut = last[:space_left]
13 | res = last[space_left:]
14 | cur_line.append(cut)
15 | reversed_chunks[-1] = res
16 | elif not cur_line:
17 | cur_line.append(reversed_chunks.pop())
18 |
19 | @contextmanager
20 | def extra_indent(self, indent):
21 | old_initial_indent = self.initial_indent
22 | old_subsequent_indent = self.subsequent_indent
23 | self.initial_indent += indent
24 | self.subsequent_indent += indent
25 | try:
26 | yield
27 | finally:
28 | self.initial_indent = old_initial_indent
29 | self.subsequent_indent = old_subsequent_indent
30 |
31 | def indent_only(self, text):
32 | rv = []
33 | for idx, line in enumerate(text.splitlines()):
34 | indent = self.initial_indent
35 | if idx > 0:
36 | indent = self.subsequent_indent
37 | rv.append(indent + line)
38 | return '\n'.join(rv)
39 |
--------------------------------------------------------------------------------
/pyblish/vendor/click/decorators.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import inspect
3 |
4 | from functools import update_wrapper
5 |
6 | from ._compat import iteritems
7 | from .utils import echo
8 |
9 |
10 | def pass_context(f):
11 | """Marks a callback as wanting to receive the current context
12 | object as first argument.
13 | """
14 | f.__click_pass_context__ = True
15 | return f
16 |
17 |
18 | def pass_obj(f):
19 | """Similar to :func:`pass_context`, but only pass the object on the
20 | context onwards (:attr:`Context.obj`). This is useful if that object
21 | represents the state of a nested system.
22 | """
23 | @pass_context
24 | def new_func(*args, **kwargs):
25 | ctx = args[0]
26 | return ctx.invoke(f, ctx.obj, *args[1:], **kwargs)
27 | return update_wrapper(new_func, f)
28 |
29 |
30 | def make_pass_decorator(object_type, ensure=False):
31 | """Given an object type this creates a decorator that will work
32 | similar to :func:`pass_obj` but instead of passing the object of the
33 | current context, it will find the innermost context of type
34 | :func:`object_type`.
35 |
36 | This generates a decorator that works roughly like this::
37 |
38 | from functools import update_wrapper
39 |
40 | def decorator(f):
41 | @pass_context
42 | def new_func(ctx, *args, **kwargs):
43 | obj = ctx.find_object(object_type)
44 | return ctx.invoke(f, obj, *args, **kwargs)
45 | return update_wrapper(new_func, f)
46 | return decorator
47 |
48 | :param object_type: the type of the object to pass.
49 | :param ensure: if set to `True`, a new object will be created and
50 | remembered on the context if it's not there yet.
51 | """
52 | def decorator(f):
53 | @pass_context
54 | def new_func(*args, **kwargs):
55 | ctx = args[0]
56 | if ensure:
57 | obj = ctx.ensure_object(object_type)
58 | else:
59 | obj = ctx.find_object(object_type)
60 | if obj is None:
61 | raise RuntimeError('Managed to invoke callback without a '
62 | 'context object of type %r existing'
63 | % object_type.__name__)
64 | return ctx.invoke(f, obj, *args[1:], **kwargs)
65 | return update_wrapper(new_func, f)
66 | return decorator
67 |
68 |
69 | def _make_command(f, name, attrs, cls):
70 | if isinstance(f, Command):
71 | raise TypeError('Attempted to convert a callback into a '
72 | 'command twice.')
73 | try:
74 | params = f.__click_params__
75 | params.reverse()
76 | del f.__click_params__
77 | except AttributeError:
78 | params = []
79 | help = attrs.get('help')
80 | if help is None:
81 | help = inspect.getdoc(f)
82 | if isinstance(help, bytes):
83 | help = help.decode('utf-8')
84 | else:
85 | help = inspect.cleandoc(help)
86 | attrs['help'] = help
87 | return cls(name=name or f.__name__.lower(),
88 | callback=f, params=params, **attrs)
89 |
90 |
91 | def command(name=None, cls=None, **attrs):
92 | """Creates a new :class:`Command` and uses the decorated function as
93 | callback. This will also automatically attach all decorated
94 | :func:`option`\s and :func:`argument`\s as parameters to the command.
95 |
96 | The name of the command defaults to the name of the function. If you
97 | want to change that, you can pass the intended name as the first
98 | argument.
99 |
100 | All keyword arguments are forwarded to the underlying command class.
101 |
102 | Once decorated the function turns into a :class:`Command` instance
103 | that can be invoked as a command line utility or be attached to a
104 | command :class:`Group`.
105 |
106 | :param name: the name of the command. This defaults to the function
107 | name.
108 | :param cls: the command class to instantiate. This defaults to
109 | :class:`Command`.
110 | """
111 | if cls is None:
112 | cls = Command
113 | def decorator(f):
114 | return _make_command(f, name, attrs, cls)
115 | return decorator
116 |
117 |
118 | def group(name=None, **attrs):
119 | """Creates a new :class:`Group` with a function as callback. This
120 | works otherwise the same as :func:`command` just that the `cls`
121 | parameter is set to :class:`Group`.
122 | """
123 | attrs.setdefault('cls', Group)
124 | return command(name, **attrs)
125 |
126 |
127 | def _param_memo(f, param):
128 | if isinstance(f, Command):
129 | f.params.append(param)
130 | else:
131 | if not hasattr(f, '__click_params__'):
132 | f.__click_params__ = []
133 | f.__click_params__.append(param)
134 |
135 |
136 | def argument(*param_decls, **attrs):
137 | """Attaches an option to the command. All positional arguments are
138 | passed as parameter declarations to :class:`Argument`; all keyword
139 | arguments are forwarded unchanged. This is equivalent to creating an
140 | :class:`Option` instance manually and attaching it to the
141 | :attr:`Command.params` list.
142 | """
143 | def decorator(f):
144 | _param_memo(f, Argument(param_decls, **attrs))
145 | return f
146 | return decorator
147 |
148 |
149 | def option(*param_decls, **attrs):
150 | """Attaches an option to the command. All positional arguments are
151 | passed as parameter declarations to :class:`Option`; all keyword
152 | arguments are forwarded unchanged. This is equivalent to creating an
153 | :class:`Option` instance manually and attaching it to the
154 | :attr:`Command.params` list.
155 | """
156 | def decorator(f):
157 | if 'help' in attrs:
158 | attrs['help'] = inspect.cleandoc(attrs['help'])
159 | _param_memo(f, Option(param_decls, **attrs))
160 | return f
161 | return decorator
162 |
163 |
164 | def confirmation_option(*param_decls, **attrs):
165 | """Shortcut for confirmation prompts that can be ignored by passing
166 | ``--yes`` as parameter.
167 |
168 | This is equivalent to decorating a function with :func:`option` with
169 | the following parameters::
170 |
171 | def callback(ctx, param, value):
172 | if not value:
173 | ctx.abort()
174 |
175 | @click.command()
176 | @click.option('--yes', is_flag=True, callback=callback,
177 | expose_value=False, prompt='Do you want to continue?')
178 | def dropdb():
179 | pass
180 | """
181 | def decorator(f):
182 | def callback(ctx, param, value):
183 | if not value:
184 | ctx.abort()
185 | attrs.setdefault('is_flag', True)
186 | attrs.setdefault('callback', callback)
187 | attrs.setdefault('expose_value', False)
188 | attrs.setdefault('prompt', 'Do you want to continue?')
189 | attrs.setdefault('help', 'Confirm the action without prompting.')
190 | return option(*(param_decls or ('--yes',)), **attrs)(f)
191 | return decorator
192 |
193 |
194 | def password_option(*param_decls, **attrs):
195 | """Shortcut for password prompts.
196 |
197 | This is equivalent to decorating a function with :func:`option` with
198 | the following parameters::
199 |
200 | @click.command()
201 | @click.option('--password', prompt=True, confirmation_prompt=True,
202 | hide_input=True)
203 | def changeadmin(password):
204 | pass
205 | """
206 | def decorator(f):
207 | attrs.setdefault('prompt', True)
208 | attrs.setdefault('confirmation_prompt', True)
209 | attrs.setdefault('hide_input', True)
210 | return option(*(param_decls or ('--password',)), **attrs)(f)
211 | return decorator
212 |
213 |
214 | def version_option(version=None, *param_decls, **attrs):
215 | """Adds a ``--version`` option which immediately ends the program
216 | printing out the version number. This is implemented as an eager
217 | option that prints the version and exits the program in the callback.
218 |
219 | :param version: the version number to show. If not provided Click
220 | attempts an auto discovery via setuptools.
221 | :param prog_name: the name of the program (defaults to autodetection)
222 | :param message: custom message to show instead of the default
223 | (``'%(prog)s, version %(version)s'``)
224 | :param others: everything else is forwarded to :func:`option`.
225 | """
226 | if version is None:
227 | module = sys._getframe(1).f_globals.get('__name__')
228 | def decorator(f):
229 | prog_name = attrs.pop('prog_name', None)
230 | message = attrs.pop('message', '%(prog)s, version %(version)s')
231 |
232 | def callback(ctx, param, value):
233 | if not value or ctx.resilient_parsing:
234 | return
235 | prog = prog_name
236 | if prog is None:
237 | prog = ctx.find_root().info_name
238 | ver = version
239 | if ver is None:
240 | try:
241 | import pkg_resources
242 | except ImportError:
243 | pass
244 | else:
245 | for dist in pkg_resources.working_set:
246 | scripts = dist.get_entry_map().get('console_scripts') or {}
247 | for script_name, entry_point in iteritems(scripts):
248 | if entry_point.module_name == module:
249 | ver = dist.version
250 | break
251 | if ver is None:
252 | raise RuntimeError('Could not determine version')
253 | echo(message % {
254 | 'prog': prog,
255 | 'version': ver,
256 | })
257 | ctx.exit()
258 |
259 | attrs.setdefault('is_flag', True)
260 | attrs.setdefault('expose_value', False)
261 | attrs.setdefault('is_eager', True)
262 | attrs.setdefault('help', 'Show the version and exit.')
263 | attrs['callback'] = callback
264 | return option(*(param_decls or ('--version',)), **attrs)(f)
265 | return decorator
266 |
267 |
268 | def help_option(*param_decls, **attrs):
269 | """Adds a ``--help`` option which immediately ends the program
270 | printing out the help page. This is usually unnecessary to add as
271 | this is added by default to all commands unless suppressed.
272 |
273 | Like :func:`version_option`, this is implemented as eager option that
274 | prints in the callback and exits.
275 |
276 | All arguments are forwarded to :func:`option`.
277 | """
278 | def decorator(f):
279 | def callback(ctx, param, value):
280 | if value and not ctx.resilient_parsing:
281 | echo(ctx.get_help())
282 | ctx.exit()
283 | attrs.setdefault('is_flag', True)
284 | attrs.setdefault('expose_value', False)
285 | attrs.setdefault('help', 'Show this message and exit.')
286 | attrs.setdefault('is_eager', True)
287 | attrs['callback'] = callback
288 | return option(*(param_decls or ('--help',)), **attrs)(f)
289 | return decorator
290 |
291 |
292 | # Circular dependencies between core and decorators
293 | from .core import Command, Group, Argument, Option
294 |
--------------------------------------------------------------------------------
/pyblish/vendor/click/exceptions.py:
--------------------------------------------------------------------------------
1 | from ._compat import PY2, filename_to_ui, get_text_stderr
2 | from .utils import echo
3 |
4 |
5 | class ClickException(Exception):
6 | """An exception that Click can handle and show to the user."""
7 |
8 | #: The exit code for this exception
9 | exit_code = 1
10 |
11 | def __init__(self, message):
12 | if PY2:
13 | Exception.__init__(self, message.encode('utf-8'))
14 | else:
15 | Exception.__init__(self, message)
16 | self.message = message
17 |
18 | def format_message(self):
19 | return self.message
20 |
21 | def show(self, file=None):
22 | if file is None:
23 | file = get_text_stderr()
24 | echo('Error: %s' % self.format_message(), file=file)
25 |
26 |
27 | class UsageError(ClickException):
28 | """An internal exception that signals a usage error. This typically
29 | aborts any further handling.
30 |
31 | :param message: the error message to display.
32 | :param ctx: optionally the context that caused this error. Click will
33 | fill in the context automatically in some situations.
34 | """
35 | exit_code = 2
36 |
37 | def __init__(self, message, ctx=None):
38 | ClickException.__init__(self, message)
39 | self.ctx = ctx
40 |
41 | def show(self, file=None):
42 | if file is None:
43 | file = get_text_stderr()
44 | if self.ctx is not None:
45 | echo(self.ctx.get_usage() + '\n', file=file)
46 | echo('Error: %s' % self.format_message(), file=file)
47 |
48 |
49 | class BadParameter(UsageError):
50 | """An exception that formats out a standardized error message for a
51 | bad parameter. This is useful when thrown from a callback or type as
52 | Click will attach contextual information to it (for instance, which
53 | parameter it is).
54 |
55 | .. versionadded:: 2.0
56 |
57 | :param param: the parameter object that caused this error. This can
58 | be left out, and Click will attach this info itself
59 | if possible.
60 | :param param_hint: a string that shows up as parameter name. This
61 | can be used as alternative to `param` in cases
62 | where custom validation should happen. If it is
63 | a string it's used as such, if it's a list then
64 | each item is quoted and separated.
65 | """
66 |
67 | def __init__(self, message, ctx=None, param=None,
68 | param_hint=None):
69 | UsageError.__init__(self, message, ctx)
70 | self.param = param
71 | self.param_hint = param_hint
72 |
73 | def format_message(self):
74 | if self.param_hint is not None:
75 | param_hint = self.param_hint
76 | elif self.param is not None:
77 | param_hint = self.param.opts or [self.param.name]
78 | else:
79 | return 'Invalid value: %s' % self.message
80 | if isinstance(param_hint, (tuple, list)):
81 | param_hint = ' / '.join('"%s"' % x for x in param_hint)
82 | return 'Invalid value for %s: %s' % (param_hint, self.message)
83 |
84 |
85 | class FileError(ClickException):
86 | """Raised if a file cannot be opened."""
87 |
88 | def __init__(self, filename, hint=None):
89 | ui_filename = filename_to_ui(filename)
90 | if hint is None:
91 | hint = 'unknown error'
92 | ClickException.__init__(self, hint)
93 | self.ui_filename = ui_filename
94 | self.filename = filename
95 |
96 | def format_message(self):
97 | return 'Could not open file %s: %s' % (self.ui_filename, self.message)
98 |
99 |
100 | class Abort(RuntimeError):
101 | """An internal signalling exception that signals Click to abort."""
102 |
--------------------------------------------------------------------------------
/pyblish/vendor/click/formatting.py:
--------------------------------------------------------------------------------
1 | from contextlib import contextmanager
2 | from .termui import get_terminal_size
3 | from .parser import split_opt
4 | from ._compat import term_len
5 |
6 |
7 | def measure_table(rows):
8 | widths = {}
9 | for row in rows:
10 | for idx, col in enumerate(row):
11 | widths[idx] = max(widths.get(idx, 0), term_len(col))
12 | return tuple(y for x, y in sorted(widths.items()))
13 |
14 |
15 | def iter_rows(rows, col_count):
16 | for row in rows:
17 | row = tuple(row)
18 | yield row + ('',) * (col_count - len(row))
19 |
20 |
21 | def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
22 | preserve_paragraphs=False):
23 | """A helper function that intelligently wraps text. By default, it
24 | assumes that it operates on a single paragraph of text but if the
25 | `preserve_paragraphs` parameter is provided it will intelligently
26 | handle paragraphs (defined by two empty lines).
27 |
28 | If paragraphs are handled, a paragraph can be prefixed with an empty
29 | line containing the ``\\b`` character (``\\x08``) to indicate that
30 | no rewrapping should happen in that block.
31 |
32 | :param text: the text that should be rewrapped.
33 | :param width: the maximum width for the text.
34 | :param initial_indent: the initial indent that should be placed on the
35 | first line as a string.
36 | :param subsequent_indent: the indent string that should be placed on
37 | each consecutive line.
38 | :param preserve_paragraphs: if this flag is set then the wrapping will
39 | intelligently handle paragraphs.
40 | """
41 | from ._textwrap import TextWrapper
42 | text = text.expandtabs()
43 | wrapper = TextWrapper(width, initial_indent=initial_indent,
44 | subsequent_indent=subsequent_indent,
45 | replace_whitespace=False)
46 | if not preserve_paragraphs:
47 | return wrapper.fill(text)
48 |
49 | p = []
50 | buf = []
51 | indent = None
52 |
53 | def _flush_par():
54 | if not buf:
55 | return
56 | if buf[0].strip() == '\b':
57 | p.append((indent or 0, True, '\n'.join(buf[1:])))
58 | else:
59 | p.append((indent or 0, False, ' '.join(buf)))
60 | del buf[:]
61 |
62 | for line in text.splitlines():
63 | if not line:
64 | _flush_par()
65 | indent = None
66 | else:
67 | if indent is None:
68 | orig_len = term_len(line)
69 | line = line.lstrip()
70 | indent = orig_len - term_len(line)
71 | buf.append(line)
72 | _flush_par()
73 |
74 | rv = []
75 | for indent, raw, text in p:
76 | with wrapper.extra_indent(' ' * indent):
77 | if raw:
78 | rv.append(wrapper.indent_only(text))
79 | else:
80 | rv.append(wrapper.fill(text))
81 |
82 | return '\n\n'.join(rv)
83 |
84 |
85 | class HelpFormatter(object):
86 | """This class helps with formatting text-based help pages. It's
87 | usually just needed for very special internal cases, but it's also
88 | exposed so that developers can write their own fancy outputs.
89 |
90 | At present, it always writes into memory.
91 |
92 | :param indent_increment: the additional increment for each level.
93 | :param width: the width for the text. This defaults to the terminal
94 | width clamped to a maximum of 78.
95 | """
96 |
97 | def __init__(self, indent_increment=2, width=None):
98 | self.indent_increment = indent_increment
99 | if width is None:
100 | width = max(min(get_terminal_size()[0], 80) - 2, 50)
101 | self.width = width
102 | self.current_indent = 0
103 | self.buffer = []
104 |
105 | def write(self, string):
106 | """Writes a unicode string into the internal buffer."""
107 | self.buffer.append(string)
108 |
109 | def indent(self):
110 | """Increases the indentation."""
111 | self.current_indent += self.indent_increment
112 |
113 | def dedent(self):
114 | """Decreases the indentation."""
115 | self.current_indent -= self.indent_increment
116 |
117 | def write_usage(self, prog, args='', prefix='Usage: '):
118 | """Writes a usage line into the buffer.
119 |
120 | :param prog: the program name.
121 | :param args: whitespace separated list of arguments.
122 | :param prefix: the prefix for the first line.
123 | """
124 | prefix = '%*s%s' % (self.current_indent, prefix, prog)
125 | self.write(prefix)
126 |
127 | text_width = max(self.width - self.current_indent - term_len(prefix), 10)
128 | indent = ' ' * (term_len(prefix) + 1)
129 | self.write(wrap_text(args, text_width,
130 | initial_indent=' ',
131 | subsequent_indent=indent))
132 |
133 | self.write('\n')
134 |
135 | def write_heading(self, heading):
136 | """Writes a heading into the buffer."""
137 | self.write('%*s%s:\n' % (self.current_indent, '', heading))
138 |
139 | def write_paragraph(self):
140 | """Writes a paragraph into the buffer."""
141 | if self.buffer:
142 | self.write('\n')
143 |
144 | def write_text(self, text):
145 | """Writes re-indented text into the buffer. This rewraps and
146 | preserves paragraphs.
147 | """
148 | text_width = max(self.width - self.current_indent, 11)
149 | indent = ' ' * self.current_indent
150 | self.write(wrap_text(text, text_width,
151 | initial_indent=indent,
152 | subsequent_indent=indent,
153 | preserve_paragraphs=True))
154 | self.write('\n')
155 |
156 | def write_dl(self, rows, col_max=30, col_spacing=2):
157 | """Writes a definition list into the buffer. This is how options
158 | and commands are usually formatted.
159 |
160 | :param rows: a list of two item tuples for the terms and values.
161 | :param col_max: the maximum width of the first column.
162 | :param col_spacing: the number of spaces between the first and
163 | second column.
164 | """
165 | rows = list(rows)
166 | widths = measure_table(rows)
167 | if len(widths) != 2:
168 | raise TypeError('Expected two columns for definition list')
169 |
170 | first_col = min(widths[0], col_max) + col_spacing
171 |
172 | for first, second in iter_rows(rows, len(widths)):
173 | self.write('%*s%s' % (self.current_indent, '', first))
174 | if not second:
175 | self.write('\n')
176 | continue
177 | if term_len(first) <= first_col - col_spacing:
178 | self.write(' ' * (first_col - term_len(first)))
179 | else:
180 | self.write('\n')
181 | self.write(' ' * (first_col + self.current_indent))
182 |
183 | text_width = max(self.width - first_col - 2, 10)
184 | lines = iter(wrap_text(second, text_width).splitlines())
185 | if lines:
186 | self.write(next(lines) + '\n')
187 | for line in lines:
188 | self.write('%*s%s\n' % (
189 | first_col + self.current_indent, '', line))
190 | else:
191 | self.write('\n')
192 |
193 | @contextmanager
194 | def section(self, name):
195 | """Helpful context manager that writes a paragraph, a heading,
196 | and the indents.
197 |
198 | :param name: the section name that is written as heading.
199 | """
200 | self.write_paragraph()
201 | self.write_heading(name)
202 | self.indent()
203 | try:
204 | yield
205 | finally:
206 | self.dedent()
207 |
208 | @contextmanager
209 | def indentation(self):
210 | """A context manager that increases the indentation."""
211 | self.indent()
212 | try:
213 | yield
214 | finally:
215 | self.dedent()
216 |
217 | def getvalue(self):
218 | """Returns the buffer contents."""
219 | return ''.join(self.buffer)
220 |
221 |
222 | def join_options(options):
223 | """Given a list of option strings this joins them in the most appropriate
224 | way and returns them in the form ``(formatted_string,
225 | any_prefix_is_slash)`` where the second item in the tuple is a flag that
226 | indicates if any of the option prefixes was a slash.
227 | """
228 | rv = []
229 | any_prefix_is_slash = False
230 | for opt in options:
231 | prefix = split_opt(opt)[0]
232 | if prefix == '/':
233 | any_prefix_is_slash = True
234 | rv.append((len(prefix), opt))
235 |
236 | rv.sort(key=lambda x: x[0])
237 |
238 | rv = ', '.join(x[1] for x in rv)
239 | return rv, any_prefix_is_slash
240 |
--------------------------------------------------------------------------------
/pyblish/vendor/click/testing.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import shutil
4 | import tempfile
5 | import contextlib
6 |
7 | from pyblish.vendor import click
8 |
9 | from ._compat import iteritems, PY2
10 |
11 |
12 | if PY2:
13 | from cStringIO import StringIO
14 | else:
15 | import io
16 | from ._compat import _find_binary_reader
17 |
18 |
19 | class EchoingStdin(object):
20 |
21 | def __init__(self, input, output):
22 | self._input = input
23 | self._output = output
24 |
25 | def __getattr__(self, x):
26 | return getattr(self._input, x)
27 |
28 | def _echo(self, rv):
29 | self._output.write(rv)
30 | return rv
31 |
32 | def read(self, n=-1):
33 | return self._echo(self._input.read(n))
34 |
35 | def readline(self, n=-1):
36 | return self._echo(self._input.readline(n))
37 |
38 | def readlines(self):
39 | return [self._echo(x) for x in self._input.readlines()]
40 |
41 | def __iter__(self):
42 | return iter(self._echo(x) for x in self._input)
43 |
44 | def __repr__(self):
45 | return repr(self._input)
46 |
47 |
48 | def make_input_stream(input, charset):
49 | # Is already an input stream.
50 | if hasattr(input, 'read'):
51 | if PY2:
52 | return input
53 | rv = _find_binary_reader(input)
54 | if rv is not None:
55 | return rv
56 | raise TypeError('Could not find binary reader for input stream.')
57 |
58 | if input is None:
59 | input = b''
60 | elif not isinstance(input, bytes):
61 | input = input.encode(charset)
62 | if PY2:
63 | return StringIO(input)
64 | return io.BytesIO(input)
65 |
66 |
67 | class Result(object):
68 | """Holds the captured result of an invoked CLI script."""
69 |
70 | def __init__(self, runner, output_bytes, exit_code, exception,
71 | exc_info=None):
72 | #: The runner that created the result
73 | self.runner = runner
74 | #: The output as bytes.
75 | self.output_bytes = output_bytes
76 | #: The exit code as integer.
77 | self.exit_code = exit_code
78 | #: The exception that happend if one did.
79 | self.exception = exception
80 | #: The traceback
81 | self.exc_info = exc_info
82 |
83 | @property
84 | def output(self):
85 | """The output as unicode string."""
86 | return self.output_bytes.decode(self.runner.charset, 'replace') \
87 | .replace('\r\n', '\n')
88 |
89 | def __repr__(self):
90 | return '' % (
91 | self.exception and repr(self.exception) or 'okay',
92 | )
93 |
94 |
95 | class CliRunner(object):
96 | """The CLI runner provides functionality to invoke a Click command line
97 | script for unittesting purposes in a isolated environment. This only
98 | works in single-threaded systems without any concurrency as it changes the
99 | global interpreter state.
100 |
101 | :param charset: the character set for the input and output data. This is
102 | UTF-8 by default and should not be changed currently as
103 | the reporting to Click only works in Python 2 properly.
104 | :param env: a dictionary with environment variables for overriding.
105 | :param echo_stdin: if this is set to `True`, then reading from stdin writes
106 | to stdout. This is useful for showing examples in
107 | some circumstances. Note that regular prompts
108 | will automatically echo the input.
109 | """
110 |
111 | def __init__(self, charset=None, env=None, echo_stdin=False):
112 | if charset is None:
113 | charset = 'utf-8'
114 | self.charset = charset
115 | self.env = env or {}
116 | self.echo_stdin = echo_stdin
117 |
118 | def get_default_prog_name(self, cli):
119 | """Given a command object it will return the default program name
120 | for it. The default is the `name` attribute or ``"root"`` if not
121 | set.
122 | """
123 | return cli.name or 'root'
124 |
125 | def make_env(self, overrides=None):
126 | """Returns the environment overrides for invoking a script."""
127 | rv = dict(self.env)
128 | if overrides:
129 | rv.update(overrides)
130 | return rv
131 |
132 | @contextlib.contextmanager
133 | def isolation(self, input=None, env=None):
134 | """A context manager that sets up the isolation for invoking of a
135 | command line tool. This sets up stdin with the given input data
136 | and `os.environ` with the overrides from the given dictionary.
137 | This also rebinds some internals in Click to be mocked (like the
138 | prompt functionality).
139 |
140 | This is automatically done in the :meth:`invoke` method.
141 |
142 | :param input: the input stream to put into sys.stdin.
143 | :param env: the environment overrides as dictionary.
144 | """
145 | input = make_input_stream(input, self.charset)
146 |
147 | old_stdin = sys.stdin
148 | old_stdout = sys.stdout
149 | old_stderr = sys.stderr
150 |
151 | env = self.make_env(env)
152 |
153 | if PY2:
154 | sys.stdout = sys.stderr = bytes_output = StringIO()
155 | if self.echo_stdin:
156 | input = EchoingStdin(input, bytes_output)
157 | else:
158 | bytes_output = io.BytesIO()
159 | if self.echo_stdin:
160 | input = EchoingStdin(input, bytes_output)
161 | input = io.TextIOWrapper(input, encoding=self.charset)
162 | sys.stdout = sys.stderr = io.TextIOWrapper(
163 | bytes_output, encoding=self.charset)
164 |
165 | sys.stdin = input
166 |
167 | def visible_input(prompt=None):
168 | sys.stdout.write(prompt or '')
169 | val = input.readline().rstrip('\r\n')
170 | sys.stdout.write(val + '\n')
171 | sys.stdout.flush()
172 | return val
173 |
174 | def hidden_input(prompt=None):
175 | sys.stdout.write((prompt or '') + '\n')
176 | sys.stdout.flush()
177 | return input.readline().rstrip('\r\n')
178 |
179 | def _getchar(echo):
180 | char = sys.stdin.read(1)
181 | if echo:
182 | sys.stdout.write(char)
183 | sys.stdout.flush()
184 | return char
185 |
186 | old_visible_prompt_func = click.termui.visible_prompt_func
187 | old_hidden_prompt_func = click.termui.hidden_prompt_func
188 | old__getchar_func = click.termui._getchar
189 | click.termui.visible_prompt_func = visible_input
190 | click.termui.hidden_prompt_func = hidden_input
191 | click.termui._getchar = _getchar
192 |
193 | old_env = {}
194 | try:
195 | for key, value in iteritems(env):
196 | old_env[key] = os.environ.get(value)
197 | if value is None:
198 | try:
199 | del os.environ[key]
200 | except Exception:
201 | pass
202 | else:
203 | os.environ[key] = value
204 | yield bytes_output
205 | finally:
206 | for key, value in iteritems(old_env):
207 | if value is None:
208 | try:
209 | del os.environ[key]
210 | except Exception:
211 | pass
212 | else:
213 | os.environ[key] = value
214 | sys.stdout = old_stdout
215 | sys.stderr = old_stderr
216 | sys.stdin = old_stdin
217 | click.termui.visible_prompt_func = old_visible_prompt_func
218 | click.termui.hidden_prompt_func = old_hidden_prompt_func
219 | click.termui._getchar = old__getchar_func
220 |
221 | def invoke(self, cli, args=None, input=None, env=None,
222 | catch_exceptions=True, **extra):
223 | """Invokes a command in an isolated environment. The arguments are
224 | forwarded directly to the command line script, the `extra` keyword
225 | arguments are passed to the :meth:`~click.Command.main` function of
226 | the command.
227 |
228 | This returns a :class:`Result` object.
229 |
230 | .. versionadded:: 3.0
231 | The ``catch_exceptions`` parameter was added.
232 |
233 | .. versionchanged:: 3.0
234 | The result object now has an `exc_info` attribute with the
235 | traceback if available.
236 |
237 | :param cli: the command to invoke
238 | :param args: the arguments to invoke
239 | :param input: the input data for `sys.stdin`.
240 | :param env: the environment overrides.
241 | :param catch_exceptions: Whether to catch any other exceptions than
242 | ``SystemExit``.
243 | :param extra: the keyword arguments to pass to :meth:`main`.
244 | """
245 | exc_info = None
246 | with self.isolation(input=input, env=env) as out:
247 | exception = None
248 | exit_code = 0
249 |
250 | try:
251 | cli.main(args=args or (),
252 | prog_name=self.get_default_prog_name(cli), **extra)
253 | except SystemExit as e:
254 | if e.code != 0:
255 | exception = e
256 | exit_code = e.code
257 | exc_info = sys.exc_info()
258 | except Exception as e:
259 | if not catch_exceptions:
260 | raise
261 | exception = e
262 | exit_code = -1
263 | exc_info = sys.exc_info()
264 | finally:
265 | sys.stdout.flush()
266 | output = out.getvalue()
267 |
268 | return Result(runner=self,
269 | output_bytes=output,
270 | exit_code=exit_code,
271 | exception=exception,
272 | exc_info=exc_info)
273 |
274 | @contextlib.contextmanager
275 | def isolated_filesystem(self):
276 | """A context manager that creates a temporary folder and changes
277 | the current working directory to it for isolated filesystem tests.
278 | """
279 | cwd = os.getcwd()
280 | t = tempfile.mkdtemp()
281 | os.chdir(t)
282 | try:
283 | yield t
284 | finally:
285 | os.chdir(cwd)
286 | try:
287 | shutil.rmtree(t)
288 | except (OSError, IOError):
289 | pass
290 |
--------------------------------------------------------------------------------
/pyblish/vendor/iscompatible.py:
--------------------------------------------------------------------------------
1 | """
2 | Python versioning with requirements.txt syntax
3 | ==============================================
4 |
5 | iscompatible v\ |version|. gives you the power of the pip requirements.txt
6 | syntax for everyday python packages, modules, classes or arbitrary
7 | functions.
8 |
9 | The requirements.txt syntax allows you to specify inexact matches
10 | between a set of requirements and a version. For example, let's
11 | assume that the single package foo-5.6.1 exists on disk. The
12 | following requirements are all compatible with foo-5.6.1.
13 |
14 | =========== =================================================
15 | Requirement Description
16 | =========== =================================================
17 | foo any version of foo
18 | foo>=5 any version of foo, above or equal to 5
19 | foo>=5.6 any version of foo, above or equal to 5.6
20 | foo==5.6.1 exact match
21 | foo>5 foo-5 or greater, including minor and patch
22 | foo>5, <5.7 foo-5 or greater, but less than foo-5.7
23 | foo>0, <5.7 any foo version less than foo-5.7
24 | =========== =================================================
25 |
26 | Example:
27 | >>> iscompatible("foo>=5", (5, 6, 1))
28 | True
29 | >>> iscompatible("foo>=5.6.1, <5.7", (5, 0, 0))
30 | False
31 | >>> MyPlugin = type("MyPlugin", (), {'version': (5, 6, 1)})
32 | >>> iscompatible("foo==5.6.1", MyPlugin.version)
33 | True
34 |
35 | References
36 | ^^^^^^^^^^
37 |
38 | - `The requirements file-format`_
39 |
40 | .. _The requirements file-format: https://pip.readthedocs.org/en/1.1/requirements.html#the-requirements-file-format
41 | .. _VCS: https://pip.readthedocs.org/en/1.1/requirements.html#version-control
42 | .. _extras: http://peak.telecommunity.com/DevCenter/setuptools#declaring-extras-optional-features-with-their-own-dependencies
43 |
44 | """
45 |
46 | version_info = (0, 1, 1)
47 | __version__ = "%s.%s.%s" % version_info
48 |
49 |
50 | import re
51 | import operator
52 |
53 |
54 | def iscompatible(requirements, version):
55 | """Return whether or not `requirements` is compatible with `version`
56 |
57 | Arguments:
58 | requirements (str): Requirement to compare, e.g. foo==1.0.1
59 | version (tuple): Version to compare against, e.g. (1, 0, 1)
60 |
61 | Example:
62 | >>> iscompatible("foo", (1, 0, 0))
63 | True
64 | >>> iscompatible("foo<=1", (0, 9, 0))
65 | True
66 | >>> iscompatible("foo>=1, <1.3", (1, 2, 0))
67 | True
68 | >>> iscompatible("foo>=0.9.9", (1, 0, 0))
69 | True
70 | >>> iscompatible("foo>=1.1, <2.1", (2, 0, 0))
71 | True
72 | >>> iscompatible("foo==1.0.0", (1, 0, 0))
73 | True
74 | >>> iscompatible("foo==1.0.0", (1, 0, 1))
75 | False
76 |
77 | """
78 |
79 | results = list()
80 |
81 | for operator_string, requirement_string in parse_requirements(requirements):
82 | operator = operators[operator_string]
83 | required = string_to_tuple(requirement_string)
84 | result = operator(version, required)
85 |
86 | results.append(result)
87 |
88 | return all(results)
89 |
90 |
91 | def parse_requirements(line):
92 | """Return list of tuples with (operator, version) from `line`
93 |
94 | .. note:: This is a minimal re-implementation of
95 | pkg_utils.parse_requirements and doesn't include support
96 | for `VCS`_ or `extras`_.
97 |
98 |
99 | Example:
100 | >>> parse_requirements("foo==1.0.0")
101 | [('==', '1.0.0')]
102 | >>> parse_requirements("foo>=1.1.0")
103 | [('>=', '1.1.0')]
104 | >>> parse_requirements("foo>=1.1.0, <1.2")
105 | [('>=', '1.1.0'), ('<', '1.2')]
106 |
107 |
108 | """
109 |
110 | LINE_END = re.compile(r"\s*(#.*)?$")
111 | DISTRO = re.compile(r"\s*((\w|[-.])+)")
112 | VERSION = re.compile(r"\s*(<=?|>=?|==|!=)\s*((\w|[-.])+)")
113 | COMMA = re.compile(r"\s*,")
114 |
115 | match = DISTRO.match(line)
116 | p = match.end()
117 | specs = list()
118 |
119 | while not LINE_END.match(line, p):
120 | match = VERSION.match(line, p)
121 | if not match:
122 | raise ValueError(
123 | "Expected version spec in",
124 | line, "at", line[p:])
125 |
126 | specs.append(match.group(*(1, 2)))
127 | p = match.end()
128 |
129 | match = COMMA.match(line, p)
130 | if match:
131 | p = match.end() # Skip comma
132 | elif not LINE_END.match(line, p):
133 | raise ValueError(
134 | "Expected ',' or end-of-list in",
135 | line, "at", line[p:])
136 |
137 | return specs
138 |
139 |
140 | def string_to_tuple(version):
141 | """Convert version as string to tuple
142 |
143 | Example:
144 | >>> string_to_tuple("1.0.0")
145 | (1, 0, 0)
146 | >>> string_to_tuple("2.5")
147 | (2, 5)
148 |
149 | """
150 |
151 | return tuple(map(int, version.split(".")))
152 |
153 |
154 | operators = {"<": operator.lt,
155 | "<=": operator.le,
156 | "==": operator.eq,
157 | "!=": operator.ne,
158 | ">=": operator.ge,
159 | ">": operator.gt}
160 |
--------------------------------------------------------------------------------
/pyblish/version.py:
--------------------------------------------------------------------------------
1 |
2 | VERSION_MAJOR = 1
3 | VERSION_MINOR = 8
4 | VERSION_PATCH = 12
5 |
6 | version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
7 | version = '%i.%i.%i' % version_info
8 | __version__ = version
9 |
10 | __all__ = ['version', 'version_info', '__version__']
11 |
--------------------------------------------------------------------------------
/run_coverage.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | # Expose Pyblish to PYTHONPATH
5 | path = os.path.dirname(__file__)
6 | sys.path.insert(0, path)
7 |
8 | import nose
9 |
10 | if __name__ == '__main__':
11 | argv = sys.argv[:]
12 | argv.extend(['-c', '.noserc'])
13 | nose.main(argv=argv)
14 |
--------------------------------------------------------------------------------
/run_testsuite.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import warnings
4 |
5 | # Expose Pyblish to PYTHONPATH
6 | path = os.path.dirname(__file__)
7 | sys.path.insert(0, path)
8 |
9 | import nose
10 | from pyblish.vendor import mock
11 |
12 | warnings.warn = mock.MagicMock()
13 |
14 |
15 | if __name__ == '__main__':
16 | argv = sys.argv[:]
17 | argv.extend(['--exclude=vendor', '--with-doctest', '--verbose'])
18 | nose.main(argv=argv)
19 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | # This includes the license file in the wheel.
3 | license_file = LICENSE.txt
4 |
5 | [bdist_wheel]
6 | # This produces a "universal" (Py2+3) wheel.
7 | universal = 1
8 |
9 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import imp
3 |
4 | from setuptools import setup, find_packages
5 |
6 | with open("README.txt") as f:
7 | readme = f.read()
8 |
9 |
10 | version_file = os.path.abspath("pyblish/version.py")
11 | version_mod = imp.load_source("version", version_file)
12 | version = version_mod.version
13 |
14 |
15 | classifiers = [
16 | "Development Status :: 5 - Production/Stable",
17 | "Intended Audience :: Developers",
18 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
19 | "Programming Language :: Python",
20 | "Programming Language :: Python :: 2",
21 | "Programming Language :: Python :: 2.6",
22 | "Programming Language :: Python :: 2.7",
23 | "Programming Language :: Python :: 3",
24 | "Programming Language :: Python :: 3.1",
25 | "Programming Language :: Python :: 3.2",
26 | "Programming Language :: Python :: 3.3",
27 | "Programming Language :: Python :: 3.4",
28 | "Programming Language :: Python :: 3.5",
29 | "Topic :: Software Development :: Libraries :: Python Modules",
30 | "Topic :: Utilities"
31 | ]
32 |
33 |
34 | setup(
35 | name="pyblish-base",
36 | version=version,
37 | description="Plug-in driven automation framework for content",
38 | long_description=readme,
39 | author="Abstract Factory and Contributors",
40 | author_email="marcus@abstractfactory.io",
41 | url="https://github.com/pyblish/pyblish",
42 | license="LGPL",
43 | packages=find_packages(),
44 | zip_safe=False,
45 | classifiers=classifiers,
46 | package_data={
47 | "pyblish": ["plugins/*.py",
48 | "*.yaml",
49 | "icons/*.svg"],
50 | },
51 | entry_points={
52 | "console_scripts": ["pyblish = pyblish.cli:main"]
53 | },
54 | )
55 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pyblish/pyblish-base/03cda36b26010642bcbdc8dbf2f256f298742f9f/tests/__init__.py
--------------------------------------------------------------------------------
/tests/lib.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import shutil
4 | import tempfile
5 | import contextlib
6 |
7 | import pyblish
8 | import pyblish.cli
9 | import pyblish.plugin
10 | from pyblish.vendor import six
11 |
12 | # Setup
13 | HOST = 'python'
14 | FAMILY = 'test.family'
15 |
16 | REGISTERED = pyblish.plugin.registered_paths()
17 | PACKAGEPATH = pyblish.lib.main_package_path()
18 | ENVIRONMENT = os.environ.get("PYBLISHPLUGINPATH", "")
19 | PLUGINPATH = os.path.join(PACKAGEPATH, '..', 'tests', 'plugins')
20 |
21 |
22 | def setup():
23 | """Disable default plugins and only use test plugins"""
24 | pyblish.plugin.deregister_all_paths()
25 |
26 |
27 | def setup_empty():
28 | """Disable all plug-ins"""
29 | setup()
30 | pyblish.plugin.deregister_all_plugins()
31 | pyblish.plugin.deregister_all_paths()
32 | pyblish.plugin.deregister_all_hosts()
33 | pyblish.plugin.deregister_all_callbacks()
34 | pyblish.plugin.deregister_all_targets()
35 | pyblish.api.deregister_all_discovery_filters()
36 |
37 |
38 | def teardown():
39 | """Restore previously REGISTERED paths"""
40 |
41 | pyblish.plugin.deregister_all_paths()
42 | for path in REGISTERED:
43 | pyblish.plugin.register_plugin_path(path)
44 |
45 | os.environ["PYBLISHPLUGINPATH"] = ENVIRONMENT
46 | pyblish.api.deregister_all_plugins()
47 | pyblish.api.deregister_all_hosts()
48 | pyblish.api.deregister_all_discovery_filters()
49 | pyblish.api.deregister_test()
50 | pyblish.api.__init__()
51 |
52 |
53 | @contextlib.contextmanager
54 | def captured_stdout():
55 | """Temporarily reassign stdout to a local variable"""
56 | try:
57 | sys.stdout = six.StringIO()
58 | yield sys.stdout
59 | finally:
60 | sys.stdout = sys.__stdout__
61 |
62 |
63 | @contextlib.contextmanager
64 | def captured_stderr():
65 | """Temporarily reassign stderr to a local variable"""
66 | try:
67 | sys.stderr = six.StringIO()
68 | yield sys.stderr
69 | finally:
70 | sys.stderr = sys.__stderr__
71 |
72 |
73 | @contextlib.contextmanager
74 | def tempdir():
75 | """Provide path to temporary directory"""
76 | try:
77 | tempdir = tempfile.mkdtemp()
78 | yield tempdir
79 | finally:
80 | shutil.rmtree(tempdir)
81 |
--------------------------------------------------------------------------------
/tests/plugins.py:
--------------------------------------------------------------------------------
1 | """Plugins for testing purposes.
2 |
3 | Source them like this from within a test function:
4 |
5 | api.deregister_all_paths()
6 | api.register_plugin_path(os.path.dirname(__file__))
7 |
8 | This ensures that the plugins are actually loaded through `plugin.discover`.
9 | """
10 | from pyblish import api
11 |
12 |
13 | class FailingExplicitPlugin(api.InstancePlugin):
14 | """Raise an exception."""
15 |
16 | def process(self, instance):
17 | raise Exception("A test exception")
18 |
19 |
20 | class FailingImplicitPlugin(api.Validator):
21 | """Raise an exception."""
22 |
23 | def process(self, instance):
24 | raise Exception("A test exception")
25 |
--------------------------------------------------------------------------------
/tests/plugins/missing_extension/myCollector:
--------------------------------------------------------------------------------
1 | import pyblish.plugin
2 |
3 | class MyCollectorWithoutExtension(pyblish.plugin.Collector):
4 | hosts = ["*"]
5 | version = (0, 1, 0)
6 |
7 | def process_context(self, context):
8 | pass
--------------------------------------------------------------------------------
/tests/plugins/missing_host/missing_host.py:
--------------------------------------------------------------------------------
1 | """This plugin is used to test how discover handles a mismatching host.
2 | note that there is a host in this plugin, but it is not registered by pyblish"""
3 |
4 | import pyblish.plugin
5 |
6 | @pyblish.api.log
7 | class CollectMissingHosts(pyblish.plugin.Collector):
8 | hosts = ['not_a_registered_host']
9 |
--------------------------------------------------------------------------------
/tests/plugins/private/_start_with_underscore.py:
--------------------------------------------------------------------------------
1 | import pyblish.plugin
2 |
3 | class MyPrivatePlugin(pyblish.plugin.Collector):
4 |
5 | hosts = ["*"]
6 | version = (0, 1, 0)
7 |
8 | def process_context(self, context):
9 | pass
10 |
11 | raise Exception("This should not be executed, plugin loading should be skipped")
--------------------------------------------------------------------------------
/tests/pre11/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pyblish/pyblish-base/03cda36b26010642bcbdc8dbf2f256f298742f9f/tests/pre11/__init__.py
--------------------------------------------------------------------------------
/tests/pre11/lib.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pyblish
4 | import pyblish.plugin
5 |
6 | # Setup
7 | HOST = 'python'
8 | FAMILY = 'test.family'
9 |
10 | REGISTERED = pyblish.plugin.registered_paths()
11 | PACKAGEPATH = pyblish.lib.main_package_path()
12 | PLUGINPATH = os.path.join(PACKAGEPATH, '..', 'tests', 'pre11', 'plugins')
13 | ENVIRONMENT = os.environ.get("PYBLISHPLUGINPATH", "")
14 |
15 |
16 | def setup():
17 | """Disable default plugins and only use test plugins"""
18 | pyblish.plugin.deregister_all_paths()
19 | pyblish.plugin.register_plugin_path(PLUGINPATH)
20 |
21 |
22 | def setup_empty():
23 | """Disable all plug-ins"""
24 | setup()
25 | pyblish.plugin.deregister_all_plugins()
26 | pyblish.plugin.deregister_all_paths()
27 |
28 |
29 | def setup_failing():
30 | """Expose failing plugins to discovery mechanism"""
31 | setup()
32 |
33 | # Append failing plugins
34 | failing_path = os.path.join(PLUGINPATH, 'failing')
35 | pyblish.plugin.register_plugin_path(failing_path)
36 |
37 |
38 | def setup_duplicate():
39 | """Expose duplicate plugins to discovery mechanism"""
40 | pyblish.plugin.deregister_all_paths()
41 |
42 | for copy in ('copy1', 'copy2'):
43 | path = os.path.join(PLUGINPATH, 'duplicate', copy)
44 | pyblish.plugin.register_plugin_path(path)
45 |
46 |
47 | def setup_wildcard():
48 | pyblish.plugin.deregister_all_paths()
49 |
50 | wildcard_path = os.path.join(PLUGINPATH, 'wildcards')
51 | pyblish.plugin.register_plugin_path(wildcard_path)
52 |
53 |
54 | def setup_invalid():
55 | """Expose invalid plugins to discovery mechanism"""
56 | pyblish.plugin.deregister_all_paths()
57 | failing_path = os.path.join(PLUGINPATH, 'invalid')
58 | pyblish.plugin.register_plugin_path(failing_path)
59 |
60 |
61 | def setup_full():
62 | """Expose a full processing chain for testing"""
63 | setup()
64 | pyblish.plugin.deregister_all_paths()
65 | path = os.path.join(PLUGINPATH, 'full')
66 | pyblish.plugin.register_plugin_path(path)
67 |
68 |
69 | def setup_echo():
70 | """Plugins that output information"""
71 | pyblish.plugin.deregister_all_paths()
72 |
73 | path = os.path.join(PLUGINPATH, 'echo')
74 | pyblish.plugin.register_plugin_path(path)
75 |
76 |
77 | def teardown():
78 | """Restore previously REGISTERED paths"""
79 |
80 | pyblish.plugin.deregister_all_paths()
81 | for path in REGISTERED:
82 | pyblish.plugin.register_plugin_path(path)
83 |
84 | os.environ["PYBLISHPLUGINPATH"] = ENVIRONMENT
85 | pyblish.api.deregister_all_plugins()
86 |
87 |
88 | # CLI Fixtures
89 |
90 |
91 | def setup_cli():
92 | os.environ["PYBLISHPLUGINPATH"] = PLUGINPATH
93 |
94 |
95 | def setup_failing_cli():
96 | """Expose failing plugins to CLI discovery mechanism"""
97 | # Append failing plugins
98 | failing_path = os.path.join(PLUGINPATH, 'failing_cli')
99 | os.environ["PYBLISHPLUGINPATH"] = failing_path
100 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/conform_instances.py:
--------------------------------------------------------------------------------
1 | """Mockup of potential integration with 3rd-party task managment suite"""
2 |
3 | import pyblish.api
4 | from pyblish.vendor import mock
5 |
6 | api = mock.MagicMock()
7 |
8 |
9 | class ConformInstances(pyblish.api.Conformer):
10 | hosts = ['python']
11 | families = ['test.family']
12 | version = (0, 1, 0)
13 |
14 | def process_instance(self, instance):
15 | uri = instance.data('assetId')
16 |
17 | if uri:
18 | # This instance has an associated entity
19 | # in the database, emit event
20 | message = "{0} was recently published".format(
21 | instance.data('name'))
22 | api.login(user='Test', password='testpass613')
23 | api.notify(message, uri)
24 |
25 | instance.set_data('notified', value=True)
26 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/custom/validate_custom_instance.py:
--------------------------------------------------------------------------------
1 | """Mockup of potential integration with 3rd-party task managment suite"""
2 |
3 | import pyblish.api
4 |
5 |
6 | class ValidateCustomInstance(pyblish.api.Validator):
7 | hosts = ['python']
8 | families = ['test.family']
9 | version = (0, 1, 0)
10 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/duplicate/copy1/select_duplicate_instances.py:
--------------------------------------------------------------------------------
1 | import pyblish.api
2 |
3 |
4 | @pyblish.api.log
5 | class SelectDuplicateInstance(pyblish.api.Selector):
6 | hosts = ['python']
7 | version = (0, 1, 0)
8 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/duplicate/copy2/select_duplicate_instances.py:
--------------------------------------------------------------------------------
1 | import pyblish.api
2 |
3 |
4 | @pyblish.api.log
5 | class SelectDuplicateInstance(pyblish.api.Selector):
6 | hosts = ['python']
7 | version = (0, 1, 0)
8 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/echo/select_echo.py:
--------------------------------------------------------------------------------
1 | import pyblish
2 |
3 |
4 | class SelectEcho(pyblish.Selector):
5 | hosts = ['*']
6 | version = (0, 0, 1)
7 |
8 | def process_context(self, context):
9 | print(context.data())
10 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/extract_documents.py:
--------------------------------------------------------------------------------
1 | import os
2 | import tempfile
3 | import pyblish.api
4 |
5 | @pyblish.api.log
6 | class ExtractDocuments(pyblish.api.Extractor):
7 | """Extract instances"""
8 |
9 | hosts = ['python']
10 | families = ['test.family']
11 | version = (0, 1, 0)
12 |
13 | def process_instance(self, instance):
14 | temp_dir = tempfile.mkdtemp()
15 |
16 | for document in instance:
17 | for name, content in document.items():
18 | temp_file = os.path.join(temp_dir,
19 | '{0}.txt'.format(name))
20 | with open(temp_file, 'w') as f:
21 | f.write(content)
22 |
23 | self.commit(temp_dir, instance)
24 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/extract_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ExtractInstances(pyblish.api.Extractor):
7 | """Extract instances"""
8 |
9 | hosts = ['python']
10 | families = ['test.family']
11 | version = (0, 1, 0)
12 |
13 | def process_instance(self, instance):
14 | self.log.debug("Extracting %s" % instance)
15 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/failing/conform_instances_fail.py:
--------------------------------------------------------------------------------
1 | """Mockup of potential integration with 3rd-party task managment suite"""
2 |
3 |
4 | import pyblish.api
5 | from pyblish.vendor import mock
6 |
7 | api = mock.MagicMock()
8 |
9 |
10 | class ConformInstancesFail(pyblish.api.Conformer):
11 | hosts = ['python']
12 | families = ['test.family']
13 | version = (0, 1, 0)
14 |
15 | def process_instance(self, instance):
16 | raise ValueError("Test fail")
17 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/failing/extract_instances_fail.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ExtractInstancesFail(pyblish.api.Extractor):
7 | hosts = ['python']
8 | families = ['test.family']
9 | version = (0, 1, 0)
10 |
11 | def process_instance(self, instance):
12 | raise ValueError("Could not extract {0}".format(instance))
13 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/failing/select_instances_fail.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class SelectInstancesError(pyblish.api.Selector):
7 | hosts = ['python']
8 | version = (0, 1, 0)
9 |
10 | def process_context(self, context):
11 | raise ValueError("Test exception")
12 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/failing/validate_instances_fail.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ValidateInstanceFail(pyblish.api.Validator):
7 | hosts = ['python']
8 | families = ['test.family']
9 | version = (0, 1, 0)
10 |
11 | def process_instance(self, instance):
12 | raise ValueError("Test fail")
13 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/failing_cli/extract_cli_instances.py:
--------------------------------------------------------------------------------
1 | import pyblish.api
2 |
3 |
4 | @pyblish.api.log
5 | class ExtractInstancesFail(pyblish.api.Extractor):
6 | hosts = ['python']
7 | families = ['test.family']
8 | version = (0, 1, 0)
9 |
10 | def process_instance(self, instance):
11 | pass
12 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/failing_cli/select_cli_instances.py:
--------------------------------------------------------------------------------
1 | import pyblish.api
2 |
3 |
4 | @pyblish.api.log
5 | class SelectCliInstances(pyblish.api.Selector):
6 | hosts = ['python']
7 | version = (0, 1, 0)
8 |
9 | def process_context(self, context):
10 | raise ValueError(context.data("fail"))
11 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/full/conform_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ConformInstances(pyblish.api.Conformer):
7 | hosts = ['python']
8 | families = ['full']
9 | version = (0, 1, 0)
10 |
11 | def process_instance(self, instance):
12 | instance.set_data('conformed', True)
13 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/full/extract_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ExtractInstances(pyblish.api.Extractor):
7 | hosts = ['python']
8 | families = ['full']
9 | version = (0, 1, 0)
10 |
11 | def process_instance(self, instance):
12 | instance.set_data('extracted', True)
13 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/full/select_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class SelectInstances(pyblish.api.Selector):
7 | hosts = ['python']
8 | version = (0, 1, 0)
9 |
10 | def process_context(self, context):
11 | inst = context.create_instance(name='Test')
12 | inst.set_data('family', 'full')
13 | inst.set_data('selected', True)
14 |
15 | # The following will be set during
16 | # processing of other plugins
17 | inst.set_data('validated', False)
18 | inst.set_data('extracted', False)
19 | inst.set_data('conformed', False)
20 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/full/validate_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ValidateInstances(pyblish.api.Validator):
7 | hosts = ['python']
8 | families = ['full']
9 | version = (0, 1, 0)
10 |
11 | def process_instance(self, instance):
12 | instance.set_data('validated', True)
13 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/invalid/select_missing_hosts.py:
--------------------------------------------------------------------------------
1 | """This plugin is incomplete and can't be used"""
2 |
3 | import pyblish.api
4 |
5 |
6 | @pyblish.api.log
7 | class SelectMissingHosts(pyblish.api.Selector):
8 | """Select instances"""
9 |
10 | requires = False
11 | version = "Invalid"
12 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/invalid/validate_missing_families.py:
--------------------------------------------------------------------------------
1 | """This plugin is incomplete and can't be used"""
2 |
3 | import pyblish.api
4 |
5 |
6 | @pyblish.api.log
7 | class ValidateMissingFamilies(pyblish.api.Validator):
8 | """Select instances"""
9 |
10 | hosts = ['python']
11 | version = list()
12 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/select_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class SelectInstances(pyblish.api.Selector):
7 | """Select instances"""
8 |
9 | hosts = ['python']
10 | version = (0, 1, 0)
11 |
12 | def process_context(self, context):
13 | instance = context.create_instance(name='inst1')
14 |
15 | for node in ('node1_PLY', 'node2_PLY', 'node3_GRP'):
16 | instance.add(node)
17 |
18 | for key, value in {
19 | 'publishable': True,
20 | 'family': 'test',
21 | 'startFrame': 1001,
22 | 'endFrame': 1025
23 | }.items():
24 |
25 | instance.set_data(key, value)
26 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/validate_instances.py:
--------------------------------------------------------------------------------
1 |
2 | import re
3 | import pyblish.api
4 |
5 |
6 | @pyblish.api.log
7 | class ValidateInstance(pyblish.api.Validator):
8 | """All nodes ends with a three-letter extension"""
9 |
10 | hosts = ['python']
11 | families = ['test.family']
12 | version = (0, 1, 0)
13 |
14 | def process_instance(self, instance):
15 | misnamed = list()
16 |
17 | for node in instance:
18 | self.log.debug("Validating {0}".format(node))
19 | if not re.match(r"^\w+_\w{3}?$", node):
20 | misnamed.append(node)
21 |
22 | if misnamed:
23 | raise ValueError("{0} was named incorrectly".format(node))
24 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/validate_other_instance.py:
--------------------------------------------------------------------------------
1 |
2 | import pyblish.api
3 |
4 |
5 | @pyblish.api.log
6 | class ValidateOtherInstance(pyblish.api.Validator):
7 | """All nodes ends with a three-letter extension"""
8 |
9 | hosts = ['python']
10 | families = ['test.other.family']
11 | version = (0, 1, 0)
12 |
13 | def process_instance(self, instance):
14 | return
15 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/wildcards/select_instances.py:
--------------------------------------------------------------------------------
1 | from pyblish import api
2 |
3 |
4 | @api.log
5 | class SelectInstances(api.Selector):
6 | hosts = ['*']
7 | version = (0, 0, 1)
8 |
9 | def process_context(self, context):
10 | files = context.create_instance(name='Files')
11 | files.add('Test1')
12 | files.add('Test2')
13 |
--------------------------------------------------------------------------------
/tests/pre11/plugins/wildcards/validate_instances.py:
--------------------------------------------------------------------------------
1 | from pyblish import api
2 |
3 |
4 | @api.log
5 | class ValidateInstances123(api.Validator):
6 | hosts = ['*']
7 | families = ['*']
8 | version = (0, 0, 1)
9 |
10 | def process_instance(self, instance):
11 | raise ValueError("I was called")
12 |
--------------------------------------------------------------------------------
/tests/pre11/test_cli.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | import pyblish
4 | import pyblish.cli
5 | import pyblish.api
6 |
7 | from pyblish.vendor import six
8 |
9 | from . import lib
10 |
11 | from pyblish.vendor.click.testing import CliRunner
12 | from nose.tools import (
13 | with_setup,
14 | )
15 | from pyblish.vendor import mock
16 |
17 |
18 | def ctx():
19 | """Return current Click context"""
20 | return pyblish.cli._ctx
21 |
22 |
23 | def context():
24 | """Return current context"""
25 | return ctx().obj["context"]
26 |
27 |
28 | @with_setup(lib.setup_empty)
29 | def test_all_commands_run():
30 | """All commands run without error"""
31 |
32 | for args in [[], # No argument
33 | ["--verbose"],
34 | ["publish"],
35 | ["--verbose", "publish"],
36 | ]:
37 |
38 | runner = CliRunner()
39 | result = runner.invoke(pyblish.cli.main, args)
40 |
41 | print("Args: %s" % args)
42 | print("Exit code: %s" % result.exit_code)
43 | print("Output: %s" % result.output)
44 | assert result.exit_code == 0
45 |
46 |
47 | def test_paths():
48 | """Paths are correctly returned from cli"""
49 | plugin = pyblish.api
50 | for flag, func in six.iteritems({
51 | "--paths": plugin.plugin_paths,
52 | "--registered-paths": plugin.registered_paths,
53 | "--environment-paths": plugin.environment_paths}):
54 |
55 | print("Flag: %s" % flag)
56 | runner = CliRunner()
57 | result = runner.invoke(pyblish.cli.main, [flag])
58 | for path in func():
59 | assert path in result.output
60 |
61 |
62 | def test_plugins():
63 | """CLI returns correct plugins"""
64 | runner = CliRunner()
65 | result = runner.invoke(pyblish.cli.main, ["--plugins"])
66 |
67 | for plugin in pyblish.api.discover():
68 | print("Plugin: %s" % plugin.__name__)
69 | assert plugin.__name__ in result.output
70 |
71 |
72 | def test_plugins_path():
73 | """Custom path via cli works"""
74 | custom_path = os.path.join(lib.PLUGINPATH, "custom")
75 | runner = CliRunner()
76 | result = runner.invoke(pyblish.cli.main,
77 | ["--plugin-path",
78 | custom_path,
79 | "--plugins"])
80 |
81 | plugins = pyblish.api.discover(paths=[custom_path])
82 | for plugin in plugins:
83 | print("Output: %s" % result.output)
84 | assert plugin.__name__ in result.output
85 |
86 |
87 | @with_setup(lib.setup_failing_cli, lib.teardown)
88 | def test_data():
89 | """Injecting data works"""
90 |
91 | runner = CliRunner()
92 | runner.invoke(pyblish.cli.main, [
93 | "--data", "key", "10", "publish"])
94 |
95 | assert context().data["key"] == 10
96 | assert not context().has_data("notExist")
97 |
98 |
99 | @mock.patch("pyblish.cli.log")
100 | def test_invalid_data(mock_log):
101 | """Data not JSON-serialisable is treated as string"""
102 |
103 | runner = CliRunner()
104 | runner.invoke(pyblish.cli.main,
105 | ["--data", "key", "['test': 'fdf}"])
106 |
107 | assert context().data["key"] == "['test': 'fdf}"
108 |
109 |
110 | def test_add_plugin_path():
111 | """Adding a plugin-path works"""
112 | custom_path = os.path.join(lib.PLUGINPATH, "custom")
113 |
114 | runner = CliRunner()
115 | runner.invoke(
116 | pyblish.cli.main,
117 | ["--add-plugin-path", custom_path, "--paths"])
118 |
119 | assert custom_path in ctx().obj["plugin_paths"]
120 |
121 |
122 | def test_version():
123 | """Version returned matches version of Pyblish"""
124 | runner = CliRunner()
125 | result = runner.invoke(pyblish.cli.main, ["--version"])
126 | print("Output: %s" % result.output)
127 | print("Version: %s" % pyblish.__version__)
128 | assert pyblish.__version__ in result.output
129 |
--------------------------------------------------------------------------------
/tests/pre11/test_plugins.py:
--------------------------------------------------------------------------------
1 |
2 | # Standard library
3 | import os
4 | import random
5 |
6 | # Local library
7 | import pyblish.plugin
8 |
9 | from .lib import (
10 | setup,
11 | teardown,
12 | setup_duplicate,
13 | setup_empty
14 | )
15 | from nose.tools import (
16 | with_setup,
17 | assert_equals,
18 | assert_true,
19 | assert_raises
20 | )
21 |
22 |
23 | @with_setup(setup, teardown)
24 | def test_print_plugin():
25 | """Printing plugin returns name of class"""
26 | plugins = pyblish.plugin.discover('validators')
27 | plugin = plugins[0]
28 | assert plugin.__name__ in repr(plugin())
29 | assert plugin.__name__ == str(plugin())
30 |
31 |
32 | @with_setup(setup, teardown)
33 | def test_name_override():
34 | """Instances return either a data-member of name or its native name"""
35 | inst = pyblish.plugin.Instance(name='my_name')
36 | assert inst.data('name') == 'my_name'
37 |
38 | inst.set_data('name', value='overridden_name')
39 | assert inst.data('name') == 'overridden_name'
40 |
41 |
42 | @with_setup(setup_duplicate, teardown)
43 | def test_no_duplicate_plugins():
44 | """Discovering plugins results in a single occurence of each plugin"""
45 | plugin_paths = pyblish.plugin.plugin_paths()
46 | assert_equals(len(plugin_paths), 2)
47 |
48 | plugins = pyblish.plugin.discover(type='selectors')
49 |
50 | # There are two plugins available, but one of them is
51 | # hidden under the duplicate module name. As a result,
52 | # only one of them is returned. A log message is printed
53 | # to alert the user.
54 | assert_equals(len(plugins), 1)
55 |
56 |
57 | def test_entities_prints_nicely():
58 | """Entities Context and Instance prints nicely"""
59 | ctx = pyblish.plugin.Context()
60 | inst = ctx.create_instance(name='Test')
61 | assert 'Instance' in repr(inst)
62 | assert 'pyblish.plugin' in repr(inst)
63 |
64 |
65 | def test_deregister_path():
66 | path = "/server/plugins"
67 | pyblish.plugin.register_plugin_path(path)
68 | assert os.path.normpath(path) in pyblish.plugin.registered_paths()
69 | pyblish.plugin.deregister_plugin_path(path)
70 | assert os.path.normpath(path) not in pyblish.plugin.registered_paths()
71 |
72 |
73 | def test_environment_paths():
74 | """Registering via the environment works"""
75 | key = "PYBLISHPLUGINPATH"
76 | path = '/test/path'
77 | existing = os.environ.get(key)
78 |
79 | try:
80 | os.environ[key] = path
81 | assert path in pyblish.plugin.plugin_paths()
82 | finally:
83 | os.environ[key] = existing or ''
84 |
85 |
86 | def test_instances_by_plugin_invariant():
87 | ctx = pyblish.plugin.Context()
88 | for i in range(10):
89 | inst = ctx.create_instance(name="Instance%i" % i)
90 | inst.set_data("family", "A")
91 |
92 | if i % 2:
93 | # Every other instance is of another family
94 | inst.set_data("family", "B")
95 |
96 | class MyPlugin(pyblish.plugin.Validator):
97 | hosts = ["python"]
98 | families = ["A"]
99 |
100 | def process(self, instance):
101 | pass
102 |
103 | compatible = pyblish.logic.instances_by_plugin(ctx, MyPlugin)
104 |
105 | # Test invariant
106 | #
107 | # in: [1, 2, 3, 4]
108 | # out: [1, 4] --> good
109 | #
110 | # in: [1, 2, 3, 4]
111 | # out: [2, 1, 4] --> bad
112 | #
113 |
114 | def test():
115 | for instance in compatible:
116 | assert ctx.index(instance) >= compatible.index(instance)
117 |
118 | test()
119 |
120 | compatible.reverse()
121 | assert_raises(AssertionError, test)
122 |
123 |
124 | def test_plugins_by_family_wildcard():
125 | """Plug-ins with wildcard family is included in every query"""
126 | Plugin1 = type("Plugin1",
127 | (pyblish.api.Validator,),
128 | {"families": ["myFamily"]})
129 | Plugin2 = type("Plugin2",
130 | (pyblish.api.Validator,),
131 | {"families": ["*"]})
132 |
133 | assert Plugin2 in pyblish.api.plugins_by_family(
134 | [Plugin1, Plugin2], "myFamily")
135 |
136 |
137 | @with_setup(setup, teardown)
138 | def test_plugins_sorted():
139 | """Plug-ins are returned sorted by their `order` attribute"""
140 | plugins = pyblish.api.discover()
141 | random.shuffle(plugins) # Randomise their order
142 | pyblish.api.sort_plugins(plugins)
143 |
144 | order = 0
145 | for plugin in plugins:
146 | assert_true(plugin.order >= order)
147 | order = plugin.order
148 |
149 | assert order > 0, plugins
150 |
151 |
152 | @with_setup(setup_empty, teardown)
153 | def test_inmemory_plugins():
154 | """In-memory plug-ins works fine"""
155 |
156 | class InMemoryPlugin(pyblish.api.Selector):
157 | hosts = ["*"]
158 | families = ["*"]
159 |
160 | def process_context(self, context):
161 | context.set_data("workingFine", True)
162 |
163 | pyblish.api.register_plugin(InMemoryPlugin)
164 |
165 | context = pyblish.api.Context()
166 | for result in pyblish.logic.process(
167 | func=pyblish.plugin.process,
168 | plugins=pyblish.api.discover,
169 | context=context):
170 | assert_true(result["plugin"].id == InMemoryPlugin.id)
171 |
172 | assert context.data("workingFine") is True
173 |
174 |
175 | @with_setup(setup_empty, teardown)
176 | def test_inmemory_query():
177 | """Asking for registered plug-ins works well"""
178 |
179 | InMemoryPlugin = type("InMemoryPlugin", (pyblish.api.Selector,), {})
180 | pyblish.api.register_plugin(InMemoryPlugin)
181 | assert pyblish.api.registered_plugins()[0].id == InMemoryPlugin.id
182 |
183 |
184 | @with_setup(setup_empty, teardown)
185 | def test_plugin_families_defaults():
186 | """Plug-ins without specific families default to wildcard"""
187 |
188 | class SelectInstances(pyblish.api.Selector):
189 | def process(self, instance):
190 | pass
191 |
192 | instance = pyblish.api.Instance("MyInstance")
193 | instance.set_data("family", "SomeFamily")
194 |
195 | assert_equals(pyblish.api.instances_by_plugin(
196 | [instance], SelectInstances)[0], instance)
197 |
198 | class ValidateInstances(pyblish.api.Validator):
199 | def process(self, instance):
200 | pass
201 |
202 | assert_equals(pyblish.api.instances_by_plugin(
203 | [instance], ValidateInstances)[0], instance)
204 |
--------------------------------------------------------------------------------
/tests/pre13/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pyblish/pyblish-base/03cda36b26010642bcbdc8dbf2f256f298742f9f/tests/pre13/__init__.py
--------------------------------------------------------------------------------
/tests/pre13/test_plugin.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import tempfile
4 |
5 | import pyblish.api
6 | import pyblish.plugin
7 | from nose.tools import (
8 | with_setup,
9 | assert_true,
10 | )
11 |
12 | from pyblish.vendor import six
13 |
14 | from .. import lib
15 |
16 |
17 | def test_plugins_from_module():
18 | """Getting plug-ins from a module works well"""
19 | import types
20 |
21 | module = types.ModuleType("myplugin")
22 | code = """
23 | import pyblish.api
24 |
25 | class MyPlugin(pyblish.api.Plugin):
26 | def process(self, context):
27 | pass
28 |
29 | class NotSubclassed(object):
30 | def process(self, context):
31 | pass
32 |
33 | def not_a_plugin():
34 | pass
35 |
36 |
37 | class InvalidPlugin(pyblish.api.Plugin):
38 | families = False
39 |
40 |
41 | class NotCompatible(pyblish.api.Plugin):
42 | hosts = ["not_compatible"]
43 |
44 |
45 | class BadRequires(pyblish.api.Plugin):
46 | requires = None
47 |
48 |
49 | class BadHosts(pyblish.api.Plugin):
50 | hosts = None
51 |
52 |
53 | class BadFamilies(pyblish.api.Plugin):
54 | families = None
55 |
56 |
57 | class BadHosts2(pyblish.api.Plugin):
58 | hosts = [None]
59 |
60 |
61 | class BadFamilies2(pyblish.api.Plugin):
62 | families = [None]
63 |
64 |
65 | """
66 |
67 | six.exec_(code, module.__dict__)
68 |
69 | plugins = pyblish.plugin.plugins_from_module(module)
70 |
71 | assert [p.__name__ for p in plugins] == ["MyPlugin"], plugins
72 |
73 |
74 | @with_setup(lib.setup_empty, lib.teardown)
75 | def test_discover_globals():
76 | """Modules imported in a plug-in are preserved in it's methods"""
77 |
78 | import types
79 |
80 | module = types.ModuleType("myplugin")
81 | code = """
82 | import pyblish.api
83 | import threading
84 |
85 | local_variable_is_present = 5
86 |
87 |
88 | class MyPlugin(pyblish.api.Plugin):
89 | def module_is_present(self):
90 | return True if threading else False
91 |
92 | def local_variable_is_present(self):
93 | return True if local_variable_is_present else False
94 |
95 | def process(self, context):
96 | return True if context else False
97 |
98 | """
99 |
100 | six.exec_(code, module.__dict__)
101 | MyPlugin = pyblish.plugin.plugins_from_module(module)[0]
102 | assert MyPlugin.__name__ == "MyPlugin"
103 |
104 | assert_true(MyPlugin().process(True))
105 | assert_true(MyPlugin().module_is_present())
106 | assert_true(MyPlugin().local_variable_is_present())
107 |
108 | try:
109 | tempdir = tempfile.mkdtemp()
110 | tempplugin = os.path.join(tempdir, "my_plugin.py")
111 | with open(tempplugin, "w") as f:
112 | f.write(code)
113 |
114 | pyblish.api.register_plugin_path(tempdir)
115 | plugins = pyblish.api.discover()
116 |
117 | finally:
118 | shutil.rmtree(tempdir)
119 |
120 | assert len(plugins) == 1
121 |
122 | MyPlugin = plugins[0]
123 |
124 | assert_true(MyPlugin().process(True))
125 | assert_true(MyPlugin().module_is_present())
126 | assert_true(MyPlugin().local_variable_is_present())
127 |
128 |
129 | @with_setup(lib.setup_empty, lib.teardown)
130 | def test_multi_families():
131 | """Instances with multiple families works well"""
132 |
133 | count = {"#": 0}
134 |
135 | class CollectInstance(pyblish.api.Collector):
136 | def process(self, context):
137 | instance = context.create_instance("MyInstance")
138 | instance.data["families"] = ["geometry", "human"]
139 |
140 | class ValidateHumans(pyblish.api.Validator):
141 | families = ["human"]
142 |
143 | def process(self, instance):
144 | assert "human" in instance.data["families"]
145 | count["#"] += 10
146 |
147 | class ValidateGeometry(pyblish.api.Validator):
148 | families = ["geometry"]
149 |
150 | def process(self, instance):
151 | assert "geometry" in instance.data["families"]
152 | count["#"] += 100
153 |
154 | for plugin in (CollectInstance, ValidateHumans, ValidateGeometry):
155 | pyblish.api.register_plugin(plugin)
156 |
157 | pyblish.util.publish()
158 |
159 | assert count["#"] == 110, count["#"]
160 |
--------------------------------------------------------------------------------
/tests/test_cli.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import shutil
4 | import tempfile
5 |
6 | import pyblish
7 | import pyblish.cli
8 | import pyblish.api
9 | from nose.tools import (
10 | with_setup,
11 | assert_equals,
12 | )
13 | from pyblish.vendor.click.testing import CliRunner
14 | from . import lib
15 |
16 | self = sys.modules[__name__]
17 |
18 |
19 | def setup():
20 | self.tempdir = tempfile.mkdtemp()
21 |
22 |
23 | def teardown():
24 | shutil.rmtree(self.tempdir)
25 |
26 |
27 | def ctx():
28 | """Return current Click context"""
29 | return pyblish.cli._ctx
30 |
31 |
32 | def context():
33 | """Return current context"""
34 | return ctx().obj["context"]
35 |
36 |
37 | def test_visualise_environment_paths():
38 | """Visualising environment paths works well"""
39 | current_path = os.environ.get("PYBLISHPLUGINPATH")
40 |
41 | try:
42 | os.environ["PYBLISHPLUGINPATH"] = "/custom/path"
43 |
44 | runner = CliRunner()
45 | result = runner.invoke(pyblish.cli.main, ["--environment-paths"])
46 |
47 | assert result.output.startswith("/custom/path"), result.output
48 |
49 | finally:
50 | if current_path is not None:
51 | os.environ["PYBLISHPLUGINPATH"] = current_path
52 |
53 |
54 | @with_setup(lib.setup_empty, lib.teardown)
55 | def test_publishing():
56 | """Basic publishing works"""
57 |
58 | count = {"#": 0}
59 |
60 | class Collector(pyblish.api.ContextPlugin):
61 | order = pyblish.api.CollectorOrder
62 |
63 | def process(self, context):
64 | self.log.warning("Running")
65 | count["#"] += 1
66 | context.create_instance("MyInstance")
67 |
68 | class MyValidator(pyblish.api.InstancePlugin):
69 | order = pyblish.api.ValidatorOrder
70 |
71 | def process(self, instance):
72 | count["#"] += 10
73 | assert instance.data["name"] == "MyInstance"
74 | count["#"] += 100
75 |
76 | pyblish.api.register_plugin(Collector)
77 | pyblish.api.register_plugin(MyValidator)
78 |
79 | runner = CliRunner()
80 | result = runner.invoke(pyblish.cli.main, ["publish"])
81 | print(result.output)
82 |
83 | assert count["#"] == 111, count
84 |
85 |
86 | @with_setup(lib.setup, lib.teardown)
87 | def test_environment_host_registration():
88 | """Host registration from PYBLISH_HOSTS works"""
89 |
90 | count = {"#": 0}
91 | hosts = ["test1", "test2"]
92 |
93 | # Test single hosts
94 | class SingleHostCollector(pyblish.api.ContextPlugin):
95 | order = pyblish.api.CollectorOrder
96 | host = hosts[0]
97 |
98 | def process(self, context):
99 | count["#"] += 1
100 |
101 | pyblish.api.register_plugin(SingleHostCollector)
102 |
103 | os.environ["PYBLISH_HOSTS"] = hosts[0]
104 |
105 | runner = CliRunner()
106 | result = runner.invoke(pyblish.cli.main, ["publish"])
107 | print(result.output)
108 |
109 | assert count["#"] == 1, count
110 |
111 | # Test multiple hosts
112 | pyblish.api.deregister_all_plugins()
113 |
114 | class MultipleHostsCollector(pyblish.api.ContextPlugin):
115 | order = pyblish.api.CollectorOrder
116 | host = hosts
117 |
118 | def process(self, context):
119 | count["#"] += 10
120 |
121 | pyblish.api.register_plugin(MultipleHostsCollector)
122 |
123 | os.environ["PYBLISH_HOSTS"] = os.pathsep.join(hosts)
124 |
125 | runner = CliRunner()
126 | result = runner.invoke(pyblish.cli.main, ["publish"])
127 | print(result.output)
128 |
129 | assert count["#"] == 11, count
130 |
131 |
132 | @with_setup(lib.setup, lib.teardown)
133 | def test_show_gui():
134 | """Showing GUI through cli works"""
135 |
136 | with tempfile.NamedTemporaryFile(dir=self.tempdir,
137 | delete=False,
138 | suffix=".py") as f:
139 | module_name = os.path.basename(f.name)[:-3]
140 | f.write(b"""\
141 | def show():
142 | print("Mock GUI shown successfully")
143 |
144 | if __name__ == '__main__':
145 | show()
146 | """)
147 |
148 | pythonpath = os.pathsep.join([
149 | self.tempdir,
150 | os.environ.get("PYTHONPATH", "")
151 | ])
152 |
153 | print(module_name)
154 |
155 | runner = CliRunner()
156 | result = runner.invoke(
157 | pyblish.cli.main, ["gui", module_name],
158 | env={"PYTHONPATH": pythonpath}
159 | )
160 |
161 | assert_equals(result.output.splitlines()[-1].rstrip(),
162 | "Mock GUI shown successfully")
163 | assert_equals(result.exit_code, 0)
164 |
165 |
166 | @with_setup(lib.setup, lib.teardown)
167 | def test_uses_gui_from_env():
168 | """Uses gui from environment var works"""
169 |
170 | with tempfile.NamedTemporaryFile(dir=self.tempdir,
171 | delete=False,
172 | suffix=".py") as f:
173 | module_name = os.path.basename(f.name)[:-3]
174 | f.write(b"""\
175 | def show():
176 | print("Mock GUI shown successfully")
177 |
178 | if __name__ == '__main__':
179 | show()
180 | """)
181 |
182 | pythonpath = os.pathsep.join([
183 | self.tempdir,
184 | os.environ.get("PYTHONPATH", "")
185 | ])
186 |
187 | runner = CliRunner()
188 | result = runner.invoke(
189 | pyblish.cli.main, ["gui"],
190 | env={
191 | "PYTHONPATH": pythonpath,
192 | "PYBLISH_GUI": module_name
193 | }
194 | )
195 |
196 | assert_equals(result.output.splitlines()[-1].rstrip(),
197 | "Mock GUI shown successfully")
198 | assert_equals(result.exit_code, 0)
199 |
200 |
201 | @with_setup(lib.setup, lib.teardown)
202 | def test_passing_data_to_gui():
203 | """Passing data to GUI works"""
204 |
205 | with tempfile.NamedTemporaryFile(dir=self.tempdir,
206 | delete=False,
207 | suffix=".py") as f:
208 | module_name = os.path.basename(f.name)[:-3]
209 | f.write(b"""\
210 | from pyblish import util
211 |
212 | def show():
213 | context = util.publish()
214 | print(context.data["passedFromTest"])
215 |
216 | if __name__ == '__main__':
217 | show()
218 | """)
219 |
220 | pythonpath = os.pathsep.join([
221 | self.tempdir,
222 | os.environ.get("PYTHONPATH", "")
223 | ])
224 |
225 | runner = CliRunner()
226 | result = runner.invoke(
227 | pyblish.cli.main, [
228 | "--data", "passedFromTest", "Data passed successfully",
229 | "gui", module_name
230 | ],
231 | env={"PYTHONPATH": pythonpath}
232 | )
233 |
234 | assert_equals(result.output.splitlines()[-1].rstrip(),
235 | "Data passed successfully")
236 | assert_equals(result.exit_code, 0)
237 |
238 |
239 | @with_setup(lib.setup, lib.teardown)
240 | def test_set_targets():
241 | """Setting targets works"""
242 |
243 | pythonpath = os.pathsep.join([
244 | self.tempdir,
245 | os.environ.get("PYTHONPATH", "")
246 | ])
247 |
248 | count = {"#": 0}
249 |
250 | class CollectorOne(pyblish.api.ContextPlugin):
251 | order = pyblish.api.CollectorOrder
252 | targets = ["imagesequence"]
253 |
254 | def process(self, context):
255 | self.log.warning("Running {0}".format(self.targets))
256 | count["#"] += 1
257 | context.create_instance("MyInstance")
258 |
259 | class CollectorTwo(pyblish.api.ContextPlugin):
260 | order = pyblish.api.CollectorOrder
261 | targets = ["model"]
262 |
263 | def process(self, context):
264 | self.log.warning("Running {0}".format(self.targets))
265 | count["#"] += 2
266 | context.create_instance("MyInstance")
267 |
268 | pyblish.api.register_plugin(CollectorOne)
269 | pyblish.api.register_plugin(CollectorTwo)
270 |
271 | runner = CliRunner()
272 | result = runner.invoke(pyblish.cli.main,
273 | ["publish", "--targets", "imagesequence"],
274 | env={"PYTHONPATH": pythonpath})
275 |
276 | print(result.output)
277 | assert count["#"] == 1, count
278 |
279 |
280 | @with_setup(lib.setup, lib.teardown)
281 | def test_set_targets_gui():
282 | """Setting targets with gui"""
283 |
284 | with tempfile.NamedTemporaryFile(dir=self.tempdir,
285 | delete=False,
286 | suffix=".py") as f:
287 | module_name = os.path.basename(f.name)[:-3]
288 | f.write(b"""\
289 | from pyblish import api
290 |
291 | def show():
292 | targets = api.registered_targets()
293 | print(targets[0])
294 |
295 | if __name__ == '__main__':
296 | show()
297 | """)
298 |
299 | pythonpath = os.pathsep.join([
300 | self.tempdir,
301 | os.environ.get("PYTHONPATH", "")
302 | ])
303 |
304 | # api.__init__ checks the PYBLISH_TARGETS variable
305 | runner = CliRunner()
306 | results = runner.invoke(pyblish.cli.main,
307 | ["gui", module_name],
308 | env={"PYTHONPATH": pythonpath,
309 | "PYBLISH_TARGETS": "imagesequence"})
310 |
311 | result = results.output.splitlines()[-1].rstrip()
312 | assert_equals(result, "imagesequence")
313 | assert_equals(results.exit_code, 0)
314 |
--------------------------------------------------------------------------------
/tests/test_context.py:
--------------------------------------------------------------------------------
1 |
2 | # Standard library
3 | import os
4 |
5 | # Local library
6 | import pyblish.lib
7 | import pyblish.plugin
8 |
9 | from . import lib
10 | from nose.tools import (
11 | with_setup,
12 | raises
13 | )
14 |
15 |
16 | package_path = pyblish.lib.main_package_path()
17 | plugin_path = os.path.join(package_path, 'tests', 'plugins')
18 |
19 | pyblish.plugin.deregister_all_paths()
20 | pyblish.plugin.register_plugin_path(plugin_path)
21 |
22 |
23 | @with_setup(lib.setup, lib.teardown)
24 | def test_data():
25 | """The data() interface works"""
26 |
27 | ctx = pyblish.plugin.Context()
28 |
29 | # Not passing a key returns all data as a dict,
30 | # but there is none yet.
31 | assert ctx.data(key=None) == dict(), ctx.data(key=None)
32 |
33 | key = 'test_key'
34 |
35 | ctx.set_data(key=key, value=True)
36 | assert ctx.data(key=key) is True
37 | assert ctx.has_data(key=key) is True
38 | ctx.remove_data(key=key)
39 | assert ctx.data(key=key) is None
40 | assert ctx.has_data(key=key) is False
41 |
42 |
43 | @with_setup(lib.setup, lib.teardown)
44 | def test_add_remove_instances():
45 | """Adding instances to context works"""
46 | ctx = pyblish.plugin.Context()
47 | inst = pyblish.plugin.Instance(name='Test', parent=ctx)
48 | ctx.remove(inst)
49 |
50 |
51 | @with_setup(lib.setup, lib.teardown)
52 | def test_instance_equality():
53 | """Instance equality works"""
54 | inst1 = pyblish.plugin.Instance('Test1')
55 | inst2 = pyblish.plugin.Instance('Test2')
56 | inst3 = pyblish.plugin.Instance('Test2')
57 |
58 | assert inst1 != inst2
59 | assert inst2 != inst3
60 |
61 |
62 | def test_context_itemgetter():
63 | """Context.get() works"""
64 | context = pyblish.api.Context()
65 | instanceA = context.create_instance("MyInstanceA")
66 | instanceB = context.create_instance("MyInstanceB")
67 |
68 | assert context[instanceA.id].name == "MyInstanceA"
69 | assert context[instanceB.id].name == "MyInstanceB"
70 | assert context.get(instanceA.id).name == "MyInstanceA"
71 | assert context.get(instanceB.id).name == "MyInstanceB"
72 | assert context[0].name == "MyInstanceA"
73 | assert context[1].name == "MyInstanceB"
74 |
75 |
76 | def test_in():
77 | """Querying whether an Instance is in a Context works"""
78 |
79 | context = pyblish.api.Context()
80 | instance = context.create_instance("MyInstance")
81 | assert instance.id in context
82 | assert "NotExist" not in context
83 |
84 |
85 | def test_add_to_context():
86 | """Adding to Context is deprecated, but still works"""
87 | context = pyblish.api.Context()
88 | instance = pyblish.api.Instance("MyInstance")
89 | context.add(instance)
90 | context.remove(instance)
91 |
92 |
93 | @raises(KeyError)
94 | def test_context_getitem_nonexisting():
95 | """Getting a nonexisting item from Context throws a KeyError"""
96 |
97 | context = pyblish.api.Context()
98 | context.create_instance("MyInstance")
99 | assert context.get("NotExist") is None
100 | context["NotExist"]
101 |
102 |
103 | @raises(IndexError)
104 | def test_context_getitem_outofrange():
105 | """Getting a item out of range throws an IndexError"""
106 |
107 | context = pyblish.api.Context()
108 | context.create_instance("MyInstance")
109 | context[10000]
110 |
111 |
112 | def test_context_getitem_validrange():
113 | """Getting an existing item works well"""
114 |
115 | context = pyblish.api.Context()
116 | context.create_instance("MyInstance")
117 | assert context[0].data["name"] == "MyInstance"
118 |
119 |
120 | def test_context_instance_unique_id():
121 | """Same named instances have unique ids"""
122 |
123 | context = pyblish.api.Context()
124 | instance1 = context.create_instance("MyInstance")
125 | instance2 = context.create_instance("MyInstance")
126 | assert instance1.id != instance2.id
127 |
128 |
129 | if __name__ == '__main__':
130 | test_add_remove_instances()
131 |
--------------------------------------------------------------------------------
/tests/test_events.py:
--------------------------------------------------------------------------------
1 | import pyblish.api
2 | import pyblish.util
3 | from nose.tools import (
4 | with_setup,
5 | )
6 | from . import lib
7 |
8 |
9 | @with_setup(lib.setup_empty)
10 | def test_published_event():
11 | """published is emitted upon finished publish"""
12 |
13 | count = {"#": 0}
14 |
15 | def on_published(context):
16 | assert isinstance(context, pyblish.api.Context)
17 | count["#"] += 1
18 |
19 | pyblish.api.register_callback("published", on_published)
20 | pyblish.util.publish()
21 |
22 | assert count["#"] == 1, count
23 |
24 |
25 | @with_setup(lib.setup_empty)
26 | def test_validated_event():
27 | """validated is emitted upon finished validation"""
28 |
29 | count = {"#": 0}
30 |
31 | def on_validated(context):
32 | assert isinstance(context, pyblish.api.Context)
33 | count["#"] += 1
34 |
35 | pyblish.api.register_callback("validated", on_validated)
36 | pyblish.util.validate()
37 |
38 | assert count["#"] == 1, count
39 |
40 | @with_setup(lib.setup_empty)
41 | def test_plugin_processed_event():
42 | """pluginProcessed is emitted upon a plugin being processed, regardless of its success"""
43 |
44 | class MyContextCollector(pyblish.api.ContextPlugin):
45 | order = pyblish.api.CollectorOrder
46 |
47 | def process(self, context):
48 | context.create_instance("A")
49 |
50 | class CheckInstancePass(pyblish.api.InstancePlugin):
51 | order = pyblish.api.ValidatorOrder
52 |
53 | def process(self, instance):
54 | pass
55 |
56 | class CheckInstanceFail(pyblish.api.InstancePlugin):
57 | order = pyblish.api.ValidatorOrder
58 |
59 | def process(self, instance):
60 | raise Exception("Test Fail")
61 |
62 | pyblish.api.register_plugin(MyContextCollector)
63 | pyblish.api.register_plugin(CheckInstancePass)
64 | pyblish.api.register_plugin(CheckInstanceFail)
65 |
66 |
67 | count = {"#": 0}
68 |
69 | def on_processed(result):
70 | assert isinstance(result, dict)
71 | count["#"] += 1
72 |
73 | pyblish.api.register_callback("pluginProcessed", on_processed)
74 | pyblish.util.publish()
75 |
76 | assert count["#"] == 3, count
77 |
78 | @with_setup(lib.setup_empty)
79 | def test_plugin_failed_event():
80 | """pluginFailed is emitted upon a plugin failing for any reason"""
81 |
82 | class MyContextCollector(pyblish.api.ContextPlugin):
83 | order = pyblish.api.CollectorOrder
84 | def process(self, context):
85 | context.create_instance("A")
86 |
87 | class CheckInstancePass(pyblish.api.InstancePlugin):
88 | order = pyblish.api.ValidatorOrder
89 | def process(self, instance):
90 | pass
91 |
92 | class CheckInstanceFail(pyblish.api.InstancePlugin):
93 | order = pyblish.api.ValidatorOrder
94 | def process(self, instance):
95 | raise Exception("Test Fail")
96 |
97 | pyblish.api.register_plugin(MyContextCollector)
98 | pyblish.api.register_plugin(CheckInstancePass)
99 | pyblish.api.register_plugin(CheckInstanceFail)
100 |
101 | count = {"#": 0}
102 |
103 | def on_failed(plugin, context, instance, error):
104 | assert issubclass(plugin, pyblish.api.InstancePlugin) #plugin == CheckInstanceFail
105 | assert isinstance(context, pyblish.api.Context)
106 | assert isinstance(instance, pyblish.api.Instance)
107 | assert isinstance(error, Exception)
108 |
109 | count["#"] += 1
110 |
111 | pyblish.api.register_callback("pluginFailed", on_failed)
112 | pyblish.util.publish()
113 |
114 | assert count["#"] == 1, count
--------------------------------------------------------------------------------
/tests/test_logic.py:
--------------------------------------------------------------------------------
1 | import os
2 | import contextlib
3 |
4 | # Local library
5 | from . import lib
6 |
7 | from pyblish import api, logic, plugin, util
8 |
9 | from nose.tools import (
10 | with_setup,
11 | assert_equals,
12 | )
13 |
14 |
15 | @contextlib.contextmanager
16 | def no_guis():
17 | os.environ.pop("PYBLISHGUI", None)
18 | for gui in logic.registered_guis():
19 | logic.deregister_gui(gui)
20 |
21 | yield
22 |
23 |
24 | @with_setup(lib.setup, lib.teardown)
25 | def test_iterator():
26 | """Iterator skips inactive plug-ins and instances"""
27 |
28 | count = {"#": 0}
29 |
30 | class MyCollector(api.ContextPlugin):
31 | order = api.CollectorOrder
32 |
33 | def process(self, context):
34 | inactive = context.create_instance("Inactive")
35 | active = context.create_instance("Active")
36 |
37 | inactive.data["publish"] = False
38 | active.data["publish"] = True
39 |
40 | count["#"] += 1
41 |
42 | class MyValidatorA(api.InstancePlugin):
43 | order = api.ValidatorOrder
44 | active = False
45 |
46 | def process(self, instance):
47 | count["#"] += 10
48 |
49 | class MyValidatorB(api.InstancePlugin):
50 | order = api.ValidatorOrder
51 |
52 | def process(self, instance):
53 | count["#"] += 100
54 |
55 | context = api.Context()
56 | plugins = [MyCollector, MyValidatorA, MyValidatorB]
57 |
58 | assert count["#"] == 0, count
59 |
60 | for Plugin, instance in logic.Iterator(plugins, context):
61 | assert instance.name != "Inactive" if instance else True
62 | assert Plugin.__name__ != "MyValidatorA"
63 |
64 | plugin.process(Plugin, context, instance)
65 |
66 | # Collector runs once, one Validator runs once
67 | assert count["#"] == 101, count
68 |
69 |
70 | def test_iterator_with_explicit_targets():
71 | """Iterator skips non-targeted plug-ins"""
72 |
73 | count = {"#": 0}
74 |
75 | class MyCollectorA(api.ContextPlugin):
76 | order = api.CollectorOrder
77 | targets = ["studio"]
78 |
79 | def process(self, context):
80 | count["#"] += 1
81 |
82 | class MyCollectorB(api.ContextPlugin):
83 | order = api.CollectorOrder
84 |
85 | def process(self, context):
86 | count["#"] += 10
87 |
88 | class MyCollectorC(api.ContextPlugin):
89 | order = api.CollectorOrder
90 | targets = ["studio"]
91 |
92 | def process(self, context):
93 | count["#"] += 100
94 |
95 | context = api.Context()
96 | plugins = [MyCollectorA, MyCollectorB, MyCollectorC]
97 |
98 | assert count["#"] == 0, count
99 |
100 | for Plugin, instance in logic.Iterator(
101 | plugins, context, targets=["studio"]
102 | ):
103 | assert Plugin.__name__ != "MyCollectorB"
104 |
105 | plugin.process(Plugin, context, instance)
106 |
107 | # Collector runs once, one Validator runs once
108 | assert count["#"] == 101, count
109 |
110 |
111 | def test_register_gui():
112 | """Registering at run-time takes precedence over those from environment"""
113 |
114 | with no_guis():
115 | os.environ["PYBLISHGUI"] = "second,third"
116 | logic.register_gui("first")
117 |
118 | print(logic.registered_guis())
119 | assert logic.registered_guis() == ["first", "second", "third"]
120 |
121 | with no_guis():
122 | os.environ["PYBLISH_GUI"] = "second,third"
123 | logic.register_gui("first")
124 |
125 | print(logic.registered_guis())
126 | assert logic.registered_guis() == ["first", "second", "third"]
127 |
128 |
129 | @with_setup(lib.setup_empty, lib.teardown)
130 | def test_subset_match():
131 | """Plugin.match = api.Subset works as expected"""
132 |
133 | count = {"#": 0}
134 |
135 | class MyPlugin(api.InstancePlugin):
136 | families = ["a", "b"]
137 | match = api.Subset
138 |
139 | def process(self, instance):
140 | count["#"] += 1
141 |
142 | context = api.Context()
143 |
144 | context.create_instance("not_included_1", families=["a"])
145 | context.create_instance("not_included_1", families=["x"])
146 | context.create_instance("included_1", families=["a", "b"])
147 | context.create_instance("included_2", families=["a", "b", "c"])
148 |
149 | util.publish(context, plugins=[MyPlugin])
150 |
151 | assert_equals(count["#"], 2)
152 |
153 | instances = logic.instances_by_plugin(context, MyPlugin)
154 | assert_equals(list(i.name for i in instances),
155 | ["included_1", "included_2"])
156 |
157 |
158 | def test_subset_exact():
159 | """Plugin.match = api.Exact works as expected"""
160 |
161 | count = {"#": 0}
162 |
163 | class MyPlugin(api.InstancePlugin):
164 | families = ["a", "b"]
165 | match = api.Exact
166 |
167 | def process(self, instance):
168 | count["#"] += 1
169 |
170 | context = api.Context()
171 |
172 | context.create_instance("not_included_1", families=["a"])
173 | context.create_instance("not_included_1", families=["x"])
174 | context.create_instance("not_included_3", families=["a", "b", "c"])
175 | instance = context.create_instance("included_1", families=["a", "b"])
176 |
177 | # Discard the solo-family member, which defaults to `default`.
178 | #
179 | # When using multiple families, it is common not to bother modifying
180 | # `family`, and in the future this member needn't be there at all and
181 | # may/should be removed. But till then, for complete clarity, it might
182 | # be worth removing this explicitly during the creation of instances
183 | # if instead choosing to use the `families` key.
184 | instance.data.pop("family")
185 |
186 | util.publish(context, plugins=[MyPlugin])
187 |
188 | assert_equals(count["#"], 1)
189 |
190 | instances = logic.instances_by_plugin(context, MyPlugin)
191 | assert_equals(list(i.name for i in instances), ["included_1"])
192 |
193 |
194 | def test_plugins_by_families():
195 | """The right plug-ins are returned from plugins_by_families"""
196 |
197 | class ClassA(api.Collector):
198 | families = ["a"]
199 |
200 | class ClassB(api.Collector):
201 | families = ["b"]
202 |
203 | class ClassC(api.Collector):
204 | families = ["c"]
205 |
206 | class ClassD(api.Collector):
207 | families = ["a", "b"]
208 | match = api.Intersection
209 |
210 | class ClassE(api.Collector):
211 | families = ["a", "b"]
212 | match = api.Subset
213 |
214 | class ClassF(api.Collector):
215 | families = ["a", "b"]
216 | match = api.Exact
217 |
218 | assert logic.plugins_by_families(
219 | [ClassA, ClassB, ClassC], ["a", "z"]) == [ClassA]
220 |
221 | assert logic.plugins_by_families(
222 | [ClassD, ClassE, ClassF], ["a"]) == [ClassD]
223 |
224 | assert logic.plugins_by_families(
225 | [ClassD, ClassE, ClassF], ["a", "b"]) == [ClassD, ClassE, ClassF]
226 |
227 | assert logic.plugins_by_families(
228 | [ClassD, ClassE, ClassF], ["a", "b", "c"]) == [ClassD, ClassE]
229 |
230 |
231 | @with_setup(lib.setup_empty, lib.teardown)
232 | def test_extracted_traceback_contains_correct_backtrace():
233 | api.register_plugin_path(os.path.dirname(__file__))
234 |
235 | context = api.Context()
236 | context.create_instance('test instance')
237 |
238 | plugins = api.discover()
239 | plugins = [p for p in plugins if p.__name__ in
240 | ('FailingExplicitPlugin', 'FailingImplicitPlugin')]
241 | util.publish(context, plugins)
242 |
243 | for result in context.data['results']:
244 | assert result["error"].traceback[0] == plugins[0].__module__
245 | formatted_tb = result['error'].formatted_traceback
246 | assert formatted_tb.startswith('Traceback (most recent call last):\n')
247 | assert formatted_tb.endswith('\nException: A test exception\n')
248 | assert 'File "{0}",'.format(plugins[0].__module__) in formatted_tb
249 |
--------------------------------------------------------------------------------
/tests/test_misc.py:
--------------------------------------------------------------------------------
1 | from . import lib
2 |
3 | import pyblish.lib
4 | import pyblish.compat
5 | from nose.tools import (
6 | with_setup
7 | )
8 |
9 |
10 | @with_setup(lib.setup, lib.teardown)
11 | def test_compat():
12 | """Using compatibility functions works"""
13 | pyblish.compat.sort([])
14 | pyblish.compat.deregister_all()
15 |
--------------------------------------------------------------------------------
/tests/test_simple.py:
--------------------------------------------------------------------------------
1 | import pyblish.api
2 | import pyblish.logic
3 | import pyblish.plugin
4 |
5 | from nose.tools import (
6 | assert_equals,
7 | with_setup
8 | )
9 |
10 | from . import lib
11 |
12 |
13 | @with_setup(lib.setup_empty, lib.teardown)
14 | def test_simple_discover():
15 | """Simple plug-ins works well with discover()"""
16 |
17 | count = {"#": 0}
18 |
19 | class SimplePlugin(pyblish.api.Plugin):
20 | def process(self, context):
21 | self.log.info("Processing context..")
22 | self.log.info("Done!")
23 | count["#"] += 1
24 |
25 | class SimplePlugin2(pyblish.api.Validator):
26 | def process(self, context):
27 | self.log.info("Processing context..")
28 | self.log.info("Done!")
29 | count["#"] += 1
30 |
31 | pyblish.api.register_plugin(SimplePlugin)
32 | pyblish.api.register_plugin(SimplePlugin2)
33 |
34 | assert_equals(
35 | list(p.id for p in pyblish.api.discover()),
36 | list(p.id for p in [SimplePlugin, SimplePlugin2])
37 | )
38 |
39 | pyblish.util.publish()
40 |
41 | assert_equals(count["#"], 2)
42 |
43 |
44 | def test_simple_manual():
45 | """Simple plug-ins work well"""
46 |
47 | count = {"#": 0}
48 |
49 | class SimplePlugin(pyblish.api.Plugin):
50 | def process(self):
51 | self.log.info("Processing..")
52 | self.log.info("Done!")
53 | count["#"] += 1
54 |
55 | pyblish.util.publish(plugins=[SimplePlugin])
56 |
57 | assert_equals(count["#"], 1)
58 |
59 |
60 | def test_simple_instance():
61 | """Simple plug-ins process instances as usual
62 |
63 | But considering they don't have an order, we will have to
64 | manually enforce an ordering if we are to expect
65 | them to run one after the other.
66 |
67 | """
68 |
69 | count = {"#": 0}
70 |
71 | class SimpleSelector(pyblish.api.Plugin):
72 | """Runs once"""
73 | order = 0
74 |
75 | def process(self, context):
76 | instance = context.create_instance(name="A")
77 | instance.set_data("family", "familyA")
78 |
79 | instance = context.create_instance(name="B")
80 | instance.set_data("family", "familyB")
81 |
82 | count["#"] += 1
83 |
84 | class SimpleValidator(pyblish.api.Plugin):
85 | """Runs twice"""
86 | order = 1
87 |
88 | def process(self, instance):
89 | count["#"] += 10
90 |
91 | class SimpleValidatorForB(pyblish.api.Plugin):
92 | """Runs once, for familyB"""
93 | families = ["familyB"]
94 | order = 2
95 |
96 | def process(self, instance):
97 | count["#"] += 100
98 |
99 | pyblish.util.publish(plugins=[SimpleSelector,
100 | SimpleValidator,
101 | SimpleValidatorForB])
102 |
103 | assert_equals(count["#"], 121)
104 |
105 |
106 | def test_simple_order():
107 | """Simple plug-ins defaults to running *before* SVEC"""
108 |
109 | order = list()
110 |
111 | class SimplePlugin(pyblish.api.Plugin):
112 | def process(self):
113 | order.append(1)
114 |
115 | class SelectSomething1234(pyblish.api.Selector):
116 | def process(self):
117 | order.append(2)
118 |
119 | class ValidateSomething1234(pyblish.api.Validator):
120 | def process(self):
121 | order.append(3)
122 |
123 | class ExtractSomething1234(pyblish.api.Extractor):
124 | def process(self):
125 | order.append(4)
126 |
127 | for plugin in (ExtractSomething1234,
128 | ValidateSomething1234,
129 | SimplePlugin,
130 | SelectSomething1234):
131 | pyblish.api.register_plugin(plugin)
132 |
133 | plugins = pyblish.api.discover()
134 | context = pyblish.api.Context()
135 | for result in pyblish.logic.process(
136 | func=pyblish.plugin.process,
137 | plugins=plugins,
138 | context=context):
139 | print(result)
140 |
141 | assert_equals(order, [1, 2, 3, 4])
142 |
--------------------------------------------------------------------------------
/tests/test_util.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from . import lib
4 |
5 | from pyblish import api, util
6 | from nose.tools import (
7 | with_setup
8 | )
9 |
10 |
11 | def test_convenience_plugins_argument():
12 | """util._convenience() `plugins` argument works
13 |
14 | Issue: #286
15 |
16 | """
17 |
18 | count = {"#": 0}
19 |
20 | class PluginA(api.ContextPlugin):
21 | order = api.CollectorOrder
22 |
23 | def process(self, context):
24 | count["#"] += 1
25 |
26 | class PluginB(api.ContextPlugin):
27 | order = api.CollectorOrder
28 |
29 | def process(self, context):
30 | count["#"] += 10
31 |
32 | assert count["#"] == 0
33 |
34 | api.register_plugin(PluginA)
35 | util._convenience(plugins=[PluginB], order=0.5)
36 |
37 | assert count["#"] == 10, count
38 |
39 |
40 | @with_setup(lib.setup, lib.teardown)
41 | def test_convenience_functions():
42 | """convenience functions works as expected"""
43 |
44 | count = {"#": 0}
45 |
46 | class Collector(api.ContextPlugin):
47 | order = api.CollectorOrder
48 |
49 | def process(self, context):
50 | context.create_instance("MyInstance")
51 | count["#"] += 1
52 |
53 | class Validator(api.InstancePlugin):
54 | order = api.ValidatorOrder
55 |
56 | def process(self, instance):
57 | count["#"] += 10
58 |
59 | class Extractor(api.InstancePlugin):
60 | order = api.ExtractorOrder
61 |
62 | def process(self, instance):
63 | count["#"] += 100
64 |
65 | class Integrator(api.ContextPlugin):
66 | order = api.IntegratorOrder
67 |
68 | def process(self, instance):
69 | count["#"] += 1000
70 |
71 | class PostIntegrator(api.ContextPlugin):
72 | order = api.IntegratorOrder + 0.1
73 |
74 | def process(self, instance):
75 | count["#"] += 10000
76 |
77 | class NotCVEI(api.ContextPlugin):
78 | """This plug-in is too far away from Integration to qualify as CVEI"""
79 | order = api.IntegratorOrder + 2.0
80 |
81 | def process(self, instance):
82 | count["#"] += 100000
83 |
84 | assert count["#"] == 0
85 |
86 | for Plugin in (Collector,
87 | Validator,
88 | Extractor,
89 | Integrator,
90 | PostIntegrator,
91 | NotCVEI):
92 | api.register_plugin(Plugin)
93 |
94 | context = util.collect()
95 |
96 | assert count["#"] == 1
97 |
98 | util.validate(context)
99 |
100 | assert count["#"] == 11
101 |
102 | util.extract(context)
103 |
104 | assert count["#"] == 111
105 |
106 | util.integrate(context)
107 |
108 | assert count["#"] == 11111
109 |
110 |
111 | @with_setup(lib.setup, lib.teardown)
112 | def test_multiple_instance_util_publish():
113 | """Multiple instances work with util.publish()
114 |
115 | This also ensures it operates correctly with an
116 | InstancePlugin collector.
117 |
118 | """
119 |
120 | count = {"#": 0}
121 |
122 | class MyContextCollector(api.ContextPlugin):
123 | order = api.CollectorOrder
124 |
125 | def process(self, context):
126 | context.create_instance("A")
127 | context.create_instance("B")
128 | count["#"] += 1
129 |
130 | class MyInstancePluginCollector(api.InstancePlugin):
131 | order = api.CollectorOrder + 0.1
132 |
133 | def process(self, instance):
134 | count["#"] += 1
135 |
136 | api.register_plugin(MyContextCollector)
137 | api.register_plugin(MyInstancePluginCollector)
138 |
139 | # Ensure it runs without errors
140 | util.publish()
141 |
142 | assert count["#"] == 3
143 |
144 |
145 | @with_setup(lib.setup, lib.teardown)
146 | def test_modify_context_during_CVEI():
147 | """Custom logic made possible via convenience members"""
148 |
149 | count = {"#": 0}
150 |
151 | class MyCollector(api.ContextPlugin):
152 | order = api.CollectorOrder
153 |
154 | def process(self, context):
155 | camera = context.create_instance("MyCamera")
156 | model = context.create_instance("MyModel")
157 |
158 | camera.data["family"] = "camera"
159 | model.data["family"] = "model"
160 |
161 | count["#"] += 1
162 |
163 | class MyValidator(api.InstancePlugin):
164 | order = api.ValidatorOrder
165 |
166 | def process(self, instance):
167 | count["#"] += 10
168 |
169 | api.register_plugin(MyCollector)
170 | api.register_plugin(MyValidator)
171 |
172 | context = api.Context()
173 |
174 | assert count["#"] == 0, count
175 |
176 | util.collect(context)
177 |
178 | assert count["#"] == 1, count
179 |
180 | context[:] = filter(lambda i: i.data["family"] == "camera", context)
181 |
182 | util.validate(context)
183 |
184 | # Only model remains
185 | assert count["#"] == 11, count
186 |
187 | # No further processing occurs.
188 | util.extract(context)
189 | util.integrate(context)
190 |
191 | assert count["#"] == 11, count
192 |
193 |
194 | @with_setup(lib.setup, lib.teardown)
195 | def test_environment_host_registration():
196 | """Host registration from PYBLISH_HOSTS works"""
197 |
198 | count = {"#": 0}
199 | hosts = ["test1", "test2"]
200 |
201 | # Test single hosts
202 | class SingleHostCollector(api.ContextPlugin):
203 | order = api.CollectorOrder
204 | host = hosts[0]
205 |
206 | def process(self, context):
207 | count["#"] += 1
208 |
209 | api.register_plugin(SingleHostCollector)
210 |
211 | context = api.Context()
212 |
213 | os.environ["PYBLISH_HOSTS"] = "test1"
214 | util.collect(context)
215 |
216 | assert count["#"] == 1, count
217 |
218 | # Test multiple hosts
219 | api.deregister_all_plugins()
220 |
221 | class MultipleHostsCollector(api.ContextPlugin):
222 | order = api.CollectorOrder
223 | host = hosts
224 |
225 | def process(self, context):
226 | count["#"] += 10
227 |
228 | api.register_plugin(MultipleHostsCollector)
229 |
230 | context = api.Context()
231 |
232 | os.environ["PYBLISH_HOSTS"] = os.pathsep.join(hosts)
233 | util.collect(context)
234 |
235 | assert count["#"] == 11, count
236 |
237 |
238 | @with_setup(lib.setup, lib.teardown)
239 | def test_publishing_explicit_targets():
240 | """Publishing with explicit targets works"""
241 |
242 | count = {"#": 0}
243 |
244 | class plugin(api.ContextPlugin):
245 | targets = ["custom"]
246 |
247 | def process(self, context):
248 | count["#"] += 1
249 |
250 | api.register_plugin(plugin)
251 |
252 | util.publish(targets=["custom"])
253 |
254 | assert count["#"] == 1, count
255 |
256 |
257 | def test_publishing_explicit_targets_with_global():
258 | """Publishing with explicit and globally registered targets works"""
259 |
260 | count = {"#": 0}
261 |
262 | class Plugin1(api.ContextPlugin):
263 | targets = ["custom"]
264 |
265 | def process(self, context):
266 | count["#"] += 1
267 |
268 | class Plugin2(api.ContextPlugin):
269 | targets = ["foo"]
270 |
271 | def process(self, context):
272 | count["#"] += 10
273 |
274 | api.register_target("foo")
275 | api.register_target("custom")
276 | api.register_plugin(Plugin1)
277 | api.register_plugin(Plugin2)
278 |
279 | util.publish(targets=["custom"])
280 |
281 | assert count["#"] == 1, count
282 | assert api.registered_targets() == ["foo", "custom"]
283 |
284 | api.deregister_all_targets()
285 |
286 |
287 | @with_setup(lib.setup, lib.teardown)
288 | def test_per_session_targets():
289 | """Register targets per session works"""
290 |
291 | util.publish(targets=["custom"])
292 |
293 | registered_targets = api.registered_targets()
294 | assert registered_targets == [], registered_targets
295 |
296 |
297 | @with_setup(lib.setup, lib.teardown)
298 | def test_publishing_collectors():
299 | """Running collectors with targets works"""
300 |
301 | count = {"#": 0}
302 |
303 | class plugin(api.ContextPlugin):
304 | order = api.CollectorOrder
305 | targets = ["custom"]
306 |
307 | def process(self, context):
308 | count["#"] += 1
309 |
310 | api.register_plugin(plugin)
311 |
312 | util.collect(targets=["custom"])
313 |
314 | assert count["#"] == 1, count
315 |
316 |
317 | @with_setup(lib.setup, lib.teardown)
318 | def test_publishing_validators():
319 | """Running validators with targets works"""
320 |
321 | count = {"#": 0}
322 |
323 | class plugin(api.ContextPlugin):
324 | order = api.ValidatorOrder
325 | targets = ["custom"]
326 |
327 | def process(self, context):
328 | count["#"] += 1
329 |
330 | api.register_plugin(plugin)
331 |
332 | util.validate(targets=["custom"])
333 |
334 | assert count["#"] == 1, count
335 |
336 |
337 | @with_setup(lib.setup, lib.teardown)
338 | def test_publishing_extractors():
339 | """Running extractors with targets works"""
340 |
341 | count = {"#": 0}
342 |
343 | class plugin(api.ContextPlugin):
344 | order = api.ExtractorOrder
345 | targets = ["custom"]
346 |
347 | def process(self, context):
348 | count["#"] += 1
349 |
350 | api.register_plugin(plugin)
351 |
352 | util.extract(targets=["custom"])
353 |
354 | assert count["#"] == 1, count
355 |
356 |
357 | @with_setup(lib.setup, lib.teardown)
358 | def test_publishing_integrators():
359 | """Running integrators with targets works"""
360 |
361 | count = {"#": 0}
362 |
363 | class plugin(api.ContextPlugin):
364 | order = api.IntegratorOrder
365 | targets = ["custom"]
366 |
367 | def process(self, context):
368 | count["#"] += 1
369 |
370 | api.register_plugin(plugin)
371 |
372 | util.integrate(targets=["custom"])
373 |
374 | assert count["#"] == 1, count
375 |
376 |
377 | @with_setup(lib.setup, lib.teardown)
378 | def test_progress_existence():
379 | """Progress data member exists"""
380 |
381 | class plugin(api.ContextPlugin):
382 | pass
383 |
384 | api.register_plugin(plugin)
385 |
386 | result = next(util.publish_iter())
387 |
388 | assert "progress" in result, result
389 |
390 |
391 | @with_setup(lib.setup, lib.teardown)
392 | def test_publish_iter_increment_progress():
393 | """Publish iteration increments progress"""
394 |
395 | class pluginA(api.ContextPlugin):
396 | pass
397 |
398 | class pluginB(api.ContextPlugin):
399 | pass
400 |
401 | api.register_plugin(pluginA)
402 | api.register_plugin(pluginB)
403 |
404 | iterator = util.publish_iter()
405 |
406 | pluginA_progress = next(iterator)["progress"]
407 | pluginB_progress = next(iterator)["progress"]
408 |
409 | assert pluginA_progress < pluginB_progress
410 |
--------------------------------------------------------------------------------