Merge pyextra subtree
commit
ae44a57565
|
@ -1,103 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: Flask
|
|
||||||
Version: 1.0.2
|
|
||||||
Summary: A simple framework for building complex web applications.
|
|
||||||
Home-page: https://www.palletsprojects.com/p/flask/
|
|
||||||
Author: Pallets team
|
|
||||||
Author-email: contact@palletsprojects.com
|
|
||||||
License: BSD
|
|
||||||
Description: Flask
|
|
||||||
=====
|
|
||||||
|
|
||||||
Flask is a lightweight `WSGI`_ web application framework. It is designed
|
|
||||||
to make getting started quick and easy, with the ability to scale up to
|
|
||||||
complex applications. It began as a simple wrapper around `Werkzeug`_
|
|
||||||
and `Jinja`_ and has become one of the most popular Python web
|
|
||||||
application frameworks.
|
|
||||||
|
|
||||||
Flask offers suggestions, but doesn't enforce any dependencies or
|
|
||||||
project layout. It is up to the developer to choose the tools and
|
|
||||||
libraries they want to use. There are many extensions provided by the
|
|
||||||
community that make adding new functionality easy.
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
pip install -U Flask
|
|
||||||
|
|
||||||
|
|
||||||
A Simple Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from flask import Flask
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def hello():
|
|
||||||
return 'Hello, World!'
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ FLASK_APP=hello.py flask run
|
|
||||||
* Serving Flask app "hello"
|
|
||||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports Flask and the libraries
|
|
||||||
it uses. In order to grow the community of contributors and users, and
|
|
||||||
allow the maintainers to devote more time to the projects, `please
|
|
||||||
donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://psfmember.org/civicrm/contribute/transact?reset=1&id=20
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Website: https://www.palletsprojects.com/p/flask/
|
|
||||||
* Documentation: http://flask.pocoo.org/docs/
|
|
||||||
* License: `BSD <https://github.com/pallets/flask/blob/master/LICENSE>`_
|
|
||||||
* Releases: https://pypi.org/project/Flask/
|
|
||||||
* Code: https://github.com/pallets/flask
|
|
||||||
* Issue tracker: https://github.com/pallets/flask/issues
|
|
||||||
* Test status:
|
|
||||||
|
|
||||||
* Linux, Mac: https://travis-ci.org/pallets/flask
|
|
||||||
* Windows: https://ci.appveyor.com/project/pallets/flask
|
|
||||||
|
|
||||||
* Test coverage: https://codecov.io/gh/pallets/flask
|
|
||||||
|
|
||||||
.. _WSGI: https://wsgi.readthedocs.io
|
|
||||||
.. _Werkzeug: https://www.palletsprojects.com/p/werkzeug/
|
|
||||||
.. _Jinja: https://www.palletsprojects.com/p/jinja/
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
Platform: any
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Framework :: Flask
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
@ -1,223 +0,0 @@
|
||||||
AUTHORS
|
|
||||||
CHANGES.rst
|
|
||||||
LICENSE
|
|
||||||
MANIFEST.in
|
|
||||||
Makefile
|
|
||||||
README.rst
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
tox.ini
|
|
||||||
Flask.egg-info/PKG-INFO
|
|
||||||
Flask.egg-info/SOURCES.txt
|
|
||||||
Flask.egg-info/dependency_links.txt
|
|
||||||
Flask.egg-info/entry_points.txt
|
|
||||||
Flask.egg-info/not-zip-safe
|
|
||||||
Flask.egg-info/requires.txt
|
|
||||||
Flask.egg-info/top_level.txt
|
|
||||||
artwork/LICENSE
|
|
||||||
artwork/logo-full.svg
|
|
||||||
artwork/logo-lineart.svg
|
|
||||||
docs/Makefile
|
|
||||||
docs/advanced_foreword.rst
|
|
||||||
docs/api.rst
|
|
||||||
docs/appcontext.rst
|
|
||||||
docs/becomingbig.rst
|
|
||||||
docs/blueprints.rst
|
|
||||||
docs/changelog.rst
|
|
||||||
docs/cli.rst
|
|
||||||
docs/conf.py
|
|
||||||
docs/config.rst
|
|
||||||
docs/contents.rst.inc
|
|
||||||
docs/contributing.rst
|
|
||||||
docs/design.rst
|
|
||||||
docs/errorhandling.rst
|
|
||||||
docs/extensiondev.rst
|
|
||||||
docs/extensions.rst
|
|
||||||
docs/flaskstyle.sty
|
|
||||||
docs/foreword.rst
|
|
||||||
docs/htmlfaq.rst
|
|
||||||
docs/index.rst
|
|
||||||
docs/installation.rst
|
|
||||||
docs/latexindex.rst
|
|
||||||
docs/license.rst
|
|
||||||
docs/logging.rst
|
|
||||||
docs/logo.pdf
|
|
||||||
docs/make.bat
|
|
||||||
docs/quickstart.rst
|
|
||||||
docs/reqcontext.rst
|
|
||||||
docs/security.rst
|
|
||||||
docs/server.rst
|
|
||||||
docs/shell.rst
|
|
||||||
docs/signals.rst
|
|
||||||
docs/styleguide.rst
|
|
||||||
docs/templating.rst
|
|
||||||
docs/testing.rst
|
|
||||||
docs/unicode.rst
|
|
||||||
docs/upgrading.rst
|
|
||||||
docs/views.rst
|
|
||||||
docs/_static/debugger.png
|
|
||||||
docs/_static/flask-favicon.ico
|
|
||||||
docs/_static/flask.png
|
|
||||||
docs/_static/logo-full.png
|
|
||||||
docs/_static/no.png
|
|
||||||
docs/_static/pycharm-runconfig.png
|
|
||||||
docs/_static/touch-icon.png
|
|
||||||
docs/_static/yes.png
|
|
||||||
docs/deploying/cgi.rst
|
|
||||||
docs/deploying/fastcgi.rst
|
|
||||||
docs/deploying/index.rst
|
|
||||||
docs/deploying/mod_wsgi.rst
|
|
||||||
docs/deploying/uwsgi.rst
|
|
||||||
docs/deploying/wsgi-standalone.rst
|
|
||||||
docs/patterns/apierrors.rst
|
|
||||||
docs/patterns/appdispatch.rst
|
|
||||||
docs/patterns/appfactories.rst
|
|
||||||
docs/patterns/caching.rst
|
|
||||||
docs/patterns/celery.rst
|
|
||||||
docs/patterns/deferredcallbacks.rst
|
|
||||||
docs/patterns/distribute.rst
|
|
||||||
docs/patterns/errorpages.rst
|
|
||||||
docs/patterns/fabric.rst
|
|
||||||
docs/patterns/favicon.rst
|
|
||||||
docs/patterns/fileuploads.rst
|
|
||||||
docs/patterns/flashing.rst
|
|
||||||
docs/patterns/index.rst
|
|
||||||
docs/patterns/jquery.rst
|
|
||||||
docs/patterns/lazyloading.rst
|
|
||||||
docs/patterns/methodoverrides.rst
|
|
||||||
docs/patterns/mongokit.rst
|
|
||||||
docs/patterns/packages.rst
|
|
||||||
docs/patterns/requestchecksum.rst
|
|
||||||
docs/patterns/sqlalchemy.rst
|
|
||||||
docs/patterns/sqlite3.rst
|
|
||||||
docs/patterns/streaming.rst
|
|
||||||
docs/patterns/subclassing.rst
|
|
||||||
docs/patterns/templateinheritance.rst
|
|
||||||
docs/patterns/urlprocessors.rst
|
|
||||||
docs/patterns/viewdecorators.rst
|
|
||||||
docs/patterns/wtforms.rst
|
|
||||||
docs/tutorial/blog.rst
|
|
||||||
docs/tutorial/database.rst
|
|
||||||
docs/tutorial/deploy.rst
|
|
||||||
docs/tutorial/factory.rst
|
|
||||||
docs/tutorial/flaskr_edit.png
|
|
||||||
docs/tutorial/flaskr_index.png
|
|
||||||
docs/tutorial/flaskr_login.png
|
|
||||||
docs/tutorial/index.rst
|
|
||||||
docs/tutorial/install.rst
|
|
||||||
docs/tutorial/layout.rst
|
|
||||||
docs/tutorial/next.rst
|
|
||||||
docs/tutorial/static.rst
|
|
||||||
docs/tutorial/templates.rst
|
|
||||||
docs/tutorial/tests.rst
|
|
||||||
docs/tutorial/views.rst
|
|
||||||
examples/javascript/.gitignore
|
|
||||||
examples/javascript/LICENSE
|
|
||||||
examples/javascript/MANIFEST.in
|
|
||||||
examples/javascript/README.rst
|
|
||||||
examples/javascript/setup.cfg
|
|
||||||
examples/javascript/setup.py
|
|
||||||
examples/javascript/js_example/__init__.py
|
|
||||||
examples/javascript/js_example/views.py
|
|
||||||
examples/javascript/js_example/templates/base.html
|
|
||||||
examples/javascript/js_example/templates/fetch.html
|
|
||||||
examples/javascript/js_example/templates/jquery.html
|
|
||||||
examples/javascript/js_example/templates/plain.html
|
|
||||||
examples/javascript/tests/conftest.py
|
|
||||||
examples/javascript/tests/test_js_example.py
|
|
||||||
examples/tutorial/.gitignore
|
|
||||||
examples/tutorial/LICENSE
|
|
||||||
examples/tutorial/MANIFEST.in
|
|
||||||
examples/tutorial/README.rst
|
|
||||||
examples/tutorial/setup.cfg
|
|
||||||
examples/tutorial/setup.py
|
|
||||||
examples/tutorial/flaskr/__init__.py
|
|
||||||
examples/tutorial/flaskr/auth.py
|
|
||||||
examples/tutorial/flaskr/blog.py
|
|
||||||
examples/tutorial/flaskr/db.py
|
|
||||||
examples/tutorial/flaskr/schema.sql
|
|
||||||
examples/tutorial/flaskr/static/style.css
|
|
||||||
examples/tutorial/flaskr/templates/base.html
|
|
||||||
examples/tutorial/flaskr/templates/auth/login.html
|
|
||||||
examples/tutorial/flaskr/templates/auth/register.html
|
|
||||||
examples/tutorial/flaskr/templates/blog/create.html
|
|
||||||
examples/tutorial/flaskr/templates/blog/index.html
|
|
||||||
examples/tutorial/flaskr/templates/blog/update.html
|
|
||||||
examples/tutorial/tests/conftest.py
|
|
||||||
examples/tutorial/tests/data.sql
|
|
||||||
examples/tutorial/tests/test_auth.py
|
|
||||||
examples/tutorial/tests/test_blog.py
|
|
||||||
examples/tutorial/tests/test_db.py
|
|
||||||
examples/tutorial/tests/test_factory.py
|
|
||||||
flask/__init__.py
|
|
||||||
flask/__main__.py
|
|
||||||
flask/_compat.py
|
|
||||||
flask/app.py
|
|
||||||
flask/blueprints.py
|
|
||||||
flask/cli.py
|
|
||||||
flask/config.py
|
|
||||||
flask/ctx.py
|
|
||||||
flask/debughelpers.py
|
|
||||||
flask/globals.py
|
|
||||||
flask/helpers.py
|
|
||||||
flask/logging.py
|
|
||||||
flask/sessions.py
|
|
||||||
flask/signals.py
|
|
||||||
flask/templating.py
|
|
||||||
flask/testing.py
|
|
||||||
flask/views.py
|
|
||||||
flask/wrappers.py
|
|
||||||
flask/json/__init__.py
|
|
||||||
flask/json/tag.py
|
|
||||||
tests/conftest.py
|
|
||||||
tests/test_appctx.py
|
|
||||||
tests/test_basic.py
|
|
||||||
tests/test_blueprints.py
|
|
||||||
tests/test_cli.py
|
|
||||||
tests/test_config.py
|
|
||||||
tests/test_helpers.py
|
|
||||||
tests/test_instance_config.py
|
|
||||||
tests/test_json_tag.py
|
|
||||||
tests/test_logging.py
|
|
||||||
tests/test_regression.py
|
|
||||||
tests/test_reqctx.py
|
|
||||||
tests/test_signals.py
|
|
||||||
tests/test_subclassing.py
|
|
||||||
tests/test_templating.py
|
|
||||||
tests/test_testing.py
|
|
||||||
tests/test_user_error_handler.py
|
|
||||||
tests/test_views.py
|
|
||||||
tests/static/config.json
|
|
||||||
tests/static/index.html
|
|
||||||
tests/templates/_macro.html
|
|
||||||
tests/templates/context_template.html
|
|
||||||
tests/templates/escaping_template.html
|
|
||||||
tests/templates/mail.txt
|
|
||||||
tests/templates/non_escaping_template.txt
|
|
||||||
tests/templates/simple_template.html
|
|
||||||
tests/templates/template_filter.html
|
|
||||||
tests/templates/template_test.html
|
|
||||||
tests/templates/nested/nested.txt
|
|
||||||
tests/test_apps/.env
|
|
||||||
tests/test_apps/.flaskenv
|
|
||||||
tests/test_apps/blueprintapp/__init__.py
|
|
||||||
tests/test_apps/blueprintapp/apps/__init__.py
|
|
||||||
tests/test_apps/blueprintapp/apps/admin/__init__.py
|
|
||||||
tests/test_apps/blueprintapp/apps/admin/static/test.txt
|
|
||||||
tests/test_apps/blueprintapp/apps/admin/static/css/test.css
|
|
||||||
tests/test_apps/blueprintapp/apps/admin/templates/admin/index.html
|
|
||||||
tests/test_apps/blueprintapp/apps/frontend/__init__.py
|
|
||||||
tests/test_apps/blueprintapp/apps/frontend/templates/frontend/index.html
|
|
||||||
tests/test_apps/cliapp/__init__.py
|
|
||||||
tests/test_apps/cliapp/app.py
|
|
||||||
tests/test_apps/cliapp/factory.py
|
|
||||||
tests/test_apps/cliapp/importerrorapp.py
|
|
||||||
tests/test_apps/cliapp/message.txt
|
|
||||||
tests/test_apps/cliapp/multiapp.py
|
|
||||||
tests/test_apps/cliapp/inner1/__init__.py
|
|
||||||
tests/test_apps/cliapp/inner1/inner2/__init__.py
|
|
||||||
tests/test_apps/cliapp/inner1/inner2/flask.py
|
|
||||||
tests/test_apps/helloworld/hello.py
|
|
||||||
tests/test_apps/helloworld/wsgi.py
|
|
||||||
tests/test_apps/subdomaintestmodule/__init__.py
|
|
||||||
tests/test_apps/subdomaintestmodule/static/hello.txt
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[console_scripts]
|
|
||||||
flask = flask.cli:main
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
../flask/testing.py
|
|
||||||
../flask/templating.py
|
|
||||||
../flask/__main__.py
|
|
||||||
../flask/sessions.py
|
|
||||||
../flask/signals.py
|
|
||||||
../flask/helpers.py
|
|
||||||
../flask/debughelpers.py
|
|
||||||
../flask/wrappers.py
|
|
||||||
../flask/app.py
|
|
||||||
../flask/ctx.py
|
|
||||||
../flask/config.py
|
|
||||||
../flask/logging.py
|
|
||||||
../flask/blueprints.py
|
|
||||||
../flask/views.py
|
|
||||||
../flask/cli.py
|
|
||||||
../flask/_compat.py
|
|
||||||
../flask/globals.py
|
|
||||||
../flask/__init__.py
|
|
||||||
../flask/json/tag.py
|
|
||||||
../flask/json/__init__.py
|
|
||||||
../flask/testing.pyc
|
|
||||||
../flask/templating.pyc
|
|
||||||
../flask/__main__.pyc
|
|
||||||
../flask/sessions.pyc
|
|
||||||
../flask/signals.pyc
|
|
||||||
../flask/helpers.pyc
|
|
||||||
../flask/debughelpers.pyc
|
|
||||||
../flask/wrappers.pyc
|
|
||||||
../flask/app.pyc
|
|
||||||
../flask/ctx.pyc
|
|
||||||
../flask/config.pyc
|
|
||||||
../flask/logging.pyc
|
|
||||||
../flask/blueprints.pyc
|
|
||||||
../flask/views.pyc
|
|
||||||
../flask/cli.pyc
|
|
||||||
../flask/_compat.pyc
|
|
||||||
../flask/globals.pyc
|
|
||||||
../flask/__init__.pyc
|
|
||||||
../flask/json/tag.pyc
|
|
||||||
../flask/json/__init__.pyc
|
|
||||||
not-zip-safe
|
|
||||||
entry_points.txt
|
|
||||||
dependency_links.txt
|
|
||||||
PKG-INFO
|
|
||||||
top_level.txt
|
|
||||||
requires.txt
|
|
||||||
SOURCES.txt
|
|
||||||
../../../../bin/flask
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
Werkzeug>=0.14
|
|
||||||
Jinja2>=2.10
|
|
||||||
itsdangerous>=0.24
|
|
||||||
click>=5.1
|
|
||||||
|
|
||||||
[dev]
|
|
||||||
pytest>=3
|
|
||||||
coverage
|
|
||||||
tox
|
|
||||||
sphinx
|
|
||||||
pallets-sphinx-themes
|
|
||||||
sphinxcontrib-log-cabinet
|
|
||||||
|
|
||||||
[docs]
|
|
||||||
sphinx
|
|
||||||
pallets-sphinx-themes
|
|
||||||
sphinxcontrib-log-cabinet
|
|
||||||
|
|
||||||
[dotenv]
|
|
||||||
python-dotenv
|
|
|
@ -1 +0,0 @@
|
||||||
flask
|
|
|
@ -1,62 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: Jinja2
|
|
||||||
Version: 2.10
|
|
||||||
Summary: A small but fast and easy to use stand-alone template engine written in pure python.
|
|
||||||
Home-page: http://jinja.pocoo.org/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
License: BSD
|
|
||||||
Description:
|
|
||||||
Jinja2
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Jinja2 is a template engine written in pure Python. It provides a
|
|
||||||
`Django`_ inspired non-XML syntax but supports inline expressions and
|
|
||||||
an optional `sandboxed`_ environment.
|
|
||||||
|
|
||||||
Nutshell
|
|
||||||
--------
|
|
||||||
|
|
||||||
Here a small example of a Jinja template::
|
|
||||||
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% block title %}Memberlist{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
Philosophy
|
|
||||||
----------
|
|
||||||
|
|
||||||
Application logic is for the controller but don't try to make the life
|
|
||||||
for the template designer too hard by giving him too few functionality.
|
|
||||||
|
|
||||||
For more informations visit the new `Jinja2 webpage`_ and `documentation`_.
|
|
||||||
|
|
||||||
.. _sandboxed: https://en.wikipedia.org/wiki/Sandbox_(computer_security)
|
|
||||||
.. _Django: https://www.djangoproject.com/
|
|
||||||
.. _Jinja2 webpage: http://jinja.pocoo.org/
|
|
||||||
.. _documentation: http://jinja.pocoo.org/2/documentation/
|
|
||||||
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.6
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
|
@ -1,133 +0,0 @@
|
||||||
AUTHORS
|
|
||||||
CHANGES.rst
|
|
||||||
LICENSE
|
|
||||||
MANIFEST.in
|
|
||||||
README.rst
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
Jinja2.egg-info/PKG-INFO
|
|
||||||
Jinja2.egg-info/SOURCES.txt
|
|
||||||
Jinja2.egg-info/dependency_links.txt
|
|
||||||
Jinja2.egg-info/entry_points.txt
|
|
||||||
Jinja2.egg-info/not-zip-safe
|
|
||||||
Jinja2.egg-info/requires.txt
|
|
||||||
Jinja2.egg-info/top_level.txt
|
|
||||||
artwork/jinjalogo.svg
|
|
||||||
docs/Makefile
|
|
||||||
docs/api.rst
|
|
||||||
docs/cache_extension.py
|
|
||||||
docs/changelog.rst
|
|
||||||
docs/conf.py
|
|
||||||
docs/contents.rst.inc
|
|
||||||
docs/extensions.rst
|
|
||||||
docs/faq.rst
|
|
||||||
docs/index.rst
|
|
||||||
docs/integration.rst
|
|
||||||
docs/intro.rst
|
|
||||||
docs/jinjaext.py
|
|
||||||
docs/jinjastyle.sty
|
|
||||||
docs/latexindex.rst
|
|
||||||
docs/logo.pdf
|
|
||||||
docs/nativetypes.rst
|
|
||||||
docs/sandbox.rst
|
|
||||||
docs/switching.rst
|
|
||||||
docs/templates.rst
|
|
||||||
docs/tricks.rst
|
|
||||||
docs/_static/.ignore
|
|
||||||
docs/_static/jinja-small.png
|
|
||||||
docs/_templates/sidebarintro.html
|
|
||||||
docs/_templates/sidebarlogo.html
|
|
||||||
docs/_themes/LICENSE
|
|
||||||
docs/_themes/README
|
|
||||||
docs/_themes/jinja/layout.html
|
|
||||||
docs/_themes/jinja/relations.html
|
|
||||||
docs/_themes/jinja/theme.conf
|
|
||||||
docs/_themes/jinja/static/jinja.css_t
|
|
||||||
examples/bench.py
|
|
||||||
examples/profile.py
|
|
||||||
examples/basic/cycle.py
|
|
||||||
examples/basic/debugger.py
|
|
||||||
examples/basic/inheritance.py
|
|
||||||
examples/basic/test.py
|
|
||||||
examples/basic/test_filter_and_linestatements.py
|
|
||||||
examples/basic/test_loop_filter.py
|
|
||||||
examples/basic/translate.py
|
|
||||||
examples/basic/templates/broken.html
|
|
||||||
examples/basic/templates/subbroken.html
|
|
||||||
examples/rwbench/djangoext.py
|
|
||||||
examples/rwbench/rwbench.py
|
|
||||||
examples/rwbench/django/_form.html
|
|
||||||
examples/rwbench/django/_input_field.html
|
|
||||||
examples/rwbench/django/_textarea.html
|
|
||||||
examples/rwbench/django/index.html
|
|
||||||
examples/rwbench/django/layout.html
|
|
||||||
examples/rwbench/genshi/helpers.html
|
|
||||||
examples/rwbench/genshi/index.html
|
|
||||||
examples/rwbench/genshi/layout.html
|
|
||||||
examples/rwbench/jinja/helpers.html
|
|
||||||
examples/rwbench/jinja/index.html
|
|
||||||
examples/rwbench/jinja/layout.html
|
|
||||||
examples/rwbench/mako/helpers.html
|
|
||||||
examples/rwbench/mako/index.html
|
|
||||||
examples/rwbench/mako/layout.html
|
|
||||||
ext/djangojinja2.py
|
|
||||||
ext/inlinegettext.py
|
|
||||||
ext/jinja.el
|
|
||||||
ext/Vim/jinja.vim
|
|
||||||
ext/django2jinja/django2jinja.py
|
|
||||||
ext/django2jinja/example.py
|
|
||||||
ext/django2jinja/templates/index.html
|
|
||||||
ext/django2jinja/templates/layout.html
|
|
||||||
ext/django2jinja/templates/subtemplate.html
|
|
||||||
jinja2/__init__.py
|
|
||||||
jinja2/_compat.py
|
|
||||||
jinja2/_identifier.py
|
|
||||||
jinja2/asyncfilters.py
|
|
||||||
jinja2/asyncsupport.py
|
|
||||||
jinja2/bccache.py
|
|
||||||
jinja2/compiler.py
|
|
||||||
jinja2/constants.py
|
|
||||||
jinja2/debug.py
|
|
||||||
jinja2/defaults.py
|
|
||||||
jinja2/environment.py
|
|
||||||
jinja2/exceptions.py
|
|
||||||
jinja2/ext.py
|
|
||||||
jinja2/filters.py
|
|
||||||
jinja2/idtracking.py
|
|
||||||
jinja2/lexer.py
|
|
||||||
jinja2/loaders.py
|
|
||||||
jinja2/meta.py
|
|
||||||
jinja2/nativetypes.py
|
|
||||||
jinja2/nodes.py
|
|
||||||
jinja2/optimizer.py
|
|
||||||
jinja2/parser.py
|
|
||||||
jinja2/runtime.py
|
|
||||||
jinja2/sandbox.py
|
|
||||||
jinja2/tests.py
|
|
||||||
jinja2/utils.py
|
|
||||||
jinja2/visitor.py
|
|
||||||
tests/conftest.py
|
|
||||||
tests/test_api.py
|
|
||||||
tests/test_async.py
|
|
||||||
tests/test_asyncfilters.py
|
|
||||||
tests/test_bytecode_cache.py
|
|
||||||
tests/test_core_tags.py
|
|
||||||
tests/test_debug.py
|
|
||||||
tests/test_ext.py
|
|
||||||
tests/test_features.py
|
|
||||||
tests/test_filters.py
|
|
||||||
tests/test_idtracking.py
|
|
||||||
tests/test_imports.py
|
|
||||||
tests/test_inheritance.py
|
|
||||||
tests/test_lexnparse.py
|
|
||||||
tests/test_loader.py
|
|
||||||
tests/test_nativetypes.py
|
|
||||||
tests/test_regression.py
|
|
||||||
tests/test_security.py
|
|
||||||
tests/test_tests.py
|
|
||||||
tests/test_utils.py
|
|
||||||
tests/res/__init__.py
|
|
||||||
tests/res/templates/broken.html
|
|
||||||
tests/res/templates/syntaxerror.html
|
|
||||||
tests/res/templates/test.html
|
|
||||||
tests/res/templates/foo/test.html
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
[babel.extractors]
|
|
||||||
jinja2 = jinja2.ext:babel_extract[i18n]
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
../jinja2/lexer.py
|
|
||||||
../jinja2/idtracking.py
|
|
||||||
../jinja2/_identifier.py
|
|
||||||
../jinja2/nodes.py
|
|
||||||
../jinja2/asyncfilters.py
|
|
||||||
../jinja2/loaders.py
|
|
||||||
../jinja2/defaults.py
|
|
||||||
../jinja2/meta.py
|
|
||||||
../jinja2/compiler.py
|
|
||||||
../jinja2/environment.py
|
|
||||||
../jinja2/tests.py
|
|
||||||
../jinja2/sandbox.py
|
|
||||||
../jinja2/filters.py
|
|
||||||
../jinja2/exceptions.py
|
|
||||||
../jinja2/asyncsupport.py
|
|
||||||
../jinja2/visitor.py
|
|
||||||
../jinja2/constants.py
|
|
||||||
../jinja2/utils.py
|
|
||||||
../jinja2/ext.py
|
|
||||||
../jinja2/optimizer.py
|
|
||||||
../jinja2/nativetypes.py
|
|
||||||
../jinja2/parser.py
|
|
||||||
../jinja2/runtime.py
|
|
||||||
../jinja2/debug.py
|
|
||||||
../jinja2/_compat.py
|
|
||||||
../jinja2/bccache.py
|
|
||||||
../jinja2/__init__.py
|
|
||||||
../jinja2/lexer.pyc
|
|
||||||
../jinja2/idtracking.pyc
|
|
||||||
../jinja2/_identifier.pyc
|
|
||||||
../jinja2/nodes.pyc
|
|
||||||
../jinja2/asyncfilters.pyc
|
|
||||||
../jinja2/loaders.pyc
|
|
||||||
../jinja2/defaults.pyc
|
|
||||||
../jinja2/meta.pyc
|
|
||||||
../jinja2/compiler.pyc
|
|
||||||
../jinja2/environment.pyc
|
|
||||||
../jinja2/tests.pyc
|
|
||||||
../jinja2/sandbox.pyc
|
|
||||||
../jinja2/filters.pyc
|
|
||||||
../jinja2/exceptions.pyc
|
|
||||||
../jinja2/asyncsupport.pyc
|
|
||||||
../jinja2/visitor.pyc
|
|
||||||
../jinja2/constants.pyc
|
|
||||||
../jinja2/utils.pyc
|
|
||||||
../jinja2/ext.pyc
|
|
||||||
../jinja2/optimizer.pyc
|
|
||||||
../jinja2/nativetypes.pyc
|
|
||||||
../jinja2/parser.pyc
|
|
||||||
../jinja2/runtime.pyc
|
|
||||||
../jinja2/debug.pyc
|
|
||||||
../jinja2/_compat.pyc
|
|
||||||
../jinja2/bccache.pyc
|
|
||||||
../jinja2/__init__.pyc
|
|
||||||
not-zip-safe
|
|
||||||
entry_points.txt
|
|
||||||
dependency_links.txt
|
|
||||||
PKG-INFO
|
|
||||||
top_level.txt
|
|
||||||
requires.txt
|
|
||||||
SOURCES.txt
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
MarkupSafe>=0.23
|
|
||||||
|
|
||||||
[i18n]
|
|
||||||
Babel>=0.8
|
|
|
@ -1 +0,0 @@
|
||||||
jinja2
|
|
|
@ -1,36 +0,0 @@
|
||||||
Jinja2
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Jinja2 is a template engine written in pure Python. It provides a
|
|
||||||
`Django`_ inspired non-XML syntax but supports inline expressions and
|
|
||||||
an optional `sandboxed`_ environment.
|
|
||||||
|
|
||||||
Nutshell
|
|
||||||
--------
|
|
||||||
|
|
||||||
Here a small example of a Jinja template::
|
|
||||||
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% block title %}Memberlist{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
Philosophy
|
|
||||||
----------
|
|
||||||
|
|
||||||
Application logic is for the controller but don't try to make the life
|
|
||||||
for the template designer too hard by giving him too few functionality.
|
|
||||||
|
|
||||||
For more informations visit the new `Jinja2 webpage`_ and `documentation`_.
|
|
||||||
|
|
||||||
.. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
|
|
||||||
.. _Django: http://www.djangoproject.com/
|
|
||||||
.. _Jinja2 webpage: http://jinja.pocoo.org/
|
|
||||||
.. _documentation: http://jinja.pocoo.org/2/documentation/
|
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,31 +0,0 @@
|
||||||
Copyright (c) 2009 by the Jinja Team, see AUTHORS for more details.
|
|
||||||
|
|
||||||
Some rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following
|
|
||||||
disclaimer in the documentation and/or other materials provided
|
|
||||||
with the distribution.
|
|
||||||
|
|
||||||
* The names of the contributors may not be used to endorse or
|
|
||||||
promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,65 +0,0 @@
|
||||||
Metadata-Version: 2.0
|
|
||||||
Name: Jinja2
|
|
||||||
Version: 2.9.6
|
|
||||||
Summary: A small but fast and easy to use stand-alone template engine written in pure python.
|
|
||||||
Home-page: http://jinja.pocoo.org/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
License: BSD
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.6
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
||||||
Requires-Dist: MarkupSafe (>=0.23)
|
|
||||||
Provides-Extra: i18n
|
|
||||||
Requires-Dist: Babel (>=0.8); extra == 'i18n'
|
|
||||||
|
|
||||||
Jinja2
|
|
||||||
~~~~~~
|
|
||||||
|
|
||||||
Jinja2 is a template engine written in pure Python. It provides a
|
|
||||||
`Django`_ inspired non-XML syntax but supports inline expressions and
|
|
||||||
an optional `sandboxed`_ environment.
|
|
||||||
|
|
||||||
Nutshell
|
|
||||||
--------
|
|
||||||
|
|
||||||
Here a small example of a Jinja template::
|
|
||||||
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% block title %}Memberlist{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
Philosophy
|
|
||||||
----------
|
|
||||||
|
|
||||||
Application logic is for the controller but don't try to make the life
|
|
||||||
for the template designer too hard by giving him too few functionality.
|
|
||||||
|
|
||||||
For more informations visit the new `Jinja2 webpage`_ and `documentation`_.
|
|
||||||
|
|
||||||
.. _sandboxed: http://en.wikipedia.org/wiki/Sandbox_(computer_security)
|
|
||||||
.. _Django: http://www.djangoproject.com/
|
|
||||||
.. _Jinja2 webpage: http://jinja.pocoo.org/
|
|
||||||
.. _documentation: http://jinja.pocoo.org/2/documentation/
|
|
||||||
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
jinja2/__init__.py,sha256=Cx_UnJO4i_GqvKQsOu__mvGE_eMJSsBqITa26irtg5A,2565
|
|
||||||
jinja2/_compat.py,sha256=xP60CE5Qr8FTYcDE1f54tbZLKGvMwYml4-8T7Q4KG9k,2596
|
|
||||||
jinja2/_stringdefs.py,sha256=PYtqTmmWIhjXlFBoH-eE6fJkQvlu7nxUyQ2YlFB97VA,589381
|
|
||||||
jinja2/asyncfilters.py,sha256=cTDPvrS8Hp_IkwsZ1m9af_lr5nHysw7uTa5gV0NmZVE,4144
|
|
||||||
jinja2/asyncsupport.py,sha256=ZJO1Fdd9R93sDLrk6TZNuMQGgtuDmpTlENNRkLwZF7c,7765
|
|
||||||
jinja2/bccache.py,sha256=0xoVw0R9nj3vtzPl9g-zB5BKTLFJ7FFMq2ABbn1IkCI,12793
|
|
||||||
jinja2/compiler.py,sha256=lE5owyPwT1cGGZxWyzQtZLW7Uj1g3Vw1oVtBU8Uc_uM,62929
|
|
||||||
jinja2/constants.py,sha256=uwwV8ZUhHhacAuz5PTwckfsbqBaqM7aKfyJL7kGX5YQ,1626
|
|
||||||
jinja2/debug.py,sha256=UqEbKb4zofBABwvyA77Kr0-5IAQawKqC9t8ZeTIzpGU,12038
|
|
||||||
jinja2/defaults.py,sha256=GvVEQqIRvRMCbQF2NZSr0mlEN8lxvGixU5wIIAeRe1A,1323
|
|
||||||
jinja2/environment.py,sha256=z91L_efdYs-KNs6DBxQWDyYncOwOqn_0J4M5CfFj0Q8,50848
|
|
||||||
jinja2/exceptions.py,sha256=_Rj-NVi98Q6AiEjYQOsP8dEIdu5AlmRHzcSNOPdWix4,4428
|
|
||||||
jinja2/ext.py,sha256=9xq8fd_QPBIe4Z7hE1XawB7f1EDHrVZjpb2JiRTiG94,23867
|
|
||||||
jinja2/filters.py,sha256=1OYGhyN84yVmFUIOwJNRV_StqTCfPhnRLfJTmWbEe_8,33424
|
|
||||||
jinja2/idtracking.py,sha256=HHcCOMsQhCrrjwYAmikKqq_XetXLovCjXAThh9WbRAc,8760
|
|
||||||
jinja2/lexer.py,sha256=W4A830e-fj12zRT6rL7H91F4D6xwED5LjR8iMxjWuVQ,28238
|
|
||||||
jinja2/loaders.py,sha256=xiTuURKAEObyym0nU8PCIXu_Qp8fn0AJ5oIADUUm-5Q,17382
|
|
||||||
jinja2/meta.py,sha256=fmKHxkmZYAOm9QyWWy8EMd6eefAIh234rkBMW2X4ZR8,4340
|
|
||||||
jinja2/nodes.py,sha256=4_Ucxbkohtj4BAlpV0w_MpVmIxJNaVXDTBb4EHBA2JI,29392
|
|
||||||
jinja2/optimizer.py,sha256=MsdlFACJ0FRdPtjmCAdt7JQ9SGrXFaDNUaslsWQaG3M,1722
|
|
||||||
jinja2/parser.py,sha256=3tc82qO1Ovs9och_PjirbAmnWNT77n4wWjIQ8pEVKvU,35465
|
|
||||||
jinja2/runtime.py,sha256=axkTQXg2-oc_Cm35NEMDDas3Jbq3ATxNrDOEa5v3wIw,26835
|
|
||||||
jinja2/sandbox.py,sha256=Jx4MTxly8KvdkSWyui_kHY1_ZZ0RAQL4ojAy1KDRyK0,16707
|
|
||||||
jinja2/tests.py,sha256=iFuUTbUYv7TFffq2aTswCRdIhQ6wyrby1YevChVPqkE,4428
|
|
||||||
jinja2/utils.py,sha256=BIFqeXXsCUSjWx6MEwYhY6V4tXzVNs9WRXfB60MA9HY,19941
|
|
||||||
jinja2/visitor.py,sha256=JD1H1cANA29JcntFfN5fPyqQxB4bI4wC00BzZa-XHks,3316
|
|
||||||
Jinja2-2.9.6.dist-info/DESCRIPTION.rst,sha256=CXIS1UnPSk5_lZBS6Lb8ko-3lqGfjsiUwNBLXCTj2lc,975
|
|
||||||
Jinja2-2.9.6.dist-info/entry_points.txt,sha256=NdzVcOrqyNyKDxD09aERj__3bFx2paZhizFDsKmVhiA,72
|
|
||||||
Jinja2-2.9.6.dist-info/LICENSE.txt,sha256=JvzUNv3Io51EiWrAPm8d_SXjhJnEjyDYvB3Tvwqqils,1554
|
|
||||||
Jinja2-2.9.6.dist-info/METADATA,sha256=53LSXlqC86JTyLSPsDyAOmyV4pXIzzmmZoUXz7ogytA,2172
|
|
||||||
Jinja2-2.9.6.dist-info/metadata.json,sha256=vzvX25T4hwMOe1EIOBo9rpfiZerOB_KVLcplGG_qYtE,1394
|
|
||||||
Jinja2-2.9.6.dist-info/RECORD,,
|
|
||||||
Jinja2-2.9.6.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
|
|
||||||
Jinja2-2.9.6.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
|
|
||||||
Jinja2-2.9.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
jinja2/_compat.pyc,,
|
|
||||||
jinja2/sandbox.pyc,,
|
|
||||||
jinja2/_stringdefs.pyc,,
|
|
||||||
jinja2/bccache.pyc,,
|
|
||||||
jinja2/runtime.pyc,,
|
|
||||||
jinja2/utils.pyc,,
|
|
||||||
jinja2/parser.pyc,,
|
|
||||||
jinja2/debug.pyc,,
|
|
||||||
jinja2/lexer.pyc,,
|
|
||||||
jinja2/defaults.pyc,,
|
|
||||||
jinja2/visitor.pyc,,
|
|
||||||
jinja2/nodes.pyc,,
|
|
||||||
jinja2/environment.pyc,,
|
|
||||||
jinja2/compiler.pyc,,
|
|
||||||
jinja2/exceptions.pyc,,
|
|
||||||
jinja2/filters.pyc,,
|
|
||||||
jinja2/__init__.pyc,,
|
|
||||||
jinja2/meta.pyc,,
|
|
||||||
jinja2/loaders.pyc,,
|
|
||||||
jinja2/ext.pyc,,
|
|
||||||
jinja2/optimizer.pyc,,
|
|
||||||
jinja2/constants.pyc,,
|
|
||||||
jinja2/tests.pyc,,
|
|
||||||
jinja2/idtracking.pyc,,
|
|
|
@ -1,6 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.24.0)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py2-none-any
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
[babel.extractors]
|
|
||||||
jinja2 = jinja2.ext:babel_extract[i18n]
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{"license": "BSD", "name": "Jinja2", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "A small but fast and easy to use stand-alone template engine written in pure python.", "run_requires": [{"requires": ["Babel (>=0.8)"], "extra": "i18n"}, {"requires": ["MarkupSafe (>=0.23)"]}], "version": "2.9.6", "extensions": {"python.details": {"project_urls": {"Home": "http://jinja.pocoo.org/"}, "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "contacts": [{"role": "author", "email": "armin.ronacher@active-4.com", "name": "Armin Ronacher"}]}, "python.exports": {"babel.extractors": {"jinja2": "jinja2.ext:babel_extract [i18n]"}}}, "classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup :: HTML"], "extras": ["i18n"]}
|
|
|
@ -1 +0,0 @@
|
||||||
jinja2
|
|
|
@ -1,133 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: MarkupSafe
|
|
||||||
Version: 1.0
|
|
||||||
Summary: Implements a XML/HTML/XHTML Markup safe string for Python
|
|
||||||
Home-page: http://github.com/pallets/markupsafe
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
License: BSD
|
|
||||||
Description: MarkupSafe
|
|
||||||
==========
|
|
||||||
|
|
||||||
Implements a unicode subclass that supports HTML strings:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
>>> from markupsafe import Markup, escape
|
|
||||||
>>> escape("<script>alert(document.cookie);</script>")
|
|
||||||
Markup(u'<script>alert(document.cookie);</script>')
|
|
||||||
>>> tmpl = Markup("<em>%s</em>")
|
|
||||||
>>> tmpl % "Peter > Lustig"
|
|
||||||
Markup(u'<em>Peter > Lustig</em>')
|
|
||||||
|
|
||||||
If you want to make an object unicode that is not yet unicode
|
|
||||||
but don't want to lose the taint information, you can use the
|
|
||||||
``soft_unicode`` function. (On Python 3 you can also use ``soft_str`` which
|
|
||||||
is a different name for the same function).
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
>>> from markupsafe import soft_unicode
|
|
||||||
>>> soft_unicode(42)
|
|
||||||
u'42'
|
|
||||||
>>> soft_unicode(Markup('foo'))
|
|
||||||
Markup(u'foo')
|
|
||||||
|
|
||||||
HTML Representations
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Objects can customize their HTML markup equivalent by overriding
|
|
||||||
the ``__html__`` function:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
>>> class Foo(object):
|
|
||||||
... def __html__(self):
|
|
||||||
... return '<strong>Nice</strong>'
|
|
||||||
...
|
|
||||||
>>> escape(Foo())
|
|
||||||
Markup(u'<strong>Nice</strong>')
|
|
||||||
>>> Markup(Foo())
|
|
||||||
Markup(u'<strong>Nice</strong>')
|
|
||||||
|
|
||||||
Silent Escapes
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Since MarkupSafe 0.10 there is now also a separate escape function
|
|
||||||
called ``escape_silent`` that returns an empty string for ``None`` for
|
|
||||||
consistency with other systems that return empty strings for ``None``
|
|
||||||
when escaping (for instance Pylons' webhelpers).
|
|
||||||
|
|
||||||
If you also want to use this for the escape method of the Markup
|
|
||||||
object, you can create your own subclass that does that:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from markupsafe import Markup, escape_silent as escape
|
|
||||||
|
|
||||||
class SilentMarkup(Markup):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def escape(cls, s):
|
|
||||||
return cls(escape(s))
|
|
||||||
|
|
||||||
New-Style String Formatting
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
Starting with MarkupSafe 0.21 new style string formats from Python 2.6 and
|
|
||||||
3.x are now fully supported. Previously the escape behavior of those
|
|
||||||
functions was spotty at best. The new implementations operates under the
|
|
||||||
following algorithm:
|
|
||||||
|
|
||||||
1. if an object has an ``__html_format__`` method it is called as
|
|
||||||
replacement for ``__format__`` with the format specifier. It either
|
|
||||||
has to return a string or markup object.
|
|
||||||
2. if an object has an ``__html__`` method it is called.
|
|
||||||
3. otherwise the default format system of Python kicks in and the result
|
|
||||||
is HTML escaped.
|
|
||||||
|
|
||||||
Here is how you can implement your own formatting:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class User(object):
|
|
||||||
|
|
||||||
def __init__(self, id, username):
|
|
||||||
self.id = id
|
|
||||||
self.username = username
|
|
||||||
|
|
||||||
def __html_format__(self, format_spec):
|
|
||||||
if format_spec == 'link':
|
|
||||||
return Markup('<a href="/user/{0}">{1}</a>').format(
|
|
||||||
self.id,
|
|
||||||
self.__html__(),
|
|
||||||
)
|
|
||||||
elif format_spec:
|
|
||||||
raise ValueError('Invalid format spec')
|
|
||||||
return self.__html__()
|
|
||||||
|
|
||||||
def __html__(self):
|
|
||||||
return Markup('<span class=user>{0}</span>').format(self.username)
|
|
||||||
|
|
||||||
And to format that user:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
>>> user = User(1, 'foo')
|
|
||||||
>>> Markup('<p>User: {0:link}').format(user)
|
|
||||||
Markup(u'<p>User: <a href="/user/1"><span class=user>foo</span></a>')
|
|
||||||
|
|
||||||
Markupsafe supports Python 2.6, 2.7 and Python 3.3 and higher.
|
|
||||||
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
|
@ -1,18 +0,0 @@
|
||||||
AUTHORS
|
|
||||||
CHANGES
|
|
||||||
LICENSE
|
|
||||||
MANIFEST.in
|
|
||||||
README.rst
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
tests.py
|
|
||||||
MarkupSafe.egg-info/PKG-INFO
|
|
||||||
MarkupSafe.egg-info/SOURCES.txt
|
|
||||||
MarkupSafe.egg-info/dependency_links.txt
|
|
||||||
MarkupSafe.egg-info/not-zip-safe
|
|
||||||
MarkupSafe.egg-info/top_level.txt
|
|
||||||
markupsafe/__init__.py
|
|
||||||
markupsafe/_compat.py
|
|
||||||
markupsafe/_constants.py
|
|
||||||
markupsafe/_native.py
|
|
||||||
markupsafe/_speedups.c
|
|
|
@ -1,15 +0,0 @@
|
||||||
../markupsafe/__init__.py
|
|
||||||
../markupsafe/_compat.py
|
|
||||||
../markupsafe/_constants.py
|
|
||||||
../markupsafe/_native.py
|
|
||||||
../markupsafe/_speedups.c
|
|
||||||
../markupsafe/__init__.pyc
|
|
||||||
../markupsafe/_compat.pyc
|
|
||||||
../markupsafe/_constants.pyc
|
|
||||||
../markupsafe/_native.pyc
|
|
||||||
../markupsafe/_speedups.so
|
|
||||||
dependency_links.txt
|
|
||||||
not-zip-safe
|
|
||||||
PKG-INFO
|
|
||||||
SOURCES.txt
|
|
||||||
top_level.txt
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
markupsafe
|
|
|
@ -1,70 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: PyJWT
|
|
||||||
Version: 1.4.1
|
|
||||||
Summary: JSON Web Token implementation in Python
|
|
||||||
Home-page: http://github.com/jpadilla/pyjwt
|
|
||||||
Author: José Padilla
|
|
||||||
Author-email: hello@jpadilla.com
|
|
||||||
License: MIT
|
|
||||||
Description: # PyJWT
|
|
||||||
|
|
||||||
[![travis-status-image]][travis]
|
|
||||||
[![appveyor-status-image]][appveyor]
|
|
||||||
[![pypi-version-image]][pypi]
|
|
||||||
[![coveralls-status-image]][coveralls]
|
|
||||||
[![docs-status-image]][docs]
|
|
||||||
|
|
||||||
A Python implementation of [RFC 7519][jwt-spec].
|
|
||||||
Original implementation was written by [@progrium][progrium].
|
|
||||||
|
|
||||||
## Installing
|
|
||||||
|
|
||||||
```
|
|
||||||
$ pip install PyJWT
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> import jwt
|
|
||||||
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
|
|
||||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'
|
|
||||||
|
|
||||||
>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
|
|
||||||
{'some': 'payload'}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
You can run tests from the project root after cloning with:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ python setup.py test
|
|
||||||
```
|
|
||||||
|
|
||||||
[travis-status-image]: https://secure.travis-ci.org/jpadilla/pyjwt.svg?branch=master
|
|
||||||
[travis]: http://travis-ci.org/jpadilla/pyjwt?branch=master
|
|
||||||
[appveyor-status-image]: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true
|
|
||||||
[appveyor]: https://ci.appveyor.com/project/jpadilla/pyjwt
|
|
||||||
[pypi-version-image]: https://img.shields.io/pypi/v/pyjwt.svg
|
|
||||||
[pypi]: https://pypi.python.org/pypi/pyjwt
|
|
||||||
[coveralls-status-image]: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master
|
|
||||||
[coveralls]: https://coveralls.io/r/jpadilla/pyjwt?branch=master
|
|
||||||
[docs-status-image]: https://readthedocs.org/projects/pyjwt/badge/?version=latest
|
|
||||||
[docs]: http://pyjwt.readthedocs.org
|
|
||||||
[jwt-spec]: https://tools.ietf.org/html/rfc7519
|
|
||||||
[progrium]: https://github.com/progrium
|
|
||||||
|
|
||||||
Keywords: jwt json web token security signing
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: Natural Language :: English
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2.6
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Topic :: Utilities
|
|
|
@ -1,49 +0,0 @@
|
||||||
AUTHORS
|
|
||||||
CHANGELOG.md
|
|
||||||
LICENSE
|
|
||||||
MANIFEST.in
|
|
||||||
README.md
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
tox.ini
|
|
||||||
PyJWT.egg-info/PKG-INFO
|
|
||||||
PyJWT.egg-info/SOURCES.txt
|
|
||||||
PyJWT.egg-info/dependency_links.txt
|
|
||||||
PyJWT.egg-info/entry_points.txt
|
|
||||||
PyJWT.egg-info/requires.txt
|
|
||||||
PyJWT.egg-info/top_level.txt
|
|
||||||
jwt/__init__.py
|
|
||||||
jwt/__main__.py
|
|
||||||
jwt/algorithms.py
|
|
||||||
jwt/api_jws.py
|
|
||||||
jwt/api_jwt.py
|
|
||||||
jwt/compat.py
|
|
||||||
jwt/exceptions.py
|
|
||||||
jwt/utils.py
|
|
||||||
jwt/contrib/__init__.py
|
|
||||||
jwt/contrib/algorithms/__init__.py
|
|
||||||
jwt/contrib/algorithms/py_ecdsa.py
|
|
||||||
jwt/contrib/algorithms/pycrypto.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/compat.py
|
|
||||||
tests/test_algorithms.py
|
|
||||||
tests/test_api_jws.py
|
|
||||||
tests/test_api_jwt.py
|
|
||||||
tests/test_compat.py
|
|
||||||
tests/test_exceptions.py
|
|
||||||
tests/test_jwt.py
|
|
||||||
tests/utils.py
|
|
||||||
tests/contrib/__init__.py
|
|
||||||
tests/contrib/test_algorithms.py
|
|
||||||
tests/keys/__init__.py
|
|
||||||
tests/keys/jwk_ec_key.json
|
|
||||||
tests/keys/jwk_ec_pub.json
|
|
||||||
tests/keys/jwk_hmac.json
|
|
||||||
tests/keys/jwk_rsa_key.json
|
|
||||||
tests/keys/jwk_rsa_pub.json
|
|
||||||
tests/keys/testkey2_rsa.pub.pem
|
|
||||||
tests/keys/testkey_ec
|
|
||||||
tests/keys/testkey_ec.pub
|
|
||||||
tests/keys/testkey_rsa
|
|
||||||
tests/keys/testkey_rsa.cer
|
|
||||||
tests/keys/testkey_rsa.pub
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[console_scripts]
|
|
||||||
jwt = jwt.__main__:main
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
../../../../bin/jwt
|
|
||||||
../jwt/__init__.py
|
|
||||||
../jwt/__init__.pyc
|
|
||||||
../jwt/__main__.py
|
|
||||||
../jwt/__main__.pyc
|
|
||||||
../jwt/algorithms.py
|
|
||||||
../jwt/algorithms.pyc
|
|
||||||
../jwt/api_jws.py
|
|
||||||
../jwt/api_jws.pyc
|
|
||||||
../jwt/api_jwt.py
|
|
||||||
../jwt/api_jwt.pyc
|
|
||||||
../jwt/compat.py
|
|
||||||
../jwt/compat.pyc
|
|
||||||
../jwt/contrib/__init__.py
|
|
||||||
../jwt/contrib/__init__.pyc
|
|
||||||
../jwt/contrib/algorithms/__init__.py
|
|
||||||
../jwt/contrib/algorithms/__init__.pyc
|
|
||||||
../jwt/contrib/algorithms/py_ecdsa.py
|
|
||||||
../jwt/contrib/algorithms/py_ecdsa.pyc
|
|
||||||
../jwt/contrib/algorithms/pycrypto.py
|
|
||||||
../jwt/contrib/algorithms/pycrypto.pyc
|
|
||||||
../jwt/exceptions.py
|
|
||||||
../jwt/exceptions.pyc
|
|
||||||
../jwt/utils.py
|
|
||||||
../jwt/utils.pyc
|
|
||||||
PKG-INFO
|
|
||||||
SOURCES.txt
|
|
||||||
dependency_links.txt
|
|
||||||
entry_points.txt
|
|
||||||
requires.txt
|
|
||||||
top_level.txt
|
|
|
@ -1,13 +0,0 @@
|
||||||
|
|
||||||
[crypto]
|
|
||||||
cryptography
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
flake8
|
|
||||||
flake8-import-order
|
|
||||||
pep8-naming
|
|
||||||
|
|
||||||
[test]
|
|
||||||
pytest==2.7.3
|
|
||||||
pytest-cov
|
|
||||||
pytest-runner
|
|
|
@ -1 +0,0 @@
|
||||||
jwt
|
|
|
@ -1,104 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: Werkzeug
|
|
||||||
Version: 0.14.1
|
|
||||||
Summary: The comprehensive WSGI web application library.
|
|
||||||
Home-page: https://www.palletsprojects.org/p/werkzeug/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
License: BSD
|
|
||||||
Description: Werkzeug
|
|
||||||
========
|
|
||||||
|
|
||||||
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
|
|
||||||
a simple collection of various utilities for WSGI applications and has
|
|
||||||
become one of the most advanced WSGI utility libraries.
|
|
||||||
|
|
||||||
It includes:
|
|
||||||
|
|
||||||
* An interactive debugger that allows inspecting stack traces and source
|
|
||||||
code in the browser with an interactive interpreter for any frame in
|
|
||||||
the stack.
|
|
||||||
* A full-featured request object with objects to interact with headers,
|
|
||||||
query args, form data, files, and cookies.
|
|
||||||
* A response object that can wrap other WSGI applications and handle
|
|
||||||
streaming data.
|
|
||||||
* A routing system for matching URLs to endpoints and generating URLs
|
|
||||||
for endpoints, with an extensible system for capturing variables from
|
|
||||||
URLs.
|
|
||||||
* HTTP utilities to handle entity tags, cache control, dates, user
|
|
||||||
agents, cookies, files, and more.
|
|
||||||
* A threaded WSGI server for use while developing applications locally.
|
|
||||||
* A test client for simulating HTTP requests during testing without
|
|
||||||
requiring running a server.
|
|
||||||
|
|
||||||
Werkzeug is Unicode aware and doesn't enforce any dependencies. It is up
|
|
||||||
to the developer to choose a template engine, database adapter, and even
|
|
||||||
how to handle requests. It can be used to build all sorts of end user
|
|
||||||
applications such as blogs, wikis, or bulletin boards.
|
|
||||||
|
|
||||||
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
|
|
||||||
providing more structure and patterns for defining powerful
|
|
||||||
applications.
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
pip install -U Werkzeug
|
|
||||||
|
|
||||||
|
|
||||||
A Simple Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from werkzeug.wrappers import Request, Response
|
|
||||||
|
|
||||||
@Request.application
|
|
||||||
def application(request):
|
|
||||||
return Response('Hello, World!')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from werkzeug.serving import run_simple
|
|
||||||
run_simple('localhost', 4000, application)
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Website: https://www.palletsprojects.com/p/werkzeug/
|
|
||||||
* Releases: https://pypi.org/project/Werkzeug/
|
|
||||||
* Code: https://github.com/pallets/werkzeug
|
|
||||||
* Issue tracker: https://github.com/pallets/werkzeug/issues
|
|
||||||
* Test status:
|
|
||||||
|
|
||||||
* Linux, Mac: https://travis-ci.org/pallets/werkzeug
|
|
||||||
* Windows: https://ci.appveyor.com/project/davidism/werkzeug
|
|
||||||
|
|
||||||
* Test coverage: https://codecov.io/gh/pallets/werkzeug
|
|
||||||
|
|
||||||
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
|
|
||||||
.. _Flask: https://www.palletsprojects.com/p/flask/
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
Platform: any
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.6
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
@ -1,299 +0,0 @@
|
||||||
AUTHORS
|
|
||||||
CHANGES.rst
|
|
||||||
LICENSE
|
|
||||||
MANIFEST.in
|
|
||||||
Makefile
|
|
||||||
README.rst
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
tox.ini
|
|
||||||
Werkzeug.egg-info/PKG-INFO
|
|
||||||
Werkzeug.egg-info/SOURCES.txt
|
|
||||||
Werkzeug.egg-info/dependency_links.txt
|
|
||||||
Werkzeug.egg-info/not-zip-safe
|
|
||||||
Werkzeug.egg-info/requires.txt
|
|
||||||
Werkzeug.egg-info/top_level.txt
|
|
||||||
artwork/.DS_Store
|
|
||||||
artwork/logo.png
|
|
||||||
artwork/logo.svg
|
|
||||||
docs/.DS_Store
|
|
||||||
docs/Makefile
|
|
||||||
docs/changes.rst
|
|
||||||
docs/conf.py
|
|
||||||
docs/contents.rst.inc
|
|
||||||
docs/datastructures.rst
|
|
||||||
docs/debug.rst
|
|
||||||
docs/exceptions.rst
|
|
||||||
docs/filesystem.rst
|
|
||||||
docs/http.rst
|
|
||||||
docs/index.rst
|
|
||||||
docs/installation.rst
|
|
||||||
docs/latexindex.rst
|
|
||||||
docs/levels.rst
|
|
||||||
docs/local.rst
|
|
||||||
docs/logo.pdf
|
|
||||||
docs/make.bat
|
|
||||||
docs/makearchive.py
|
|
||||||
docs/middlewares.rst
|
|
||||||
docs/python3.rst
|
|
||||||
docs/quickstart.rst
|
|
||||||
docs/request_data.rst
|
|
||||||
docs/routing.rst
|
|
||||||
docs/serving.rst
|
|
||||||
docs/terms.rst
|
|
||||||
docs/test.rst
|
|
||||||
docs/transition.rst
|
|
||||||
docs/tutorial.rst
|
|
||||||
docs/unicode.rst
|
|
||||||
docs/urls.rst
|
|
||||||
docs/utils.rst
|
|
||||||
docs/werkzeugext.py
|
|
||||||
docs/werkzeugstyle.sty
|
|
||||||
docs/wrappers.rst
|
|
||||||
docs/wsgi.rst
|
|
||||||
docs/_static/background.png
|
|
||||||
docs/_static/codebackground.png
|
|
||||||
docs/_static/contents.png
|
|
||||||
docs/_static/debug-screenshot.png
|
|
||||||
docs/_static/favicon.ico
|
|
||||||
docs/_static/header.png
|
|
||||||
docs/_static/navigation.png
|
|
||||||
docs/_static/navigation_active.png
|
|
||||||
docs/_static/shortly.png
|
|
||||||
docs/_static/shorty-screenshot.png
|
|
||||||
docs/_static/style.css
|
|
||||||
docs/_static/werkzeug.js
|
|
||||||
docs/_static/werkzeug.png
|
|
||||||
docs/_templates/sidebarintro.html
|
|
||||||
docs/_templates/sidebarlogo.html
|
|
||||||
docs/contrib/atom.rst
|
|
||||||
docs/contrib/cache.rst
|
|
||||||
docs/contrib/fixers.rst
|
|
||||||
docs/contrib/index.rst
|
|
||||||
docs/contrib/iterio.rst
|
|
||||||
docs/contrib/lint.rst
|
|
||||||
docs/contrib/profiler.rst
|
|
||||||
docs/contrib/securecookie.rst
|
|
||||||
docs/contrib/sessions.rst
|
|
||||||
docs/contrib/wrappers.rst
|
|
||||||
docs/deployment/cgi.rst
|
|
||||||
docs/deployment/fastcgi.rst
|
|
||||||
docs/deployment/index.rst
|
|
||||||
docs/deployment/mod_wsgi.rst
|
|
||||||
docs/deployment/proxying.rst
|
|
||||||
examples/README
|
|
||||||
examples/cookieauth.py
|
|
||||||
examples/httpbasicauth.py
|
|
||||||
examples/manage-coolmagic.py
|
|
||||||
examples/manage-couchy.py
|
|
||||||
examples/manage-cupoftee.py
|
|
||||||
examples/manage-i18nurls.py
|
|
||||||
examples/manage-plnt.py
|
|
||||||
examples/manage-shorty.py
|
|
||||||
examples/manage-simplewiki.py
|
|
||||||
examples/manage-webpylike.py
|
|
||||||
examples/upload.py
|
|
||||||
examples/contrib/README
|
|
||||||
examples/contrib/securecookie.py
|
|
||||||
examples/contrib/sessions.py
|
|
||||||
examples/coolmagic/__init__.py
|
|
||||||
examples/coolmagic/application.py
|
|
||||||
examples/coolmagic/helpers.py
|
|
||||||
examples/coolmagic/utils.py
|
|
||||||
examples/coolmagic/public/style.css
|
|
||||||
examples/coolmagic/templates/layout.html
|
|
||||||
examples/coolmagic/templates/static/about.html
|
|
||||||
examples/coolmagic/templates/static/index.html
|
|
||||||
examples/coolmagic/templates/static/not_found.html
|
|
||||||
examples/coolmagic/views/__init__.py
|
|
||||||
examples/coolmagic/views/static.py
|
|
||||||
examples/couchy/README
|
|
||||||
examples/couchy/__init__.py
|
|
||||||
examples/couchy/application.py
|
|
||||||
examples/couchy/models.py
|
|
||||||
examples/couchy/utils.py
|
|
||||||
examples/couchy/views.py
|
|
||||||
examples/couchy/static/style.css
|
|
||||||
examples/couchy/templates/display.html
|
|
||||||
examples/couchy/templates/layout.html
|
|
||||||
examples/couchy/templates/list.html
|
|
||||||
examples/couchy/templates/new.html
|
|
||||||
examples/couchy/templates/not_found.html
|
|
||||||
examples/cupoftee/__init__.py
|
|
||||||
examples/cupoftee/application.py
|
|
||||||
examples/cupoftee/db.py
|
|
||||||
examples/cupoftee/network.py
|
|
||||||
examples/cupoftee/pages.py
|
|
||||||
examples/cupoftee/utils.py
|
|
||||||
examples/cupoftee/shared/content.png
|
|
||||||
examples/cupoftee/shared/down.png
|
|
||||||
examples/cupoftee/shared/favicon.ico
|
|
||||||
examples/cupoftee/shared/header.png
|
|
||||||
examples/cupoftee/shared/logo.png
|
|
||||||
examples/cupoftee/shared/style.css
|
|
||||||
examples/cupoftee/shared/up.png
|
|
||||||
examples/cupoftee/templates/layout.html
|
|
||||||
examples/cupoftee/templates/missingpage.html
|
|
||||||
examples/cupoftee/templates/search.html
|
|
||||||
examples/cupoftee/templates/server.html
|
|
||||||
examples/cupoftee/templates/serverlist.html
|
|
||||||
examples/i18nurls/__init__.py
|
|
||||||
examples/i18nurls/application.py
|
|
||||||
examples/i18nurls/urls.py
|
|
||||||
examples/i18nurls/views.py
|
|
||||||
examples/i18nurls/templates/about.html
|
|
||||||
examples/i18nurls/templates/blog.html
|
|
||||||
examples/i18nurls/templates/index.html
|
|
||||||
examples/i18nurls/templates/layout.html
|
|
||||||
examples/partial/README
|
|
||||||
examples/partial/complex_routing.py
|
|
||||||
examples/plnt/__init__.py
|
|
||||||
examples/plnt/database.py
|
|
||||||
examples/plnt/sync.py
|
|
||||||
examples/plnt/utils.py
|
|
||||||
examples/plnt/views.py
|
|
||||||
examples/plnt/webapp.py
|
|
||||||
examples/plnt/shared/style.css
|
|
||||||
examples/plnt/templates/about.html
|
|
||||||
examples/plnt/templates/index.html
|
|
||||||
examples/plnt/templates/layout.html
|
|
||||||
examples/shortly/shortly.py
|
|
||||||
examples/shortly/static/style.css
|
|
||||||
examples/shortly/templates/404.html
|
|
||||||
examples/shortly/templates/layout.html
|
|
||||||
examples/shortly/templates/new_url.html
|
|
||||||
examples/shortly/templates/short_link_details.html
|
|
||||||
examples/shorty/__init__.py
|
|
||||||
examples/shorty/application.py
|
|
||||||
examples/shorty/models.py
|
|
||||||
examples/shorty/utils.py
|
|
||||||
examples/shorty/views.py
|
|
||||||
examples/shorty/static/style.css
|
|
||||||
examples/shorty/templates/display.html
|
|
||||||
examples/shorty/templates/layout.html
|
|
||||||
examples/shorty/templates/list.html
|
|
||||||
examples/shorty/templates/new.html
|
|
||||||
examples/shorty/templates/not_found.html
|
|
||||||
examples/simplewiki/__init__.py
|
|
||||||
examples/simplewiki/actions.py
|
|
||||||
examples/simplewiki/application.py
|
|
||||||
examples/simplewiki/database.py
|
|
||||||
examples/simplewiki/specialpages.py
|
|
||||||
examples/simplewiki/utils.py
|
|
||||||
examples/simplewiki/shared/style.css
|
|
||||||
examples/simplewiki/templates/action_diff.html
|
|
||||||
examples/simplewiki/templates/action_edit.html
|
|
||||||
examples/simplewiki/templates/action_log.html
|
|
||||||
examples/simplewiki/templates/action_revert.html
|
|
||||||
examples/simplewiki/templates/action_show.html
|
|
||||||
examples/simplewiki/templates/layout.html
|
|
||||||
examples/simplewiki/templates/macros.xml
|
|
||||||
examples/simplewiki/templates/missing_action.html
|
|
||||||
examples/simplewiki/templates/page_index.html
|
|
||||||
examples/simplewiki/templates/page_missing.html
|
|
||||||
examples/simplewiki/templates/recent_changes.html
|
|
||||||
examples/webpylike/example.py
|
|
||||||
examples/webpylike/webpylike.py
|
|
||||||
tests/__init__.py
|
|
||||||
tests/conftest.py
|
|
||||||
tests/test_compat.py
|
|
||||||
tests/test_datastructures.py
|
|
||||||
tests/test_debug.py
|
|
||||||
tests/test_exceptions.py
|
|
||||||
tests/test_formparser.py
|
|
||||||
tests/test_http.py
|
|
||||||
tests/test_internal.py
|
|
||||||
tests/test_local.py
|
|
||||||
tests/test_routing.py
|
|
||||||
tests/test_security.py
|
|
||||||
tests/test_serving.py
|
|
||||||
tests/test_test.py
|
|
||||||
tests/test_urls.py
|
|
||||||
tests/test_utils.py
|
|
||||||
tests/test_wrappers.py
|
|
||||||
tests/test_wsgi.py
|
|
||||||
tests/contrib/__init__.py
|
|
||||||
tests/contrib/test_atom.py
|
|
||||||
tests/contrib/test_cache.py
|
|
||||||
tests/contrib/test_fixers.py
|
|
||||||
tests/contrib/test_iterio.py
|
|
||||||
tests/contrib/test_securecookie.py
|
|
||||||
tests/contrib/test_sessions.py
|
|
||||||
tests/contrib/test_wrappers.py
|
|
||||||
tests/contrib/cache/conftest.py
|
|
||||||
tests/contrib/cache/test_cache.py
|
|
||||||
tests/hypothesis/__init__.py
|
|
||||||
tests/hypothesis/test_urls.py
|
|
||||||
tests/multipart/__init__.py
|
|
||||||
tests/multipart/ie7_full_path_request.txt
|
|
||||||
tests/multipart/test_collect.py
|
|
||||||
tests/multipart/firefox3-2png1txt/file1.png
|
|
||||||
tests/multipart/firefox3-2png1txt/file2.png
|
|
||||||
tests/multipart/firefox3-2png1txt/request.txt
|
|
||||||
tests/multipart/firefox3-2png1txt/text.txt
|
|
||||||
tests/multipart/firefox3-2pnglongtext/file1.png
|
|
||||||
tests/multipart/firefox3-2pnglongtext/file2.png
|
|
||||||
tests/multipart/firefox3-2pnglongtext/request.txt
|
|
||||||
tests/multipart/firefox3-2pnglongtext/text.txt
|
|
||||||
tests/multipart/ie6-2png1txt/file1.png
|
|
||||||
tests/multipart/ie6-2png1txt/file2.png
|
|
||||||
tests/multipart/ie6-2png1txt/request.txt
|
|
||||||
tests/multipart/ie6-2png1txt/text.txt
|
|
||||||
tests/multipart/opera8-2png1txt/file1.png
|
|
||||||
tests/multipart/opera8-2png1txt/file2.png
|
|
||||||
tests/multipart/opera8-2png1txt/request.txt
|
|
||||||
tests/multipart/opera8-2png1txt/text.txt
|
|
||||||
tests/multipart/webkit3-2png1txt/file1.png
|
|
||||||
tests/multipart/webkit3-2png1txt/file2.png
|
|
||||||
tests/multipart/webkit3-2png1txt/request.txt
|
|
||||||
tests/multipart/webkit3-2png1txt/text.txt
|
|
||||||
tests/res/chunked.txt
|
|
||||||
tests/res/test.txt
|
|
||||||
werkzeug/__init__.py
|
|
||||||
werkzeug/_compat.py
|
|
||||||
werkzeug/_internal.py
|
|
||||||
werkzeug/_reloader.py
|
|
||||||
werkzeug/datastructures.py
|
|
||||||
werkzeug/exceptions.py
|
|
||||||
werkzeug/filesystem.py
|
|
||||||
werkzeug/formparser.py
|
|
||||||
werkzeug/http.py
|
|
||||||
werkzeug/local.py
|
|
||||||
werkzeug/posixemulation.py
|
|
||||||
werkzeug/routing.py
|
|
||||||
werkzeug/security.py
|
|
||||||
werkzeug/serving.py
|
|
||||||
werkzeug/test.py
|
|
||||||
werkzeug/testapp.py
|
|
||||||
werkzeug/urls.py
|
|
||||||
werkzeug/useragents.py
|
|
||||||
werkzeug/utils.py
|
|
||||||
werkzeug/websocket.py
|
|
||||||
werkzeug/wrappers.py
|
|
||||||
werkzeug/wsgi.py
|
|
||||||
werkzeug/contrib/__init__.py
|
|
||||||
werkzeug/contrib/atom.py
|
|
||||||
werkzeug/contrib/cache.py
|
|
||||||
werkzeug/contrib/fixers.py
|
|
||||||
werkzeug/contrib/iterio.py
|
|
||||||
werkzeug/contrib/jsrouting.py
|
|
||||||
werkzeug/contrib/limiter.py
|
|
||||||
werkzeug/contrib/lint.py
|
|
||||||
werkzeug/contrib/profiler.py
|
|
||||||
werkzeug/contrib/securecookie.py
|
|
||||||
werkzeug/contrib/sessions.py
|
|
||||||
werkzeug/contrib/testtools.py
|
|
||||||
werkzeug/contrib/wrappers.py
|
|
||||||
werkzeug/debug/__init__.py
|
|
||||||
werkzeug/debug/console.py
|
|
||||||
werkzeug/debug/repr.py
|
|
||||||
werkzeug/debug/tbtools.py
|
|
||||||
werkzeug/debug/shared/FONT_LICENSE
|
|
||||||
werkzeug/debug/shared/console.png
|
|
||||||
werkzeug/debug/shared/debugger.js
|
|
||||||
werkzeug/debug/shared/jquery.js
|
|
||||||
werkzeug/debug/shared/less.png
|
|
||||||
werkzeug/debug/shared/more.png
|
|
||||||
werkzeug/debug/shared/source.png
|
|
||||||
werkzeug/debug/shared/style.css
|
|
||||||
werkzeug/debug/shared/ubuntu.ttf
|
|
|
@ -1,93 +0,0 @@
|
||||||
../werkzeug/_reloader.py
|
|
||||||
../werkzeug/_internal.py
|
|
||||||
../werkzeug/serving.py
|
|
||||||
../werkzeug/local.py
|
|
||||||
../werkzeug/filesystem.py
|
|
||||||
../werkzeug/security.py
|
|
||||||
../werkzeug/__init__.py
|
|
||||||
../werkzeug/test.py
|
|
||||||
../werkzeug/formparser.py
|
|
||||||
../werkzeug/posixemulation.py
|
|
||||||
../werkzeug/utils.py
|
|
||||||
../werkzeug/wrappers.py
|
|
||||||
../werkzeug/routing.py
|
|
||||||
../werkzeug/http.py
|
|
||||||
../werkzeug/useragents.py
|
|
||||||
../werkzeug/exceptions.py
|
|
||||||
../werkzeug/_compat.py
|
|
||||||
../werkzeug/datastructures.py
|
|
||||||
../werkzeug/urls.py
|
|
||||||
../werkzeug/websocket.py
|
|
||||||
../werkzeug/wsgi.py
|
|
||||||
../werkzeug/testapp.py
|
|
||||||
../werkzeug/contrib/sessions.py
|
|
||||||
../werkzeug/contrib/cache.py
|
|
||||||
../werkzeug/contrib/__init__.py
|
|
||||||
../werkzeug/contrib/testtools.py
|
|
||||||
../werkzeug/contrib/wrappers.py
|
|
||||||
../werkzeug/contrib/jsrouting.py
|
|
||||||
../werkzeug/contrib/fixers.py
|
|
||||||
../werkzeug/contrib/profiler.py
|
|
||||||
../werkzeug/contrib/iterio.py
|
|
||||||
../werkzeug/contrib/atom.py
|
|
||||||
../werkzeug/contrib/securecookie.py
|
|
||||||
../werkzeug/contrib/limiter.py
|
|
||||||
../werkzeug/contrib/lint.py
|
|
||||||
../werkzeug/debug/console.py
|
|
||||||
../werkzeug/debug/tbtools.py
|
|
||||||
../werkzeug/debug/__init__.py
|
|
||||||
../werkzeug/debug/repr.py
|
|
||||||
../werkzeug/debug/shared/FONT_LICENSE
|
|
||||||
../werkzeug/debug/shared/console.png
|
|
||||||
../werkzeug/debug/shared/debugger.js
|
|
||||||
../werkzeug/debug/shared/jquery.js
|
|
||||||
../werkzeug/debug/shared/less.png
|
|
||||||
../werkzeug/debug/shared/more.png
|
|
||||||
../werkzeug/debug/shared/source.png
|
|
||||||
../werkzeug/debug/shared/style.css
|
|
||||||
../werkzeug/debug/shared/ubuntu.ttf
|
|
||||||
../werkzeug/_reloader.pyc
|
|
||||||
../werkzeug/_internal.pyc
|
|
||||||
../werkzeug/serving.pyc
|
|
||||||
../werkzeug/local.pyc
|
|
||||||
../werkzeug/filesystem.pyc
|
|
||||||
../werkzeug/security.pyc
|
|
||||||
../werkzeug/__init__.pyc
|
|
||||||
../werkzeug/test.pyc
|
|
||||||
../werkzeug/formparser.pyc
|
|
||||||
../werkzeug/posixemulation.pyc
|
|
||||||
../werkzeug/utils.pyc
|
|
||||||
../werkzeug/wrappers.pyc
|
|
||||||
../werkzeug/routing.pyc
|
|
||||||
../werkzeug/http.pyc
|
|
||||||
../werkzeug/useragents.pyc
|
|
||||||
../werkzeug/exceptions.pyc
|
|
||||||
../werkzeug/_compat.pyc
|
|
||||||
../werkzeug/datastructures.pyc
|
|
||||||
../werkzeug/urls.pyc
|
|
||||||
../werkzeug/websocket.pyc
|
|
||||||
../werkzeug/wsgi.pyc
|
|
||||||
../werkzeug/testapp.pyc
|
|
||||||
../werkzeug/contrib/sessions.pyc
|
|
||||||
../werkzeug/contrib/cache.pyc
|
|
||||||
../werkzeug/contrib/__init__.pyc
|
|
||||||
../werkzeug/contrib/testtools.pyc
|
|
||||||
../werkzeug/contrib/wrappers.pyc
|
|
||||||
../werkzeug/contrib/jsrouting.pyc
|
|
||||||
../werkzeug/contrib/fixers.pyc
|
|
||||||
../werkzeug/contrib/profiler.pyc
|
|
||||||
../werkzeug/contrib/iterio.pyc
|
|
||||||
../werkzeug/contrib/atom.pyc
|
|
||||||
../werkzeug/contrib/securecookie.pyc
|
|
||||||
../werkzeug/contrib/limiter.pyc
|
|
||||||
../werkzeug/contrib/lint.pyc
|
|
||||||
../werkzeug/debug/console.pyc
|
|
||||||
../werkzeug/debug/tbtools.pyc
|
|
||||||
../werkzeug/debug/__init__.pyc
|
|
||||||
../werkzeug/debug/repr.pyc
|
|
||||||
PKG-INFO
|
|
||||||
not-zip-safe
|
|
||||||
SOURCES.txt
|
|
||||||
requires.txt
|
|
||||||
top_level.txt
|
|
||||||
dependency_links.txt
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
[dev]
|
|
||||||
pytest
|
|
||||||
coverage
|
|
||||||
tox
|
|
||||||
sphinx
|
|
||||||
|
|
||||||
[termcolor]
|
|
||||||
termcolor
|
|
||||||
|
|
||||||
[watchdog]
|
|
||||||
watchdog
|
|
|
@ -1 +0,0 @@
|
||||||
werkzeug
|
|
|
@ -1,89 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: backports.ssl-match-hostname
|
|
||||||
Version: 3.7.0.1
|
|
||||||
Summary: The ssl.match_hostname() function from Python 3.5
|
|
||||||
Home-page: http://bitbucket.org/brandon/backports.ssl_match_hostname
|
|
||||||
Author: Toshio Kuratomi
|
|
||||||
Author-email: toshio@fedoraproject.org
|
|
||||||
License: Python Software Foundation License
|
|
||||||
Description:
|
|
||||||
The ssl.match_hostname() function from Python 3.7
|
|
||||||
=================================================
|
|
||||||
|
|
||||||
The Secure Sockets Layer is only actually *secure*
|
|
||||||
if you check the hostname in the certificate returned
|
|
||||||
by the server to which you are connecting,
|
|
||||||
and verify that it matches to hostname
|
|
||||||
that you are trying to reach.
|
|
||||||
|
|
||||||
But the matching logic, defined in `RFC2818`_,
|
|
||||||
can be a bit tricky to implement on your own.
|
|
||||||
So the ``ssl`` package in the Standard Library of Python 3.2
|
|
||||||
and greater now includes a ``match_hostname()`` function
|
|
||||||
for performing this check instead of requiring every application
|
|
||||||
to implement the check separately.
|
|
||||||
|
|
||||||
This backport brings ``match_hostname()`` to users
|
|
||||||
of earlier versions of Python.
|
|
||||||
Simply make this distribution a dependency of your package,
|
|
||||||
and then use it like this::
|
|
||||||
|
|
||||||
from backports.ssl_match_hostname import match_hostname, CertificateError
|
|
||||||
[...]
|
|
||||||
sslsock = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23,
|
|
||||||
cert_reqs=ssl.CERT_REQUIRED, ca_certs=...)
|
|
||||||
try:
|
|
||||||
match_hostname(sslsock.getpeercert(), hostname)
|
|
||||||
except CertificateError, ce:
|
|
||||||
...
|
|
||||||
|
|
||||||
Brandon Craig Rhodes is merely the packager of this distribution;
|
|
||||||
the actual code inside comes from Python 3.7 with small changes for
|
|
||||||
portability.
|
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
------------
|
|
||||||
|
|
||||||
* If you need to use this on Python versions earlier than 2.6 you will need to
|
|
||||||
install the `ssl module`_. From Python 2.6 upwards ``ssl`` is included in
|
|
||||||
the Python Standard Library so you do not need to install it separately.
|
|
||||||
|
|
||||||
.. _`ssl module`:: https://pypi.python.org/pypi/ssl
|
|
||||||
|
|
||||||
History
|
|
||||||
-------
|
|
||||||
|
|
||||||
* This function was introduced in python-3.2
|
|
||||||
* It was updated for python-3.4a1 for a CVE
|
|
||||||
(backports-ssl_match_hostname-3.4.0.1)
|
|
||||||
* It was updated from RFC2818 to RFC 6125 compliance in order to fix another
|
|
||||||
security flaw for python-3.3.3 and python-3.4a5
|
|
||||||
(backports-ssl_match_hostname-3.4.0.2)
|
|
||||||
* It was updated in python-3.5 to handle IPAddresses in ServerAltName fields
|
|
||||||
(something that backports.ssl_match_hostname will do if you also install the
|
|
||||||
ipaddress library from pypi).
|
|
||||||
* It was updated in python-3.7 to handle IPAddresses without the ipaddress library and dropped
|
|
||||||
support for partial wildcards
|
|
||||||
|
|
||||||
.. _`ipaddress module`:: https://pypi.python.org/pypi/ipaddress
|
|
||||||
|
|
||||||
.. _RFC2818: http://tools.ietf.org/html/rfc2818.html
|
|
||||||
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: License :: OSI Approved :: Python Software Foundation License
|
|
||||||
Classifier: Programming Language :: Python :: 2.4
|
|
||||||
Classifier: Programming Language :: Python :: 2.5
|
|
||||||
Classifier: Programming Language :: Python :: 2.6
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.0
|
|
||||||
Classifier: Programming Language :: Python :: 3.1
|
|
||||||
Classifier: Programming Language :: Python :: 3.2
|
|
||||||
Classifier: Programming Language :: Python :: 3.3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Topic :: Security :: Cryptography
|
|
|
@ -1,8 +0,0 @@
|
||||||
README.txt
|
|
||||||
setup.cfg
|
|
||||||
backports/__init__.py
|
|
||||||
backports.ssl_match_hostname.egg-info/PKG-INFO
|
|
||||||
backports.ssl_match_hostname.egg-info/SOURCES.txt
|
|
||||||
backports.ssl_match_hostname.egg-info/dependency_links.txt
|
|
||||||
backports.ssl_match_hostname.egg-info/top_level.txt
|
|
||||||
backports/ssl_match_hostname/__init__.py
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
../backports/__init__.py
|
|
||||||
../backports/__init__.pyc
|
|
||||||
../backports/ssl_match_hostname/__init__.py
|
|
||||||
../backports/ssl_match_hostname/__init__.pyc
|
|
||||||
PKG-INFO
|
|
||||||
SOURCES.txt
|
|
||||||
dependency_links.txt
|
|
||||||
top_level.txt
|
|
|
@ -1 +0,0 @@
|
||||||
backports
|
|
|
@ -1,3 +0,0 @@
|
||||||
# This is a Python "namespace package" http://www.python.org/dev/peps/pep-0382/
|
|
||||||
from pkgutil import extend_path
|
|
||||||
__path__ = extend_path(__path__, __name__)
|
|
|
@ -1,204 +0,0 @@
|
||||||
"""The match_hostname() function from Python 3.7.0, essential when using SSL."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import socket as _socket
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Divergence: Python-3.7+'s _ssl has this exception type but older Pythons do not
|
|
||||||
from _ssl import SSLCertVerificationError
|
|
||||||
CertificateError = SSLCertVerificationError
|
|
||||||
except:
|
|
||||||
class CertificateError(ValueError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
__version__ = '3.7.0.1'
|
|
||||||
|
|
||||||
|
|
||||||
# Divergence: Added to deal with ipaddess as bytes on python2
|
|
||||||
def _to_text(obj):
|
|
||||||
if isinstance(obj, str) and sys.version_info < (3,):
|
|
||||||
obj = unicode(obj, encoding='ascii', errors='strict')
|
|
||||||
elif sys.version_info >= (3,) and isinstance(obj, bytes):
|
|
||||||
obj = str(obj, encoding='ascii', errors='strict')
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def _to_bytes(obj):
|
|
||||||
if isinstance(obj, str) and sys.version_info >= (3,):
|
|
||||||
obj = bytes(obj, encoding='ascii', errors='strict')
|
|
||||||
elif sys.version_info < (3,) and isinstance(obj, unicode):
|
|
||||||
obj = obj.encode('ascii', 'strict')
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def _dnsname_match(dn, hostname):
|
|
||||||
"""Matching according to RFC 6125, section 6.4.3
|
|
||||||
|
|
||||||
- Hostnames are compared lower case.
|
|
||||||
- For IDNA, both dn and hostname must be encoded as IDN A-label (ACE).
|
|
||||||
- Partial wildcards like 'www*.example.org', multiple wildcards, sole
|
|
||||||
wildcard or wildcards in labels other then the left-most label are not
|
|
||||||
supported and a CertificateError is raised.
|
|
||||||
- A wildcard must match at least one character.
|
|
||||||
"""
|
|
||||||
if not dn:
|
|
||||||
return False
|
|
||||||
|
|
||||||
wildcards = dn.count('*')
|
|
||||||
# speed up common case w/o wildcards
|
|
||||||
if not wildcards:
|
|
||||||
return dn.lower() == hostname.lower()
|
|
||||||
|
|
||||||
if wildcards > 1:
|
|
||||||
# Divergence .format() to percent formatting for Python < 2.6
|
|
||||||
raise CertificateError(
|
|
||||||
"too many wildcards in certificate DNS name: %s" % repr(dn))
|
|
||||||
|
|
||||||
dn_leftmost, sep, dn_remainder = dn.partition('.')
|
|
||||||
|
|
||||||
if '*' in dn_remainder:
|
|
||||||
# Only match wildcard in leftmost segment.
|
|
||||||
# Divergence .format() to percent formatting for Python < 2.6
|
|
||||||
raise CertificateError(
|
|
||||||
"wildcard can only be present in the leftmost label: "
|
|
||||||
"%s." % repr(dn))
|
|
||||||
|
|
||||||
if not sep:
|
|
||||||
# no right side
|
|
||||||
# Divergence .format() to percent formatting for Python < 2.6
|
|
||||||
raise CertificateError(
|
|
||||||
"sole wildcard without additional labels are not support: "
|
|
||||||
"%s." % repr(dn))
|
|
||||||
|
|
||||||
if dn_leftmost != '*':
|
|
||||||
# no partial wildcard matching
|
|
||||||
# Divergence .format() to percent formatting for Python < 2.6
|
|
||||||
raise CertificateError(
|
|
||||||
"partial wildcards in leftmost label are not supported: "
|
|
||||||
"%s." % repr(dn))
|
|
||||||
|
|
||||||
hostname_leftmost, sep, hostname_remainder = hostname.partition('.')
|
|
||||||
if not hostname_leftmost or not sep:
|
|
||||||
# wildcard must match at least one char
|
|
||||||
return False
|
|
||||||
return dn_remainder.lower() == hostname_remainder.lower()
|
|
||||||
|
|
||||||
|
|
||||||
def _inet_paton(ipname):
|
|
||||||
"""Try to convert an IP address to packed binary form
|
|
||||||
|
|
||||||
Supports IPv4 addresses on all platforms and IPv6 on platforms with IPv6
|
|
||||||
support.
|
|
||||||
"""
|
|
||||||
# inet_aton() also accepts strings like '1'
|
|
||||||
# Divergence: We make sure we have native string type for all python versions
|
|
||||||
try:
|
|
||||||
b_ipname = _to_bytes(ipname)
|
|
||||||
except UnicodeError:
|
|
||||||
raise ValueError("%s must be an all-ascii string." % repr(ipname))
|
|
||||||
|
|
||||||
# Set ipname in native string format
|
|
||||||
if sys.version_info < (3,):
|
|
||||||
n_ipname = b_ipname
|
|
||||||
else:
|
|
||||||
n_ipname = ipname
|
|
||||||
|
|
||||||
if n_ipname.count('.') == 3:
|
|
||||||
try:
|
|
||||||
return _socket.inet_aton(n_ipname)
|
|
||||||
# Divergence: OSError on late python3. socket.error earlier.
|
|
||||||
# Null bytes generate ValueError on python3(we want to raise
|
|
||||||
# ValueError anyway), TypeError # earlier
|
|
||||||
except (OSError, _socket.error, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
return _socket.inet_pton(_socket.AF_INET6, n_ipname)
|
|
||||||
# Divergence: OSError on late python3. socket.error earlier.
|
|
||||||
# Null bytes generate ValueError on python3(we want to raise
|
|
||||||
# ValueError anyway), TypeError # earlier
|
|
||||||
except (OSError, _socket.error, TypeError):
|
|
||||||
# Divergence .format() to percent formatting for Python < 2.6
|
|
||||||
raise ValueError("%s is neither an IPv4 nor an IP6 "
|
|
||||||
"address." % repr(ipname))
|
|
||||||
except AttributeError:
|
|
||||||
# AF_INET6 not available
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Divergence .format() to percent formatting for Python < 2.6
|
|
||||||
raise ValueError("%s is not an IPv4 address." % repr(ipname))
|
|
||||||
|
|
||||||
|
|
||||||
def _ipaddress_match(ipname, host_ip):
|
|
||||||
"""Exact matching of IP addresses.
|
|
||||||
|
|
||||||
RFC 6125 explicitly doesn't define an algorithm for this
|
|
||||||
(section 1.7.2 - "Out of Scope").
|
|
||||||
"""
|
|
||||||
# OpenSSL may add a trailing newline to a subjectAltName's IP address
|
|
||||||
ip = _inet_paton(ipname.rstrip())
|
|
||||||
return ip == host_ip
|
|
||||||
|
|
||||||
|
|
||||||
def match_hostname(cert, hostname):
|
|
||||||
"""Verify that *cert* (in decoded format as returned by
|
|
||||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
|
||||||
rules are followed.
|
|
||||||
|
|
||||||
The function matches IP addresses rather than dNSNames if hostname is a
|
|
||||||
valid ipaddress string. IPv4 addresses are supported on all platforms.
|
|
||||||
IPv6 addresses are supported on platforms with IPv6 support (AF_INET6
|
|
||||||
and inet_pton).
|
|
||||||
|
|
||||||
CertificateError is raised on failure. On success, the function
|
|
||||||
returns nothing.
|
|
||||||
"""
|
|
||||||
if not cert:
|
|
||||||
raise ValueError("empty or no certificate, match_hostname needs a "
|
|
||||||
"SSL socket or SSL context with either "
|
|
||||||
"CERT_OPTIONAL or CERT_REQUIRED")
|
|
||||||
try:
|
|
||||||
# Divergence: Deal with hostname as bytes
|
|
||||||
host_ip = _inet_paton(_to_text(hostname))
|
|
||||||
except ValueError:
|
|
||||||
# Not an IP address (common case)
|
|
||||||
host_ip = None
|
|
||||||
except UnicodeError:
|
|
||||||
# Divergence: Deal with hostname as byte strings.
|
|
||||||
# IP addresses should be all ascii, so we consider it not
|
|
||||||
# an IP address if this fails
|
|
||||||
host_ip = None
|
|
||||||
dnsnames = []
|
|
||||||
san = cert.get('subjectAltName', ())
|
|
||||||
for key, value in san:
|
|
||||||
if key == 'DNS':
|
|
||||||
if host_ip is None and _dnsname_match(value, hostname):
|
|
||||||
return
|
|
||||||
dnsnames.append(value)
|
|
||||||
elif key == 'IP Address':
|
|
||||||
if host_ip is not None and _ipaddress_match(value, host_ip):
|
|
||||||
return
|
|
||||||
dnsnames.append(value)
|
|
||||||
if not dnsnames:
|
|
||||||
# The subject is only checked when there is no dNSName entry
|
|
||||||
# in subjectAltName
|
|
||||||
for sub in cert.get('subject', ()):
|
|
||||||
for key, value in sub:
|
|
||||||
# XXX according to RFC 2818, the most specific Common Name
|
|
||||||
# must be used.
|
|
||||||
if key == 'commonName':
|
|
||||||
if _dnsname_match(value, hostname):
|
|
||||||
return
|
|
||||||
dnsnames.append(value)
|
|
||||||
if len(dnsnames) > 1:
|
|
||||||
raise CertificateError("hostname %r "
|
|
||||||
"doesn't match either of %s"
|
|
||||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
|
||||||
elif len(dnsnames) == 1:
|
|
||||||
raise CertificateError("hostname %r "
|
|
||||||
"doesn't match %r"
|
|
||||||
% (hostname, dnsnames[0]))
|
|
||||||
else:
|
|
||||||
raise CertificateError("no appropriate commonName or "
|
|
||||||
"subjectAltName fields were found")
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/local/bin/python
|
|
||||||
# EASY-INSTALL-ENTRY-SCRIPT: 'Flask==1.0.2','console_scripts','flask'
|
|
||||||
__requires__ = 'Flask==1.0.2'
|
|
||||||
import sys
|
|
||||||
from pkg_resources import load_entry_point
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(
|
|
||||||
load_entry_point('Flask==1.0.2', 'console_scripts', 'flask')()
|
|
||||||
)
|
|
|
@ -1,201 +0,0 @@
|
||||||
#!/usr/local/bin/python
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import code
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
import six
|
|
||||||
from six.moves.urllib.parse import urlparse
|
|
||||||
|
|
||||||
import websocket
|
|
||||||
|
|
||||||
try:
|
|
||||||
import readline
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def get_encoding():
|
|
||||||
encoding = getattr(sys.stdin, "encoding", "")
|
|
||||||
if not encoding:
|
|
||||||
return "utf-8"
|
|
||||||
else:
|
|
||||||
return encoding.lower()
|
|
||||||
|
|
||||||
|
|
||||||
OPCODE_DATA = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
|
||||||
ENCODING = get_encoding()
|
|
||||||
|
|
||||||
|
|
||||||
class VAction(argparse.Action):
|
|
||||||
|
|
||||||
def __call__(self, parser, args, values, option_string=None):
|
|
||||||
if values is None:
|
|
||||||
values = "1"
|
|
||||||
try:
|
|
||||||
values = int(values)
|
|
||||||
except ValueError:
|
|
||||||
values = values.count("v") + 1
|
|
||||||
setattr(args, self.dest, values)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
parser = argparse.ArgumentParser(description="WebSocket Simple Dump Tool")
|
|
||||||
parser.add_argument("url", metavar="ws_url",
|
|
||||||
help="websocket url. ex. ws://echo.websocket.org/")
|
|
||||||
parser.add_argument("-p", "--proxy",
|
|
||||||
help="proxy url. ex. http://127.0.0.1:8080")
|
|
||||||
parser.add_argument("-v", "--verbose", default=0, nargs='?', action=VAction,
|
|
||||||
dest="verbose",
|
|
||||||
help="set verbose mode. If set to 1, show opcode. "
|
|
||||||
"If set to 2, enable to trace websocket module")
|
|
||||||
parser.add_argument("-n", "--nocert", action='store_true',
|
|
||||||
help="Ignore invalid SSL cert")
|
|
||||||
parser.add_argument("-r", "--raw", action="store_true",
|
|
||||||
help="raw output")
|
|
||||||
parser.add_argument("-s", "--subprotocols", nargs='*',
|
|
||||||
help="Set subprotocols")
|
|
||||||
parser.add_argument("-o", "--origin",
|
|
||||||
help="Set origin")
|
|
||||||
parser.add_argument("--eof-wait", default=0, type=int,
|
|
||||||
help="wait time(second) after 'EOF' received.")
|
|
||||||
parser.add_argument("-t", "--text",
|
|
||||||
help="Send initial text")
|
|
||||||
parser.add_argument("--timings", action="store_true",
|
|
||||||
help="Print timings in seconds")
|
|
||||||
parser.add_argument("--headers",
|
|
||||||
help="Set custom headers. Use ',' as separator")
|
|
||||||
|
|
||||||
return parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
class RawInput:
|
|
||||||
|
|
||||||
def raw_input(self, prompt):
|
|
||||||
if six.PY3:
|
|
||||||
line = input(prompt)
|
|
||||||
else:
|
|
||||||
line = raw_input(prompt)
|
|
||||||
|
|
||||||
if ENCODING and ENCODING != "utf-8" and not isinstance(line, six.text_type):
|
|
||||||
line = line.decode(ENCODING).encode("utf-8")
|
|
||||||
elif isinstance(line, six.text_type):
|
|
||||||
line = line.encode("utf-8")
|
|
||||||
|
|
||||||
return line
|
|
||||||
|
|
||||||
|
|
||||||
class InteractiveConsole(RawInput, code.InteractiveConsole):
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
sys.stdout.write("\033[2K\033[E")
|
|
||||||
# sys.stdout.write("\n")
|
|
||||||
sys.stdout.write("\033[34m< " + data + "\033[39m")
|
|
||||||
sys.stdout.write("\n> ")
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
return self.raw_input("> ")
|
|
||||||
|
|
||||||
|
|
||||||
class NonInteractive(RawInput):
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
sys.stdout.write(data)
|
|
||||||
sys.stdout.write("\n")
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def read(self):
|
|
||||||
return self.raw_input("")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
start_time = time.time()
|
|
||||||
args = parse_args()
|
|
||||||
if args.verbose > 1:
|
|
||||||
websocket.enableTrace(True)
|
|
||||||
options = {}
|
|
||||||
if args.proxy:
|
|
||||||
p = urlparse(args.proxy)
|
|
||||||
options["http_proxy_host"] = p.hostname
|
|
||||||
options["http_proxy_port"] = p.port
|
|
||||||
if args.origin:
|
|
||||||
options["origin"] = args.origin
|
|
||||||
if args.subprotocols:
|
|
||||||
options["subprotocols"] = args.subprotocols
|
|
||||||
opts = {}
|
|
||||||
if args.nocert:
|
|
||||||
opts = {"cert_reqs": ssl.CERT_NONE, "check_hostname": False}
|
|
||||||
if args.headers:
|
|
||||||
options['header'] = map(str.strip, args.headers.split(','))
|
|
||||||
ws = websocket.create_connection(args.url, sslopt=opts, **options)
|
|
||||||
if args.raw:
|
|
||||||
console = NonInteractive()
|
|
||||||
else:
|
|
||||||
console = InteractiveConsole()
|
|
||||||
print("Press Ctrl+C to quit")
|
|
||||||
|
|
||||||
def recv():
|
|
||||||
try:
|
|
||||||
frame = ws.recv_frame()
|
|
||||||
except websocket.WebSocketException:
|
|
||||||
return websocket.ABNF.OPCODE_CLOSE, None
|
|
||||||
if not frame:
|
|
||||||
raise websocket.WebSocketException("Not a valid frame %s" % frame)
|
|
||||||
elif frame.opcode in OPCODE_DATA:
|
|
||||||
return frame.opcode, frame.data
|
|
||||||
elif frame.opcode == websocket.ABNF.OPCODE_CLOSE:
|
|
||||||
ws.send_close()
|
|
||||||
return frame.opcode, None
|
|
||||||
elif frame.opcode == websocket.ABNF.OPCODE_PING:
|
|
||||||
ws.pong(frame.data)
|
|
||||||
return frame.opcode, frame.data
|
|
||||||
|
|
||||||
return frame.opcode, frame.data
|
|
||||||
|
|
||||||
def recv_ws():
|
|
||||||
while True:
|
|
||||||
opcode, data = recv()
|
|
||||||
msg = None
|
|
||||||
if six.PY3 and opcode == websocket.ABNF.OPCODE_TEXT and isinstance(data, bytes):
|
|
||||||
data = str(data, "utf-8")
|
|
||||||
if not args.verbose and opcode in OPCODE_DATA:
|
|
||||||
msg = data
|
|
||||||
elif args.verbose:
|
|
||||||
msg = "%s: %s" % (websocket.ABNF.OPCODE_MAP.get(opcode), data)
|
|
||||||
|
|
||||||
if msg is not None:
|
|
||||||
if args.timings:
|
|
||||||
console.write(str(time.time() - start_time) + ": " + msg)
|
|
||||||
else:
|
|
||||||
console.write(msg)
|
|
||||||
|
|
||||||
if opcode == websocket.ABNF.OPCODE_CLOSE:
|
|
||||||
break
|
|
||||||
|
|
||||||
thread = threading.Thread(target=recv_ws)
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
if args.text:
|
|
||||||
ws.send(args.text)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
message = console.read()
|
|
||||||
ws.send(message)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
return
|
|
||||||
except EOFError:
|
|
||||||
time.sleep(args.eof_wait)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
|
@ -1,13 +0,0 @@
|
||||||
Metadata-Version: 1.1
|
|
||||||
Name: click
|
|
||||||
Version: 6.7
|
|
||||||
Summary: A simple wrapper around optparse for powerful command line utilities.
|
|
||||||
Home-page: http://github.com/mitsuhiko/click
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
License: UNKNOWN
|
|
||||||
Description: UNKNOWN
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
|
@ -1,114 +0,0 @@
|
||||||
CHANGES
|
|
||||||
LICENSE
|
|
||||||
MANIFEST.in
|
|
||||||
Makefile
|
|
||||||
README
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
artwork/logo.svg
|
|
||||||
click/__init__.py
|
|
||||||
click/_bashcomplete.py
|
|
||||||
click/_compat.py
|
|
||||||
click/_termui_impl.py
|
|
||||||
click/_textwrap.py
|
|
||||||
click/_unicodefun.py
|
|
||||||
click/_winconsole.py
|
|
||||||
click/core.py
|
|
||||||
click/decorators.py
|
|
||||||
click/exceptions.py
|
|
||||||
click/formatting.py
|
|
||||||
click/globals.py
|
|
||||||
click/parser.py
|
|
||||||
click/termui.py
|
|
||||||
click/testing.py
|
|
||||||
click/types.py
|
|
||||||
click/utils.py
|
|
||||||
click.egg-info/PKG-INFO
|
|
||||||
click.egg-info/SOURCES.txt
|
|
||||||
click.egg-info/dependency_links.txt
|
|
||||||
click.egg-info/top_level.txt
|
|
||||||
docs/Makefile
|
|
||||||
docs/advanced.rst
|
|
||||||
docs/api.rst
|
|
||||||
docs/arguments.rst
|
|
||||||
docs/bashcomplete.rst
|
|
||||||
docs/changelog.rst
|
|
||||||
docs/clickdoctools.py
|
|
||||||
docs/commands.rst
|
|
||||||
docs/complex.rst
|
|
||||||
docs/conf.py
|
|
||||||
docs/contrib.rst
|
|
||||||
docs/documentation.rst
|
|
||||||
docs/exceptions.rst
|
|
||||||
docs/index.rst
|
|
||||||
docs/license.rst
|
|
||||||
docs/make.bat
|
|
||||||
docs/options.rst
|
|
||||||
docs/parameters.rst
|
|
||||||
docs/prompts.rst
|
|
||||||
docs/python3.rst
|
|
||||||
docs/quickstart.rst
|
|
||||||
docs/setuptools.rst
|
|
||||||
docs/testing.rst
|
|
||||||
docs/upgrading.rst
|
|
||||||
docs/utils.rst
|
|
||||||
docs/why.rst
|
|
||||||
docs/wincmd.rst
|
|
||||||
docs/_static/click-small.png
|
|
||||||
docs/_static/click-small@2x.png
|
|
||||||
docs/_static/click.png
|
|
||||||
docs/_static/click@2x.png
|
|
||||||
docs/_templates/sidebarintro.html
|
|
||||||
docs/_templates/sidebarlogo.html
|
|
||||||
examples/README
|
|
||||||
examples/aliases/README
|
|
||||||
examples/aliases/aliases.ini
|
|
||||||
examples/aliases/aliases.py
|
|
||||||
examples/aliases/setup.py
|
|
||||||
examples/colors/README
|
|
||||||
examples/colors/colors.py
|
|
||||||
examples/colors/setup.py
|
|
||||||
examples/complex/README
|
|
||||||
examples/complex/setup.py
|
|
||||||
examples/complex/complex/__init__.py
|
|
||||||
examples/complex/complex/cli.py
|
|
||||||
examples/complex/complex/commands/__init__.py
|
|
||||||
examples/complex/complex/commands/cmd_init.py
|
|
||||||
examples/complex/complex/commands/cmd_status.py
|
|
||||||
examples/imagepipe/.gitignore
|
|
||||||
examples/imagepipe/README
|
|
||||||
examples/imagepipe/example01.jpg
|
|
||||||
examples/imagepipe/example02.jpg
|
|
||||||
examples/imagepipe/imagepipe.py
|
|
||||||
examples/imagepipe/setup.py
|
|
||||||
examples/inout/README
|
|
||||||
examples/inout/inout.py
|
|
||||||
examples/inout/setup.py
|
|
||||||
examples/naval/README
|
|
||||||
examples/naval/naval.py
|
|
||||||
examples/naval/setup.py
|
|
||||||
examples/repo/README
|
|
||||||
examples/repo/repo.py
|
|
||||||
examples/repo/setup.py
|
|
||||||
examples/termui/README
|
|
||||||
examples/termui/setup.py
|
|
||||||
examples/termui/termui.py
|
|
||||||
examples/validation/README
|
|
||||||
examples/validation/setup.py
|
|
||||||
examples/validation/validation.py
|
|
||||||
tests/conftest.py
|
|
||||||
tests/test_arguments.py
|
|
||||||
tests/test_bashcomplete.py
|
|
||||||
tests/test_basic.py
|
|
||||||
tests/test_chain.py
|
|
||||||
tests/test_commands.py
|
|
||||||
tests/test_compat.py
|
|
||||||
tests/test_context.py
|
|
||||||
tests/test_defaults.py
|
|
||||||
tests/test_formatting.py
|
|
||||||
tests/test_imports.py
|
|
||||||
tests/test_normalization.py
|
|
||||||
tests/test_options.py
|
|
||||||
tests/test_termui.py
|
|
||||||
tests/test_testing.py
|
|
||||||
tests/test_utils.py
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
../click/exceptions.py
|
|
||||||
../click/testing.py
|
|
||||||
../click/decorators.py
|
|
||||||
../click/parser.py
|
|
||||||
../click/formatting.py
|
|
||||||
../click/globals.py
|
|
||||||
../click/_termui_impl.py
|
|
||||||
../click/__init__.py
|
|
||||||
../click/_compat.py
|
|
||||||
../click/_winconsole.py
|
|
||||||
../click/_unicodefun.py
|
|
||||||
../click/_textwrap.py
|
|
||||||
../click/_bashcomplete.py
|
|
||||||
../click/core.py
|
|
||||||
../click/types.py
|
|
||||||
../click/termui.py
|
|
||||||
../click/utils.py
|
|
||||||
../click/exceptions.pyc
|
|
||||||
../click/testing.pyc
|
|
||||||
../click/decorators.pyc
|
|
||||||
../click/parser.pyc
|
|
||||||
../click/formatting.pyc
|
|
||||||
../click/globals.pyc
|
|
||||||
../click/_termui_impl.pyc
|
|
||||||
../click/__init__.pyc
|
|
||||||
../click/_compat.pyc
|
|
||||||
../click/_winconsole.pyc
|
|
||||||
../click/_unicodefun.pyc
|
|
||||||
../click/_textwrap.pyc
|
|
||||||
../click/_bashcomplete.pyc
|
|
||||||
../click/core.pyc
|
|
||||||
../click/types.pyc
|
|
||||||
../click/termui.pyc
|
|
||||||
../click/utils.pyc
|
|
||||||
SOURCES.txt
|
|
||||||
top_level.txt
|
|
||||||
PKG-INFO
|
|
||||||
dependency_links.txt
|
|
|
@ -1 +0,0 @@
|
||||||
click
|
|
|
@ -1,98 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
click
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
Click is a simple Python module that wraps the stdlib's optparse to make
|
|
||||||
writing command line scripts fun. Unlike other modules, it's based around
|
|
||||||
a simple API that does not come with too much magic and is composable.
|
|
||||||
|
|
||||||
In case optparse ever gets removed from the stdlib, it will be shipped by
|
|
||||||
this module.
|
|
||||||
|
|
||||||
:copyright: (c) 2014 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Core classes
|
|
||||||
from .core import Context, BaseCommand, Command, MultiCommand, Group, \
|
|
||||||
CommandCollection, Parameter, Option, Argument
|
|
||||||
|
|
||||||
# Globals
|
|
||||||
from .globals import get_current_context
|
|
||||||
|
|
||||||
# Decorators
|
|
||||||
from .decorators import pass_context, pass_obj, make_pass_decorator, \
|
|
||||||
command, group, argument, option, confirmation_option, \
|
|
||||||
password_option, version_option, help_option
|
|
||||||
|
|
||||||
# Types
|
|
||||||
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
|
||||||
STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED
|
|
||||||
|
|
||||||
# Utilities
|
|
||||||
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
|
||||||
format_filename, get_app_dir, get_os_args
|
|
||||||
|
|
||||||
# Terminal functions
|
|
||||||
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
|
||||||
progressbar, clear, style, unstyle, secho, edit, launch, getchar, \
|
|
||||||
pause
|
|
||||||
|
|
||||||
# Exceptions
|
|
||||||
from .exceptions import ClickException, UsageError, BadParameter, \
|
|
||||||
FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
|
|
||||||
MissingParameter
|
|
||||||
|
|
||||||
# Formatting
|
|
||||||
from .formatting import HelpFormatter, wrap_text
|
|
||||||
|
|
||||||
# Parsing
|
|
||||||
from .parser import OptionParser
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
# Core classes
|
|
||||||
'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
|
|
||||||
'CommandCollection', 'Parameter', 'Option', 'Argument',
|
|
||||||
|
|
||||||
# Globals
|
|
||||||
'get_current_context',
|
|
||||||
|
|
||||||
# Decorators
|
|
||||||
'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
|
|
||||||
'argument', 'option', 'confirmation_option', 'password_option',
|
|
||||||
'version_option', 'help_option',
|
|
||||||
|
|
||||||
# Types
|
|
||||||
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple', 'STRING',
|
|
||||||
'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
|
|
||||||
|
|
||||||
# Utilities
|
|
||||||
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
|
||||||
'format_filename', 'get_app_dir', 'get_os_args',
|
|
||||||
|
|
||||||
# Terminal functions
|
|
||||||
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
|
||||||
'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch',
|
|
||||||
'getchar', 'pause',
|
|
||||||
|
|
||||||
# Exceptions
|
|
||||||
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
|
||||||
'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
|
|
||||||
'MissingParameter',
|
|
||||||
|
|
||||||
# Formatting
|
|
||||||
'HelpFormatter', 'wrap_text',
|
|
||||||
|
|
||||||
# Parsing
|
|
||||||
'OptionParser',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Controls if click should emit the warning about the use of unicode
|
|
||||||
# literals.
|
|
||||||
disable_unicode_literals_warning = False
|
|
||||||
|
|
||||||
|
|
||||||
__version__ = '6.7'
|
|
|
@ -1,83 +0,0 @@
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from .utils import echo
|
|
||||||
from .parser import split_arg_string
|
|
||||||
from .core import MultiCommand, Option
|
|
||||||
|
|
||||||
|
|
||||||
COMPLETION_SCRIPT = '''
|
|
||||||
%(complete_func)s() {
|
|
||||||
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
|
||||||
COMP_CWORD=$COMP_CWORD \\
|
|
||||||
%(autocomplete_var)s=complete $1 ) )
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
complete -F %(complete_func)s -o default %(script_names)s
|
|
||||||
'''
|
|
||||||
|
|
||||||
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
|
||||||
|
|
||||||
|
|
||||||
def get_completion_script(prog_name, complete_var):
|
|
||||||
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
|
|
||||||
return (COMPLETION_SCRIPT % {
|
|
||||||
'complete_func': '_%s_completion' % cf_name,
|
|
||||||
'script_names': prog_name,
|
|
||||||
'autocomplete_var': complete_var,
|
|
||||||
}).strip() + ';'
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_ctx(cli, prog_name, args):
|
|
||||||
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
|
||||||
while ctx.protected_args + ctx.args and isinstance(ctx.command, MultiCommand):
|
|
||||||
a = ctx.protected_args + ctx.args
|
|
||||||
cmd = ctx.command.get_command(ctx, a[0])
|
|
||||||
if cmd is None:
|
|
||||||
return None
|
|
||||||
ctx = cmd.make_context(a[0], a[1:], parent=ctx, resilient_parsing=True)
|
|
||||||
return ctx
|
|
||||||
|
|
||||||
|
|
||||||
def get_choices(cli, prog_name, args, incomplete):
|
|
||||||
ctx = resolve_ctx(cli, prog_name, args)
|
|
||||||
if ctx is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
choices = []
|
|
||||||
if incomplete and not incomplete[:1].isalnum():
|
|
||||||
for param in ctx.command.params:
|
|
||||||
if not isinstance(param, Option):
|
|
||||||
continue
|
|
||||||
choices.extend(param.opts)
|
|
||||||
choices.extend(param.secondary_opts)
|
|
||||||
elif isinstance(ctx.command, MultiCommand):
|
|
||||||
choices.extend(ctx.command.list_commands(ctx))
|
|
||||||
|
|
||||||
for item in choices:
|
|
||||||
if item.startswith(incomplete):
|
|
||||||
yield item
|
|
||||||
|
|
||||||
|
|
||||||
def do_complete(cli, prog_name):
|
|
||||||
cwords = split_arg_string(os.environ['COMP_WORDS'])
|
|
||||||
cword = int(os.environ['COMP_CWORD'])
|
|
||||||
args = cwords[1:cword]
|
|
||||||
try:
|
|
||||||
incomplete = cwords[cword]
|
|
||||||
except IndexError:
|
|
||||||
incomplete = ''
|
|
||||||
|
|
||||||
for item in get_choices(cli, prog_name, args, incomplete):
|
|
||||||
echo(item)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
|
||||||
if complete_instr == 'source':
|
|
||||||
echo(get_completion_script(prog_name, complete_var))
|
|
||||||
return True
|
|
||||||
elif complete_instr == 'complete':
|
|
||||||
return do_complete(cli, prog_name)
|
|
||||||
return False
|
|
|
@ -1,648 +0,0 @@
|
||||||
import re
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import codecs
|
|
||||||
from weakref import WeakKeyDictionary
|
|
||||||
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
|
||||||
WIN = sys.platform.startswith('win')
|
|
||||||
DEFAULT_COLUMNS = 80
|
|
||||||
|
|
||||||
|
|
||||||
_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
|
|
||||||
|
|
||||||
|
|
||||||
def get_filesystem_encoding():
|
|
||||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
|
||||||
|
|
||||||
|
|
||||||
def _make_text_stream(stream, encoding, errors):
|
|
||||||
if encoding is None:
|
|
||||||
encoding = get_best_encoding(stream)
|
|
||||||
if errors is None:
|
|
||||||
errors = 'replace'
|
|
||||||
return _NonClosingTextIOWrapper(stream, encoding, errors,
|
|
||||||
line_buffering=True)
|
|
||||||
|
|
||||||
|
|
||||||
def is_ascii_encoding(encoding):
|
|
||||||
"""Checks if a given encoding is ascii."""
|
|
||||||
try:
|
|
||||||
return codecs.lookup(encoding).name == 'ascii'
|
|
||||||
except LookupError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_best_encoding(stream):
|
|
||||||
"""Returns the default stream encoding if not found."""
|
|
||||||
rv = getattr(stream, 'encoding', None) or sys.getdefaultencoding()
|
|
||||||
if is_ascii_encoding(rv):
|
|
||||||
return 'utf-8'
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
|
||||||
|
|
||||||
def __init__(self, stream, encoding, errors, **extra):
|
|
||||||
self._stream = stream = _FixupStream(stream)
|
|
||||||
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
|
||||||
|
|
||||||
# The io module is a place where the Python 3 text behavior
|
|
||||||
# was forced upon Python 2, so we need to unbreak
|
|
||||||
# it to look like Python 2.
|
|
||||||
if PY2:
|
|
||||||
def write(self, x):
|
|
||||||
if isinstance(x, str) or is_bytes(x):
|
|
||||||
try:
|
|
||||||
self.flush()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return self.buffer.write(str(x))
|
|
||||||
return io.TextIOWrapper.write(self, x)
|
|
||||||
|
|
||||||
def writelines(self, lines):
|
|
||||||
for line in lines:
|
|
||||||
self.write(line)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
try:
|
|
||||||
self.detach()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
# https://bitbucket.org/pypy/pypy/issue/1803
|
|
||||||
return self._stream.isatty()
|
|
||||||
|
|
||||||
|
|
||||||
class _FixupStream(object):
|
|
||||||
"""The new io interface needs more from streams than streams
|
|
||||||
traditionally implement. As such, this fix-up code is necessary in
|
|
||||||
some circumstances.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, stream):
|
|
||||||
self._stream = stream
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._stream, name)
|
|
||||||
|
|
||||||
def read1(self, size):
|
|
||||||
f = getattr(self._stream, 'read1', None)
|
|
||||||
if f is not None:
|
|
||||||
return f(size)
|
|
||||||
# We only dispatch to readline instead of read in Python 2 as we
|
|
||||||
# do not want cause problems with the different implementation
|
|
||||||
# of line buffering.
|
|
||||||
if PY2:
|
|
||||||
return self._stream.readline(size)
|
|
||||||
return self._stream.read(size)
|
|
||||||
|
|
||||||
def readable(self):
|
|
||||||
x = getattr(self._stream, 'readable', None)
|
|
||||||
if x is not None:
|
|
||||||
return x()
|
|
||||||
try:
|
|
||||||
self._stream.read(0)
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def writable(self):
|
|
||||||
x = getattr(self._stream, 'writable', None)
|
|
||||||
if x is not None:
|
|
||||||
return x()
|
|
||||||
try:
|
|
||||||
self._stream.write('')
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self._stream.write(b'')
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def seekable(self):
|
|
||||||
x = getattr(self._stream, 'seekable', None)
|
|
||||||
if x is not None:
|
|
||||||
return x()
|
|
||||||
try:
|
|
||||||
self._stream.seek(self._stream.tell())
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
text_type = unicode
|
|
||||||
bytes = str
|
|
||||||
raw_input = raw_input
|
|
||||||
string_types = (str, unicode)
|
|
||||||
iteritems = lambda x: x.iteritems()
|
|
||||||
range_type = xrange
|
|
||||||
|
|
||||||
def is_bytes(x):
|
|
||||||
return isinstance(x, (buffer, bytearray))
|
|
||||||
|
|
||||||
_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
|
|
||||||
|
|
||||||
# For Windows, we need to force stdout/stdin/stderr to binary if it's
|
|
||||||
# fetched for that. This obviously is not the most correct way to do
|
|
||||||
# it as it changes global state. Unfortunately, there does not seem to
|
|
||||||
# be a clear better way to do it as just reopening the file in binary
|
|
||||||
# mode does not change anything.
|
|
||||||
#
|
|
||||||
# An option would be to do what Python 3 does and to open the file as
|
|
||||||
# binary only, patch it back to the system, and then use a wrapper
|
|
||||||
# stream that converts newlines. It's not quite clear what's the
|
|
||||||
# correct option here.
|
|
||||||
#
|
|
||||||
# This code also lives in _winconsole for the fallback to the console
|
|
||||||
# emulation stream.
|
|
||||||
#
|
|
||||||
# There are also Windows environments where the `msvcrt` module is not
|
|
||||||
# available (which is why we use try-catch instead of the WIN variable
|
|
||||||
# here), such as the Google App Engine development server on Windows. In
|
|
||||||
# those cases there is just nothing we can do.
|
|
||||||
try:
|
|
||||||
import msvcrt
|
|
||||||
except ImportError:
|
|
||||||
set_binary_mode = lambda x: x
|
|
||||||
else:
|
|
||||||
def set_binary_mode(f):
|
|
||||||
try:
|
|
||||||
fileno = f.fileno()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
msvcrt.setmode(fileno, os.O_BINARY)
|
|
||||||
return f
|
|
||||||
|
|
||||||
def isidentifier(x):
|
|
||||||
return _identifier_re.search(x) is not None
|
|
||||||
|
|
||||||
def get_binary_stdin():
|
|
||||||
return set_binary_mode(sys.stdin)
|
|
||||||
|
|
||||||
def get_binary_stdout():
|
|
||||||
return set_binary_mode(sys.stdout)
|
|
||||||
|
|
||||||
def get_binary_stderr():
|
|
||||||
return set_binary_mode(sys.stderr)
|
|
||||||
|
|
||||||
def get_text_stdin(encoding=None, errors=None):
|
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _make_text_stream(sys.stdin, encoding, errors)
|
|
||||||
|
|
||||||
def get_text_stdout(encoding=None, errors=None):
|
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _make_text_stream(sys.stdout, encoding, errors)
|
|
||||||
|
|
||||||
def get_text_stderr(encoding=None, errors=None):
|
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _make_text_stream(sys.stderr, encoding, errors)
|
|
||||||
|
|
||||||
def filename_to_ui(value):
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
value = value.decode(get_filesystem_encoding(), 'replace')
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
import io
|
|
||||||
text_type = str
|
|
||||||
raw_input = input
|
|
||||||
string_types = (str,)
|
|
||||||
range_type = range
|
|
||||||
isidentifier = lambda x: x.isidentifier()
|
|
||||||
iteritems = lambda x: iter(x.items())
|
|
||||||
|
|
||||||
def is_bytes(x):
|
|
||||||
return isinstance(x, (bytes, memoryview, bytearray))
|
|
||||||
|
|
||||||
def _is_binary_reader(stream, default=False):
|
|
||||||
try:
|
|
||||||
return isinstance(stream.read(0), bytes)
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
# This happens in some cases where the stream was already
|
|
||||||
# closed. In this case, we assume the default.
|
|
||||||
|
|
||||||
def _is_binary_writer(stream, default=False):
|
|
||||||
try:
|
|
||||||
stream.write(b'')
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
stream.write('')
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return default
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _find_binary_reader(stream):
|
|
||||||
# We need to figure out if the given stream is already binary.
|
|
||||||
# This can happen because the official docs recommend detaching
|
|
||||||
# the streams to get binary streams. Some code might do this, so
|
|
||||||
# we need to deal with this case explicitly.
|
|
||||||
if _is_binary_reader(stream, False):
|
|
||||||
return stream
|
|
||||||
|
|
||||||
buf = getattr(stream, 'buffer', None)
|
|
||||||
|
|
||||||
# Same situation here; this time we assume that the buffer is
|
|
||||||
# actually binary in case it's closed.
|
|
||||||
if buf is not None and _is_binary_reader(buf, True):
|
|
||||||
return buf
|
|
||||||
|
|
||||||
def _find_binary_writer(stream):
|
|
||||||
# We need to figure out if the given stream is already binary.
|
|
||||||
# This can happen because the official docs recommend detatching
|
|
||||||
# the streams to get binary streams. Some code might do this, so
|
|
||||||
# we need to deal with this case explicitly.
|
|
||||||
if _is_binary_writer(stream, False):
|
|
||||||
return stream
|
|
||||||
|
|
||||||
buf = getattr(stream, 'buffer', None)
|
|
||||||
|
|
||||||
# Same situation here; this time we assume that the buffer is
|
|
||||||
# actually binary in case it's closed.
|
|
||||||
if buf is not None and _is_binary_writer(buf, True):
|
|
||||||
return buf
|
|
||||||
|
|
||||||
def _stream_is_misconfigured(stream):
|
|
||||||
"""A stream is misconfigured if its encoding is ASCII."""
|
|
||||||
# If the stream does not have an encoding set, we assume it's set
|
|
||||||
# to ASCII. This appears to happen in certain unittest
|
|
||||||
# environments. It's not quite clear what the correct behavior is
|
|
||||||
# but this at least will force Click to recover somehow.
|
|
||||||
return is_ascii_encoding(getattr(stream, 'encoding', None) or 'ascii')
|
|
||||||
|
|
||||||
def _is_compatible_text_stream(stream, encoding, errors):
|
|
||||||
stream_encoding = getattr(stream, 'encoding', None)
|
|
||||||
stream_errors = getattr(stream, 'errors', None)
|
|
||||||
|
|
||||||
# Perfect match.
|
|
||||||
if stream_encoding == encoding and stream_errors == errors:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Otherwise, it's only a compatible stream if we did not ask for
|
|
||||||
# an encoding.
|
|
||||||
if encoding is None:
|
|
||||||
return stream_encoding is not None
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _force_correct_text_reader(text_reader, encoding, errors):
|
|
||||||
if _is_binary_reader(text_reader, False):
|
|
||||||
binary_reader = text_reader
|
|
||||||
else:
|
|
||||||
# If there is no target encoding set, we need to verify that the
|
|
||||||
# reader is not actually misconfigured.
|
|
||||||
if encoding is None and not _stream_is_misconfigured(text_reader):
|
|
||||||
return text_reader
|
|
||||||
|
|
||||||
if _is_compatible_text_stream(text_reader, encoding, errors):
|
|
||||||
return text_reader
|
|
||||||
|
|
||||||
# If the reader has no encoding, we try to find the underlying
|
|
||||||
# binary reader for it. If that fails because the environment is
|
|
||||||
# misconfigured, we silently go with the same reader because this
|
|
||||||
# is too common to happen. In that case, mojibake is better than
|
|
||||||
# exceptions.
|
|
||||||
binary_reader = _find_binary_reader(text_reader)
|
|
||||||
if binary_reader is None:
|
|
||||||
return text_reader
|
|
||||||
|
|
||||||
# At this point, we default the errors to replace instead of strict
|
|
||||||
# because nobody handles those errors anyways and at this point
|
|
||||||
# we're so fundamentally fucked that nothing can repair it.
|
|
||||||
if errors is None:
|
|
||||||
errors = 'replace'
|
|
||||||
return _make_text_stream(binary_reader, encoding, errors)
|
|
||||||
|
|
||||||
def _force_correct_text_writer(text_writer, encoding, errors):
|
|
||||||
if _is_binary_writer(text_writer, False):
|
|
||||||
binary_writer = text_writer
|
|
||||||
else:
|
|
||||||
# If there is no target encoding set, we need to verify that the
|
|
||||||
# writer is not actually misconfigured.
|
|
||||||
if encoding is None and not _stream_is_misconfigured(text_writer):
|
|
||||||
return text_writer
|
|
||||||
|
|
||||||
if _is_compatible_text_stream(text_writer, encoding, errors):
|
|
||||||
return text_writer
|
|
||||||
|
|
||||||
# If the writer has no encoding, we try to find the underlying
|
|
||||||
# binary writer for it. If that fails because the environment is
|
|
||||||
# misconfigured, we silently go with the same writer because this
|
|
||||||
# is too common to happen. In that case, mojibake is better than
|
|
||||||
# exceptions.
|
|
||||||
binary_writer = _find_binary_writer(text_writer)
|
|
||||||
if binary_writer is None:
|
|
||||||
return text_writer
|
|
||||||
|
|
||||||
# At this point, we default the errors to replace instead of strict
|
|
||||||
# because nobody handles those errors anyways and at this point
|
|
||||||
# we're so fundamentally fucked that nothing can repair it.
|
|
||||||
if errors is None:
|
|
||||||
errors = 'replace'
|
|
||||||
return _make_text_stream(binary_writer, encoding, errors)
|
|
||||||
|
|
||||||
def get_binary_stdin():
|
|
||||||
reader = _find_binary_reader(sys.stdin)
|
|
||||||
if reader is None:
|
|
||||||
raise RuntimeError('Was not able to determine binary '
|
|
||||||
'stream for sys.stdin.')
|
|
||||||
return reader
|
|
||||||
|
|
||||||
def get_binary_stdout():
|
|
||||||
writer = _find_binary_writer(sys.stdout)
|
|
||||||
if writer is None:
|
|
||||||
raise RuntimeError('Was not able to determine binary '
|
|
||||||
'stream for sys.stdout.')
|
|
||||||
return writer
|
|
||||||
|
|
||||||
def get_binary_stderr():
|
|
||||||
writer = _find_binary_writer(sys.stderr)
|
|
||||||
if writer is None:
|
|
||||||
raise RuntimeError('Was not able to determine binary '
|
|
||||||
'stream for sys.stderr.')
|
|
||||||
return writer
|
|
||||||
|
|
||||||
def get_text_stdin(encoding=None, errors=None):
|
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _force_correct_text_reader(sys.stdin, encoding, errors)
|
|
||||||
|
|
||||||
def get_text_stdout(encoding=None, errors=None):
|
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _force_correct_text_writer(sys.stdout, encoding, errors)
|
|
||||||
|
|
||||||
def get_text_stderr(encoding=None, errors=None):
|
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _force_correct_text_writer(sys.stderr, encoding, errors)
|
|
||||||
|
|
||||||
def filename_to_ui(value):
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
value = value.decode(get_filesystem_encoding(), 'replace')
|
|
||||||
else:
|
|
||||||
value = value.encode('utf-8', 'surrogateescape') \
|
|
||||||
.decode('utf-8', 'replace')
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
def get_streerror(e, default=None):
|
|
||||||
if hasattr(e, 'strerror'):
|
|
||||||
msg = e.strerror
|
|
||||||
else:
|
|
||||||
if default is not None:
|
|
||||||
msg = default
|
|
||||||
else:
|
|
||||||
msg = str(e)
|
|
||||||
if isinstance(msg, bytes):
|
|
||||||
msg = msg.decode('utf-8', 'replace')
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
def open_stream(filename, mode='r', encoding=None, errors='strict',
|
|
||||||
atomic=False):
|
|
||||||
# Standard streams first. These are simple because they don't need
|
|
||||||
# special handling for the atomic flag. It's entirely ignored.
|
|
||||||
if filename == '-':
|
|
||||||
if 'w' in mode:
|
|
||||||
if 'b' in mode:
|
|
||||||
return get_binary_stdout(), False
|
|
||||||
return get_text_stdout(encoding=encoding, errors=errors), False
|
|
||||||
if 'b' in mode:
|
|
||||||
return get_binary_stdin(), False
|
|
||||||
return get_text_stdin(encoding=encoding, errors=errors), False
|
|
||||||
|
|
||||||
# Non-atomic writes directly go out through the regular open functions.
|
|
||||||
if not atomic:
|
|
||||||
if encoding is None:
|
|
||||||
return open(filename, mode), True
|
|
||||||
return io.open(filename, mode, encoding=encoding, errors=errors), True
|
|
||||||
|
|
||||||
# Some usability stuff for atomic writes
|
|
||||||
if 'a' in mode:
|
|
||||||
raise ValueError(
|
|
||||||
'Appending to an existing file is not supported, because that '
|
|
||||||
'would involve an expensive `copy`-operation to a temporary '
|
|
||||||
'file. Open the file in normal `w`-mode and copy explicitly '
|
|
||||||
'if that\'s what you\'re after.'
|
|
||||||
)
|
|
||||||
if 'x' in mode:
|
|
||||||
raise ValueError('Use the `overwrite`-parameter instead.')
|
|
||||||
if 'w' not in mode:
|
|
||||||
raise ValueError('Atomic writes only make sense with `w`-mode.')
|
|
||||||
|
|
||||||
# Atomic writes are more complicated. They work by opening a file
|
|
||||||
# as a proxy in the same folder and then using the fdopen
|
|
||||||
# functionality to wrap it in a Python file. Then we wrap it in an
|
|
||||||
# atomic file that moves the file over on close.
|
|
||||||
import tempfile
|
|
||||||
fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename),
|
|
||||||
prefix='.__atomic-write')
|
|
||||||
|
|
||||||
if encoding is not None:
|
|
||||||
f = io.open(fd, mode, encoding=encoding, errors=errors)
|
|
||||||
else:
|
|
||||||
f = os.fdopen(fd, mode)
|
|
||||||
|
|
||||||
return _AtomicFile(f, tmp_filename, filename), True
|
|
||||||
|
|
||||||
|
|
||||||
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
|
||||||
if hasattr(os, 'replace'):
|
|
||||||
_replace = os.replace
|
|
||||||
_can_replace = True
|
|
||||||
else:
|
|
||||||
_replace = os.rename
|
|
||||||
_can_replace = not WIN
|
|
||||||
|
|
||||||
|
|
||||||
class _AtomicFile(object):
|
|
||||||
|
|
||||||
def __init__(self, f, tmp_filename, real_filename):
|
|
||||||
self._f = f
|
|
||||||
self._tmp_filename = tmp_filename
|
|
||||||
self._real_filename = real_filename
|
|
||||||
self.closed = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._real_filename
|
|
||||||
|
|
||||||
def close(self, delete=False):
|
|
||||||
if self.closed:
|
|
||||||
return
|
|
||||||
self._f.close()
|
|
||||||
if not _can_replace:
|
|
||||||
try:
|
|
||||||
os.remove(self._real_filename)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
_replace(self._tmp_filename, self._real_filename)
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._f, name)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.close(delete=exc_type is not None)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self._f)
|
|
||||||
|
|
||||||
|
|
||||||
auto_wrap_for_ansi = None
|
|
||||||
colorama = None
|
|
||||||
get_winterm_size = None
|
|
||||||
|
|
||||||
|
|
||||||
def strip_ansi(value):
|
|
||||||
return _ansi_re.sub('', value)
|
|
||||||
|
|
||||||
|
|
||||||
def should_strip_ansi(stream=None, color=None):
|
|
||||||
if color is None:
|
|
||||||
if stream is None:
|
|
||||||
stream = sys.stdin
|
|
||||||
return not isatty(stream)
|
|
||||||
return not color
|
|
||||||
|
|
||||||
|
|
||||||
# If we're on Windows, we provide transparent integration through
|
|
||||||
# colorama. This will make ANSI colors through the echo function
|
|
||||||
# work automatically.
|
|
||||||
if WIN:
|
|
||||||
# Windows has a smaller terminal
|
|
||||||
DEFAULT_COLUMNS = 79
|
|
||||||
|
|
||||||
from ._winconsole import _get_windows_console_stream
|
|
||||||
|
|
||||||
def _get_argv_encoding():
|
|
||||||
import locale
|
|
||||||
return locale.getpreferredencoding()
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
def raw_input(prompt=''):
|
|
||||||
sys.stderr.flush()
|
|
||||||
if prompt:
|
|
||||||
stdout = _default_text_stdout()
|
|
||||||
stdout.write(prompt)
|
|
||||||
stdin = _default_text_stdin()
|
|
||||||
return stdin.readline().rstrip('\r\n')
|
|
||||||
|
|
||||||
try:
|
|
||||||
import colorama
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
_ansi_stream_wrappers = WeakKeyDictionary()
|
|
||||||
|
|
||||||
def auto_wrap_for_ansi(stream, color=None):
|
|
||||||
"""This function wraps a stream so that calls through colorama
|
|
||||||
are issued to the win32 console API to recolor on demand. It
|
|
||||||
also ensures to reset the colors if a write call is interrupted
|
|
||||||
to not destroy the console afterwards.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cached = _ansi_stream_wrappers.get(stream)
|
|
||||||
except Exception:
|
|
||||||
cached = None
|
|
||||||
if cached is not None:
|
|
||||||
return cached
|
|
||||||
strip = should_strip_ansi(stream, color)
|
|
||||||
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
|
||||||
rv = ansi_wrapper.stream
|
|
||||||
_write = rv.write
|
|
||||||
|
|
||||||
def _safe_write(s):
|
|
||||||
try:
|
|
||||||
return _write(s)
|
|
||||||
except:
|
|
||||||
ansi_wrapper.reset_all()
|
|
||||||
raise
|
|
||||||
|
|
||||||
rv.write = _safe_write
|
|
||||||
try:
|
|
||||||
_ansi_stream_wrappers[stream] = rv
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def get_winterm_size():
|
|
||||||
win = colorama.win32.GetConsoleScreenBufferInfo(
|
|
||||||
colorama.win32.STDOUT).srWindow
|
|
||||||
return win.Right - win.Left, win.Bottom - win.Top
|
|
||||||
else:
|
|
||||||
def _get_argv_encoding():
|
|
||||||
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
|
|
||||||
|
|
||||||
_get_windows_console_stream = lambda *x: None
|
|
||||||
|
|
||||||
|
|
||||||
def term_len(x):
|
|
||||||
return len(strip_ansi(x))
|
|
||||||
|
|
||||||
|
|
||||||
def isatty(stream):
|
|
||||||
try:
|
|
||||||
return stream.isatty()
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _make_cached_stream_func(src_func, wrapper_func):
|
|
||||||
cache = WeakKeyDictionary()
|
|
||||||
def func():
|
|
||||||
stream = src_func()
|
|
||||||
try:
|
|
||||||
rv = cache.get(stream)
|
|
||||||
except Exception:
|
|
||||||
rv = None
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
rv = wrapper_func()
|
|
||||||
try:
|
|
||||||
cache[stream] = rv
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return rv
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
_default_text_stdin = _make_cached_stream_func(
|
|
||||||
lambda: sys.stdin, get_text_stdin)
|
|
||||||
_default_text_stdout = _make_cached_stream_func(
|
|
||||||
lambda: sys.stdout, get_text_stdout)
|
|
||||||
_default_text_stderr = _make_cached_stream_func(
|
|
||||||
lambda: sys.stderr, get_text_stderr)
|
|
||||||
|
|
||||||
|
|
||||||
binary_streams = {
|
|
||||||
'stdin': get_binary_stdin,
|
|
||||||
'stdout': get_binary_stdout,
|
|
||||||
'stderr': get_binary_stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
text_streams = {
|
|
||||||
'stdin': get_text_stdin,
|
|
||||||
'stdout': get_text_stdout,
|
|
||||||
'stderr': get_text_stderr,
|
|
||||||
}
|
|
|
@ -1,547 +0,0 @@
|
||||||
"""
|
|
||||||
click._termui_impl
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module contains implementations for the termui module. To keep the
|
|
||||||
import time of Click down, some infrequently used functionality is placed
|
|
||||||
in this module and only imported as needed.
|
|
||||||
|
|
||||||
:copyright: (c) 2014 by Armin Ronacher.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import math
|
|
||||||
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
|
||||||
open_stream, strip_ansi, term_len, get_best_encoding, WIN
|
|
||||||
from .utils import echo
|
|
||||||
from .exceptions import ClickException
|
|
||||||
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
BEFORE_BAR = '\r'
|
|
||||||
AFTER_BAR = '\n'
|
|
||||||
else:
|
|
||||||
BEFORE_BAR = '\r\033[?25l'
|
|
||||||
AFTER_BAR = '\033[?25h\n'
|
|
||||||
|
|
||||||
|
|
||||||
def _length_hint(obj):
|
|
||||||
"""Returns the length hint of an object."""
|
|
||||||
try:
|
|
||||||
return len(obj)
|
|
||||||
except (AttributeError, TypeError):
|
|
||||||
try:
|
|
||||||
get_hint = type(obj).__length_hint__
|
|
||||||
except AttributeError:
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
hint = get_hint(obj)
|
|
||||||
except TypeError:
|
|
||||||
return None
|
|
||||||
if hint is NotImplemented or \
|
|
||||||
not isinstance(hint, (int, long)) or \
|
|
||||||
hint < 0:
|
|
||||||
return None
|
|
||||||
return hint
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressBar(object):
|
|
||||||
|
|
||||||
def __init__(self, iterable, length=None, fill_char='#', empty_char=' ',
|
|
||||||
bar_template='%(bar)s', info_sep=' ', show_eta=True,
|
|
||||||
show_percent=None, show_pos=False, item_show_func=None,
|
|
||||||
label=None, file=None, color=None, width=30):
|
|
||||||
self.fill_char = fill_char
|
|
||||||
self.empty_char = empty_char
|
|
||||||
self.bar_template = bar_template
|
|
||||||
self.info_sep = info_sep
|
|
||||||
self.show_eta = show_eta
|
|
||||||
self.show_percent = show_percent
|
|
||||||
self.show_pos = show_pos
|
|
||||||
self.item_show_func = item_show_func
|
|
||||||
self.label = label or ''
|
|
||||||
if file is None:
|
|
||||||
file = _default_text_stdout()
|
|
||||||
self.file = file
|
|
||||||
self.color = color
|
|
||||||
self.width = width
|
|
||||||
self.autowidth = width == 0
|
|
||||||
|
|
||||||
if length is None:
|
|
||||||
length = _length_hint(iterable)
|
|
||||||
if iterable is None:
|
|
||||||
if length is None:
|
|
||||||
raise TypeError('iterable or length is required')
|
|
||||||
iterable = range_type(length)
|
|
||||||
self.iter = iter(iterable)
|
|
||||||
self.length = length
|
|
||||||
self.length_known = length is not None
|
|
||||||
self.pos = 0
|
|
||||||
self.avg = []
|
|
||||||
self.start = self.last_eta = time.time()
|
|
||||||
self.eta_known = False
|
|
||||||
self.finished = False
|
|
||||||
self.max_width = None
|
|
||||||
self.entered = False
|
|
||||||
self.current_item = None
|
|
||||||
self.is_hidden = not isatty(self.file)
|
|
||||||
self._last_line = None
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.entered = True
|
|
||||||
self.render_progress()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.render_finish()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
if not self.entered:
|
|
||||||
raise RuntimeError('You need to use progress bars in a with block.')
|
|
||||||
self.render_progress()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def render_finish(self):
|
|
||||||
if self.is_hidden:
|
|
||||||
return
|
|
||||||
self.file.write(AFTER_BAR)
|
|
||||||
self.file.flush()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pct(self):
|
|
||||||
if self.finished:
|
|
||||||
return 1.0
|
|
||||||
return min(self.pos / (float(self.length) or 1), 1.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def time_per_iteration(self):
|
|
||||||
if not self.avg:
|
|
||||||
return 0.0
|
|
||||||
return sum(self.avg) / float(len(self.avg))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def eta(self):
|
|
||||||
if self.length_known and not self.finished:
|
|
||||||
return self.time_per_iteration * (self.length - self.pos)
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
def format_eta(self):
|
|
||||||
if self.eta_known:
|
|
||||||
t = self.eta + 1
|
|
||||||
seconds = t % 60
|
|
||||||
t /= 60
|
|
||||||
minutes = t % 60
|
|
||||||
t /= 60
|
|
||||||
hours = t % 24
|
|
||||||
t /= 24
|
|
||||||
if t > 0:
|
|
||||||
days = t
|
|
||||||
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
|
||||||
else:
|
|
||||||
return '%02d:%02d:%02d' % (hours, minutes, seconds)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def format_pos(self):
|
|
||||||
pos = str(self.pos)
|
|
||||||
if self.length_known:
|
|
||||||
pos += '/%s' % self.length
|
|
||||||
return pos
|
|
||||||
|
|
||||||
def format_pct(self):
|
|
||||||
return ('% 4d%%' % int(self.pct * 100))[1:]
|
|
||||||
|
|
||||||
def format_progress_line(self):
|
|
||||||
show_percent = self.show_percent
|
|
||||||
|
|
||||||
info_bits = []
|
|
||||||
if self.length_known:
|
|
||||||
bar_length = int(self.pct * self.width)
|
|
||||||
bar = self.fill_char * bar_length
|
|
||||||
bar += self.empty_char * (self.width - bar_length)
|
|
||||||
if show_percent is None:
|
|
||||||
show_percent = not self.show_pos
|
|
||||||
else:
|
|
||||||
if self.finished:
|
|
||||||
bar = self.fill_char * self.width
|
|
||||||
else:
|
|
||||||
bar = list(self.empty_char * (self.width or 1))
|
|
||||||
if self.time_per_iteration != 0:
|
|
||||||
bar[int((math.cos(self.pos * self.time_per_iteration)
|
|
||||||
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
|
||||||
bar = ''.join(bar)
|
|
||||||
|
|
||||||
if self.show_pos:
|
|
||||||
info_bits.append(self.format_pos())
|
|
||||||
if show_percent:
|
|
||||||
info_bits.append(self.format_pct())
|
|
||||||
if self.show_eta and self.eta_known and not self.finished:
|
|
||||||
info_bits.append(self.format_eta())
|
|
||||||
if self.item_show_func is not None:
|
|
||||||
item_info = self.item_show_func(self.current_item)
|
|
||||||
if item_info is not None:
|
|
||||||
info_bits.append(item_info)
|
|
||||||
|
|
||||||
return (self.bar_template % {
|
|
||||||
'label': self.label,
|
|
||||||
'bar': bar,
|
|
||||||
'info': self.info_sep.join(info_bits)
|
|
||||||
}).rstrip()
|
|
||||||
|
|
||||||
def render_progress(self):
|
|
||||||
from .termui import get_terminal_size
|
|
||||||
nl = False
|
|
||||||
|
|
||||||
if self.is_hidden:
|
|
||||||
buf = [self.label]
|
|
||||||
nl = True
|
|
||||||
else:
|
|
||||||
buf = []
|
|
||||||
# Update width in case the terminal has been resized
|
|
||||||
if self.autowidth:
|
|
||||||
old_width = self.width
|
|
||||||
self.width = 0
|
|
||||||
clutter_length = term_len(self.format_progress_line())
|
|
||||||
new_width = max(0, get_terminal_size()[0] - clutter_length)
|
|
||||||
if new_width < old_width:
|
|
||||||
buf.append(BEFORE_BAR)
|
|
||||||
buf.append(' ' * self.max_width)
|
|
||||||
self.max_width = new_width
|
|
||||||
self.width = new_width
|
|
||||||
|
|
||||||
clear_width = self.width
|
|
||||||
if self.max_width is not None:
|
|
||||||
clear_width = self.max_width
|
|
||||||
|
|
||||||
buf.append(BEFORE_BAR)
|
|
||||||
line = self.format_progress_line()
|
|
||||||
line_len = term_len(line)
|
|
||||||
if self.max_width is None or self.max_width < line_len:
|
|
||||||
self.max_width = line_len
|
|
||||||
buf.append(line)
|
|
||||||
|
|
||||||
buf.append(' ' * (clear_width - line_len))
|
|
||||||
line = ''.join(buf)
|
|
||||||
|
|
||||||
# Render the line only if it changed.
|
|
||||||
if line != self._last_line:
|
|
||||||
self._last_line = line
|
|
||||||
echo(line, file=self.file, color=self.color, nl=nl)
|
|
||||||
self.file.flush()
|
|
||||||
|
|
||||||
def make_step(self, n_steps):
|
|
||||||
self.pos += n_steps
|
|
||||||
if self.length_known and self.pos >= self.length:
|
|
||||||
self.finished = True
|
|
||||||
|
|
||||||
if (time.time() - self.last_eta) < 1.0:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.last_eta = time.time()
|
|
||||||
self.avg = self.avg[-6:] + [-(self.start - time.time()) / (self.pos)]
|
|
||||||
|
|
||||||
self.eta_known = self.length_known
|
|
||||||
|
|
||||||
def update(self, n_steps):
|
|
||||||
self.make_step(n_steps)
|
|
||||||
self.render_progress()
|
|
||||||
|
|
||||||
def finish(self):
|
|
||||||
self.eta_known = 0
|
|
||||||
self.current_item = None
|
|
||||||
self.finished = True
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
if self.is_hidden:
|
|
||||||
return next(self.iter)
|
|
||||||
try:
|
|
||||||
rv = next(self.iter)
|
|
||||||
self.current_item = rv
|
|
||||||
except StopIteration:
|
|
||||||
self.finish()
|
|
||||||
self.render_progress()
|
|
||||||
raise StopIteration()
|
|
||||||
else:
|
|
||||||
self.update(1)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
__next__ = next
|
|
||||||
del next
|
|
||||||
|
|
||||||
|
|
||||||
def pager(text, color=None):
|
|
||||||
"""Decide what method to use for paging through text."""
|
|
||||||
stdout = _default_text_stdout()
|
|
||||||
if not isatty(sys.stdin) or not isatty(stdout):
|
|
||||||
return _nullpager(stdout, text, color)
|
|
||||||
pager_cmd = (os.environ.get('PAGER', None) or '').strip()
|
|
||||||
if pager_cmd:
|
|
||||||
if WIN:
|
|
||||||
return _tempfilepager(text, pager_cmd, color)
|
|
||||||
return _pipepager(text, pager_cmd, color)
|
|
||||||
if os.environ.get('TERM') in ('dumb', 'emacs'):
|
|
||||||
return _nullpager(stdout, text, color)
|
|
||||||
if WIN or sys.platform.startswith('os2'):
|
|
||||||
return _tempfilepager(text, 'more <', color)
|
|
||||||
if hasattr(os, 'system') and os.system('(less) 2>/dev/null') == 0:
|
|
||||||
return _pipepager(text, 'less', color)
|
|
||||||
|
|
||||||
import tempfile
|
|
||||||
fd, filename = tempfile.mkstemp()
|
|
||||||
os.close(fd)
|
|
||||||
try:
|
|
||||||
if hasattr(os, 'system') and os.system('more "%s"' % filename) == 0:
|
|
||||||
return _pipepager(text, 'more', color)
|
|
||||||
return _nullpager(stdout, text, color)
|
|
||||||
finally:
|
|
||||||
os.unlink(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def _pipepager(text, cmd, color):
|
|
||||||
"""Page through text by feeding it to another program. Invoking a
|
|
||||||
pager through this might support colors.
|
|
||||||
"""
|
|
||||||
import subprocess
|
|
||||||
env = dict(os.environ)
|
|
||||||
|
|
||||||
# If we're piping to less we might support colors under the
|
|
||||||
# condition that
|
|
||||||
cmd_detail = cmd.rsplit('/', 1)[-1].split()
|
|
||||||
if color is None and cmd_detail[0] == 'less':
|
|
||||||
less_flags = os.environ.get('LESS', '') + ' '.join(cmd_detail[1:])
|
|
||||||
if not less_flags:
|
|
||||||
env['LESS'] = '-R'
|
|
||||||
color = True
|
|
||||||
elif 'r' in less_flags or 'R' in less_flags:
|
|
||||||
color = True
|
|
||||||
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
|
|
||||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
|
|
||||||
env=env)
|
|
||||||
encoding = get_best_encoding(c.stdin)
|
|
||||||
try:
|
|
||||||
c.stdin.write(text.encode(encoding, 'replace'))
|
|
||||||
c.stdin.close()
|
|
||||||
except (IOError, KeyboardInterrupt):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
|
||||||
# search or other commands inside less).
|
|
||||||
#
|
|
||||||
# That means when the user hits ^C, the parent process (click) terminates,
|
|
||||||
# but less is still alive, paging the output and messing up the terminal.
|
|
||||||
#
|
|
||||||
# If the user wants to make the pager exit on ^C, they should set
|
|
||||||
# `LESS='-K'`. It's not our decision to make.
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
c.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def _tempfilepager(text, cmd, color):
|
|
||||||
"""Page through text by invoking a program on a temporary file."""
|
|
||||||
import tempfile
|
|
||||||
filename = tempfile.mktemp()
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
encoding = get_best_encoding(sys.stdout)
|
|
||||||
with open_stream(filename, 'wb')[0] as f:
|
|
||||||
f.write(text.encode(encoding))
|
|
||||||
try:
|
|
||||||
os.system(cmd + ' "' + filename + '"')
|
|
||||||
finally:
|
|
||||||
os.unlink(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def _nullpager(stream, text, color):
|
|
||||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
stream.write(text)
|
|
||||||
|
|
||||||
|
|
||||||
class Editor(object):
|
|
||||||
|
|
||||||
def __init__(self, editor=None, env=None, require_save=True,
|
|
||||||
extension='.txt'):
|
|
||||||
self.editor = editor
|
|
||||||
self.env = env
|
|
||||||
self.require_save = require_save
|
|
||||||
self.extension = extension
|
|
||||||
|
|
||||||
def get_editor(self):
|
|
||||||
if self.editor is not None:
|
|
||||||
return self.editor
|
|
||||||
for key in 'VISUAL', 'EDITOR':
|
|
||||||
rv = os.environ.get(key)
|
|
||||||
if rv:
|
|
||||||
return rv
|
|
||||||
if WIN:
|
|
||||||
return 'notepad'
|
|
||||||
for editor in 'vim', 'nano':
|
|
||||||
if os.system('which %s >/dev/null 2>&1' % editor) == 0:
|
|
||||||
return editor
|
|
||||||
return 'vi'
|
|
||||||
|
|
||||||
def edit_file(self, filename):
|
|
||||||
import subprocess
|
|
||||||
editor = self.get_editor()
|
|
||||||
if self.env:
|
|
||||||
environ = os.environ.copy()
|
|
||||||
environ.update(self.env)
|
|
||||||
else:
|
|
||||||
environ = None
|
|
||||||
try:
|
|
||||||
c = subprocess.Popen('%s "%s"' % (editor, filename),
|
|
||||||
env=environ, shell=True)
|
|
||||||
exit_code = c.wait()
|
|
||||||
if exit_code != 0:
|
|
||||||
raise ClickException('%s: Editing failed!' % editor)
|
|
||||||
except OSError as e:
|
|
||||||
raise ClickException('%s: Editing failed: %s' % (editor, e))
|
|
||||||
|
|
||||||
def edit(self, text):
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
text = text or ''
|
|
||||||
if text and not text.endswith('\n'):
|
|
||||||
text += '\n'
|
|
||||||
|
|
||||||
fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension)
|
|
||||||
try:
|
|
||||||
if WIN:
|
|
||||||
encoding = 'utf-8-sig'
|
|
||||||
text = text.replace('\n', '\r\n')
|
|
||||||
else:
|
|
||||||
encoding = 'utf-8'
|
|
||||||
text = text.encode(encoding)
|
|
||||||
|
|
||||||
f = os.fdopen(fd, 'wb')
|
|
||||||
f.write(text)
|
|
||||||
f.close()
|
|
||||||
timestamp = os.path.getmtime(name)
|
|
||||||
|
|
||||||
self.edit_file(name)
|
|
||||||
|
|
||||||
if self.require_save \
|
|
||||||
and os.path.getmtime(name) == timestamp:
|
|
||||||
return None
|
|
||||||
|
|
||||||
f = open(name, 'rb')
|
|
||||||
try:
|
|
||||||
rv = f.read()
|
|
||||||
finally:
|
|
||||||
f.close()
|
|
||||||
return rv.decode('utf-8-sig').replace('\r\n', '\n')
|
|
||||||
finally:
|
|
||||||
os.unlink(name)
|
|
||||||
|
|
||||||
|
|
||||||
def open_url(url, wait=False, locate=False):
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def _unquote_file(url):
|
|
||||||
try:
|
|
||||||
import urllib
|
|
||||||
except ImportError:
|
|
||||||
import urllib
|
|
||||||
if url.startswith('file://'):
|
|
||||||
url = urllib.unquote(url[7:])
|
|
||||||
return url
|
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
args = ['open']
|
|
||||||
if wait:
|
|
||||||
args.append('-W')
|
|
||||||
if locate:
|
|
||||||
args.append('-R')
|
|
||||||
args.append(_unquote_file(url))
|
|
||||||
null = open('/dev/null', 'w')
|
|
||||||
try:
|
|
||||||
return subprocess.Popen(args, stderr=null).wait()
|
|
||||||
finally:
|
|
||||||
null.close()
|
|
||||||
elif WIN:
|
|
||||||
if locate:
|
|
||||||
url = _unquote_file(url)
|
|
||||||
args = 'explorer /select,"%s"' % _unquote_file(
|
|
||||||
url.replace('"', ''))
|
|
||||||
else:
|
|
||||||
args = 'start %s "" "%s"' % (
|
|
||||||
wait and '/WAIT' or '', url.replace('"', ''))
|
|
||||||
return os.system(args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if locate:
|
|
||||||
url = os.path.dirname(_unquote_file(url)) or '.'
|
|
||||||
else:
|
|
||||||
url = _unquote_file(url)
|
|
||||||
c = subprocess.Popen(['xdg-open', url])
|
|
||||||
if wait:
|
|
||||||
return c.wait()
|
|
||||||
return 0
|
|
||||||
except OSError:
|
|
||||||
if url.startswith(('http://', 'https://')) and not locate and not wait:
|
|
||||||
import webbrowser
|
|
||||||
webbrowser.open(url)
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def _translate_ch_to_exc(ch):
|
|
||||||
if ch == '\x03':
|
|
||||||
raise KeyboardInterrupt()
|
|
||||||
if ch == '\x04':
|
|
||||||
raise EOFError()
|
|
||||||
|
|
||||||
|
|
||||||
if WIN:
|
|
||||||
import msvcrt
|
|
||||||
|
|
||||||
def getchar(echo):
|
|
||||||
rv = msvcrt.getch()
|
|
||||||
if echo:
|
|
||||||
msvcrt.putchar(rv)
|
|
||||||
_translate_ch_to_exc(rv)
|
|
||||||
if PY2:
|
|
||||||
enc = getattr(sys.stdin, 'encoding', None)
|
|
||||||
if enc is not None:
|
|
||||||
rv = rv.decode(enc, 'replace')
|
|
||||||
else:
|
|
||||||
rv = rv.decode('cp1252', 'replace')
|
|
||||||
return rv
|
|
||||||
else:
|
|
||||||
import tty
|
|
||||||
import termios
|
|
||||||
|
|
||||||
def getchar(echo):
|
|
||||||
if not isatty(sys.stdin):
|
|
||||||
f = open('/dev/tty')
|
|
||||||
fd = f.fileno()
|
|
||||||
else:
|
|
||||||
fd = sys.stdin.fileno()
|
|
||||||
f = None
|
|
||||||
try:
|
|
||||||
old_settings = termios.tcgetattr(fd)
|
|
||||||
try:
|
|
||||||
tty.setraw(fd)
|
|
||||||
ch = os.read(fd, 32)
|
|
||||||
if echo and isatty(sys.stdout):
|
|
||||||
sys.stdout.write(ch)
|
|
||||||
finally:
|
|
||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
||||||
sys.stdout.flush()
|
|
||||||
if f is not None:
|
|
||||||
f.close()
|
|
||||||
except termios.error:
|
|
||||||
pass
|
|
||||||
_translate_ch_to_exc(ch)
|
|
||||||
return ch.decode(get_best_encoding(sys.stdin), 'replace')
|
|
|
@ -1,38 +0,0 @@
|
||||||
import textwrap
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
|
|
||||||
class TextWrapper(textwrap.TextWrapper):
|
|
||||||
|
|
||||||
def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
|
|
||||||
space_left = max(width - cur_len, 1)
|
|
||||||
|
|
||||||
if self.break_long_words:
|
|
||||||
last = reversed_chunks[-1]
|
|
||||||
cut = last[:space_left]
|
|
||||||
res = last[space_left:]
|
|
||||||
cur_line.append(cut)
|
|
||||||
reversed_chunks[-1] = res
|
|
||||||
elif not cur_line:
|
|
||||||
cur_line.append(reversed_chunks.pop())
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def extra_indent(self, indent):
|
|
||||||
old_initial_indent = self.initial_indent
|
|
||||||
old_subsequent_indent = self.subsequent_indent
|
|
||||||
self.initial_indent += indent
|
|
||||||
self.subsequent_indent += indent
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self.initial_indent = old_initial_indent
|
|
||||||
self.subsequent_indent = old_subsequent_indent
|
|
||||||
|
|
||||||
def indent_only(self, text):
|
|
||||||
rv = []
|
|
||||||
for idx, line in enumerate(text.splitlines()):
|
|
||||||
indent = self.initial_indent
|
|
||||||
if idx > 0:
|
|
||||||
indent = self.subsequent_indent
|
|
||||||
rv.append(indent + line)
|
|
||||||
return '\n'.join(rv)
|
|
|
@ -1,118 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import codecs
|
|
||||||
|
|
||||||
from ._compat import PY2
|
|
||||||
|
|
||||||
|
|
||||||
# If someone wants to vendor click, we want to ensure the
|
|
||||||
# correct package is discovered. Ideally we could use a
|
|
||||||
# relative import here but unfortunately Python does not
|
|
||||||
# support that.
|
|
||||||
click = sys.modules[__name__.rsplit('.', 1)[0]]
|
|
||||||
|
|
||||||
|
|
||||||
def _find_unicode_literals_frame():
|
|
||||||
import __future__
|
|
||||||
frm = sys._getframe(1)
|
|
||||||
idx = 1
|
|
||||||
while frm is not None:
|
|
||||||
if frm.f_globals.get('__name__', '').startswith('click.'):
|
|
||||||
frm = frm.f_back
|
|
||||||
idx += 1
|
|
||||||
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
|
||||||
return idx
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def _check_for_unicode_literals():
|
|
||||||
if not __debug__:
|
|
||||||
return
|
|
||||||
if not PY2 or click.disable_unicode_literals_warning:
|
|
||||||
return
|
|
||||||
bad_frame = _find_unicode_literals_frame()
|
|
||||||
if bad_frame <= 0:
|
|
||||||
return
|
|
||||||
from warnings import warn
|
|
||||||
warn(Warning('Click detected the use of the unicode_literals '
|
|
||||||
'__future__ import. This is heavily discouraged '
|
|
||||||
'because it can introduce subtle bugs in your '
|
|
||||||
'code. You should instead use explicit u"" literals '
|
|
||||||
'for your unicode strings. For more information see '
|
|
||||||
'http://click.pocoo.org/python3/'),
|
|
||||||
stacklevel=bad_frame)
|
|
||||||
|
|
||||||
|
|
||||||
def _verify_python3_env():
|
|
||||||
"""Ensures that the environment is good for unicode on Python 3."""
|
|
||||||
if PY2:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
import locale
|
|
||||||
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
|
||||||
except Exception:
|
|
||||||
fs_enc = 'ascii'
|
|
||||||
if fs_enc != 'ascii':
|
|
||||||
return
|
|
||||||
|
|
||||||
extra = ''
|
|
||||||
if os.name == 'posix':
|
|
||||||
import subprocess
|
|
||||||
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE).communicate()[0]
|
|
||||||
good_locales = set()
|
|
||||||
has_c_utf8 = False
|
|
||||||
|
|
||||||
# Make sure we're operating on text here.
|
|
||||||
if isinstance(rv, bytes):
|
|
||||||
rv = rv.decode('ascii', 'replace')
|
|
||||||
|
|
||||||
for line in rv.splitlines():
|
|
||||||
locale = line.strip()
|
|
||||||
if locale.lower().endswith(('.utf-8', '.utf8')):
|
|
||||||
good_locales.add(locale)
|
|
||||||
if locale.lower() in ('c.utf8', 'c.utf-8'):
|
|
||||||
has_c_utf8 = True
|
|
||||||
|
|
||||||
extra += '\n\n'
|
|
||||||
if not good_locales:
|
|
||||||
extra += (
|
|
||||||
'Additional information: on this system no suitable UTF-8\n'
|
|
||||||
'locales were discovered. This most likely requires resolving\n'
|
|
||||||
'by reconfiguring the locale system.'
|
|
||||||
)
|
|
||||||
elif has_c_utf8:
|
|
||||||
extra += (
|
|
||||||
'This system supports the C.UTF-8 locale which is recommended.\n'
|
|
||||||
'You might be able to resolve your issue by exporting the\n'
|
|
||||||
'following environment variables:\n\n'
|
|
||||||
' export LC_ALL=C.UTF-8\n'
|
|
||||||
' export LANG=C.UTF-8'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
extra += (
|
|
||||||
'This system lists a couple of UTF-8 supporting locales that\n'
|
|
||||||
'you can pick from. The following suitable locales where\n'
|
|
||||||
'discovered: %s'
|
|
||||||
) % ', '.join(sorted(good_locales))
|
|
||||||
|
|
||||||
bad_locale = None
|
|
||||||
for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'):
|
|
||||||
if locale and locale.lower().endswith(('.utf-8', '.utf8')):
|
|
||||||
bad_locale = locale
|
|
||||||
if locale is not None:
|
|
||||||
break
|
|
||||||
if bad_locale is not None:
|
|
||||||
extra += (
|
|
||||||
'\n\nClick discovered that you exported a UTF-8 locale\n'
|
|
||||||
'but the locale system could not pick up from it because\n'
|
|
||||||
'it does not exist. The exported locale is "%s" but it\n'
|
|
||||||
'is not supported'
|
|
||||||
) % bad_locale
|
|
||||||
|
|
||||||
raise RuntimeError('Click will abort further execution because Python 3 '
|
|
||||||
'was configured to use ASCII as encoding for the '
|
|
||||||
'environment. Consult http://click.pocoo.org/python3/'
|
|
||||||
'for mitigation steps.' + extra)
|
|
|
@ -1,273 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# This module is based on the excellent work by Adam Bartoš who
|
|
||||||
# provided a lot of what went into the implementation here in
|
|
||||||
# the discussion to issue1602 in the Python bug tracker.
|
|
||||||
#
|
|
||||||
# There are some general differences in regards to how this works
|
|
||||||
# compared to the original patches as we do not need to patch
|
|
||||||
# the entire interpreter but just work in our little world of
|
|
||||||
# echo and prmopt.
|
|
||||||
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import zlib
|
|
||||||
import time
|
|
||||||
import ctypes
|
|
||||||
import msvcrt
|
|
||||||
from click._compat import _NonClosingTextIOWrapper, text_type, PY2
|
|
||||||
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
|
|
||||||
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
|
||||||
try:
|
|
||||||
from ctypes import pythonapi
|
|
||||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
|
||||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
|
||||||
except ImportError:
|
|
||||||
pythonapi = None
|
|
||||||
from ctypes.wintypes import LPWSTR, LPCWSTR
|
|
||||||
|
|
||||||
|
|
||||||
c_ssize_p = POINTER(c_ssize_t)
|
|
||||||
|
|
||||||
kernel32 = windll.kernel32
|
|
||||||
GetStdHandle = kernel32.GetStdHandle
|
|
||||||
ReadConsoleW = kernel32.ReadConsoleW
|
|
||||||
WriteConsoleW = kernel32.WriteConsoleW
|
|
||||||
GetLastError = kernel32.GetLastError
|
|
||||||
GetCommandLineW = WINFUNCTYPE(LPWSTR)(
|
|
||||||
('GetCommandLineW', windll.kernel32))
|
|
||||||
CommandLineToArgvW = WINFUNCTYPE(
|
|
||||||
POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
|
||||||
('CommandLineToArgvW', windll.shell32))
|
|
||||||
|
|
||||||
|
|
||||||
STDIN_HANDLE = GetStdHandle(-10)
|
|
||||||
STDOUT_HANDLE = GetStdHandle(-11)
|
|
||||||
STDERR_HANDLE = GetStdHandle(-12)
|
|
||||||
|
|
||||||
|
|
||||||
PyBUF_SIMPLE = 0
|
|
||||||
PyBUF_WRITABLE = 1
|
|
||||||
|
|
||||||
ERROR_SUCCESS = 0
|
|
||||||
ERROR_NOT_ENOUGH_MEMORY = 8
|
|
||||||
ERROR_OPERATION_ABORTED = 995
|
|
||||||
|
|
||||||
STDIN_FILENO = 0
|
|
||||||
STDOUT_FILENO = 1
|
|
||||||
STDERR_FILENO = 2
|
|
||||||
|
|
||||||
EOF = b'\x1a'
|
|
||||||
MAX_BYTES_WRITTEN = 32767
|
|
||||||
|
|
||||||
|
|
||||||
class Py_buffer(ctypes.Structure):
|
|
||||||
_fields_ = [
|
|
||||||
('buf', c_void_p),
|
|
||||||
('obj', py_object),
|
|
||||||
('len', c_ssize_t),
|
|
||||||
('itemsize', c_ssize_t),
|
|
||||||
('readonly', c_int),
|
|
||||||
('ndim', c_int),
|
|
||||||
('format', c_char_p),
|
|
||||||
('shape', c_ssize_p),
|
|
||||||
('strides', c_ssize_p),
|
|
||||||
('suboffsets', c_ssize_p),
|
|
||||||
('internal', c_void_p)
|
|
||||||
]
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
_fields_.insert(-1, ('smalltable', c_ssize_t * 2))
|
|
||||||
|
|
||||||
|
|
||||||
# On PyPy we cannot get buffers so our ability to operate here is
|
|
||||||
# serverly limited.
|
|
||||||
if pythonapi is None:
|
|
||||||
get_buffer = None
|
|
||||||
else:
|
|
||||||
def get_buffer(obj, writable=False):
|
|
||||||
buf = Py_buffer()
|
|
||||||
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
|
|
||||||
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
|
|
||||||
try:
|
|
||||||
buffer_type = c_char * buf.len
|
|
||||||
return buffer_type.from_address(buf.buf)
|
|
||||||
finally:
|
|
||||||
PyBuffer_Release(byref(buf))
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsConsoleRawIOBase(io.RawIOBase):
|
|
||||||
|
|
||||||
def __init__(self, handle):
|
|
||||||
self.handle = handle
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
io.RawIOBase.isatty(self)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
|
||||||
|
|
||||||
def readable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def readinto(self, b):
|
|
||||||
bytes_to_be_read = len(b)
|
|
||||||
if not bytes_to_be_read:
|
|
||||||
return 0
|
|
||||||
elif bytes_to_be_read % 2:
|
|
||||||
raise ValueError('cannot read odd number of bytes from '
|
|
||||||
'UTF-16-LE encoded console')
|
|
||||||
|
|
||||||
buffer = get_buffer(b, writable=True)
|
|
||||||
code_units_to_be_read = bytes_to_be_read // 2
|
|
||||||
code_units_read = c_ulong()
|
|
||||||
|
|
||||||
rv = ReadConsoleW(self.handle, buffer, code_units_to_be_read,
|
|
||||||
byref(code_units_read), None)
|
|
||||||
if GetLastError() == ERROR_OPERATION_ABORTED:
|
|
||||||
# wait for KeyboardInterrupt
|
|
||||||
time.sleep(0.1)
|
|
||||||
if not rv:
|
|
||||||
raise OSError('Windows error: %s' % GetLastError())
|
|
||||||
|
|
||||||
if buffer[0] == EOF:
|
|
||||||
return 0
|
|
||||||
return 2 * code_units_read.value
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
|
||||||
|
|
||||||
def writable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_error_message(errno):
|
|
||||||
if errno == ERROR_SUCCESS:
|
|
||||||
return 'ERROR_SUCCESS'
|
|
||||||
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
|
||||||
return 'ERROR_NOT_ENOUGH_MEMORY'
|
|
||||||
return 'Windows error %s' % errno
|
|
||||||
|
|
||||||
def write(self, b):
|
|
||||||
bytes_to_be_written = len(b)
|
|
||||||
buf = get_buffer(b)
|
|
||||||
code_units_to_be_written = min(bytes_to_be_written,
|
|
||||||
MAX_BYTES_WRITTEN) // 2
|
|
||||||
code_units_written = c_ulong()
|
|
||||||
|
|
||||||
WriteConsoleW(self.handle, buf, code_units_to_be_written,
|
|
||||||
byref(code_units_written), None)
|
|
||||||
bytes_written = 2 * code_units_written.value
|
|
||||||
|
|
||||||
if bytes_written == 0 and bytes_to_be_written > 0:
|
|
||||||
raise OSError(self._get_error_message(GetLastError()))
|
|
||||||
return bytes_written
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleStream(object):
|
|
||||||
|
|
||||||
def __init__(self, text_stream, byte_stream):
|
|
||||||
self._text_stream = text_stream
|
|
||||||
self.buffer = byte_stream
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self.buffer.name
|
|
||||||
|
|
||||||
def write(self, x):
|
|
||||||
if isinstance(x, text_type):
|
|
||||||
return self._text_stream.write(x)
|
|
||||||
try:
|
|
||||||
self.flush()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return self.buffer.write(x)
|
|
||||||
|
|
||||||
def writelines(self, lines):
|
|
||||||
for line in lines:
|
|
||||||
self.write(line)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._text_stream, name)
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
return self.buffer.isatty()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<ConsoleStream name=%r encoding=%r>' % (
|
|
||||||
self.name,
|
|
||||||
self.encoding,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stdin(buffer_stream):
|
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
|
||||||
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
|
||||||
'utf-16-le', 'strict', line_buffering=True)
|
|
||||||
return ConsoleStream(text_stream, buffer_stream)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stdout(buffer_stream):
|
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
|
||||||
_WindowsConsoleWriter(STDOUT_HANDLE),
|
|
||||||
'utf-16-le', 'strict', line_buffering=True)
|
|
||||||
return ConsoleStream(text_stream, buffer_stream)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stderr(buffer_stream):
|
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
|
||||||
_WindowsConsoleWriter(STDERR_HANDLE),
|
|
||||||
'utf-16-le', 'strict', line_buffering=True)
|
|
||||||
return ConsoleStream(text_stream, buffer_stream)
|
|
||||||
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
def _hash_py_argv():
|
|
||||||
return zlib.crc32('\x00'.join(sys.argv[1:]))
|
|
||||||
|
|
||||||
_initial_argv_hash = _hash_py_argv()
|
|
||||||
|
|
||||||
def _get_windows_argv():
|
|
||||||
argc = c_int(0)
|
|
||||||
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
|
|
||||||
argv = [argv_unicode[i] for i in range(0, argc.value)]
|
|
||||||
|
|
||||||
if not hasattr(sys, 'frozen'):
|
|
||||||
argv = argv[1:]
|
|
||||||
while len(argv) > 0:
|
|
||||||
arg = argv[0]
|
|
||||||
if not arg.startswith('-') or arg == '-':
|
|
||||||
break
|
|
||||||
argv = argv[1:]
|
|
||||||
if arg.startswith(('-c', '-m')):
|
|
||||||
break
|
|
||||||
|
|
||||||
return argv[1:]
|
|
||||||
|
|
||||||
|
|
||||||
_stream_factories = {
|
|
||||||
0: _get_text_stdin,
|
|
||||||
1: _get_text_stdout,
|
|
||||||
2: _get_text_stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_windows_console_stream(f, encoding, errors):
|
|
||||||
if get_buffer is not None and \
|
|
||||||
encoding in ('utf-16-le', None) \
|
|
||||||
and errors in ('strict', None) and \
|
|
||||||
hasattr(f, 'isatty') and f.isatty():
|
|
||||||
func = _stream_factories.get(f.fileno())
|
|
||||||
if func is not None:
|
|
||||||
if not PY2:
|
|
||||||
f = getattr(f, 'buffer')
|
|
||||||
if f is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
# If we are on Python 2 we need to set the stream that we
|
|
||||||
# deal with to binary mode as otherwise the exercise if a
|
|
||||||
# bit moot. The same problems apply as for
|
|
||||||
# get_binary_stdin and friends from _compat.
|
|
||||||
msvcrt.setmode(f.fileno(), os.O_BINARY)
|
|
||||||
return func(f)
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,304 +0,0 @@
|
||||||
import sys
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
from ._compat import iteritems
|
|
||||||
from ._unicodefun import _check_for_unicode_literals
|
|
||||||
from .utils import echo
|
|
||||||
from .globals import get_current_context
|
|
||||||
|
|
||||||
|
|
||||||
def pass_context(f):
|
|
||||||
"""Marks a callback as wanting to receive the current context
|
|
||||||
object as first argument.
|
|
||||||
"""
|
|
||||||
def new_func(*args, **kwargs):
|
|
||||||
return f(get_current_context(), *args, **kwargs)
|
|
||||||
return update_wrapper(new_func, f)
|
|
||||||
|
|
||||||
|
|
||||||
def pass_obj(f):
|
|
||||||
"""Similar to :func:`pass_context`, but only pass the object on the
|
|
||||||
context onwards (:attr:`Context.obj`). This is useful if that object
|
|
||||||
represents the state of a nested system.
|
|
||||||
"""
|
|
||||||
def new_func(*args, **kwargs):
|
|
||||||
return f(get_current_context().obj, *args, **kwargs)
|
|
||||||
return update_wrapper(new_func, f)
|
|
||||||
|
|
||||||
|
|
||||||
def make_pass_decorator(object_type, ensure=False):
|
|
||||||
"""Given an object type this creates a decorator that will work
|
|
||||||
similar to :func:`pass_obj` but instead of passing the object of the
|
|
||||||
current context, it will find the innermost context of type
|
|
||||||
:func:`object_type`.
|
|
||||||
|
|
||||||
This generates a decorator that works roughly like this::
|
|
||||||
|
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
def decorator(f):
|
|
||||||
@pass_context
|
|
||||||
def new_func(ctx, *args, **kwargs):
|
|
||||||
obj = ctx.find_object(object_type)
|
|
||||||
return ctx.invoke(f, obj, *args, **kwargs)
|
|
||||||
return update_wrapper(new_func, f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
:param object_type: the type of the object to pass.
|
|
||||||
:param ensure: if set to `True`, a new object will be created and
|
|
||||||
remembered on the context if it's not there yet.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
def new_func(*args, **kwargs):
|
|
||||||
ctx = get_current_context()
|
|
||||||
if ensure:
|
|
||||||
obj = ctx.ensure_object(object_type)
|
|
||||||
else:
|
|
||||||
obj = ctx.find_object(object_type)
|
|
||||||
if obj is None:
|
|
||||||
raise RuntimeError('Managed to invoke callback without a '
|
|
||||||
'context object of type %r existing'
|
|
||||||
% object_type.__name__)
|
|
||||||
return ctx.invoke(f, obj, *args[1:], **kwargs)
|
|
||||||
return update_wrapper(new_func, f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def _make_command(f, name, attrs, cls):
|
|
||||||
if isinstance(f, Command):
|
|
||||||
raise TypeError('Attempted to convert a callback into a '
|
|
||||||
'command twice.')
|
|
||||||
try:
|
|
||||||
params = f.__click_params__
|
|
||||||
params.reverse()
|
|
||||||
del f.__click_params__
|
|
||||||
except AttributeError:
|
|
||||||
params = []
|
|
||||||
help = attrs.get('help')
|
|
||||||
if help is None:
|
|
||||||
help = inspect.getdoc(f)
|
|
||||||
if isinstance(help, bytes):
|
|
||||||
help = help.decode('utf-8')
|
|
||||||
else:
|
|
||||||
help = inspect.cleandoc(help)
|
|
||||||
attrs['help'] = help
|
|
||||||
_check_for_unicode_literals()
|
|
||||||
return cls(name=name or f.__name__.lower(),
|
|
||||||
callback=f, params=params, **attrs)
|
|
||||||
|
|
||||||
|
|
||||||
def command(name=None, cls=None, **attrs):
|
|
||||||
"""Creates a new :class:`Command` and uses the decorated function as
|
|
||||||
callback. This will also automatically attach all decorated
|
|
||||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
|
||||||
|
|
||||||
The name of the command defaults to the name of the function. If you
|
|
||||||
want to change that, you can pass the intended name as the first
|
|
||||||
argument.
|
|
||||||
|
|
||||||
All keyword arguments are forwarded to the underlying command class.
|
|
||||||
|
|
||||||
Once decorated the function turns into a :class:`Command` instance
|
|
||||||
that can be invoked as a command line utility or be attached to a
|
|
||||||
command :class:`Group`.
|
|
||||||
|
|
||||||
:param name: the name of the command. This defaults to the function
|
|
||||||
name.
|
|
||||||
:param cls: the command class to instantiate. This defaults to
|
|
||||||
:class:`Command`.
|
|
||||||
"""
|
|
||||||
if cls is None:
|
|
||||||
cls = Command
|
|
||||||
def decorator(f):
|
|
||||||
cmd = _make_command(f, name, attrs, cls)
|
|
||||||
cmd.__doc__ = f.__doc__
|
|
||||||
return cmd
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def group(name=None, **attrs):
|
|
||||||
"""Creates a new :class:`Group` with a function as callback. This
|
|
||||||
works otherwise the same as :func:`command` just that the `cls`
|
|
||||||
parameter is set to :class:`Group`.
|
|
||||||
"""
|
|
||||||
attrs.setdefault('cls', Group)
|
|
||||||
return command(name, **attrs)
|
|
||||||
|
|
||||||
|
|
||||||
def _param_memo(f, param):
|
|
||||||
if isinstance(f, Command):
|
|
||||||
f.params.append(param)
|
|
||||||
else:
|
|
||||||
if not hasattr(f, '__click_params__'):
|
|
||||||
f.__click_params__ = []
|
|
||||||
f.__click_params__.append(param)
|
|
||||||
|
|
||||||
|
|
||||||
def argument(*param_decls, **attrs):
|
|
||||||
"""Attaches an argument to the command. All positional arguments are
|
|
||||||
passed as parameter declarations to :class:`Argument`; all keyword
|
|
||||||
arguments are forwarded unchanged (except ``cls``).
|
|
||||||
This is equivalent to creating an :class:`Argument` instance manually
|
|
||||||
and attaching it to the :attr:`Command.params` list.
|
|
||||||
|
|
||||||
:param cls: the argument class to instantiate. This defaults to
|
|
||||||
:class:`Argument`.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
ArgumentClass = attrs.pop('cls', Argument)
|
|
||||||
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def option(*param_decls, **attrs):
|
|
||||||
"""Attaches an option to the command. All positional arguments are
|
|
||||||
passed as parameter declarations to :class:`Option`; all keyword
|
|
||||||
arguments are forwarded unchanged (except ``cls``).
|
|
||||||
This is equivalent to creating an :class:`Option` instance manually
|
|
||||||
and attaching it to the :attr:`Command.params` list.
|
|
||||||
|
|
||||||
:param cls: the option class to instantiate. This defaults to
|
|
||||||
:class:`Option`.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
if 'help' in attrs:
|
|
||||||
attrs['help'] = inspect.cleandoc(attrs['help'])
|
|
||||||
OptionClass = attrs.pop('cls', Option)
|
|
||||||
_param_memo(f, OptionClass(param_decls, **attrs))
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def confirmation_option(*param_decls, **attrs):
|
|
||||||
"""Shortcut for confirmation prompts that can be ignored by passing
|
|
||||||
``--yes`` as parameter.
|
|
||||||
|
|
||||||
This is equivalent to decorating a function with :func:`option` with
|
|
||||||
the following parameters::
|
|
||||||
|
|
||||||
def callback(ctx, param, value):
|
|
||||||
if not value:
|
|
||||||
ctx.abort()
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option('--yes', is_flag=True, callback=callback,
|
|
||||||
expose_value=False, prompt='Do you want to continue?')
|
|
||||||
def dropdb():
|
|
||||||
pass
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
def callback(ctx, param, value):
|
|
||||||
if not value:
|
|
||||||
ctx.abort()
|
|
||||||
attrs.setdefault('is_flag', True)
|
|
||||||
attrs.setdefault('callback', callback)
|
|
||||||
attrs.setdefault('expose_value', False)
|
|
||||||
attrs.setdefault('prompt', 'Do you want to continue?')
|
|
||||||
attrs.setdefault('help', 'Confirm the action without prompting.')
|
|
||||||
return option(*(param_decls or ('--yes',)), **attrs)(f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def password_option(*param_decls, **attrs):
|
|
||||||
"""Shortcut for password prompts.
|
|
||||||
|
|
||||||
This is equivalent to decorating a function with :func:`option` with
|
|
||||||
the following parameters::
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option('--password', prompt=True, confirmation_prompt=True,
|
|
||||||
hide_input=True)
|
|
||||||
def changeadmin(password):
|
|
||||||
pass
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
attrs.setdefault('prompt', True)
|
|
||||||
attrs.setdefault('confirmation_prompt', True)
|
|
||||||
attrs.setdefault('hide_input', True)
|
|
||||||
return option(*(param_decls or ('--password',)), **attrs)(f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def version_option(version=None, *param_decls, **attrs):
|
|
||||||
"""Adds a ``--version`` option which immediately ends the program
|
|
||||||
printing out the version number. This is implemented as an eager
|
|
||||||
option that prints the version and exits the program in the callback.
|
|
||||||
|
|
||||||
:param version: the version number to show. If not provided Click
|
|
||||||
attempts an auto discovery via setuptools.
|
|
||||||
:param prog_name: the name of the program (defaults to autodetection)
|
|
||||||
:param message: custom message to show instead of the default
|
|
||||||
(``'%(prog)s, version %(version)s'``)
|
|
||||||
:param others: everything else is forwarded to :func:`option`.
|
|
||||||
"""
|
|
||||||
if version is None:
|
|
||||||
module = sys._getframe(1).f_globals.get('__name__')
|
|
||||||
def decorator(f):
|
|
||||||
prog_name = attrs.pop('prog_name', None)
|
|
||||||
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
|
||||||
|
|
||||||
def callback(ctx, param, value):
|
|
||||||
if not value or ctx.resilient_parsing:
|
|
||||||
return
|
|
||||||
prog = prog_name
|
|
||||||
if prog is None:
|
|
||||||
prog = ctx.find_root().info_name
|
|
||||||
ver = version
|
|
||||||
if ver is None:
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
for dist in pkg_resources.working_set:
|
|
||||||
scripts = dist.get_entry_map().get('console_scripts') or {}
|
|
||||||
for script_name, entry_point in iteritems(scripts):
|
|
||||||
if entry_point.module_name == module:
|
|
||||||
ver = dist.version
|
|
||||||
break
|
|
||||||
if ver is None:
|
|
||||||
raise RuntimeError('Could not determine version')
|
|
||||||
echo(message % {
|
|
||||||
'prog': prog,
|
|
||||||
'version': ver,
|
|
||||||
}, color=ctx.color)
|
|
||||||
ctx.exit()
|
|
||||||
|
|
||||||
attrs.setdefault('is_flag', True)
|
|
||||||
attrs.setdefault('expose_value', False)
|
|
||||||
attrs.setdefault('is_eager', True)
|
|
||||||
attrs.setdefault('help', 'Show the version and exit.')
|
|
||||||
attrs['callback'] = callback
|
|
||||||
return option(*(param_decls or ('--version',)), **attrs)(f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def help_option(*param_decls, **attrs):
|
|
||||||
"""Adds a ``--help`` option which immediately ends the program
|
|
||||||
printing out the help page. This is usually unnecessary to add as
|
|
||||||
this is added by default to all commands unless suppressed.
|
|
||||||
|
|
||||||
Like :func:`version_option`, this is implemented as eager option that
|
|
||||||
prints in the callback and exits.
|
|
||||||
|
|
||||||
All arguments are forwarded to :func:`option`.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
def callback(ctx, param, value):
|
|
||||||
if value and not ctx.resilient_parsing:
|
|
||||||
echo(ctx.get_help(), color=ctx.color)
|
|
||||||
ctx.exit()
|
|
||||||
attrs.setdefault('is_flag', True)
|
|
||||||
attrs.setdefault('expose_value', False)
|
|
||||||
attrs.setdefault('help', 'Show this message and exit.')
|
|
||||||
attrs.setdefault('is_eager', True)
|
|
||||||
attrs['callback'] = callback
|
|
||||||
return option(*(param_decls or ('--help',)), **attrs)(f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
# Circular dependencies between core and decorators
|
|
||||||
from .core import Command, Group, Argument, Option
|
|
|
@ -1,201 +0,0 @@
|
||||||
from ._compat import PY2, filename_to_ui, get_text_stderr
|
|
||||||
from .utils import echo
|
|
||||||
|
|
||||||
|
|
||||||
class ClickException(Exception):
|
|
||||||
"""An exception that Click can handle and show to the user."""
|
|
||||||
|
|
||||||
#: The exit code for this exception
|
|
||||||
exit_code = 1
|
|
||||||
|
|
||||||
def __init__(self, message):
|
|
||||||
if PY2:
|
|
||||||
if message is not None:
|
|
||||||
message = message.encode('utf-8')
|
|
||||||
Exception.__init__(self, message)
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def format_message(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def show(self, file=None):
|
|
||||||
if file is None:
|
|
||||||
file = get_text_stderr()
|
|
||||||
echo('Error: %s' % self.format_message(), file=file)
|
|
||||||
|
|
||||||
|
|
||||||
class UsageError(ClickException):
|
|
||||||
"""An internal exception that signals a usage error. This typically
|
|
||||||
aborts any further handling.
|
|
||||||
|
|
||||||
:param message: the error message to display.
|
|
||||||
:param ctx: optionally the context that caused this error. Click will
|
|
||||||
fill in the context automatically in some situations.
|
|
||||||
"""
|
|
||||||
exit_code = 2
|
|
||||||
|
|
||||||
def __init__(self, message, ctx=None):
|
|
||||||
ClickException.__init__(self, message)
|
|
||||||
self.ctx = ctx
|
|
||||||
|
|
||||||
def show(self, file=None):
|
|
||||||
if file is None:
|
|
||||||
file = get_text_stderr()
|
|
||||||
color = None
|
|
||||||
if self.ctx is not None:
|
|
||||||
color = self.ctx.color
|
|
||||||
echo(self.ctx.get_usage() + '\n', file=file, color=color)
|
|
||||||
echo('Error: %s' % self.format_message(), file=file, color=color)
|
|
||||||
|
|
||||||
|
|
||||||
class BadParameter(UsageError):
|
|
||||||
"""An exception that formats out a standardized error message for a
|
|
||||||
bad parameter. This is useful when thrown from a callback or type as
|
|
||||||
Click will attach contextual information to it (for instance, which
|
|
||||||
parameter it is).
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param param: the parameter object that caused this error. This can
|
|
||||||
be left out, and Click will attach this info itself
|
|
||||||
if possible.
|
|
||||||
:param param_hint: a string that shows up as parameter name. This
|
|
||||||
can be used as alternative to `param` in cases
|
|
||||||
where custom validation should happen. If it is
|
|
||||||
a string it's used as such, if it's a list then
|
|
||||||
each item is quoted and separated.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, ctx=None, param=None,
|
|
||||||
param_hint=None):
|
|
||||||
UsageError.__init__(self, message, ctx)
|
|
||||||
self.param = param
|
|
||||||
self.param_hint = param_hint
|
|
||||||
|
|
||||||
def format_message(self):
|
|
||||||
if self.param_hint is not None:
|
|
||||||
param_hint = self.param_hint
|
|
||||||
elif self.param is not None:
|
|
||||||
param_hint = self.param.opts or [self.param.human_readable_name]
|
|
||||||
else:
|
|
||||||
return 'Invalid value: %s' % self.message
|
|
||||||
if isinstance(param_hint, (tuple, list)):
|
|
||||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
|
||||||
return 'Invalid value for %s: %s' % (param_hint, self.message)
|
|
||||||
|
|
||||||
|
|
||||||
class MissingParameter(BadParameter):
|
|
||||||
"""Raised if click required an option or argument but it was not
|
|
||||||
provided when invoking the script.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
|
|
||||||
:param param_type: a string that indicates the type of the parameter.
|
|
||||||
The default is to inherit the parameter type from
|
|
||||||
the given `param`. Valid values are ``'parameter'``,
|
|
||||||
``'option'`` or ``'argument'``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message=None, ctx=None, param=None,
|
|
||||||
param_hint=None, param_type=None):
|
|
||||||
BadParameter.__init__(self, message, ctx, param, param_hint)
|
|
||||||
self.param_type = param_type
|
|
||||||
|
|
||||||
def format_message(self):
|
|
||||||
if self.param_hint is not None:
|
|
||||||
param_hint = self.param_hint
|
|
||||||
elif self.param is not None:
|
|
||||||
param_hint = self.param.opts or [self.param.human_readable_name]
|
|
||||||
else:
|
|
||||||
param_hint = None
|
|
||||||
if isinstance(param_hint, (tuple, list)):
|
|
||||||
param_hint = ' / '.join('"%s"' % x for x in param_hint)
|
|
||||||
|
|
||||||
param_type = self.param_type
|
|
||||||
if param_type is None and self.param is not None:
|
|
||||||
param_type = self.param.param_type_name
|
|
||||||
|
|
||||||
msg = self.message
|
|
||||||
if self.param is not None:
|
|
||||||
msg_extra = self.param.type.get_missing_message(self.param)
|
|
||||||
if msg_extra:
|
|
||||||
if msg:
|
|
||||||
msg += '. ' + msg_extra
|
|
||||||
else:
|
|
||||||
msg = msg_extra
|
|
||||||
|
|
||||||
return 'Missing %s%s%s%s' % (
|
|
||||||
param_type,
|
|
||||||
param_hint and ' %s' % param_hint or '',
|
|
||||||
msg and '. ' or '.',
|
|
||||||
msg or '',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NoSuchOption(UsageError):
|
|
||||||
"""Raised if click attempted to handle an option that does not
|
|
||||||
exist.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, option_name, message=None, possibilities=None,
|
|
||||||
ctx=None):
|
|
||||||
if message is None:
|
|
||||||
message = 'no such option: %s' % option_name
|
|
||||||
UsageError.__init__(self, message, ctx)
|
|
||||||
self.option_name = option_name
|
|
||||||
self.possibilities = possibilities
|
|
||||||
|
|
||||||
def format_message(self):
|
|
||||||
bits = [self.message]
|
|
||||||
if self.possibilities:
|
|
||||||
if len(self.possibilities) == 1:
|
|
||||||
bits.append('Did you mean %s?' % self.possibilities[0])
|
|
||||||
else:
|
|
||||||
possibilities = sorted(self.possibilities)
|
|
||||||
bits.append('(Possible options: %s)' % ', '.join(possibilities))
|
|
||||||
return ' '.join(bits)
|
|
||||||
|
|
||||||
|
|
||||||
class BadOptionUsage(UsageError):
|
|
||||||
"""Raised if an option is generally supplied but the use of the option
|
|
||||||
was incorrect. This is for instance raised if the number of arguments
|
|
||||||
for an option is not correct.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, ctx=None):
|
|
||||||
UsageError.__init__(self, message, ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class BadArgumentUsage(UsageError):
|
|
||||||
"""Raised if an argument is generally supplied but the use of the argument
|
|
||||||
was incorrect. This is for instance raised if the number of values
|
|
||||||
for an argument is not correct.
|
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, message, ctx=None):
|
|
||||||
UsageError.__init__(self, message, ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class FileError(ClickException):
|
|
||||||
"""Raised if a file cannot be opened."""
|
|
||||||
|
|
||||||
def __init__(self, filename, hint=None):
|
|
||||||
ui_filename = filename_to_ui(filename)
|
|
||||||
if hint is None:
|
|
||||||
hint = 'unknown error'
|
|
||||||
ClickException.__init__(self, hint)
|
|
||||||
self.ui_filename = ui_filename
|
|
||||||
self.filename = filename
|
|
||||||
|
|
||||||
def format_message(self):
|
|
||||||
return 'Could not open file %s: %s' % (self.ui_filename, self.message)
|
|
||||||
|
|
||||||
|
|
||||||
class Abort(RuntimeError):
|
|
||||||
"""An internal signalling exception that signals Click to abort."""
|
|
|
@ -1,256 +0,0 @@
|
||||||
from contextlib import contextmanager
|
|
||||||
from .termui import get_terminal_size
|
|
||||||
from .parser import split_opt
|
|
||||||
from ._compat import term_len
|
|
||||||
|
|
||||||
|
|
||||||
# Can force a width. This is used by the test system
|
|
||||||
FORCED_WIDTH = None
|
|
||||||
|
|
||||||
|
|
||||||
def measure_table(rows):
|
|
||||||
widths = {}
|
|
||||||
for row in rows:
|
|
||||||
for idx, col in enumerate(row):
|
|
||||||
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
|
||||||
return tuple(y for x, y in sorted(widths.items()))
|
|
||||||
|
|
||||||
|
|
||||||
def iter_rows(rows, col_count):
|
|
||||||
for row in rows:
|
|
||||||
row = tuple(row)
|
|
||||||
yield row + ('',) * (col_count - len(row))
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
|
||||||
preserve_paragraphs=False):
|
|
||||||
"""A helper function that intelligently wraps text. By default, it
|
|
||||||
assumes that it operates on a single paragraph of text but if the
|
|
||||||
`preserve_paragraphs` parameter is provided it will intelligently
|
|
||||||
handle paragraphs (defined by two empty lines).
|
|
||||||
|
|
||||||
If paragraphs are handled, a paragraph can be prefixed with an empty
|
|
||||||
line containing the ``\\b`` character (``\\x08``) to indicate that
|
|
||||||
no rewrapping should happen in that block.
|
|
||||||
|
|
||||||
:param text: the text that should be rewrapped.
|
|
||||||
:param width: the maximum width for the text.
|
|
||||||
:param initial_indent: the initial indent that should be placed on the
|
|
||||||
first line as a string.
|
|
||||||
:param subsequent_indent: the indent string that should be placed on
|
|
||||||
each consecutive line.
|
|
||||||
:param preserve_paragraphs: if this flag is set then the wrapping will
|
|
||||||
intelligently handle paragraphs.
|
|
||||||
"""
|
|
||||||
from ._textwrap import TextWrapper
|
|
||||||
text = text.expandtabs()
|
|
||||||
wrapper = TextWrapper(width, initial_indent=initial_indent,
|
|
||||||
subsequent_indent=subsequent_indent,
|
|
||||||
replace_whitespace=False)
|
|
||||||
if not preserve_paragraphs:
|
|
||||||
return wrapper.fill(text)
|
|
||||||
|
|
||||||
p = []
|
|
||||||
buf = []
|
|
||||||
indent = None
|
|
||||||
|
|
||||||
def _flush_par():
|
|
||||||
if not buf:
|
|
||||||
return
|
|
||||||
if buf[0].strip() == '\b':
|
|
||||||
p.append((indent or 0, True, '\n'.join(buf[1:])))
|
|
||||||
else:
|
|
||||||
p.append((indent or 0, False, ' '.join(buf)))
|
|
||||||
del buf[:]
|
|
||||||
|
|
||||||
for line in text.splitlines():
|
|
||||||
if not line:
|
|
||||||
_flush_par()
|
|
||||||
indent = None
|
|
||||||
else:
|
|
||||||
if indent is None:
|
|
||||||
orig_len = term_len(line)
|
|
||||||
line = line.lstrip()
|
|
||||||
indent = orig_len - term_len(line)
|
|
||||||
buf.append(line)
|
|
||||||
_flush_par()
|
|
||||||
|
|
||||||
rv = []
|
|
||||||
for indent, raw, text in p:
|
|
||||||
with wrapper.extra_indent(' ' * indent):
|
|
||||||
if raw:
|
|
||||||
rv.append(wrapper.indent_only(text))
|
|
||||||
else:
|
|
||||||
rv.append(wrapper.fill(text))
|
|
||||||
|
|
||||||
return '\n\n'.join(rv)
|
|
||||||
|
|
||||||
|
|
||||||
class HelpFormatter(object):
|
|
||||||
"""This class helps with formatting text-based help pages. It's
|
|
||||||
usually just needed for very special internal cases, but it's also
|
|
||||||
exposed so that developers can write their own fancy outputs.
|
|
||||||
|
|
||||||
At present, it always writes into memory.
|
|
||||||
|
|
||||||
:param indent_increment: the additional increment for each level.
|
|
||||||
:param width: the width for the text. This defaults to the terminal
|
|
||||||
width clamped to a maximum of 78.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, indent_increment=2, width=None, max_width=None):
|
|
||||||
self.indent_increment = indent_increment
|
|
||||||
if max_width is None:
|
|
||||||
max_width = 80
|
|
||||||
if width is None:
|
|
||||||
width = FORCED_WIDTH
|
|
||||||
if width is None:
|
|
||||||
width = max(min(get_terminal_size()[0], max_width) - 2, 50)
|
|
||||||
self.width = width
|
|
||||||
self.current_indent = 0
|
|
||||||
self.buffer = []
|
|
||||||
|
|
||||||
def write(self, string):
|
|
||||||
"""Writes a unicode string into the internal buffer."""
|
|
||||||
self.buffer.append(string)
|
|
||||||
|
|
||||||
def indent(self):
|
|
||||||
"""Increases the indentation."""
|
|
||||||
self.current_indent += self.indent_increment
|
|
||||||
|
|
||||||
def dedent(self):
|
|
||||||
"""Decreases the indentation."""
|
|
||||||
self.current_indent -= self.indent_increment
|
|
||||||
|
|
||||||
def write_usage(self, prog, args='', prefix='Usage: '):
|
|
||||||
"""Writes a usage line into the buffer.
|
|
||||||
|
|
||||||
:param prog: the program name.
|
|
||||||
:param args: whitespace separated list of arguments.
|
|
||||||
:param prefix: the prefix for the first line.
|
|
||||||
"""
|
|
||||||
usage_prefix = '%*s%s ' % (self.current_indent, prefix, prog)
|
|
||||||
text_width = self.width - self.current_indent
|
|
||||||
|
|
||||||
if text_width >= (term_len(usage_prefix) + 20):
|
|
||||||
# The arguments will fit to the right of the prefix.
|
|
||||||
indent = ' ' * term_len(usage_prefix)
|
|
||||||
self.write(wrap_text(args, text_width,
|
|
||||||
initial_indent=usage_prefix,
|
|
||||||
subsequent_indent=indent))
|
|
||||||
else:
|
|
||||||
# The prefix is too long, put the arguments on the next line.
|
|
||||||
self.write(usage_prefix)
|
|
||||||
self.write('\n')
|
|
||||||
indent = ' ' * (max(self.current_indent, term_len(prefix)) + 4)
|
|
||||||
self.write(wrap_text(args, text_width,
|
|
||||||
initial_indent=indent,
|
|
||||||
subsequent_indent=indent))
|
|
||||||
|
|
||||||
self.write('\n')
|
|
||||||
|
|
||||||
def write_heading(self, heading):
|
|
||||||
"""Writes a heading into the buffer."""
|
|
||||||
self.write('%*s%s:\n' % (self.current_indent, '', heading))
|
|
||||||
|
|
||||||
def write_paragraph(self):
|
|
||||||
"""Writes a paragraph into the buffer."""
|
|
||||||
if self.buffer:
|
|
||||||
self.write('\n')
|
|
||||||
|
|
||||||
def write_text(self, text):
|
|
||||||
"""Writes re-indented text into the buffer. This rewraps and
|
|
||||||
preserves paragraphs.
|
|
||||||
"""
|
|
||||||
text_width = max(self.width - self.current_indent, 11)
|
|
||||||
indent = ' ' * self.current_indent
|
|
||||||
self.write(wrap_text(text, text_width,
|
|
||||||
initial_indent=indent,
|
|
||||||
subsequent_indent=indent,
|
|
||||||
preserve_paragraphs=True))
|
|
||||||
self.write('\n')
|
|
||||||
|
|
||||||
def write_dl(self, rows, col_max=30, col_spacing=2):
|
|
||||||
"""Writes a definition list into the buffer. This is how options
|
|
||||||
and commands are usually formatted.
|
|
||||||
|
|
||||||
:param rows: a list of two item tuples for the terms and values.
|
|
||||||
:param col_max: the maximum width of the first column.
|
|
||||||
:param col_spacing: the number of spaces between the first and
|
|
||||||
second column.
|
|
||||||
"""
|
|
||||||
rows = list(rows)
|
|
||||||
widths = measure_table(rows)
|
|
||||||
if len(widths) != 2:
|
|
||||||
raise TypeError('Expected two columns for definition list')
|
|
||||||
|
|
||||||
first_col = min(widths[0], col_max) + col_spacing
|
|
||||||
|
|
||||||
for first, second in iter_rows(rows, len(widths)):
|
|
||||||
self.write('%*s%s' % (self.current_indent, '', first))
|
|
||||||
if not second:
|
|
||||||
self.write('\n')
|
|
||||||
continue
|
|
||||||
if term_len(first) <= first_col - col_spacing:
|
|
||||||
self.write(' ' * (first_col - term_len(first)))
|
|
||||||
else:
|
|
||||||
self.write('\n')
|
|
||||||
self.write(' ' * (first_col + self.current_indent))
|
|
||||||
|
|
||||||
text_width = max(self.width - first_col - 2, 10)
|
|
||||||
lines = iter(wrap_text(second, text_width).splitlines())
|
|
||||||
if lines:
|
|
||||||
self.write(next(lines) + '\n')
|
|
||||||
for line in lines:
|
|
||||||
self.write('%*s%s\n' % (
|
|
||||||
first_col + self.current_indent, '', line))
|
|
||||||
else:
|
|
||||||
self.write('\n')
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def section(self, name):
|
|
||||||
"""Helpful context manager that writes a paragraph, a heading,
|
|
||||||
and the indents.
|
|
||||||
|
|
||||||
:param name: the section name that is written as heading.
|
|
||||||
"""
|
|
||||||
self.write_paragraph()
|
|
||||||
self.write_heading(name)
|
|
||||||
self.indent()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self.dedent()
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def indentation(self):
|
|
||||||
"""A context manager that increases the indentation."""
|
|
||||||
self.indent()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self.dedent()
|
|
||||||
|
|
||||||
def getvalue(self):
|
|
||||||
"""Returns the buffer contents."""
|
|
||||||
return ''.join(self.buffer)
|
|
||||||
|
|
||||||
|
|
||||||
def join_options(options):
|
|
||||||
"""Given a list of option strings this joins them in the most appropriate
|
|
||||||
way and returns them in the form ``(formatted_string,
|
|
||||||
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
|
||||||
indicates if any of the option prefixes was a slash.
|
|
||||||
"""
|
|
||||||
rv = []
|
|
||||||
any_prefix_is_slash = False
|
|
||||||
for opt in options:
|
|
||||||
prefix = split_opt(opt)[0]
|
|
||||||
if prefix == '/':
|
|
||||||
any_prefix_is_slash = True
|
|
||||||
rv.append((len(prefix), opt))
|
|
||||||
|
|
||||||
rv.sort(key=lambda x: x[0])
|
|
||||||
|
|
||||||
rv = ', '.join(x[1] for x in rv)
|
|
||||||
return rv, any_prefix_is_slash
|
|
|
@ -1,48 +0,0 @@
|
||||||
from threading import local
|
|
||||||
|
|
||||||
|
|
||||||
_local = local()
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_context(silent=False):
|
|
||||||
"""Returns the current click context. This can be used as a way to
|
|
||||||
access the current context object from anywhere. This is a more implicit
|
|
||||||
alternative to the :func:`pass_context` decorator. This function is
|
|
||||||
primarily useful for helpers such as :func:`echo` which might be
|
|
||||||
interested in changing it's behavior based on the current context.
|
|
||||||
|
|
||||||
To push the current context, :meth:`Context.scope` can be used.
|
|
||||||
|
|
||||||
.. versionadded:: 5.0
|
|
||||||
|
|
||||||
:param silent: is set to `True` the return value is `None` if no context
|
|
||||||
is available. The default behavior is to raise a
|
|
||||||
:exc:`RuntimeError`.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return getattr(_local, 'stack')[-1]
|
|
||||||
except (AttributeError, IndexError):
|
|
||||||
if not silent:
|
|
||||||
raise RuntimeError('There is no active click context.')
|
|
||||||
|
|
||||||
|
|
||||||
def push_context(ctx):
|
|
||||||
"""Pushes a new context to the current stack."""
|
|
||||||
_local.__dict__.setdefault('stack', []).append(ctx)
|
|
||||||
|
|
||||||
|
|
||||||
def pop_context():
|
|
||||||
"""Removes the top level from the stack."""
|
|
||||||
_local.stack.pop()
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_color_default(color=None):
|
|
||||||
""""Internal helper to get the default value of the color flag. If a
|
|
||||||
value is passed it's returned unchanged, otherwise it's looked up from
|
|
||||||
the current context.
|
|
||||||
"""
|
|
||||||
if color is not None:
|
|
||||||
return color
|
|
||||||
ctx = get_current_context(silent=True)
|
|
||||||
if ctx is not None:
|
|
||||||
return ctx.color
|
|
|
@ -1,426 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
click.parser
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module started out as largely a copy paste from the stdlib's
|
|
||||||
optparse module with the features removed that we do not need from
|
|
||||||
optparse because we implement them in Click on a higher level (for
|
|
||||||
instance type handling, help formatting and a lot more).
|
|
||||||
|
|
||||||
The plan is to remove more and more from here over time.
|
|
||||||
|
|
||||||
The reason this is a different module and not optparse from the stdlib
|
|
||||||
is that there are differences in 2.x and 3.x about the error messages
|
|
||||||
generated and optparse in the stdlib uses gettext for no good reason
|
|
||||||
and might cause us issues.
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from collections import deque
|
|
||||||
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
|
||||||
BadArgumentUsage
|
|
||||||
|
|
||||||
|
|
||||||
def _unpack_args(args, nargs_spec):
|
|
||||||
"""Given an iterable of arguments and an iterable of nargs specifications,
|
|
||||||
it returns a tuple with all the unpacked arguments at the first index
|
|
||||||
and all remaining arguments as the second.
|
|
||||||
|
|
||||||
The nargs specification is the number of arguments that should be consumed
|
|
||||||
or `-1` to indicate that this position should eat up all the remainders.
|
|
||||||
|
|
||||||
Missing items are filled with `None`.
|
|
||||||
"""
|
|
||||||
args = deque(args)
|
|
||||||
nargs_spec = deque(nargs_spec)
|
|
||||||
rv = []
|
|
||||||
spos = None
|
|
||||||
|
|
||||||
def _fetch(c):
|
|
||||||
try:
|
|
||||||
if spos is None:
|
|
||||||
return c.popleft()
|
|
||||||
else:
|
|
||||||
return c.pop()
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
while nargs_spec:
|
|
||||||
nargs = _fetch(nargs_spec)
|
|
||||||
if nargs == 1:
|
|
||||||
rv.append(_fetch(args))
|
|
||||||
elif nargs > 1:
|
|
||||||
x = [_fetch(args) for _ in range(nargs)]
|
|
||||||
# If we're reversed, we're pulling in the arguments in reverse,
|
|
||||||
# so we need to turn them around.
|
|
||||||
if spos is not None:
|
|
||||||
x.reverse()
|
|
||||||
rv.append(tuple(x))
|
|
||||||
elif nargs < 0:
|
|
||||||
if spos is not None:
|
|
||||||
raise TypeError('Cannot have two nargs < 0')
|
|
||||||
spos = len(rv)
|
|
||||||
rv.append(None)
|
|
||||||
|
|
||||||
# spos is the position of the wildcard (star). If it's not `None`,
|
|
||||||
# we fill it with the remainder.
|
|
||||||
if spos is not None:
|
|
||||||
rv[spos] = tuple(args)
|
|
||||||
args = []
|
|
||||||
rv[spos + 1:] = reversed(rv[spos + 1:])
|
|
||||||
|
|
||||||
return tuple(rv), list(args)
|
|
||||||
|
|
||||||
|
|
||||||
def _error_opt_args(nargs, opt):
|
|
||||||
if nargs == 1:
|
|
||||||
raise BadOptionUsage('%s option requires an argument' % opt)
|
|
||||||
raise BadOptionUsage('%s option requires %d arguments' % (opt, nargs))
|
|
||||||
|
|
||||||
|
|
||||||
def split_opt(opt):
|
|
||||||
first = opt[:1]
|
|
||||||
if first.isalnum():
|
|
||||||
return '', opt
|
|
||||||
if opt[1:2] == first:
|
|
||||||
return opt[:2], opt[2:]
|
|
||||||
return first, opt[1:]
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_opt(opt, ctx):
|
|
||||||
if ctx is None or ctx.token_normalize_func is None:
|
|
||||||
return opt
|
|
||||||
prefix, opt = split_opt(opt)
|
|
||||||
return prefix + ctx.token_normalize_func(opt)
|
|
||||||
|
|
||||||
|
|
||||||
def split_arg_string(string):
|
|
||||||
"""Given an argument string this attempts to split it into small parts."""
|
|
||||||
rv = []
|
|
||||||
for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
|
||||||
r'|"([^"\\]*(?:\\.[^"\\]*)*)"'
|
|
||||||
r'|\S+)\s*', string, re.S):
|
|
||||||
arg = match.group().strip()
|
|
||||||
if arg[:1] == arg[-1:] and arg[:1] in '"\'':
|
|
||||||
arg = arg[1:-1].encode('ascii', 'backslashreplace') \
|
|
||||||
.decode('unicode-escape')
|
|
||||||
try:
|
|
||||||
arg = type(string)(arg)
|
|
||||||
except UnicodeError:
|
|
||||||
pass
|
|
||||||
rv.append(arg)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class Option(object):
|
|
||||||
|
|
||||||
def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None):
|
|
||||||
self._short_opts = []
|
|
||||||
self._long_opts = []
|
|
||||||
self.prefixes = set()
|
|
||||||
|
|
||||||
for opt in opts:
|
|
||||||
prefix, value = split_opt(opt)
|
|
||||||
if not prefix:
|
|
||||||
raise ValueError('Invalid start character for option (%s)'
|
|
||||||
% opt)
|
|
||||||
self.prefixes.add(prefix[0])
|
|
||||||
if len(prefix) == 1 and len(value) == 1:
|
|
||||||
self._short_opts.append(opt)
|
|
||||||
else:
|
|
||||||
self._long_opts.append(opt)
|
|
||||||
self.prefixes.add(prefix)
|
|
||||||
|
|
||||||
if action is None:
|
|
||||||
action = 'store'
|
|
||||||
|
|
||||||
self.dest = dest
|
|
||||||
self.action = action
|
|
||||||
self.nargs = nargs
|
|
||||||
self.const = const
|
|
||||||
self.obj = obj
|
|
||||||
|
|
||||||
@property
|
|
||||||
def takes_value(self):
|
|
||||||
return self.action in ('store', 'append')
|
|
||||||
|
|
||||||
def process(self, value, state):
|
|
||||||
if self.action == 'store':
|
|
||||||
state.opts[self.dest] = value
|
|
||||||
elif self.action == 'store_const':
|
|
||||||
state.opts[self.dest] = self.const
|
|
||||||
elif self.action == 'append':
|
|
||||||
state.opts.setdefault(self.dest, []).append(value)
|
|
||||||
elif self.action == 'append_const':
|
|
||||||
state.opts.setdefault(self.dest, []).append(self.const)
|
|
||||||
elif self.action == 'count':
|
|
||||||
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1
|
|
||||||
else:
|
|
||||||
raise ValueError('unknown action %r' % self.action)
|
|
||||||
state.order.append(self.obj)
|
|
||||||
|
|
||||||
|
|
||||||
class Argument(object):
|
|
||||||
|
|
||||||
def __init__(self, dest, nargs=1, obj=None):
|
|
||||||
self.dest = dest
|
|
||||||
self.nargs = nargs
|
|
||||||
self.obj = obj
|
|
||||||
|
|
||||||
def process(self, value, state):
|
|
||||||
if self.nargs > 1:
|
|
||||||
holes = sum(1 for x in value if x is None)
|
|
||||||
if holes == len(value):
|
|
||||||
value = None
|
|
||||||
elif holes != 0:
|
|
||||||
raise BadArgumentUsage('argument %s takes %d values'
|
|
||||||
% (self.dest, self.nargs))
|
|
||||||
state.opts[self.dest] = value
|
|
||||||
state.order.append(self.obj)
|
|
||||||
|
|
||||||
|
|
||||||
class ParsingState(object):
|
|
||||||
|
|
||||||
def __init__(self, rargs):
|
|
||||||
self.opts = {}
|
|
||||||
self.largs = []
|
|
||||||
self.rargs = rargs
|
|
||||||
self.order = []
|
|
||||||
|
|
||||||
|
|
||||||
class OptionParser(object):
|
|
||||||
"""The option parser is an internal class that is ultimately used to
|
|
||||||
parse options and arguments. It's modelled after optparse and brings
|
|
||||||
a similar but vastly simplified API. It should generally not be used
|
|
||||||
directly as the high level Click classes wrap it for you.
|
|
||||||
|
|
||||||
It's not nearly as extensible as optparse or argparse as it does not
|
|
||||||
implement features that are implemented on a higher level (such as
|
|
||||||
types or defaults).
|
|
||||||
|
|
||||||
:param ctx: optionally the :class:`~click.Context` where this parser
|
|
||||||
should go with.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, ctx=None):
|
|
||||||
#: The :class:`~click.Context` for this parser. This might be
|
|
||||||
#: `None` for some advanced use cases.
|
|
||||||
self.ctx = ctx
|
|
||||||
#: This controls how the parser deals with interspersed arguments.
|
|
||||||
#: If this is set to `False`, the parser will stop on the first
|
|
||||||
#: non-option. Click uses this to implement nested subcommands
|
|
||||||
#: safely.
|
|
||||||
self.allow_interspersed_args = True
|
|
||||||
#: This tells the parser how to deal with unknown options. By
|
|
||||||
#: default it will error out (which is sensible), but there is a
|
|
||||||
#: second mode where it will ignore it and continue processing
|
|
||||||
#: after shifting all the unknown options into the resulting args.
|
|
||||||
self.ignore_unknown_options = False
|
|
||||||
if ctx is not None:
|
|
||||||
self.allow_interspersed_args = ctx.allow_interspersed_args
|
|
||||||
self.ignore_unknown_options = ctx.ignore_unknown_options
|
|
||||||
self._short_opt = {}
|
|
||||||
self._long_opt = {}
|
|
||||||
self._opt_prefixes = set(['-', '--'])
|
|
||||||
self._args = []
|
|
||||||
|
|
||||||
def add_option(self, opts, dest, action=None, nargs=1, const=None,
|
|
||||||
obj=None):
|
|
||||||
"""Adds a new option named `dest` to the parser. The destination
|
|
||||||
is not inferred (unlike with optparse) and needs to be explicitly
|
|
||||||
provided. Action can be any of ``store``, ``store_const``,
|
|
||||||
``append``, ``appnd_const`` or ``count``.
|
|
||||||
|
|
||||||
The `obj` can be used to identify the option in the order list
|
|
||||||
that is returned from the parser.
|
|
||||||
"""
|
|
||||||
if obj is None:
|
|
||||||
obj = dest
|
|
||||||
opts = [normalize_opt(opt, self.ctx) for opt in opts]
|
|
||||||
option = Option(opts, dest, action=action, nargs=nargs,
|
|
||||||
const=const, obj=obj)
|
|
||||||
self._opt_prefixes.update(option.prefixes)
|
|
||||||
for opt in option._short_opts:
|
|
||||||
self._short_opt[opt] = option
|
|
||||||
for opt in option._long_opts:
|
|
||||||
self._long_opt[opt] = option
|
|
||||||
|
|
||||||
def add_argument(self, dest, nargs=1, obj=None):
|
|
||||||
"""Adds a positional argument named `dest` to the parser.
|
|
||||||
|
|
||||||
The `obj` can be used to identify the option in the order list
|
|
||||||
that is returned from the parser.
|
|
||||||
"""
|
|
||||||
if obj is None:
|
|
||||||
obj = dest
|
|
||||||
self._args.append(Argument(dest=dest, nargs=nargs, obj=obj))
|
|
||||||
|
|
||||||
def parse_args(self, args):
|
|
||||||
"""Parses positional arguments and returns ``(values, args, order)``
|
|
||||||
for the parsed options and arguments as well as the leftover
|
|
||||||
arguments if there are any. The order is a list of objects as they
|
|
||||||
appear on the command line. If arguments appear multiple times they
|
|
||||||
will be memorized multiple times as well.
|
|
||||||
"""
|
|
||||||
state = ParsingState(args)
|
|
||||||
try:
|
|
||||||
self._process_args_for_options(state)
|
|
||||||
self._process_args_for_args(state)
|
|
||||||
except UsageError:
|
|
||||||
if self.ctx is None or not self.ctx.resilient_parsing:
|
|
||||||
raise
|
|
||||||
return state.opts, state.largs, state.order
|
|
||||||
|
|
||||||
def _process_args_for_args(self, state):
|
|
||||||
pargs, args = _unpack_args(state.largs + state.rargs,
|
|
||||||
[x.nargs for x in self._args])
|
|
||||||
|
|
||||||
for idx, arg in enumerate(self._args):
|
|
||||||
arg.process(pargs[idx], state)
|
|
||||||
|
|
||||||
state.largs = args
|
|
||||||
state.rargs = []
|
|
||||||
|
|
||||||
def _process_args_for_options(self, state):
|
|
||||||
while state.rargs:
|
|
||||||
arg = state.rargs.pop(0)
|
|
||||||
arglen = len(arg)
|
|
||||||
# Double dashes always handled explicitly regardless of what
|
|
||||||
# prefixes are valid.
|
|
||||||
if arg == '--':
|
|
||||||
return
|
|
||||||
elif arg[:1] in self._opt_prefixes and arglen > 1:
|
|
||||||
self._process_opts(arg, state)
|
|
||||||
elif self.allow_interspersed_args:
|
|
||||||
state.largs.append(arg)
|
|
||||||
else:
|
|
||||||
state.rargs.insert(0, arg)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Say this is the original argument list:
|
|
||||||
# [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
|
|
||||||
# ^
|
|
||||||
# (we are about to process arg(i)).
|
|
||||||
#
|
|
||||||
# Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
|
|
||||||
# [arg0, ..., arg(i-1)] (any options and their arguments will have
|
|
||||||
# been removed from largs).
|
|
||||||
#
|
|
||||||
# The while loop will usually consume 1 or more arguments per pass.
|
|
||||||
# If it consumes 1 (eg. arg is an option that takes no arguments),
|
|
||||||
# then after _process_arg() is done the situation is:
|
|
||||||
#
|
|
||||||
# largs = subset of [arg0, ..., arg(i)]
|
|
||||||
# rargs = [arg(i+1), ..., arg(N-1)]
|
|
||||||
#
|
|
||||||
# If allow_interspersed_args is false, largs will always be
|
|
||||||
# *empty* -- still a subset of [arg0, ..., arg(i-1)], but
|
|
||||||
# not a very interesting subset!
|
|
||||||
|
|
||||||
def _match_long_opt(self, opt, explicit_value, state):
|
|
||||||
if opt not in self._long_opt:
|
|
||||||
possibilities = [word for word in self._long_opt
|
|
||||||
if word.startswith(opt)]
|
|
||||||
raise NoSuchOption(opt, possibilities=possibilities)
|
|
||||||
|
|
||||||
option = self._long_opt[opt]
|
|
||||||
if option.takes_value:
|
|
||||||
# At this point it's safe to modify rargs by injecting the
|
|
||||||
# explicit value, because no exception is raised in this
|
|
||||||
# branch. This means that the inserted value will be fully
|
|
||||||
# consumed.
|
|
||||||
if explicit_value is not None:
|
|
||||||
state.rargs.insert(0, explicit_value)
|
|
||||||
|
|
||||||
nargs = option.nargs
|
|
||||||
if len(state.rargs) < nargs:
|
|
||||||
_error_opt_args(nargs, opt)
|
|
||||||
elif nargs == 1:
|
|
||||||
value = state.rargs.pop(0)
|
|
||||||
else:
|
|
||||||
value = tuple(state.rargs[:nargs])
|
|
||||||
del state.rargs[:nargs]
|
|
||||||
|
|
||||||
elif explicit_value is not None:
|
|
||||||
raise BadOptionUsage('%s option does not take a value' % opt)
|
|
||||||
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
|
|
||||||
option.process(value, state)
|
|
||||||
|
|
||||||
def _match_short_opt(self, arg, state):
|
|
||||||
stop = False
|
|
||||||
i = 1
|
|
||||||
prefix = arg[0]
|
|
||||||
unknown_options = []
|
|
||||||
|
|
||||||
for ch in arg[1:]:
|
|
||||||
opt = normalize_opt(prefix + ch, self.ctx)
|
|
||||||
option = self._short_opt.get(opt)
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
if not option:
|
|
||||||
if self.ignore_unknown_options:
|
|
||||||
unknown_options.append(ch)
|
|
||||||
continue
|
|
||||||
raise NoSuchOption(opt)
|
|
||||||
if option.takes_value:
|
|
||||||
# Any characters left in arg? Pretend they're the
|
|
||||||
# next arg, and stop consuming characters of arg.
|
|
||||||
if i < len(arg):
|
|
||||||
state.rargs.insert(0, arg[i:])
|
|
||||||
stop = True
|
|
||||||
|
|
||||||
nargs = option.nargs
|
|
||||||
if len(state.rargs) < nargs:
|
|
||||||
_error_opt_args(nargs, opt)
|
|
||||||
elif nargs == 1:
|
|
||||||
value = state.rargs.pop(0)
|
|
||||||
else:
|
|
||||||
value = tuple(state.rargs[:nargs])
|
|
||||||
del state.rargs[:nargs]
|
|
||||||
|
|
||||||
else:
|
|
||||||
value = None
|
|
||||||
|
|
||||||
option.process(value, state)
|
|
||||||
|
|
||||||
if stop:
|
|
||||||
break
|
|
||||||
|
|
||||||
# If we got any unknown options we re-combinate the string of the
|
|
||||||
# remaining options and re-attach the prefix, then report that
|
|
||||||
# to the state as new larg. This way there is basic combinatorics
|
|
||||||
# that can be achieved while still ignoring unknown arguments.
|
|
||||||
if self.ignore_unknown_options and unknown_options:
|
|
||||||
state.largs.append(prefix + ''.join(unknown_options))
|
|
||||||
|
|
||||||
def _process_opts(self, arg, state):
|
|
||||||
explicit_value = None
|
|
||||||
# Long option handling happens in two parts. The first part is
|
|
||||||
# supporting explicitly attached values. In any case, we will try
|
|
||||||
# to long match the option first.
|
|
||||||
if '=' in arg:
|
|
||||||
long_opt, explicit_value = arg.split('=', 1)
|
|
||||||
else:
|
|
||||||
long_opt = arg
|
|
||||||
norm_long_opt = normalize_opt(long_opt, self.ctx)
|
|
||||||
|
|
||||||
# At this point we will match the (assumed) long option through
|
|
||||||
# the long option matching code. Note that this allows options
|
|
||||||
# like "-foo" to be matched as long options.
|
|
||||||
try:
|
|
||||||
self._match_long_opt(norm_long_opt, explicit_value, state)
|
|
||||||
except NoSuchOption:
|
|
||||||
# At this point the long option matching failed, and we need
|
|
||||||
# to try with short options. However there is a special rule
|
|
||||||
# which says, that if we have a two character options prefix
|
|
||||||
# (applies to "--foo" for instance), we do not dispatch to the
|
|
||||||
# short option code and will instead raise the no option
|
|
||||||
# error.
|
|
||||||
if arg[:2] not in self._opt_prefixes:
|
|
||||||
return self._match_short_opt(arg, state)
|
|
||||||
if not self.ignore_unknown_options:
|
|
||||||
raise
|
|
||||||
state.largs.append(arg)
|
|
|
@ -1,539 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import struct
|
|
||||||
|
|
||||||
from ._compat import raw_input, text_type, string_types, \
|
|
||||||
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
|
||||||
from .utils import echo
|
|
||||||
from .exceptions import Abort, UsageError
|
|
||||||
from .types import convert_type
|
|
||||||
from .globals import resolve_color_default
|
|
||||||
|
|
||||||
|
|
||||||
# The prompt functions to use. The doc tools currently override these
|
|
||||||
# functions to customize how they work.
|
|
||||||
visible_prompt_func = raw_input
|
|
||||||
|
|
||||||
_ansi_colors = ('black', 'red', 'green', 'yellow', 'blue', 'magenta',
|
|
||||||
'cyan', 'white', 'reset')
|
|
||||||
_ansi_reset_all = '\033[0m'
|
|
||||||
|
|
||||||
|
|
||||||
def hidden_prompt_func(prompt):
|
|
||||||
import getpass
|
|
||||||
return getpass.getpass(prompt)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_prompt(text, suffix, show_default=False, default=None):
|
|
||||||
prompt = text
|
|
||||||
if default is not None and show_default:
|
|
||||||
prompt = '%s [%s]' % (prompt, default)
|
|
||||||
return prompt + suffix
|
|
||||||
|
|
||||||
|
|
||||||
def prompt(text, default=None, hide_input=False,
|
|
||||||
confirmation_prompt=False, type=None,
|
|
||||||
value_proc=None, prompt_suffix=': ',
|
|
||||||
show_default=True, err=False):
|
|
||||||
"""Prompts a user for input. This is a convenience function that can
|
|
||||||
be used to prompt a user for input later.
|
|
||||||
|
|
||||||
If the user aborts the input by sending a interrupt signal, this
|
|
||||||
function will catch it and raise a :exc:`Abort` exception.
|
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
|
||||||
Added unicode support for cmd.exe on Windows.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
Added the `err` parameter.
|
|
||||||
|
|
||||||
:param text: the text to show for the prompt.
|
|
||||||
:param default: the default value to use if no input happens. If this
|
|
||||||
is not given it will prompt until it's aborted.
|
|
||||||
:param hide_input: if this is set to true then the input value will
|
|
||||||
be hidden.
|
|
||||||
:param confirmation_prompt: asks for confirmation for the value.
|
|
||||||
:param type: the type to use to check the value against.
|
|
||||||
:param value_proc: if this parameter is provided it's a function that
|
|
||||||
is invoked instead of the type conversion to
|
|
||||||
convert a value.
|
|
||||||
:param prompt_suffix: a suffix that should be added to the prompt.
|
|
||||||
:param show_default: shows or hides the default value in the prompt.
|
|
||||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
|
||||||
``stdout``, the same as with echo.
|
|
||||||
"""
|
|
||||||
result = None
|
|
||||||
|
|
||||||
def prompt_func(text):
|
|
||||||
f = hide_input and hidden_prompt_func or visible_prompt_func
|
|
||||||
try:
|
|
||||||
# Write the prompt separately so that we get nice
|
|
||||||
# coloring through colorama on Windows
|
|
||||||
echo(text, nl=False, err=err)
|
|
||||||
return f('')
|
|
||||||
except (KeyboardInterrupt, EOFError):
|
|
||||||
# getpass doesn't print a newline if the user aborts input with ^C.
|
|
||||||
# Allegedly this behavior is inherited from getpass(3).
|
|
||||||
# A doc bug has been filed at https://bugs.python.org/issue24711
|
|
||||||
if hide_input:
|
|
||||||
echo(None, err=err)
|
|
||||||
raise Abort()
|
|
||||||
|
|
||||||
if value_proc is None:
|
|
||||||
value_proc = convert_type(type, default)
|
|
||||||
|
|
||||||
prompt = _build_prompt(text, prompt_suffix, show_default, default)
|
|
||||||
|
|
||||||
while 1:
|
|
||||||
while 1:
|
|
||||||
value = prompt_func(prompt)
|
|
||||||
if value:
|
|
||||||
break
|
|
||||||
# If a default is set and used, then the confirmation
|
|
||||||
# prompt is always skipped because that's the only thing
|
|
||||||
# that really makes sense.
|
|
||||||
elif default is not None:
|
|
||||||
return default
|
|
||||||
try:
|
|
||||||
result = value_proc(value)
|
|
||||||
except UsageError as e:
|
|
||||||
echo('Error: %s' % e.message, err=err)
|
|
||||||
continue
|
|
||||||
if not confirmation_prompt:
|
|
||||||
return result
|
|
||||||
while 1:
|
|
||||||
value2 = prompt_func('Repeat for confirmation: ')
|
|
||||||
if value2:
|
|
||||||
break
|
|
||||||
if value == value2:
|
|
||||||
return result
|
|
||||||
echo('Error: the two entered values do not match', err=err)
|
|
||||||
|
|
||||||
|
|
||||||
def confirm(text, default=False, abort=False, prompt_suffix=': ',
|
|
||||||
show_default=True, err=False):
|
|
||||||
"""Prompts for confirmation (yes/no question).
|
|
||||||
|
|
||||||
If the user aborts the input by sending a interrupt signal this
|
|
||||||
function will catch it and raise a :exc:`Abort` exception.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
Added the `err` parameter.
|
|
||||||
|
|
||||||
:param text: the question to ask.
|
|
||||||
:param default: the default for the prompt.
|
|
||||||
:param abort: if this is set to `True` a negative answer aborts the
|
|
||||||
exception by raising :exc:`Abort`.
|
|
||||||
:param prompt_suffix: a suffix that should be added to the prompt.
|
|
||||||
:param show_default: shows or hides the default value in the prompt.
|
|
||||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
|
||||||
``stdout``, the same as with echo.
|
|
||||||
"""
|
|
||||||
prompt = _build_prompt(text, prompt_suffix, show_default,
|
|
||||||
default and 'Y/n' or 'y/N')
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
# Write the prompt separately so that we get nice
|
|
||||||
# coloring through colorama on Windows
|
|
||||||
echo(prompt, nl=False, err=err)
|
|
||||||
value = visible_prompt_func('').lower().strip()
|
|
||||||
except (KeyboardInterrupt, EOFError):
|
|
||||||
raise Abort()
|
|
||||||
if value in ('y', 'yes'):
|
|
||||||
rv = True
|
|
||||||
elif value in ('n', 'no'):
|
|
||||||
rv = False
|
|
||||||
elif value == '':
|
|
||||||
rv = default
|
|
||||||
else:
|
|
||||||
echo('Error: invalid input', err=err)
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
if abort and not rv:
|
|
||||||
raise Abort()
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def get_terminal_size():
|
|
||||||
"""Returns the current size of the terminal as tuple in the form
|
|
||||||
``(width, height)`` in columns and rows.
|
|
||||||
"""
|
|
||||||
# If shutil has get_terminal_size() (Python 3.3 and later) use that
|
|
||||||
if sys.version_info >= (3, 3):
|
|
||||||
import shutil
|
|
||||||
shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None)
|
|
||||||
if shutil_get_terminal_size:
|
|
||||||
sz = shutil_get_terminal_size()
|
|
||||||
return sz.columns, sz.lines
|
|
||||||
|
|
||||||
if get_winterm_size is not None:
|
|
||||||
return get_winterm_size()
|
|
||||||
|
|
||||||
def ioctl_gwinsz(fd):
|
|
||||||
try:
|
|
||||||
import fcntl
|
|
||||||
import termios
|
|
||||||
cr = struct.unpack(
|
|
||||||
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
|
||||||
except Exception:
|
|
||||||
return
|
|
||||||
return cr
|
|
||||||
|
|
||||||
cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
|
|
||||||
if not cr:
|
|
||||||
try:
|
|
||||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
|
||||||
try:
|
|
||||||
cr = ioctl_gwinsz(fd)
|
|
||||||
finally:
|
|
||||||
os.close(fd)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if not cr or not cr[0] or not cr[1]:
|
|
||||||
cr = (os.environ.get('LINES', 25),
|
|
||||||
os.environ.get('COLUMNS', DEFAULT_COLUMNS))
|
|
||||||
return int(cr[1]), int(cr[0])
|
|
||||||
|
|
||||||
|
|
||||||
def echo_via_pager(text, color=None):
|
|
||||||
"""This function takes a text and shows it via an environment specific
|
|
||||||
pager on stdout.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
Added the `color` flag.
|
|
||||||
|
|
||||||
:param text: the text to page.
|
|
||||||
:param color: controls if the pager supports ANSI colors or not. The
|
|
||||||
default is autodetection.
|
|
||||||
"""
|
|
||||||
color = resolve_color_default(color)
|
|
||||||
if not isinstance(text, string_types):
|
|
||||||
text = text_type(text)
|
|
||||||
from ._termui_impl import pager
|
|
||||||
return pager(text + '\n', color)
|
|
||||||
|
|
||||||
|
|
||||||
def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
|
||||||
show_percent=None, show_pos=False,
|
|
||||||
item_show_func=None, fill_char='#', empty_char='-',
|
|
||||||
bar_template='%(label)s [%(bar)s] %(info)s',
|
|
||||||
info_sep=' ', width=36, file=None, color=None):
|
|
||||||
"""This function creates an iterable context manager that can be used
|
|
||||||
to iterate over something while showing a progress bar. It will
|
|
||||||
either iterate over the `iterable` or `length` items (that are counted
|
|
||||||
up). While iteration happens, this function will print a rendered
|
|
||||||
progress bar to the given `file` (defaults to stdout) and will attempt
|
|
||||||
to calculate remaining time and more. By default, this progress bar
|
|
||||||
will not be rendered if the file is not a terminal.
|
|
||||||
|
|
||||||
The context manager creates the progress bar. When the context
|
|
||||||
manager is entered the progress bar is already displayed. With every
|
|
||||||
iteration over the progress bar, the iterable passed to the bar is
|
|
||||||
advanced and the bar is updated. When the context manager exits,
|
|
||||||
a newline is printed and the progress bar is finalized on screen.
|
|
||||||
|
|
||||||
No printing must happen or the progress bar will be unintentionally
|
|
||||||
destroyed.
|
|
||||||
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
with progressbar(items) as bar:
|
|
||||||
for item in bar:
|
|
||||||
do_something_with(item)
|
|
||||||
|
|
||||||
Alternatively, if no iterable is specified, one can manually update the
|
|
||||||
progress bar through the `update()` method instead of directly
|
|
||||||
iterating over the progress bar. The update method accepts the number
|
|
||||||
of steps to increment the bar with::
|
|
||||||
|
|
||||||
with progressbar(length=chunks.total_bytes) as bar:
|
|
||||||
for chunk in chunks:
|
|
||||||
process_chunk(chunk)
|
|
||||||
bar.update(chunks.bytes)
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
Added the `color` parameter. Added a `update` method to the
|
|
||||||
progressbar object.
|
|
||||||
|
|
||||||
:param iterable: an iterable to iterate over. If not provided the length
|
|
||||||
is required.
|
|
||||||
:param length: the number of items to iterate over. By default the
|
|
||||||
progressbar will attempt to ask the iterator about its
|
|
||||||
length, which might or might not work. If an iterable is
|
|
||||||
also provided this parameter can be used to override the
|
|
||||||
length. If an iterable is not provided the progress bar
|
|
||||||
will iterate over a range of that length.
|
|
||||||
:param label: the label to show next to the progress bar.
|
|
||||||
:param show_eta: enables or disables the estimated time display. This is
|
|
||||||
automatically disabled if the length cannot be
|
|
||||||
determined.
|
|
||||||
:param show_percent: enables or disables the percentage display. The
|
|
||||||
default is `True` if the iterable has a length or
|
|
||||||
`False` if not.
|
|
||||||
:param show_pos: enables or disables the absolute position display. The
|
|
||||||
default is `False`.
|
|
||||||
:param item_show_func: a function called with the current item which
|
|
||||||
can return a string to show the current item
|
|
||||||
next to the progress bar. Note that the current
|
|
||||||
item can be `None`!
|
|
||||||
:param fill_char: the character to use to show the filled part of the
|
|
||||||
progress bar.
|
|
||||||
:param empty_char: the character to use to show the non-filled part of
|
|
||||||
the progress bar.
|
|
||||||
:param bar_template: the format string to use as template for the bar.
|
|
||||||
The parameters in it are ``label`` for the label,
|
|
||||||
``bar`` for the progress bar and ``info`` for the
|
|
||||||
info section.
|
|
||||||
:param info_sep: the separator between multiple info items (eta etc.)
|
|
||||||
:param width: the width of the progress bar in characters, 0 means full
|
|
||||||
terminal width
|
|
||||||
:param file: the file to write to. If this is not a terminal then
|
|
||||||
only the label is printed.
|
|
||||||
:param color: controls if the terminal supports ANSI colors or not. The
|
|
||||||
default is autodetection. This is only needed if ANSI
|
|
||||||
codes are included anywhere in the progress bar output
|
|
||||||
which is not the case by default.
|
|
||||||
"""
|
|
||||||
from ._termui_impl import ProgressBar
|
|
||||||
color = resolve_color_default(color)
|
|
||||||
return ProgressBar(iterable=iterable, length=length, show_eta=show_eta,
|
|
||||||
show_percent=show_percent, show_pos=show_pos,
|
|
||||||
item_show_func=item_show_func, fill_char=fill_char,
|
|
||||||
empty_char=empty_char, bar_template=bar_template,
|
|
||||||
info_sep=info_sep, file=file, label=label,
|
|
||||||
width=width, color=color)
|
|
||||||
|
|
||||||
|
|
||||||
def clear():
|
|
||||||
"""Clears the terminal screen. This will have the effect of clearing
|
|
||||||
the whole visible space of the terminal and moving the cursor to the
|
|
||||||
top left. This does not do anything if not connected to a terminal.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
"""
|
|
||||||
if not isatty(sys.stdout):
|
|
||||||
return
|
|
||||||
# If we're on Windows and we don't have colorama available, then we
|
|
||||||
# clear the screen by shelling out. Otherwise we can use an escape
|
|
||||||
# sequence.
|
|
||||||
if WIN:
|
|
||||||
os.system('cls')
|
|
||||||
else:
|
|
||||||
sys.stdout.write('\033[2J\033[1;1H')
|
|
||||||
|
|
||||||
|
|
||||||
def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
|
||||||
blink=None, reverse=None, reset=True):
|
|
||||||
"""Styles a text with ANSI styles and returns the new string. By
|
|
||||||
default the styling is self contained which means that at the end
|
|
||||||
of the string a reset code is issued. This can be prevented by
|
|
||||||
passing ``reset=False``.
|
|
||||||
|
|
||||||
Examples::
|
|
||||||
|
|
||||||
click.echo(click.style('Hello World!', fg='green'))
|
|
||||||
click.echo(click.style('ATTENTION!', blink=True))
|
|
||||||
click.echo(click.style('Some things', reverse=True, fg='cyan'))
|
|
||||||
|
|
||||||
Supported color names:
|
|
||||||
|
|
||||||
* ``black`` (might be a gray)
|
|
||||||
* ``red``
|
|
||||||
* ``green``
|
|
||||||
* ``yellow`` (might be an orange)
|
|
||||||
* ``blue``
|
|
||||||
* ``magenta``
|
|
||||||
* ``cyan``
|
|
||||||
* ``white`` (might be light gray)
|
|
||||||
* ``reset`` (reset the color code only)
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param text: the string to style with ansi codes.
|
|
||||||
:param fg: if provided this will become the foreground color.
|
|
||||||
:param bg: if provided this will become the background color.
|
|
||||||
:param bold: if provided this will enable or disable bold mode.
|
|
||||||
:param dim: if provided this will enable or disable dim mode. This is
|
|
||||||
badly supported.
|
|
||||||
:param underline: if provided this will enable or disable underline.
|
|
||||||
:param blink: if provided this will enable or disable blinking.
|
|
||||||
:param reverse: if provided this will enable or disable inverse
|
|
||||||
rendering (foreground becomes background and the
|
|
||||||
other way round).
|
|
||||||
:param reset: by default a reset-all code is added at the end of the
|
|
||||||
string which means that styles do not carry over. This
|
|
||||||
can be disabled to compose styles.
|
|
||||||
"""
|
|
||||||
bits = []
|
|
||||||
if fg:
|
|
||||||
try:
|
|
||||||
bits.append('\033[%dm' % (_ansi_colors.index(fg) + 30))
|
|
||||||
except ValueError:
|
|
||||||
raise TypeError('Unknown color %r' % fg)
|
|
||||||
if bg:
|
|
||||||
try:
|
|
||||||
bits.append('\033[%dm' % (_ansi_colors.index(bg) + 40))
|
|
||||||
except ValueError:
|
|
||||||
raise TypeError('Unknown color %r' % bg)
|
|
||||||
if bold is not None:
|
|
||||||
bits.append('\033[%dm' % (1 if bold else 22))
|
|
||||||
if dim is not None:
|
|
||||||
bits.append('\033[%dm' % (2 if dim else 22))
|
|
||||||
if underline is not None:
|
|
||||||
bits.append('\033[%dm' % (4 if underline else 24))
|
|
||||||
if blink is not None:
|
|
||||||
bits.append('\033[%dm' % (5 if blink else 25))
|
|
||||||
if reverse is not None:
|
|
||||||
bits.append('\033[%dm' % (7 if reverse else 27))
|
|
||||||
bits.append(text)
|
|
||||||
if reset:
|
|
||||||
bits.append(_ansi_reset_all)
|
|
||||||
return ''.join(bits)
|
|
||||||
|
|
||||||
|
|
||||||
def unstyle(text):
|
|
||||||
"""Removes ANSI styling information from a string. Usually it's not
|
|
||||||
necessary to use this function as Click's echo function will
|
|
||||||
automatically remove styling if necessary.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param text: the text to remove style information from.
|
|
||||||
"""
|
|
||||||
return strip_ansi(text)
|
|
||||||
|
|
||||||
|
|
||||||
def secho(text, file=None, nl=True, err=False, color=None, **styles):
|
|
||||||
"""This function combines :func:`echo` and :func:`style` into one
|
|
||||||
call. As such the following two calls are the same::
|
|
||||||
|
|
||||||
click.secho('Hello World!', fg='green')
|
|
||||||
click.echo(click.style('Hello World!', fg='green'))
|
|
||||||
|
|
||||||
All keyword arguments are forwarded to the underlying functions
|
|
||||||
depending on which one they go with.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
"""
|
|
||||||
return echo(style(text, **styles), file=file, nl=nl, err=err, color=color)
|
|
||||||
|
|
||||||
|
|
||||||
def edit(text=None, editor=None, env=None, require_save=True,
|
|
||||||
extension='.txt', filename=None):
|
|
||||||
r"""Edits the given text in the defined editor. If an editor is given
|
|
||||||
(should be the full path to the executable but the regular operating
|
|
||||||
system search path is used for finding the executable) it overrides
|
|
||||||
the detected editor. Optionally, some environment variables can be
|
|
||||||
used. If the editor is closed without changes, `None` is returned. In
|
|
||||||
case a file is edited directly the return value is always `None` and
|
|
||||||
`require_save` and `extension` are ignored.
|
|
||||||
|
|
||||||
If the editor cannot be opened a :exc:`UsageError` is raised.
|
|
||||||
|
|
||||||
Note for Windows: to simplify cross-platform usage, the newlines are
|
|
||||||
automatically converted from POSIX to Windows and vice versa. As such,
|
|
||||||
the message here will have ``\n`` as newline markers.
|
|
||||||
|
|
||||||
:param text: the text to edit.
|
|
||||||
:param editor: optionally the editor to use. Defaults to automatic
|
|
||||||
detection.
|
|
||||||
:param env: environment variables to forward to the editor.
|
|
||||||
:param require_save: if this is true, then not saving in the editor
|
|
||||||
will make the return value become `None`.
|
|
||||||
:param extension: the extension to tell the editor about. This defaults
|
|
||||||
to `.txt` but changing this might change syntax
|
|
||||||
highlighting.
|
|
||||||
:param filename: if provided it will edit this file instead of the
|
|
||||||
provided text contents. It will not use a temporary
|
|
||||||
file as an indirection in that case.
|
|
||||||
"""
|
|
||||||
from ._termui_impl import Editor
|
|
||||||
editor = Editor(editor=editor, env=env, require_save=require_save,
|
|
||||||
extension=extension)
|
|
||||||
if filename is None:
|
|
||||||
return editor.edit(text)
|
|
||||||
editor.edit_file(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def launch(url, wait=False, locate=False):
|
|
||||||
"""This function launches the given URL (or filename) in the default
|
|
||||||
viewer application for this file type. If this is an executable, it
|
|
||||||
might launch the executable in a new session. The return value is
|
|
||||||
the exit code of the launched application. Usually, ``0`` indicates
|
|
||||||
success.
|
|
||||||
|
|
||||||
Examples::
|
|
||||||
|
|
||||||
click.launch('http://click.pocoo.org/')
|
|
||||||
click.launch('/my/downloaded/file', locate=True)
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param url: URL or filename of the thing to launch.
|
|
||||||
:param wait: waits for the program to stop.
|
|
||||||
:param locate: if this is set to `True` then instead of launching the
|
|
||||||
application associated with the URL it will attempt to
|
|
||||||
launch a file manager with the file located. This
|
|
||||||
might have weird effects if the URL does not point to
|
|
||||||
the filesystem.
|
|
||||||
"""
|
|
||||||
from ._termui_impl import open_url
|
|
||||||
return open_url(url, wait=wait, locate=locate)
|
|
||||||
|
|
||||||
|
|
||||||
# If this is provided, getchar() calls into this instead. This is used
|
|
||||||
# for unittesting purposes.
|
|
||||||
_getchar = None
|
|
||||||
|
|
||||||
|
|
||||||
def getchar(echo=False):
|
|
||||||
"""Fetches a single character from the terminal and returns it. This
|
|
||||||
will always return a unicode character and under certain rare
|
|
||||||
circumstances this might return more than one character. The
|
|
||||||
situations which more than one character is returned is when for
|
|
||||||
whatever reason multiple characters end up in the terminal buffer or
|
|
||||||
standard input was not actually a terminal.
|
|
||||||
|
|
||||||
Note that this will always read from the terminal, even if something
|
|
||||||
is piped into the standard input.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param echo: if set to `True`, the character read will also show up on
|
|
||||||
the terminal. The default is to not show it.
|
|
||||||
"""
|
|
||||||
f = _getchar
|
|
||||||
if f is None:
|
|
||||||
from ._termui_impl import getchar as f
|
|
||||||
return f(echo)
|
|
||||||
|
|
||||||
|
|
||||||
def pause(info='Press any key to continue ...', err=False):
|
|
||||||
"""This command stops execution and waits for the user to press any
|
|
||||||
key to continue. This is similar to the Windows batch "pause"
|
|
||||||
command. If the program is not run through a terminal, this command
|
|
||||||
will instead do nothing.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
Added the `err` parameter.
|
|
||||||
|
|
||||||
:param info: the info string to print before pausing.
|
|
||||||
:param err: if set to message goes to ``stderr`` instead of
|
|
||||||
``stdout``, the same as with echo.
|
|
||||||
"""
|
|
||||||
if not isatty(sys.stdin) or not isatty(sys.stdout):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
if info:
|
|
||||||
echo(info, nl=False, err=err)
|
|
||||||
try:
|
|
||||||
getchar()
|
|
||||||
except (KeyboardInterrupt, EOFError):
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
if info:
|
|
||||||
echo(err=err)
|
|
|
@ -1,322 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
import contextlib
|
|
||||||
|
|
||||||
from ._compat import iteritems, PY2
|
|
||||||
|
|
||||||
|
|
||||||
# If someone wants to vendor click, we want to ensure the
|
|
||||||
# correct package is discovered. Ideally we could use a
|
|
||||||
# relative import here but unfortunately Python does not
|
|
||||||
# support that.
|
|
||||||
clickpkg = sys.modules[__name__.rsplit('.', 1)[0]]
|
|
||||||
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
else:
|
|
||||||
import io
|
|
||||||
from ._compat import _find_binary_reader
|
|
||||||
|
|
||||||
|
|
||||||
class EchoingStdin(object):
|
|
||||||
|
|
||||||
def __init__(self, input, output):
|
|
||||||
self._input = input
|
|
||||||
self._output = output
|
|
||||||
|
|
||||||
def __getattr__(self, x):
|
|
||||||
return getattr(self._input, x)
|
|
||||||
|
|
||||||
def _echo(self, rv):
|
|
||||||
self._output.write(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def read(self, n=-1):
|
|
||||||
return self._echo(self._input.read(n))
|
|
||||||
|
|
||||||
def readline(self, n=-1):
|
|
||||||
return self._echo(self._input.readline(n))
|
|
||||||
|
|
||||||
def readlines(self):
|
|
||||||
return [self._echo(x) for x in self._input.readlines()]
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._echo(x) for x in self._input)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self._input)
|
|
||||||
|
|
||||||
|
|
||||||
def make_input_stream(input, charset):
|
|
||||||
# Is already an input stream.
|
|
||||||
if hasattr(input, 'read'):
|
|
||||||
if PY2:
|
|
||||||
return input
|
|
||||||
rv = _find_binary_reader(input)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
raise TypeError('Could not find binary reader for input stream.')
|
|
||||||
|
|
||||||
if input is None:
|
|
||||||
input = b''
|
|
||||||
elif not isinstance(input, bytes):
|
|
||||||
input = input.encode(charset)
|
|
||||||
if PY2:
|
|
||||||
return StringIO(input)
|
|
||||||
return io.BytesIO(input)
|
|
||||||
|
|
||||||
|
|
||||||
class Result(object):
|
|
||||||
"""Holds the captured result of an invoked CLI script."""
|
|
||||||
|
|
||||||
def __init__(self, runner, output_bytes, exit_code, exception,
|
|
||||||
exc_info=None):
|
|
||||||
#: The runner that created the result
|
|
||||||
self.runner = runner
|
|
||||||
#: The output as bytes.
|
|
||||||
self.output_bytes = output_bytes
|
|
||||||
#: The exit code as integer.
|
|
||||||
self.exit_code = exit_code
|
|
||||||
#: The exception that happend if one did.
|
|
||||||
self.exception = exception
|
|
||||||
#: The traceback
|
|
||||||
self.exc_info = exc_info
|
|
||||||
|
|
||||||
@property
|
|
||||||
def output(self):
|
|
||||||
"""The output as unicode string."""
|
|
||||||
return self.output_bytes.decode(self.runner.charset, 'replace') \
|
|
||||||
.replace('\r\n', '\n')
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Result %s>' % (
|
|
||||||
self.exception and repr(self.exception) or 'okay',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CliRunner(object):
|
|
||||||
"""The CLI runner provides functionality to invoke a Click command line
|
|
||||||
script for unittesting purposes in a isolated environment. This only
|
|
||||||
works in single-threaded systems without any concurrency as it changes the
|
|
||||||
global interpreter state.
|
|
||||||
|
|
||||||
:param charset: the character set for the input and output data. This is
|
|
||||||
UTF-8 by default and should not be changed currently as
|
|
||||||
the reporting to Click only works in Python 2 properly.
|
|
||||||
:param env: a dictionary with environment variables for overriding.
|
|
||||||
:param echo_stdin: if this is set to `True`, then reading from stdin writes
|
|
||||||
to stdout. This is useful for showing examples in
|
|
||||||
some circumstances. Note that regular prompts
|
|
||||||
will automatically echo the input.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, charset=None, env=None, echo_stdin=False):
|
|
||||||
if charset is None:
|
|
||||||
charset = 'utf-8'
|
|
||||||
self.charset = charset
|
|
||||||
self.env = env or {}
|
|
||||||
self.echo_stdin = echo_stdin
|
|
||||||
|
|
||||||
def get_default_prog_name(self, cli):
|
|
||||||
"""Given a command object it will return the default program name
|
|
||||||
for it. The default is the `name` attribute or ``"root"`` if not
|
|
||||||
set.
|
|
||||||
"""
|
|
||||||
return cli.name or 'root'
|
|
||||||
|
|
||||||
def make_env(self, overrides=None):
|
|
||||||
"""Returns the environment overrides for invoking a script."""
|
|
||||||
rv = dict(self.env)
|
|
||||||
if overrides:
|
|
||||||
rv.update(overrides)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def isolation(self, input=None, env=None, color=False):
|
|
||||||
"""A context manager that sets up the isolation for invoking of a
|
|
||||||
command line tool. This sets up stdin with the given input data
|
|
||||||
and `os.environ` with the overrides from the given dictionary.
|
|
||||||
This also rebinds some internals in Click to be mocked (like the
|
|
||||||
prompt functionality).
|
|
||||||
|
|
||||||
This is automatically done in the :meth:`invoke` method.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
The ``color`` parameter was added.
|
|
||||||
|
|
||||||
:param input: the input stream to put into sys.stdin.
|
|
||||||
:param env: the environment overrides as dictionary.
|
|
||||||
:param color: whether the output should contain color codes. The
|
|
||||||
application can still override this explicitly.
|
|
||||||
"""
|
|
||||||
input = make_input_stream(input, self.charset)
|
|
||||||
|
|
||||||
old_stdin = sys.stdin
|
|
||||||
old_stdout = sys.stdout
|
|
||||||
old_stderr = sys.stderr
|
|
||||||
old_forced_width = clickpkg.formatting.FORCED_WIDTH
|
|
||||||
clickpkg.formatting.FORCED_WIDTH = 80
|
|
||||||
|
|
||||||
env = self.make_env(env)
|
|
||||||
|
|
||||||
if PY2:
|
|
||||||
sys.stdout = sys.stderr = bytes_output = StringIO()
|
|
||||||
if self.echo_stdin:
|
|
||||||
input = EchoingStdin(input, bytes_output)
|
|
||||||
else:
|
|
||||||
bytes_output = io.BytesIO()
|
|
||||||
if self.echo_stdin:
|
|
||||||
input = EchoingStdin(input, bytes_output)
|
|
||||||
input = io.TextIOWrapper(input, encoding=self.charset)
|
|
||||||
sys.stdout = sys.stderr = io.TextIOWrapper(
|
|
||||||
bytes_output, encoding=self.charset)
|
|
||||||
|
|
||||||
sys.stdin = input
|
|
||||||
|
|
||||||
def visible_input(prompt=None):
|
|
||||||
sys.stdout.write(prompt or '')
|
|
||||||
val = input.readline().rstrip('\r\n')
|
|
||||||
sys.stdout.write(val + '\n')
|
|
||||||
sys.stdout.flush()
|
|
||||||
return val
|
|
||||||
|
|
||||||
def hidden_input(prompt=None):
|
|
||||||
sys.stdout.write((prompt or '') + '\n')
|
|
||||||
sys.stdout.flush()
|
|
||||||
return input.readline().rstrip('\r\n')
|
|
||||||
|
|
||||||
def _getchar(echo):
|
|
||||||
char = sys.stdin.read(1)
|
|
||||||
if echo:
|
|
||||||
sys.stdout.write(char)
|
|
||||||
sys.stdout.flush()
|
|
||||||
return char
|
|
||||||
|
|
||||||
default_color = color
|
|
||||||
def should_strip_ansi(stream=None, color=None):
|
|
||||||
if color is None:
|
|
||||||
return not default_color
|
|
||||||
return not color
|
|
||||||
|
|
||||||
old_visible_prompt_func = clickpkg.termui.visible_prompt_func
|
|
||||||
old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func
|
|
||||||
old__getchar_func = clickpkg.termui._getchar
|
|
||||||
old_should_strip_ansi = clickpkg.utils.should_strip_ansi
|
|
||||||
clickpkg.termui.visible_prompt_func = visible_input
|
|
||||||
clickpkg.termui.hidden_prompt_func = hidden_input
|
|
||||||
clickpkg.termui._getchar = _getchar
|
|
||||||
clickpkg.utils.should_strip_ansi = should_strip_ansi
|
|
||||||
|
|
||||||
old_env = {}
|
|
||||||
try:
|
|
||||||
for key, value in iteritems(env):
|
|
||||||
old_env[key] = os.environ.get(key)
|
|
||||||
if value is None:
|
|
||||||
try:
|
|
||||||
del os.environ[key]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
os.environ[key] = value
|
|
||||||
yield bytes_output
|
|
||||||
finally:
|
|
||||||
for key, value in iteritems(old_env):
|
|
||||||
if value is None:
|
|
||||||
try:
|
|
||||||
del os.environ[key]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
os.environ[key] = value
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
sys.stderr = old_stderr
|
|
||||||
sys.stdin = old_stdin
|
|
||||||
clickpkg.termui.visible_prompt_func = old_visible_prompt_func
|
|
||||||
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
|
|
||||||
clickpkg.termui._getchar = old__getchar_func
|
|
||||||
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
|
|
||||||
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
|
||||||
|
|
||||||
def invoke(self, cli, args=None, input=None, env=None,
|
|
||||||
catch_exceptions=True, color=False, **extra):
|
|
||||||
"""Invokes a command in an isolated environment. The arguments are
|
|
||||||
forwarded directly to the command line script, the `extra` keyword
|
|
||||||
arguments are passed to the :meth:`~clickpkg.Command.main` function of
|
|
||||||
the command.
|
|
||||||
|
|
||||||
This returns a :class:`Result` object.
|
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
|
||||||
The ``catch_exceptions`` parameter was added.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.0
|
|
||||||
The result object now has an `exc_info` attribute with the
|
|
||||||
traceback if available.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
The ``color`` parameter was added.
|
|
||||||
|
|
||||||
:param cli: the command to invoke
|
|
||||||
:param args: the arguments to invoke
|
|
||||||
:param input: the input data for `sys.stdin`.
|
|
||||||
:param env: the environment overrides.
|
|
||||||
:param catch_exceptions: Whether to catch any other exceptions than
|
|
||||||
``SystemExit``.
|
|
||||||
:param extra: the keyword arguments to pass to :meth:`main`.
|
|
||||||
:param color: whether the output should contain color codes. The
|
|
||||||
application can still override this explicitly.
|
|
||||||
"""
|
|
||||||
exc_info = None
|
|
||||||
with self.isolation(input=input, env=env, color=color) as out:
|
|
||||||
exception = None
|
|
||||||
exit_code = 0
|
|
||||||
|
|
||||||
try:
|
|
||||||
cli.main(args=args or (),
|
|
||||||
prog_name=self.get_default_prog_name(cli), **extra)
|
|
||||||
except SystemExit as e:
|
|
||||||
if e.code != 0:
|
|
||||||
exception = e
|
|
||||||
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
|
|
||||||
exit_code = e.code
|
|
||||||
if not isinstance(exit_code, int):
|
|
||||||
sys.stdout.write(str(exit_code))
|
|
||||||
sys.stdout.write('\n')
|
|
||||||
exit_code = 1
|
|
||||||
except Exception as e:
|
|
||||||
if not catch_exceptions:
|
|
||||||
raise
|
|
||||||
exception = e
|
|
||||||
exit_code = -1
|
|
||||||
exc_info = sys.exc_info()
|
|
||||||
finally:
|
|
||||||
sys.stdout.flush()
|
|
||||||
output = out.getvalue()
|
|
||||||
|
|
||||||
return Result(runner=self,
|
|
||||||
output_bytes=output,
|
|
||||||
exit_code=exit_code,
|
|
||||||
exception=exception,
|
|
||||||
exc_info=exc_info)
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def isolated_filesystem(self):
|
|
||||||
"""A context manager that creates a temporary folder and changes
|
|
||||||
the current working directory to it for isolated filesystem tests.
|
|
||||||
"""
|
|
||||||
cwd = os.getcwd()
|
|
||||||
t = tempfile.mkdtemp()
|
|
||||||
os.chdir(t)
|
|
||||||
try:
|
|
||||||
yield t
|
|
||||||
finally:
|
|
||||||
os.chdir(cwd)
|
|
||||||
try:
|
|
||||||
shutil.rmtree(t)
|
|
||||||
except (OSError, IOError):
|
|
||||||
pass
|
|
|
@ -1,550 +0,0 @@
|
||||||
import os
|
|
||||||
import stat
|
|
||||||
|
|
||||||
from ._compat import open_stream, text_type, filename_to_ui, \
|
|
||||||
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
|
||||||
from .exceptions import BadParameter
|
|
||||||
from .utils import safecall, LazyFile
|
|
||||||
|
|
||||||
|
|
||||||
class ParamType(object):
|
|
||||||
"""Helper for converting values through types. The following is
|
|
||||||
necessary for a valid type:
|
|
||||||
|
|
||||||
* it needs a name
|
|
||||||
* it needs to pass through None unchanged
|
|
||||||
* it needs to convert from a string
|
|
||||||
* it needs to convert its result type through unchanged
|
|
||||||
(eg: needs to be idempotent)
|
|
||||||
* it needs to be able to deal with param and context being `None`.
|
|
||||||
This can be the case when the object is used with prompt
|
|
||||||
inputs.
|
|
||||||
"""
|
|
||||||
is_composite = False
|
|
||||||
|
|
||||||
#: the descriptive name of this type
|
|
||||||
name = None
|
|
||||||
|
|
||||||
#: if a list of this type is expected and the value is pulled from a
|
|
||||||
#: string environment variable, this is what splits it up. `None`
|
|
||||||
#: means any whitespace. For all parameters the general rule is that
|
|
||||||
#: whitespace splits them up. The exception are paths and files which
|
|
||||||
#: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
|
|
||||||
#: Windows).
|
|
||||||
envvar_list_splitter = None
|
|
||||||
|
|
||||||
def __call__(self, value, param=None, ctx=None):
|
|
||||||
if value is not None:
|
|
||||||
return self.convert(value, param, ctx)
|
|
||||||
|
|
||||||
def get_metavar(self, param):
|
|
||||||
"""Returns the metavar default for this param if it provides one."""
|
|
||||||
|
|
||||||
def get_missing_message(self, param):
|
|
||||||
"""Optionally might return extra information about a missing
|
|
||||||
parameter.
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
"""Converts the value. This is not invoked for values that are
|
|
||||||
`None` (the missing value).
|
|
||||||
"""
|
|
||||||
return value
|
|
||||||
|
|
||||||
def split_envvar_value(self, rv):
|
|
||||||
"""Given a value from an environment variable this splits it up
|
|
||||||
into small chunks depending on the defined envvar list splitter.
|
|
||||||
|
|
||||||
If the splitter is set to `None`, which means that whitespace splits,
|
|
||||||
then leading and trailing whitespace is ignored. Otherwise, leading
|
|
||||||
and trailing splitters usually lead to empty items being included.
|
|
||||||
"""
|
|
||||||
return (rv or '').split(self.envvar_list_splitter)
|
|
||||||
|
|
||||||
def fail(self, message, param=None, ctx=None):
|
|
||||||
"""Helper method to fail with an invalid value message."""
|
|
||||||
raise BadParameter(message, ctx=ctx, param=param)
|
|
||||||
|
|
||||||
|
|
||||||
class CompositeParamType(ParamType):
|
|
||||||
is_composite = True
|
|
||||||
|
|
||||||
@property
|
|
||||||
def arity(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class FuncParamType(ParamType):
|
|
||||||
|
|
||||||
def __init__(self, func):
|
|
||||||
self.name = func.__name__
|
|
||||||
self.func = func
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
try:
|
|
||||||
return self.func(value)
|
|
||||||
except ValueError:
|
|
||||||
try:
|
|
||||||
value = text_type(value)
|
|
||||||
except UnicodeError:
|
|
||||||
value = str(value).decode('utf-8', 'replace')
|
|
||||||
self.fail(value, param, ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class UnprocessedParamType(ParamType):
|
|
||||||
name = 'text'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'UNPROCESSED'
|
|
||||||
|
|
||||||
|
|
||||||
class StringParamType(ParamType):
|
|
||||||
name = 'text'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
enc = _get_argv_encoding()
|
|
||||||
try:
|
|
||||||
value = value.decode(enc)
|
|
||||||
except UnicodeError:
|
|
||||||
fs_enc = get_filesystem_encoding()
|
|
||||||
if fs_enc != enc:
|
|
||||||
try:
|
|
||||||
value = value.decode(fs_enc)
|
|
||||||
except UnicodeError:
|
|
||||||
value = value.decode('utf-8', 'replace')
|
|
||||||
return value
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'STRING'
|
|
||||||
|
|
||||||
|
|
||||||
class Choice(ParamType):
|
|
||||||
"""The choice type allows a value to be checked against a fixed set of
|
|
||||||
supported values. All of these values have to be strings.
|
|
||||||
|
|
||||||
See :ref:`choice-opts` for an example.
|
|
||||||
"""
|
|
||||||
name = 'choice'
|
|
||||||
|
|
||||||
def __init__(self, choices):
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def get_metavar(self, param):
|
|
||||||
return '[%s]' % '|'.join(self.choices)
|
|
||||||
|
|
||||||
def get_missing_message(self, param):
|
|
||||||
return 'Choose from %s.' % ', '.join(self.choices)
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
# Exact match
|
|
||||||
if value in self.choices:
|
|
||||||
return value
|
|
||||||
|
|
||||||
# Match through normalization
|
|
||||||
if ctx is not None and \
|
|
||||||
ctx.token_normalize_func is not None:
|
|
||||||
value = ctx.token_normalize_func(value)
|
|
||||||
for choice in self.choices:
|
|
||||||
if ctx.token_normalize_func(choice) == value:
|
|
||||||
return choice
|
|
||||||
|
|
||||||
self.fail('invalid choice: %s. (choose from %s)' %
|
|
||||||
(value, ', '.join(self.choices)), param, ctx)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Choice(%r)' % list(self.choices)
|
|
||||||
|
|
||||||
|
|
||||||
class IntParamType(ParamType):
|
|
||||||
name = 'integer'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
try:
|
|
||||||
return int(value)
|
|
||||||
except (ValueError, UnicodeError):
|
|
||||||
self.fail('%s is not a valid integer' % value, param, ctx)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'INT'
|
|
||||||
|
|
||||||
|
|
||||||
class IntRange(IntParamType):
|
|
||||||
"""A parameter that works similar to :data:`click.INT` but restricts
|
|
||||||
the value to fit into a range. The default behavior is to fail if the
|
|
||||||
value falls outside the range, but it can also be silently clamped
|
|
||||||
between the two edges.
|
|
||||||
|
|
||||||
See :ref:`ranges` for an example.
|
|
||||||
"""
|
|
||||||
name = 'integer range'
|
|
||||||
|
|
||||||
def __init__(self, min=None, max=None, clamp=False):
|
|
||||||
self.min = min
|
|
||||||
self.max = max
|
|
||||||
self.clamp = clamp
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
rv = IntParamType.convert(self, value, param, ctx)
|
|
||||||
if self.clamp:
|
|
||||||
if self.min is not None and rv < self.min:
|
|
||||||
return self.min
|
|
||||||
if self.max is not None and rv > self.max:
|
|
||||||
return self.max
|
|
||||||
if self.min is not None and rv < self.min or \
|
|
||||||
self.max is not None and rv > self.max:
|
|
||||||
if self.min is None:
|
|
||||||
self.fail('%s is bigger than the maximum valid value '
|
|
||||||
'%s.' % (rv, self.max), param, ctx)
|
|
||||||
elif self.max is None:
|
|
||||||
self.fail('%s is smaller than the minimum valid value '
|
|
||||||
'%s.' % (rv, self.min), param, ctx)
|
|
||||||
else:
|
|
||||||
self.fail('%s is not in the valid range of %s to %s.'
|
|
||||||
% (rv, self.min, self.max), param, ctx)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'IntRange(%r, %r)' % (self.min, self.max)
|
|
||||||
|
|
||||||
|
|
||||||
class BoolParamType(ParamType):
|
|
||||||
name = 'boolean'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
if isinstance(value, bool):
|
|
||||||
return bool(value)
|
|
||||||
value = value.lower()
|
|
||||||
if value in ('true', '1', 'yes', 'y'):
|
|
||||||
return True
|
|
||||||
elif value in ('false', '0', 'no', 'n'):
|
|
||||||
return False
|
|
||||||
self.fail('%s is not a valid boolean' % value, param, ctx)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'BOOL'
|
|
||||||
|
|
||||||
|
|
||||||
class FloatParamType(ParamType):
|
|
||||||
name = 'float'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
try:
|
|
||||||
return float(value)
|
|
||||||
except (UnicodeError, ValueError):
|
|
||||||
self.fail('%s is not a valid floating point value' %
|
|
||||||
value, param, ctx)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'FLOAT'
|
|
||||||
|
|
||||||
|
|
||||||
class UUIDParameterType(ParamType):
|
|
||||||
name = 'uuid'
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
import uuid
|
|
||||||
try:
|
|
||||||
if PY2 and isinstance(value, text_type):
|
|
||||||
value = value.encode('ascii')
|
|
||||||
return uuid.UUID(value)
|
|
||||||
except (UnicodeError, ValueError):
|
|
||||||
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'UUID'
|
|
||||||
|
|
||||||
|
|
||||||
class File(ParamType):
|
|
||||||
"""Declares a parameter to be a file for reading or writing. The file
|
|
||||||
is automatically closed once the context tears down (after the command
|
|
||||||
finished working).
|
|
||||||
|
|
||||||
Files can be opened for reading or writing. The special value ``-``
|
|
||||||
indicates stdin or stdout depending on the mode.
|
|
||||||
|
|
||||||
By default, the file is opened for reading text data, but it can also be
|
|
||||||
opened in binary mode or for writing. The encoding parameter can be used
|
|
||||||
to force a specific encoding.
|
|
||||||
|
|
||||||
The `lazy` flag controls if the file should be opened immediately or
|
|
||||||
upon first IO. The default is to be non lazy for standard input and
|
|
||||||
output streams as well as files opened for reading, lazy otherwise.
|
|
||||||
|
|
||||||
Starting with Click 2.0, files can also be opened atomically in which
|
|
||||||
case all writes go into a separate file in the same folder and upon
|
|
||||||
completion the file will be moved over to the original location. This
|
|
||||||
is useful if a file regularly read by other users is modified.
|
|
||||||
|
|
||||||
See :ref:`file-args` for more information.
|
|
||||||
"""
|
|
||||||
name = 'filename'
|
|
||||||
envvar_list_splitter = os.path.pathsep
|
|
||||||
|
|
||||||
def __init__(self, mode='r', encoding=None, errors='strict', lazy=None,
|
|
||||||
atomic=False):
|
|
||||||
self.mode = mode
|
|
||||||
self.encoding = encoding
|
|
||||||
self.errors = errors
|
|
||||||
self.lazy = lazy
|
|
||||||
self.atomic = atomic
|
|
||||||
|
|
||||||
def resolve_lazy_flag(self, value):
|
|
||||||
if self.lazy is not None:
|
|
||||||
return self.lazy
|
|
||||||
if value == '-':
|
|
||||||
return False
|
|
||||||
elif 'w' in self.mode:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
try:
|
|
||||||
if hasattr(value, 'read') or hasattr(value, 'write'):
|
|
||||||
return value
|
|
||||||
|
|
||||||
lazy = self.resolve_lazy_flag(value)
|
|
||||||
|
|
||||||
if lazy:
|
|
||||||
f = LazyFile(value, self.mode, self.encoding, self.errors,
|
|
||||||
atomic=self.atomic)
|
|
||||||
if ctx is not None:
|
|
||||||
ctx.call_on_close(f.close_intelligently)
|
|
||||||
return f
|
|
||||||
|
|
||||||
f, should_close = open_stream(value, self.mode,
|
|
||||||
self.encoding, self.errors,
|
|
||||||
atomic=self.atomic)
|
|
||||||
# If a context is provided, we automatically close the file
|
|
||||||
# at the end of the context execution (or flush out). If a
|
|
||||||
# context does not exist, it's the caller's responsibility to
|
|
||||||
# properly close the file. This for instance happens when the
|
|
||||||
# type is used with prompts.
|
|
||||||
if ctx is not None:
|
|
||||||
if should_close:
|
|
||||||
ctx.call_on_close(safecall(f.close))
|
|
||||||
else:
|
|
||||||
ctx.call_on_close(safecall(f.flush))
|
|
||||||
return f
|
|
||||||
except (IOError, OSError) as e:
|
|
||||||
self.fail('Could not open file: %s: %s' % (
|
|
||||||
filename_to_ui(value),
|
|
||||||
get_streerror(e),
|
|
||||||
), param, ctx)
|
|
||||||
|
|
||||||
|
|
||||||
class Path(ParamType):
|
|
||||||
"""The path type is similar to the :class:`File` type but it performs
|
|
||||||
different checks. First of all, instead of returning an open file
|
|
||||||
handle it returns just the filename. Secondly, it can perform various
|
|
||||||
basic checks about what the file or directory should be.
|
|
||||||
|
|
||||||
.. versionchanged:: 6.0
|
|
||||||
`allow_dash` was added.
|
|
||||||
|
|
||||||
:param exists: if set to true, the file or directory needs to exist for
|
|
||||||
this value to be valid. If this is not required and a
|
|
||||||
file does indeed not exist, then all further checks are
|
|
||||||
silently skipped.
|
|
||||||
:param file_okay: controls if a file is a possible value.
|
|
||||||
:param dir_okay: controls if a directory is a possible value.
|
|
||||||
:param writable: if true, a writable check is performed.
|
|
||||||
:param readable: if true, a readable check is performed.
|
|
||||||
:param resolve_path: if this is true, then the path is fully resolved
|
|
||||||
before the value is passed onwards. This means
|
|
||||||
that it's absolute and symlinks are resolved.
|
|
||||||
:param allow_dash: If this is set to `True`, a single dash to indicate
|
|
||||||
standard streams is permitted.
|
|
||||||
:param type: optionally a string type that should be used to
|
|
||||||
represent the path. The default is `None` which
|
|
||||||
means the return value will be either bytes or
|
|
||||||
unicode depending on what makes most sense given the
|
|
||||||
input data Click deals with.
|
|
||||||
"""
|
|
||||||
envvar_list_splitter = os.path.pathsep
|
|
||||||
|
|
||||||
def __init__(self, exists=False, file_okay=True, dir_okay=True,
|
|
||||||
writable=False, readable=True, resolve_path=False,
|
|
||||||
allow_dash=False, path_type=None):
|
|
||||||
self.exists = exists
|
|
||||||
self.file_okay = file_okay
|
|
||||||
self.dir_okay = dir_okay
|
|
||||||
self.writable = writable
|
|
||||||
self.readable = readable
|
|
||||||
self.resolve_path = resolve_path
|
|
||||||
self.allow_dash = allow_dash
|
|
||||||
self.type = path_type
|
|
||||||
|
|
||||||
if self.file_okay and not self.dir_okay:
|
|
||||||
self.name = 'file'
|
|
||||||
self.path_type = 'File'
|
|
||||||
if self.dir_okay and not self.file_okay:
|
|
||||||
self.name = 'directory'
|
|
||||||
self.path_type = 'Directory'
|
|
||||||
else:
|
|
||||||
self.name = 'path'
|
|
||||||
self.path_type = 'Path'
|
|
||||||
|
|
||||||
def coerce_path_result(self, rv):
|
|
||||||
if self.type is not None and not isinstance(rv, self.type):
|
|
||||||
if self.type is text_type:
|
|
||||||
rv = rv.decode(get_filesystem_encoding())
|
|
||||||
else:
|
|
||||||
rv = rv.encode(get_filesystem_encoding())
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
rv = value
|
|
||||||
|
|
||||||
is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-')
|
|
||||||
|
|
||||||
if not is_dash:
|
|
||||||
if self.resolve_path:
|
|
||||||
rv = os.path.realpath(rv)
|
|
||||||
|
|
||||||
try:
|
|
||||||
st = os.stat(rv)
|
|
||||||
except OSError:
|
|
||||||
if not self.exists:
|
|
||||||
return self.coerce_path_result(rv)
|
|
||||||
self.fail('%s "%s" does not exist.' % (
|
|
||||||
self.path_type,
|
|
||||||
filename_to_ui(value)
|
|
||||||
), param, ctx)
|
|
||||||
|
|
||||||
if not self.file_okay and stat.S_ISREG(st.st_mode):
|
|
||||||
self.fail('%s "%s" is a file.' % (
|
|
||||||
self.path_type,
|
|
||||||
filename_to_ui(value)
|
|
||||||
), param, ctx)
|
|
||||||
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
|
||||||
self.fail('%s "%s" is a directory.' % (
|
|
||||||
self.path_type,
|
|
||||||
filename_to_ui(value)
|
|
||||||
), param, ctx)
|
|
||||||
if self.writable and not os.access(value, os.W_OK):
|
|
||||||
self.fail('%s "%s" is not writable.' % (
|
|
||||||
self.path_type,
|
|
||||||
filename_to_ui(value)
|
|
||||||
), param, ctx)
|
|
||||||
if self.readable and not os.access(value, os.R_OK):
|
|
||||||
self.fail('%s "%s" is not readable.' % (
|
|
||||||
self.path_type,
|
|
||||||
filename_to_ui(value)
|
|
||||||
), param, ctx)
|
|
||||||
|
|
||||||
return self.coerce_path_result(rv)
|
|
||||||
|
|
||||||
|
|
||||||
class Tuple(CompositeParamType):
|
|
||||||
"""The default behavior of Click is to apply a type on a value directly.
|
|
||||||
This works well in most cases, except for when `nargs` is set to a fixed
|
|
||||||
count and different types should be used for different items. In this
|
|
||||||
case the :class:`Tuple` type can be used. This type can only be used
|
|
||||||
if `nargs` is set to a fixed number.
|
|
||||||
|
|
||||||
For more information see :ref:`tuple-type`.
|
|
||||||
|
|
||||||
This can be selected by using a Python tuple literal as a type.
|
|
||||||
|
|
||||||
:param types: a list of types that should be used for the tuple items.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, types):
|
|
||||||
self.types = [convert_type(ty) for ty in types]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return "<" + " ".join(ty.name for ty in self.types) + ">"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def arity(self):
|
|
||||||
return len(self.types)
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
if len(value) != len(self.types):
|
|
||||||
raise TypeError('It would appear that nargs is set to conflict '
|
|
||||||
'with the composite type arity.')
|
|
||||||
return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
|
|
||||||
|
|
||||||
|
|
||||||
def convert_type(ty, default=None):
|
|
||||||
"""Converts a callable or python ty into the most appropriate param
|
|
||||||
ty.
|
|
||||||
"""
|
|
||||||
guessed_type = False
|
|
||||||
if ty is None and default is not None:
|
|
||||||
if isinstance(default, tuple):
|
|
||||||
ty = tuple(map(type, default))
|
|
||||||
else:
|
|
||||||
ty = type(default)
|
|
||||||
guessed_type = True
|
|
||||||
|
|
||||||
if isinstance(ty, tuple):
|
|
||||||
return Tuple(ty)
|
|
||||||
if isinstance(ty, ParamType):
|
|
||||||
return ty
|
|
||||||
if ty is text_type or ty is str or ty is None:
|
|
||||||
return STRING
|
|
||||||
if ty is int:
|
|
||||||
return INT
|
|
||||||
# Booleans are only okay if not guessed. This is done because for
|
|
||||||
# flags the default value is actually a bit of a lie in that it
|
|
||||||
# indicates which of the flags is the one we want. See get_default()
|
|
||||||
# for more information.
|
|
||||||
if ty is bool and not guessed_type:
|
|
||||||
return BOOL
|
|
||||||
if ty is float:
|
|
||||||
return FLOAT
|
|
||||||
if guessed_type:
|
|
||||||
return STRING
|
|
||||||
|
|
||||||
# Catch a common mistake
|
|
||||||
if __debug__:
|
|
||||||
try:
|
|
||||||
if issubclass(ty, ParamType):
|
|
||||||
raise AssertionError('Attempted to use an uninstantiated '
|
|
||||||
'parameter type (%s).' % ty)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
return FuncParamType(ty)
|
|
||||||
|
|
||||||
|
|
||||||
#: A dummy parameter type that just does nothing. From a user's
|
|
||||||
#: perspective this appears to just be the same as `STRING` but internally
|
|
||||||
#: no string conversion takes place. This is necessary to achieve the
|
|
||||||
#: same bytes/unicode behavior on Python 2/3 in situations where you want
|
|
||||||
#: to not convert argument types. This is usually useful when working
|
|
||||||
#: with file paths as they can appear in bytes and unicode.
|
|
||||||
#:
|
|
||||||
#: For path related uses the :class:`Path` type is a better choice but
|
|
||||||
#: there are situations where an unprocessed type is useful which is why
|
|
||||||
#: it is is provided.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 4.0
|
|
||||||
UNPROCESSED = UnprocessedParamType()
|
|
||||||
|
|
||||||
#: A unicode string parameter type which is the implicit default. This
|
|
||||||
#: can also be selected by using ``str`` as type.
|
|
||||||
STRING = StringParamType()
|
|
||||||
|
|
||||||
#: An integer parameter. This can also be selected by using ``int`` as
|
|
||||||
#: type.
|
|
||||||
INT = IntParamType()
|
|
||||||
|
|
||||||
#: A floating point value parameter. This can also be selected by using
|
|
||||||
#: ``float`` as type.
|
|
||||||
FLOAT = FloatParamType()
|
|
||||||
|
|
||||||
#: A boolean parameter. This is the default for boolean flags. This can
|
|
||||||
#: also be selected by using ``bool`` as a type.
|
|
||||||
BOOL = BoolParamType()
|
|
||||||
|
|
||||||
#: A UUID parameter.
|
|
||||||
UUID = UUIDParameterType()
|
|
|
@ -1,415 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from .globals import resolve_color_default
|
|
||||||
|
|
||||||
from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
|
||||||
get_streerror, string_types, PY2, binary_streams, text_streams, \
|
|
||||||
filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \
|
|
||||||
_default_text_stdout, _default_text_stderr, is_bytes, WIN
|
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
from ._compat import _find_binary_writer
|
|
||||||
elif WIN:
|
|
||||||
from ._winconsole import _get_windows_argv, \
|
|
||||||
_hash_py_argv, _initial_argv_hash
|
|
||||||
|
|
||||||
|
|
||||||
echo_native_types = string_types + (bytes, bytearray)
|
|
||||||
|
|
||||||
|
|
||||||
def _posixify(name):
|
|
||||||
return '-'.join(name.split()).lower()
|
|
||||||
|
|
||||||
|
|
||||||
def safecall(func):
|
|
||||||
"""Wraps a function so that it swallows exceptions."""
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def make_str(value):
|
|
||||||
"""Converts a value into a valid string."""
|
|
||||||
if isinstance(value, bytes):
|
|
||||||
try:
|
|
||||||
return value.decode(get_filesystem_encoding())
|
|
||||||
except UnicodeError:
|
|
||||||
return value.decode('utf-8', 'replace')
|
|
||||||
return text_type(value)
|
|
||||||
|
|
||||||
|
|
||||||
def make_default_short_help(help, max_length=45):
|
|
||||||
words = help.split()
|
|
||||||
total_length = 0
|
|
||||||
result = []
|
|
||||||
done = False
|
|
||||||
|
|
||||||
for word in words:
|
|
||||||
if word[-1:] == '.':
|
|
||||||
done = True
|
|
||||||
new_length = result and 1 + len(word) or len(word)
|
|
||||||
if total_length + new_length > max_length:
|
|
||||||
result.append('...')
|
|
||||||
done = True
|
|
||||||
else:
|
|
||||||
if result:
|
|
||||||
result.append(' ')
|
|
||||||
result.append(word)
|
|
||||||
if done:
|
|
||||||
break
|
|
||||||
total_length += new_length
|
|
||||||
|
|
||||||
return ''.join(result)
|
|
||||||
|
|
||||||
|
|
||||||
class LazyFile(object):
|
|
||||||
"""A lazy file works like a regular file but it does not fully open
|
|
||||||
the file but it does perform some basic checks early to see if the
|
|
||||||
filename parameter does make sense. This is useful for safely opening
|
|
||||||
files for writing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, filename, mode='r', encoding=None, errors='strict',
|
|
||||||
atomic=False):
|
|
||||||
self.name = filename
|
|
||||||
self.mode = mode
|
|
||||||
self.encoding = encoding
|
|
||||||
self.errors = errors
|
|
||||||
self.atomic = atomic
|
|
||||||
|
|
||||||
if filename == '-':
|
|
||||||
self._f, self.should_close = open_stream(filename, mode,
|
|
||||||
encoding, errors)
|
|
||||||
else:
|
|
||||||
if 'r' in mode:
|
|
||||||
# Open and close the file in case we're opening it for
|
|
||||||
# reading so that we can catch at least some errors in
|
|
||||||
# some cases early.
|
|
||||||
open(filename, mode).close()
|
|
||||||
self._f = None
|
|
||||||
self.should_close = True
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self.open(), name)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self._f is not None:
|
|
||||||
return repr(self._f)
|
|
||||||
return '<unopened file %r %s>' % (self.name, self.mode)
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
"""Opens the file if it's not yet open. This call might fail with
|
|
||||||
a :exc:`FileError`. Not handling this error will produce an error
|
|
||||||
that Click shows.
|
|
||||||
"""
|
|
||||||
if self._f is not None:
|
|
||||||
return self._f
|
|
||||||
try:
|
|
||||||
rv, self.should_close = open_stream(self.name, self.mode,
|
|
||||||
self.encoding,
|
|
||||||
self.errors,
|
|
||||||
atomic=self.atomic)
|
|
||||||
except (IOError, OSError) as e:
|
|
||||||
from .exceptions import FileError
|
|
||||||
raise FileError(self.name, hint=get_streerror(e))
|
|
||||||
self._f = rv
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
"""Closes the underlying file, no matter what."""
|
|
||||||
if self._f is not None:
|
|
||||||
self._f.close()
|
|
||||||
|
|
||||||
def close_intelligently(self):
|
|
||||||
"""This function only closes the file if it was opened by the lazy
|
|
||||||
file wrapper. For instance this will never close stdin.
|
|
||||||
"""
|
|
||||||
if self.should_close:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.close_intelligently()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
self.open()
|
|
||||||
return iter(self._f)
|
|
||||||
|
|
||||||
|
|
||||||
class KeepOpenFile(object):
|
|
||||||
|
|
||||||
def __init__(self, file):
|
|
||||||
self._file = file
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._file, name)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return repr(self._file)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self._file)
|
|
||||||
|
|
||||||
|
|
||||||
def echo(message=None, file=None, nl=True, err=False, color=None):
|
|
||||||
"""Prints a message plus a newline to the given file or stdout. On
|
|
||||||
first sight, this looks like the print function, but it has improved
|
|
||||||
support for handling Unicode and binary data that does not fail no
|
|
||||||
matter how badly configured the system is.
|
|
||||||
|
|
||||||
Primarily it means that you can print binary data as well as Unicode
|
|
||||||
data on both 2.x and 3.x to the given file in the most appropriate way
|
|
||||||
possible. This is a very carefree function as in that it will try its
|
|
||||||
best to not fail. As of Click 6.0 this includes support for unicode
|
|
||||||
output on the Windows console.
|
|
||||||
|
|
||||||
In addition to that, if `colorama`_ is installed, the echo function will
|
|
||||||
also support clever handling of ANSI codes. Essentially it will then
|
|
||||||
do the following:
|
|
||||||
|
|
||||||
- add transparent handling of ANSI color codes on Windows.
|
|
||||||
- hide ANSI codes automatically if the destination file is not a
|
|
||||||
terminal.
|
|
||||||
|
|
||||||
.. _colorama: http://pypi.python.org/pypi/colorama
|
|
||||||
|
|
||||||
.. versionchanged:: 6.0
|
|
||||||
As of Click 6.0 the echo function will properly support unicode
|
|
||||||
output on the windows console. Not that click does not modify
|
|
||||||
the interpreter in any way which means that `sys.stdout` or the
|
|
||||||
print statement or function will still not provide unicode support.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.0
|
|
||||||
Starting with version 2.0 of Click, the echo function will work
|
|
||||||
with colorama if it's installed.
|
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
|
||||||
The `err` parameter was added.
|
|
||||||
|
|
||||||
.. versionchanged:: 4.0
|
|
||||||
Added the `color` flag.
|
|
||||||
|
|
||||||
:param message: the message to print
|
|
||||||
:param file: the file to write to (defaults to ``stdout``)
|
|
||||||
:param err: if set to true the file defaults to ``stderr`` instead of
|
|
||||||
``stdout``. This is faster and easier than calling
|
|
||||||
:func:`get_text_stderr` yourself.
|
|
||||||
:param nl: if set to `True` (the default) a newline is printed afterwards.
|
|
||||||
:param color: controls if the terminal supports ANSI colors or not. The
|
|
||||||
default is autodetection.
|
|
||||||
"""
|
|
||||||
if file is None:
|
|
||||||
if err:
|
|
||||||
file = _default_text_stderr()
|
|
||||||
else:
|
|
||||||
file = _default_text_stdout()
|
|
||||||
|
|
||||||
# Convert non bytes/text into the native string type.
|
|
||||||
if message is not None and not isinstance(message, echo_native_types):
|
|
||||||
message = text_type(message)
|
|
||||||
|
|
||||||
if nl:
|
|
||||||
message = message or u''
|
|
||||||
if isinstance(message, text_type):
|
|
||||||
message += u'\n'
|
|
||||||
else:
|
|
||||||
message += b'\n'
|
|
||||||
|
|
||||||
# If there is a message, and we're in Python 3, and the value looks
|
|
||||||
# like bytes, we manually need to find the binary stream and write the
|
|
||||||
# message in there. This is done separately so that most stream
|
|
||||||
# types will work as you would expect. Eg: you can write to StringIO
|
|
||||||
# for other cases.
|
|
||||||
if message and not PY2 and is_bytes(message):
|
|
||||||
binary_file = _find_binary_writer(file)
|
|
||||||
if binary_file is not None:
|
|
||||||
file.flush()
|
|
||||||
binary_file.write(message)
|
|
||||||
binary_file.flush()
|
|
||||||
return
|
|
||||||
|
|
||||||
# ANSI-style support. If there is no message or we are dealing with
|
|
||||||
# bytes nothing is happening. If we are connected to a file we want
|
|
||||||
# to strip colors. If we are on windows we either wrap the stream
|
|
||||||
# to strip the color or we use the colorama support to translate the
|
|
||||||
# ansi codes to API calls.
|
|
||||||
if message and not is_bytes(message):
|
|
||||||
color = resolve_color_default(color)
|
|
||||||
if should_strip_ansi(file, color):
|
|
||||||
message = strip_ansi(message)
|
|
||||||
elif WIN:
|
|
||||||
if auto_wrap_for_ansi is not None:
|
|
||||||
file = auto_wrap_for_ansi(file)
|
|
||||||
elif not color:
|
|
||||||
message = strip_ansi(message)
|
|
||||||
|
|
||||||
if message:
|
|
||||||
file.write(message)
|
|
||||||
file.flush()
|
|
||||||
|
|
||||||
|
|
||||||
def get_binary_stream(name):
|
|
||||||
"""Returns a system stream for byte processing. This essentially
|
|
||||||
returns the stream from the sys module with the given name but it
|
|
||||||
solves some compatibility issues between different Python versions.
|
|
||||||
Primarily this function is necessary for getting binary streams on
|
|
||||||
Python 3.
|
|
||||||
|
|
||||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
|
||||||
``'stdout'`` and ``'stderr'``
|
|
||||||
"""
|
|
||||||
opener = binary_streams.get(name)
|
|
||||||
if opener is None:
|
|
||||||
raise TypeError('Unknown standard stream %r' % name)
|
|
||||||
return opener()
|
|
||||||
|
|
||||||
|
|
||||||
def get_text_stream(name, encoding=None, errors='strict'):
|
|
||||||
"""Returns a system stream for text processing. This usually returns
|
|
||||||
a wrapped stream around a binary stream returned from
|
|
||||||
:func:`get_binary_stream` but it also can take shortcuts on Python 3
|
|
||||||
for already correctly configured streams.
|
|
||||||
|
|
||||||
:param name: the name of the stream to open. Valid names are ``'stdin'``,
|
|
||||||
``'stdout'`` and ``'stderr'``
|
|
||||||
:param encoding: overrides the detected default encoding.
|
|
||||||
:param errors: overrides the default error mode.
|
|
||||||
"""
|
|
||||||
opener = text_streams.get(name)
|
|
||||||
if opener is None:
|
|
||||||
raise TypeError('Unknown standard stream %r' % name)
|
|
||||||
return opener(encoding, errors)
|
|
||||||
|
|
||||||
|
|
||||||
def open_file(filename, mode='r', encoding=None, errors='strict',
|
|
||||||
lazy=False, atomic=False):
|
|
||||||
"""This is similar to how the :class:`File` works but for manual
|
|
||||||
usage. Files are opened non lazy by default. This can open regular
|
|
||||||
files as well as stdin/stdout if ``'-'`` is passed.
|
|
||||||
|
|
||||||
If stdin/stdout is returned the stream is wrapped so that the context
|
|
||||||
manager will not close the stream accidentally. This makes it possible
|
|
||||||
to always use the function like this without having to worry to
|
|
||||||
accidentally close a standard stream::
|
|
||||||
|
|
||||||
with open_file(filename) as f:
|
|
||||||
...
|
|
||||||
|
|
||||||
.. versionadded:: 3.0
|
|
||||||
|
|
||||||
:param filename: the name of the file to open (or ``'-'`` for stdin/stdout).
|
|
||||||
:param mode: the mode in which to open the file.
|
|
||||||
:param encoding: the encoding to use.
|
|
||||||
:param errors: the error handling for this file.
|
|
||||||
:param lazy: can be flipped to true to open the file lazily.
|
|
||||||
:param atomic: in atomic mode writes go into a temporary file and it's
|
|
||||||
moved on close.
|
|
||||||
"""
|
|
||||||
if lazy:
|
|
||||||
return LazyFile(filename, mode, encoding, errors, atomic=atomic)
|
|
||||||
f, should_close = open_stream(filename, mode, encoding, errors,
|
|
||||||
atomic=atomic)
|
|
||||||
if not should_close:
|
|
||||||
f = KeepOpenFile(f)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def get_os_args():
|
|
||||||
"""This returns the argument part of sys.argv in the most appropriate
|
|
||||||
form for processing. What this means is that this return value is in
|
|
||||||
a format that works for Click to process but does not necessarily
|
|
||||||
correspond well to what's actually standard for the interpreter.
|
|
||||||
|
|
||||||
On most environments the return value is ``sys.argv[:1]`` unchanged.
|
|
||||||
However if you are on Windows and running Python 2 the return value
|
|
||||||
will actually be a list of unicode strings instead because the
|
|
||||||
default behavior on that platform otherwise will not be able to
|
|
||||||
carry all possible values that sys.argv can have.
|
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
|
||||||
"""
|
|
||||||
# We can only extract the unicode argv if sys.argv has not been
|
|
||||||
# changed since the startup of the application.
|
|
||||||
if PY2 and WIN and _initial_argv_hash == _hash_py_argv():
|
|
||||||
return _get_windows_argv()
|
|
||||||
return sys.argv[1:]
|
|
||||||
|
|
||||||
|
|
||||||
def format_filename(filename, shorten=False):
|
|
||||||
"""Formats a filename for user display. The main purpose of this
|
|
||||||
function is to ensure that the filename can be displayed at all. This
|
|
||||||
will decode the filename to unicode if necessary in a way that it will
|
|
||||||
not fail. Optionally, it can shorten the filename to not include the
|
|
||||||
full path to the filename.
|
|
||||||
|
|
||||||
:param filename: formats a filename for UI display. This will also convert
|
|
||||||
the filename into unicode without failing.
|
|
||||||
:param shorten: this optionally shortens the filename to strip of the
|
|
||||||
path that leads up to it.
|
|
||||||
"""
|
|
||||||
if shorten:
|
|
||||||
filename = os.path.basename(filename)
|
|
||||||
return filename_to_ui(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def get_app_dir(app_name, roaming=True, force_posix=False):
|
|
||||||
r"""Returns the config folder for the application. The default behavior
|
|
||||||
is to return whatever is most appropriate for the operating system.
|
|
||||||
|
|
||||||
To give you an idea, for an app called ``"Foo Bar"``, something like
|
|
||||||
the following folders could be returned:
|
|
||||||
|
|
||||||
Mac OS X:
|
|
||||||
``~/Library/Application Support/Foo Bar``
|
|
||||||
Mac OS X (POSIX):
|
|
||||||
``~/.foo-bar``
|
|
||||||
Unix:
|
|
||||||
``~/.config/foo-bar``
|
|
||||||
Unix (POSIX):
|
|
||||||
``~/.foo-bar``
|
|
||||||
Win XP (roaming):
|
|
||||||
``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
|
|
||||||
Win XP (not roaming):
|
|
||||||
``C:\Documents and Settings\<user>\Application Data\Foo Bar``
|
|
||||||
Win 7 (roaming):
|
|
||||||
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
|
||||||
Win 7 (not roaming):
|
|
||||||
``C:\Users\<user>\AppData\Local\Foo Bar``
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param app_name: the application name. This should be properly capitalized
|
|
||||||
and can contain whitespace.
|
|
||||||
:param roaming: controls if the folder should be roaming or not on Windows.
|
|
||||||
Has no affect otherwise.
|
|
||||||
:param force_posix: if this is set to `True` then on any POSIX system the
|
|
||||||
folder will be stored in the home folder with a leading
|
|
||||||
dot instead of the XDG config home or darwin's
|
|
||||||
application support folder.
|
|
||||||
"""
|
|
||||||
if WIN:
|
|
||||||
key = roaming and 'APPDATA' or 'LOCALAPPDATA'
|
|
||||||
folder = os.environ.get(key)
|
|
||||||
if folder is None:
|
|
||||||
folder = os.path.expanduser('~')
|
|
||||||
return os.path.join(folder, app_name)
|
|
||||||
if force_posix:
|
|
||||||
return os.path.join(os.path.expanduser('~/.' + _posixify(app_name)))
|
|
||||||
if sys.platform == 'darwin':
|
|
||||||
return os.path.join(os.path.expanduser(
|
|
||||||
'~/Library/Application Support'), app_name)
|
|
||||||
return os.path.join(
|
|
||||||
os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')),
|
|
||||||
_posixify(app_name))
|
|
|
@ -1,49 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask
|
|
||||||
~~~~~
|
|
||||||
|
|
||||||
A microframework based on Werkzeug. It's extensively documented
|
|
||||||
and follows best practice patterns.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__version__ = '1.0.2'
|
|
||||||
|
|
||||||
# utilities we import from Werkzeug and Jinja2 that are unused
|
|
||||||
# in the module but are exported as public interface.
|
|
||||||
from werkzeug.exceptions import abort
|
|
||||||
from werkzeug.utils import redirect
|
|
||||||
from jinja2 import Markup, escape
|
|
||||||
|
|
||||||
from .app import Flask, Request, Response
|
|
||||||
from .config import Config
|
|
||||||
from .helpers import url_for, flash, send_file, send_from_directory, \
|
|
||||||
get_flashed_messages, get_template_attribute, make_response, safe_join, \
|
|
||||||
stream_with_context
|
|
||||||
from .globals import current_app, g, request, session, _request_ctx_stack, \
|
|
||||||
_app_ctx_stack
|
|
||||||
from .ctx import has_request_context, has_app_context, \
|
|
||||||
after_this_request, copy_current_request_context
|
|
||||||
from .blueprints import Blueprint
|
|
||||||
from .templating import render_template, render_template_string
|
|
||||||
|
|
||||||
# the signals
|
|
||||||
from .signals import signals_available, template_rendered, request_started, \
|
|
||||||
request_finished, got_request_exception, request_tearing_down, \
|
|
||||||
appcontext_tearing_down, appcontext_pushed, \
|
|
||||||
appcontext_popped, message_flashed, before_render_template
|
|
||||||
|
|
||||||
# We're not exposing the actual json module but a convenient wrapper around
|
|
||||||
# it.
|
|
||||||
from . import json
|
|
||||||
|
|
||||||
# This was the only thing that Flask used to export at one point and it had
|
|
||||||
# a more generic name.
|
|
||||||
jsonify = json.jsonify
|
|
||||||
|
|
||||||
# backwards compat, goes away in 1.0
|
|
||||||
from .sessions import SecureCookieSession as Session
|
|
||||||
json_available = True
|
|
|
@ -1,14 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.__main__
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Alias for flask.run for the command line.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from .cli import main
|
|
||||||
main(as_module=True)
|
|
|
@ -1,99 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask._compat
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Some py2/py3 compatibility support based on a stripped down
|
|
||||||
version of six so we don't have to depend on a specific version
|
|
||||||
of it.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
PY2 = sys.version_info[0] == 2
|
|
||||||
_identity = lambda x: x
|
|
||||||
|
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
text_type = str
|
|
||||||
string_types = (str,)
|
|
||||||
integer_types = (int,)
|
|
||||||
|
|
||||||
iterkeys = lambda d: iter(d.keys())
|
|
||||||
itervalues = lambda d: iter(d.values())
|
|
||||||
iteritems = lambda d: iter(d.items())
|
|
||||||
|
|
||||||
from inspect import getfullargspec as getargspec
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
def reraise(tp, value, tb=None):
|
|
||||||
if value.__traceback__ is not tb:
|
|
||||||
raise value.with_traceback(tb)
|
|
||||||
raise value
|
|
||||||
|
|
||||||
implements_to_string = _identity
|
|
||||||
|
|
||||||
else:
|
|
||||||
text_type = unicode
|
|
||||||
string_types = (str, unicode)
|
|
||||||
integer_types = (int, long)
|
|
||||||
|
|
||||||
iterkeys = lambda d: d.iterkeys()
|
|
||||||
itervalues = lambda d: d.itervalues()
|
|
||||||
iteritems = lambda d: d.iteritems()
|
|
||||||
|
|
||||||
from inspect import getargspec
|
|
||||||
from cStringIO import StringIO
|
|
||||||
|
|
||||||
exec('def reraise(tp, value, tb=None):\n raise tp, value, tb')
|
|
||||||
|
|
||||||
def implements_to_string(cls):
|
|
||||||
cls.__unicode__ = cls.__str__
|
|
||||||
cls.__str__ = lambda x: x.__unicode__().encode('utf-8')
|
|
||||||
return cls
|
|
||||||
|
|
||||||
|
|
||||||
def with_metaclass(meta, *bases):
|
|
||||||
"""Create a base class with a metaclass."""
|
|
||||||
# This requires a bit of explanation: the basic idea is to make a
|
|
||||||
# dummy metaclass for one level of class instantiation that replaces
|
|
||||||
# itself with the actual metaclass.
|
|
||||||
class metaclass(type):
|
|
||||||
def __new__(cls, name, this_bases, d):
|
|
||||||
return meta(name, bases, d)
|
|
||||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
|
||||||
|
|
||||||
|
|
||||||
# Certain versions of pypy have a bug where clearing the exception stack
|
|
||||||
# breaks the __exit__ function in a very peculiar way. The second level of
|
|
||||||
# exception blocks is necessary because pypy seems to forget to check if an
|
|
||||||
# exception happened until the next bytecode instruction?
|
|
||||||
#
|
|
||||||
# Relevant PyPy bugfix commit:
|
|
||||||
# https://bitbucket.org/pypy/pypy/commits/77ecf91c635a287e88e60d8ddb0f4e9df4003301
|
|
||||||
# According to ronan on #pypy IRC, it is released in PyPy2 2.3 and later
|
|
||||||
# versions.
|
|
||||||
#
|
|
||||||
# Ubuntu 14.04 has PyPy 2.2.1, which does exhibit this bug.
|
|
||||||
BROKEN_PYPY_CTXMGR_EXIT = False
|
|
||||||
if hasattr(sys, 'pypy_version_info'):
|
|
||||||
class _Mgr(object):
|
|
||||||
def __enter__(self):
|
|
||||||
return self
|
|
||||||
def __exit__(self, *args):
|
|
||||||
if hasattr(sys, 'exc_clear'):
|
|
||||||
# Python 3 (PyPy3) doesn't have exc_clear
|
|
||||||
sys.exc_clear()
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
with _Mgr():
|
|
||||||
raise AssertionError()
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
except TypeError:
|
|
||||||
BROKEN_PYPY_CTXMGR_EXIT = True
|
|
||||||
except AssertionError:
|
|
||||||
pass
|
|
2315
pyextra/flask/app.py
2315
pyextra/flask/app.py
File diff suppressed because it is too large
Load Diff
|
@ -1,448 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.blueprints
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Blueprints are the recommended way to implement larger or more
|
|
||||||
pluggable applications in Flask 0.7 and later.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
from functools import update_wrapper
|
|
||||||
from werkzeug.urls import url_join
|
|
||||||
|
|
||||||
from .helpers import _PackageBoundObject, _endpoint_from_view_func
|
|
||||||
|
|
||||||
|
|
||||||
class BlueprintSetupState(object):
|
|
||||||
"""Temporary holder object for registering a blueprint with the
|
|
||||||
application. An instance of this class is created by the
|
|
||||||
:meth:`~flask.Blueprint.make_setup_state` method and later passed
|
|
||||||
to all register callback functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, blueprint, app, options, first_registration):
|
|
||||||
#: a reference to the current application
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
#: a reference to the blueprint that created this setup state.
|
|
||||||
self.blueprint = blueprint
|
|
||||||
|
|
||||||
#: a dictionary with all options that were passed to the
|
|
||||||
#: :meth:`~flask.Flask.register_blueprint` method.
|
|
||||||
self.options = options
|
|
||||||
|
|
||||||
#: as blueprints can be registered multiple times with the
|
|
||||||
#: application and not everything wants to be registered
|
|
||||||
#: multiple times on it, this attribute can be used to figure
|
|
||||||
#: out if the blueprint was registered in the past already.
|
|
||||||
self.first_registration = first_registration
|
|
||||||
|
|
||||||
subdomain = self.options.get('subdomain')
|
|
||||||
if subdomain is None:
|
|
||||||
subdomain = self.blueprint.subdomain
|
|
||||||
|
|
||||||
#: The subdomain that the blueprint should be active for, ``None``
|
|
||||||
#: otherwise.
|
|
||||||
self.subdomain = subdomain
|
|
||||||
|
|
||||||
url_prefix = self.options.get('url_prefix')
|
|
||||||
if url_prefix is None:
|
|
||||||
url_prefix = self.blueprint.url_prefix
|
|
||||||
#: The prefix that should be used for all URLs defined on the
|
|
||||||
#: blueprint.
|
|
||||||
self.url_prefix = url_prefix
|
|
||||||
|
|
||||||
#: A dictionary with URL defaults that is added to each and every
|
|
||||||
#: URL that was defined with the blueprint.
|
|
||||||
self.url_defaults = dict(self.blueprint.url_values_defaults)
|
|
||||||
self.url_defaults.update(self.options.get('url_defaults', ()))
|
|
||||||
|
|
||||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
|
||||||
"""A helper method to register a rule (and optionally a view function)
|
|
||||||
to the application. The endpoint is automatically prefixed with the
|
|
||||||
blueprint's name.
|
|
||||||
"""
|
|
||||||
if self.url_prefix is not None:
|
|
||||||
if rule:
|
|
||||||
rule = '/'.join((
|
|
||||||
self.url_prefix.rstrip('/'), rule.lstrip('/')))
|
|
||||||
else:
|
|
||||||
rule = self.url_prefix
|
|
||||||
options.setdefault('subdomain', self.subdomain)
|
|
||||||
if endpoint is None:
|
|
||||||
endpoint = _endpoint_from_view_func(view_func)
|
|
||||||
defaults = self.url_defaults
|
|
||||||
if 'defaults' in options:
|
|
||||||
defaults = dict(defaults, **options.pop('defaults'))
|
|
||||||
self.app.add_url_rule(rule, '%s.%s' % (self.blueprint.name, endpoint),
|
|
||||||
view_func, defaults=defaults, **options)
|
|
||||||
|
|
||||||
|
|
||||||
class Blueprint(_PackageBoundObject):
|
|
||||||
"""Represents a blueprint. A blueprint is an object that records
|
|
||||||
functions that will be called with the
|
|
||||||
:class:`~flask.blueprints.BlueprintSetupState` later to register functions
|
|
||||||
or other things on the main application. See :ref:`blueprints` for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
"""
|
|
||||||
|
|
||||||
warn_on_modifications = False
|
|
||||||
_got_registered_once = False
|
|
||||||
|
|
||||||
#: Blueprint local JSON decoder class to use.
|
|
||||||
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_encoder`.
|
|
||||||
json_encoder = None
|
|
||||||
#: Blueprint local JSON decoder class to use.
|
|
||||||
#: Set to ``None`` to use the app's :class:`~flask.app.Flask.json_decoder`.
|
|
||||||
json_decoder = None
|
|
||||||
|
|
||||||
# TODO remove the next three attrs when Sphinx :inherited-members: works
|
|
||||||
# https://github.com/sphinx-doc/sphinx/issues/741
|
|
||||||
|
|
||||||
#: The name of the package or module that this app belongs to. Do not
|
|
||||||
#: change this once it is set by the constructor.
|
|
||||||
import_name = None
|
|
||||||
|
|
||||||
#: Location of the template files to be added to the template lookup.
|
|
||||||
#: ``None`` if templates should not be added.
|
|
||||||
template_folder = None
|
|
||||||
|
|
||||||
#: Absolute path to the package on the filesystem. Used to look up
|
|
||||||
#: resources contained in the package.
|
|
||||||
root_path = None
|
|
||||||
|
|
||||||
def __init__(self, name, import_name, static_folder=None,
|
|
||||||
static_url_path=None, template_folder=None,
|
|
||||||
url_prefix=None, subdomain=None, url_defaults=None,
|
|
||||||
root_path=None):
|
|
||||||
_PackageBoundObject.__init__(self, import_name, template_folder,
|
|
||||||
root_path=root_path)
|
|
||||||
self.name = name
|
|
||||||
self.url_prefix = url_prefix
|
|
||||||
self.subdomain = subdomain
|
|
||||||
self.static_folder = static_folder
|
|
||||||
self.static_url_path = static_url_path
|
|
||||||
self.deferred_functions = []
|
|
||||||
if url_defaults is None:
|
|
||||||
url_defaults = {}
|
|
||||||
self.url_values_defaults = url_defaults
|
|
||||||
|
|
||||||
def record(self, func):
|
|
||||||
"""Registers a function that is called when the blueprint is
|
|
||||||
registered on the application. This function is called with the
|
|
||||||
state as argument as returned by the :meth:`make_setup_state`
|
|
||||||
method.
|
|
||||||
"""
|
|
||||||
if self._got_registered_once and self.warn_on_modifications:
|
|
||||||
from warnings import warn
|
|
||||||
warn(Warning('The blueprint was already registered once '
|
|
||||||
'but is getting modified now. These changes '
|
|
||||||
'will not show up.'))
|
|
||||||
self.deferred_functions.append(func)
|
|
||||||
|
|
||||||
def record_once(self, func):
|
|
||||||
"""Works like :meth:`record` but wraps the function in another
|
|
||||||
function that will ensure the function is only called once. If the
|
|
||||||
blueprint is registered a second time on the application, the
|
|
||||||
function passed is not called.
|
|
||||||
"""
|
|
||||||
def wrapper(state):
|
|
||||||
if state.first_registration:
|
|
||||||
func(state)
|
|
||||||
return self.record(update_wrapper(wrapper, func))
|
|
||||||
|
|
||||||
def make_setup_state(self, app, options, first_registration=False):
|
|
||||||
"""Creates an instance of :meth:`~flask.blueprints.BlueprintSetupState`
|
|
||||||
object that is later passed to the register callback functions.
|
|
||||||
Subclasses can override this to return a subclass of the setup state.
|
|
||||||
"""
|
|
||||||
return BlueprintSetupState(self, app, options, first_registration)
|
|
||||||
|
|
||||||
def register(self, app, options, first_registration=False):
|
|
||||||
"""Called by :meth:`Flask.register_blueprint` to register all views
|
|
||||||
and callbacks registered on the blueprint with the application. Creates
|
|
||||||
a :class:`.BlueprintSetupState` and calls each :meth:`record` callback
|
|
||||||
with it.
|
|
||||||
|
|
||||||
:param app: The application this blueprint is being registered with.
|
|
||||||
:param options: Keyword arguments forwarded from
|
|
||||||
:meth:`~Flask.register_blueprint`.
|
|
||||||
:param first_registration: Whether this is the first time this
|
|
||||||
blueprint has been registered on the application.
|
|
||||||
"""
|
|
||||||
self._got_registered_once = True
|
|
||||||
state = self.make_setup_state(app, options, first_registration)
|
|
||||||
|
|
||||||
if self.has_static_folder:
|
|
||||||
state.add_url_rule(
|
|
||||||
self.static_url_path + '/<path:filename>',
|
|
||||||
view_func=self.send_static_file, endpoint='static'
|
|
||||||
)
|
|
||||||
|
|
||||||
for deferred in self.deferred_functions:
|
|
||||||
deferred(state)
|
|
||||||
|
|
||||||
def route(self, rule, **options):
|
|
||||||
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
|
|
||||||
:func:`url_for` function is prefixed with the name of the blueprint.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
endpoint = options.pop("endpoint", f.__name__)
|
|
||||||
self.add_url_rule(rule, endpoint, f, **options)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
|
|
||||||
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
|
|
||||||
the :func:`url_for` function is prefixed with the name of the blueprint.
|
|
||||||
"""
|
|
||||||
if endpoint:
|
|
||||||
assert '.' not in endpoint, "Blueprint endpoints should not contain dots"
|
|
||||||
if view_func and hasattr(view_func, '__name__'):
|
|
||||||
assert '.' not in view_func.__name__, "Blueprint view function name should not contain dots"
|
|
||||||
self.record(lambda s:
|
|
||||||
s.add_url_rule(rule, endpoint, view_func, **options))
|
|
||||||
|
|
||||||
def endpoint(self, endpoint):
|
|
||||||
"""Like :meth:`Flask.endpoint` but for a blueprint. This does not
|
|
||||||
prefix the endpoint with the blueprint name, this has to be done
|
|
||||||
explicitly by the user of this method. If the endpoint is prefixed
|
|
||||||
with a `.` it will be registered to the current blueprint, otherwise
|
|
||||||
it's an application independent endpoint.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
def register_endpoint(state):
|
|
||||||
state.app.view_functions[endpoint] = f
|
|
||||||
self.record_once(register_endpoint)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def app_template_filter(self, name=None):
|
|
||||||
"""Register a custom template filter, available application wide. Like
|
|
||||||
:meth:`Flask.template_filter` but for a blueprint.
|
|
||||||
|
|
||||||
:param name: the optional name of the filter, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.add_app_template_filter(f, name=name)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def add_app_template_filter(self, f, name=None):
|
|
||||||
"""Register a custom template filter, available application wide. Like
|
|
||||||
:meth:`Flask.add_template_filter` but for a blueprint. Works exactly
|
|
||||||
like the :meth:`app_template_filter` decorator.
|
|
||||||
|
|
||||||
:param name: the optional name of the filter, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def register_template(state):
|
|
||||||
state.app.jinja_env.filters[name or f.__name__] = f
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
def app_template_test(self, name=None):
|
|
||||||
"""Register a custom template test, available application wide. Like
|
|
||||||
:meth:`Flask.template_test` but for a blueprint.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the test, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.add_app_template_test(f, name=name)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def add_app_template_test(self, f, name=None):
|
|
||||||
"""Register a custom template test, available application wide. Like
|
|
||||||
:meth:`Flask.add_template_test` but for a blueprint. Works exactly
|
|
||||||
like the :meth:`app_template_test` decorator.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the test, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def register_template(state):
|
|
||||||
state.app.jinja_env.tests[name or f.__name__] = f
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
def app_template_global(self, name=None):
|
|
||||||
"""Register a custom template global, available application wide. Like
|
|
||||||
:meth:`Flask.template_global` but for a blueprint.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the global, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.add_app_template_global(f, name=name)
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def add_app_template_global(self, f, name=None):
|
|
||||||
"""Register a custom template global, available application wide. Like
|
|
||||||
:meth:`Flask.add_template_global` but for a blueprint. Works exactly
|
|
||||||
like the :meth:`app_template_global` decorator.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
:param name: the optional name of the global, otherwise the
|
|
||||||
function name will be used.
|
|
||||||
"""
|
|
||||||
def register_template(state):
|
|
||||||
state.app.jinja_env.globals[name or f.__name__] = f
|
|
||||||
self.record_once(register_template)
|
|
||||||
|
|
||||||
def before_request(self, f):
|
|
||||||
"""Like :meth:`Flask.before_request` but for a blueprint. This function
|
|
||||||
is only executed before each request that is handled by a function of
|
|
||||||
that blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.before_request_funcs
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def before_app_request(self, f):
|
|
||||||
"""Like :meth:`Flask.before_request`. Such a function is executed
|
|
||||||
before each request, even if outside of a blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.before_request_funcs
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def before_app_first_request(self, f):
|
|
||||||
"""Like :meth:`Flask.before_first_request`. Such a function is
|
|
||||||
executed before the first request to the application.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def after_request(self, f):
|
|
||||||
"""Like :meth:`Flask.after_request` but for a blueprint. This function
|
|
||||||
is only executed after each request that is handled by a function of
|
|
||||||
that blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.after_request_funcs
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def after_app_request(self, f):
|
|
||||||
"""Like :meth:`Flask.after_request` but for a blueprint. Such a function
|
|
||||||
is executed after each request, even if outside of the blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.after_request_funcs
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def teardown_request(self, f):
|
|
||||||
"""Like :meth:`Flask.teardown_request` but for a blueprint. This
|
|
||||||
function is only executed when tearing down requests handled by a
|
|
||||||
function of that blueprint. Teardown request functions are executed
|
|
||||||
when the request context is popped, even when no actual request was
|
|
||||||
performed.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.teardown_request_funcs
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def teardown_app_request(self, f):
|
|
||||||
"""Like :meth:`Flask.teardown_request` but for a blueprint. Such a
|
|
||||||
function is executed when tearing down each request, even if outside of
|
|
||||||
the blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.teardown_request_funcs
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def context_processor(self, f):
|
|
||||||
"""Like :meth:`Flask.context_processor` but for a blueprint. This
|
|
||||||
function is only executed for requests handled by a blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.template_context_processors
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_context_processor(self, f):
|
|
||||||
"""Like :meth:`Flask.context_processor` but for a blueprint. Such a
|
|
||||||
function is executed each request, even if outside of the blueprint.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.template_context_processors
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_errorhandler(self, code):
|
|
||||||
"""Like :meth:`Flask.errorhandler` but for a blueprint. This
|
|
||||||
handler is used for all requests, even if outside of the blueprint.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.record_once(lambda s: s.app.errorhandler(code)(f))
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def url_value_preprocessor(self, f):
|
|
||||||
"""Registers a function as URL value preprocessor for this
|
|
||||||
blueprint. It's called before the view functions are called and
|
|
||||||
can modify the url values provided.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_value_preprocessors
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def url_defaults(self, f):
|
|
||||||
"""Callback function for URL defaults for this blueprint. It's called
|
|
||||||
with the endpoint and values and should update the values passed
|
|
||||||
in place.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_default_functions
|
|
||||||
.setdefault(self.name, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_url_value_preprocessor(self, f):
|
|
||||||
"""Same as :meth:`url_value_preprocessor` but application wide.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_value_preprocessors
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def app_url_defaults(self, f):
|
|
||||||
"""Same as :meth:`url_defaults` but application wide.
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app.url_default_functions
|
|
||||||
.setdefault(None, []).append(f))
|
|
||||||
return f
|
|
||||||
|
|
||||||
def errorhandler(self, code_or_exception):
|
|
||||||
"""Registers an error handler that becomes active for this blueprint
|
|
||||||
only. Please be aware that routing does not happen local to a
|
|
||||||
blueprint so an error handler for 404 usually is not handled by
|
|
||||||
a blueprint unless it is caused inside a view function. Another
|
|
||||||
special case is the 500 internal server error which is always looked
|
|
||||||
up from the application.
|
|
||||||
|
|
||||||
Otherwise works as the :meth:`~flask.Flask.errorhandler` decorator
|
|
||||||
of the :class:`~flask.Flask` object.
|
|
||||||
"""
|
|
||||||
def decorator(f):
|
|
||||||
self.record_once(lambda s: s.app._register_error_handler(
|
|
||||||
self.name, code_or_exception, f))
|
|
||||||
return f
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def register_error_handler(self, code_or_exception, f):
|
|
||||||
"""Non-decorator version of the :meth:`errorhandler` error attach
|
|
||||||
function, akin to the :meth:`~flask.Flask.register_error_handler`
|
|
||||||
application-wide function of the :class:`~flask.Flask` object but
|
|
||||||
for error handlers limited to this blueprint.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
self.record_once(lambda s: s.app._register_error_handler(
|
|
||||||
self.name, code_or_exception, f))
|
|
|
@ -1,898 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.cli
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
A simple command line application to run flask apps.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import ast
|
|
||||||
import inspect
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import ssl
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from functools import update_wrapper
|
|
||||||
from operator import attrgetter
|
|
||||||
from threading import Lock, Thread
|
|
||||||
|
|
||||||
import click
|
|
||||||
from werkzeug.utils import import_string
|
|
||||||
|
|
||||||
from . import __version__
|
|
||||||
from ._compat import getargspec, iteritems, reraise, text_type
|
|
||||||
from .globals import current_app
|
|
||||||
from .helpers import get_debug_flag, get_env, get_load_dotenv
|
|
||||||
|
|
||||||
try:
|
|
||||||
import dotenv
|
|
||||||
except ImportError:
|
|
||||||
dotenv = None
|
|
||||||
|
|
||||||
|
|
||||||
class NoAppException(click.UsageError):
|
|
||||||
"""Raised if an application cannot be found or loaded."""
|
|
||||||
|
|
||||||
|
|
||||||
def find_best_app(script_info, module):
|
|
||||||
"""Given a module instance this tries to find the best possible
|
|
||||||
application in the module or raises an exception.
|
|
||||||
"""
|
|
||||||
from . import Flask
|
|
||||||
|
|
||||||
# Search for the most common names first.
|
|
||||||
for attr_name in ('app', 'application'):
|
|
||||||
app = getattr(module, attr_name, None)
|
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
|
||||||
return app
|
|
||||||
|
|
||||||
# Otherwise find the only object that is a Flask instance.
|
|
||||||
matches = [
|
|
||||||
v for k, v in iteritems(module.__dict__) if isinstance(v, Flask)
|
|
||||||
]
|
|
||||||
|
|
||||||
if len(matches) == 1:
|
|
||||||
return matches[0]
|
|
||||||
elif len(matches) > 1:
|
|
||||||
raise NoAppException(
|
|
||||||
'Detected multiple Flask applications in module "{module}". Use '
|
|
||||||
'"FLASK_APP={module}:name" to specify the correct '
|
|
||||||
'one.'.format(module=module.__name__)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Search for app factory functions.
|
|
||||||
for attr_name in ('create_app', 'make_app'):
|
|
||||||
app_factory = getattr(module, attr_name, None)
|
|
||||||
|
|
||||||
if inspect.isfunction(app_factory):
|
|
||||||
try:
|
|
||||||
app = call_factory(script_info, app_factory)
|
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
|
||||||
return app
|
|
||||||
except TypeError:
|
|
||||||
if not _called_with_wrong_args(app_factory):
|
|
||||||
raise
|
|
||||||
raise NoAppException(
|
|
||||||
'Detected factory "{factory}" in module "{module}", but '
|
|
||||||
'could not call it without arguments. Use '
|
|
||||||
'"FLASK_APP=\'{module}:{factory}(args)\'" to specify '
|
|
||||||
'arguments.'.format(
|
|
||||||
factory=attr_name, module=module.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
raise NoAppException(
|
|
||||||
'Failed to find Flask application or factory in module "{module}". '
|
|
||||||
'Use "FLASK_APP={module}:name to specify one.'.format(
|
|
||||||
module=module.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def call_factory(script_info, app_factory, arguments=()):
|
|
||||||
"""Takes an app factory, a ``script_info` object and optionally a tuple
|
|
||||||
of arguments. Checks for the existence of a script_info argument and calls
|
|
||||||
the app_factory depending on that and the arguments provided.
|
|
||||||
"""
|
|
||||||
args_spec = getargspec(app_factory)
|
|
||||||
arg_names = args_spec.args
|
|
||||||
arg_defaults = args_spec.defaults
|
|
||||||
|
|
||||||
if 'script_info' in arg_names:
|
|
||||||
return app_factory(*arguments, script_info=script_info)
|
|
||||||
elif arguments:
|
|
||||||
return app_factory(*arguments)
|
|
||||||
elif not arguments and len(arg_names) == 1 and arg_defaults is None:
|
|
||||||
return app_factory(script_info)
|
|
||||||
|
|
||||||
return app_factory()
|
|
||||||
|
|
||||||
|
|
||||||
def _called_with_wrong_args(factory):
|
|
||||||
"""Check whether calling a function raised a ``TypeError`` because
|
|
||||||
the call failed or because something in the factory raised the
|
|
||||||
error.
|
|
||||||
|
|
||||||
:param factory: the factory function that was called
|
|
||||||
:return: true if the call failed
|
|
||||||
"""
|
|
||||||
tb = sys.exc_info()[2]
|
|
||||||
|
|
||||||
try:
|
|
||||||
while tb is not None:
|
|
||||||
if tb.tb_frame.f_code is factory.__code__:
|
|
||||||
# in the factory, it was called successfully
|
|
||||||
return False
|
|
||||||
|
|
||||||
tb = tb.tb_next
|
|
||||||
|
|
||||||
# didn't reach the factory
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
del tb
|
|
||||||
|
|
||||||
|
|
||||||
def find_app_by_string(script_info, module, app_name):
|
|
||||||
"""Checks if the given string is a variable name or a function. If it is a
|
|
||||||
function, it checks for specified arguments and whether it takes a
|
|
||||||
``script_info`` argument and calls the function with the appropriate
|
|
||||||
arguments.
|
|
||||||
"""
|
|
||||||
from flask import Flask
|
|
||||||
match = re.match(r'^ *([^ ()]+) *(?:\((.*?) *,? *\))? *$', app_name)
|
|
||||||
|
|
||||||
if not match:
|
|
||||||
raise NoAppException(
|
|
||||||
'"{name}" is not a valid variable name or function '
|
|
||||||
'expression.'.format(name=app_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
name, args = match.groups()
|
|
||||||
|
|
||||||
try:
|
|
||||||
attr = getattr(module, name)
|
|
||||||
except AttributeError as e:
|
|
||||||
raise NoAppException(e.args[0])
|
|
||||||
|
|
||||||
if inspect.isfunction(attr):
|
|
||||||
if args:
|
|
||||||
try:
|
|
||||||
args = ast.literal_eval('({args},)'.format(args=args))
|
|
||||||
except (ValueError, SyntaxError)as e:
|
|
||||||
raise NoAppException(
|
|
||||||
'Could not parse the arguments in '
|
|
||||||
'"{app_name}".'.format(e=e, app_name=app_name)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
args = ()
|
|
||||||
|
|
||||||
try:
|
|
||||||
app = call_factory(script_info, attr, args)
|
|
||||||
except TypeError as e:
|
|
||||||
if not _called_with_wrong_args(attr):
|
|
||||||
raise
|
|
||||||
|
|
||||||
raise NoAppException(
|
|
||||||
'{e}\nThe factory "{app_name}" in module "{module}" could not '
|
|
||||||
'be called with the specified arguments.'.format(
|
|
||||||
e=e, app_name=app_name, module=module.__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
app = attr
|
|
||||||
|
|
||||||
if isinstance(app, Flask):
|
|
||||||
return app
|
|
||||||
|
|
||||||
raise NoAppException(
|
|
||||||
'A valid Flask application was not obtained from '
|
|
||||||
'"{module}:{app_name}".'.format(
|
|
||||||
module=module.__name__, app_name=app_name
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def prepare_import(path):
|
|
||||||
"""Given a filename this will try to calculate the python path, add it
|
|
||||||
to the search path and return the actual module name that is expected.
|
|
||||||
"""
|
|
||||||
path = os.path.realpath(path)
|
|
||||||
|
|
||||||
if os.path.splitext(path)[1] == '.py':
|
|
||||||
path = os.path.splitext(path)[0]
|
|
||||||
|
|
||||||
if os.path.basename(path) == '__init__':
|
|
||||||
path = os.path.dirname(path)
|
|
||||||
|
|
||||||
module_name = []
|
|
||||||
|
|
||||||
# move up until outside package structure (no __init__.py)
|
|
||||||
while True:
|
|
||||||
path, name = os.path.split(path)
|
|
||||||
module_name.append(name)
|
|
||||||
|
|
||||||
if not os.path.exists(os.path.join(path, '__init__.py')):
|
|
||||||
break
|
|
||||||
|
|
||||||
if sys.path[0] != path:
|
|
||||||
sys.path.insert(0, path)
|
|
||||||
|
|
||||||
return '.'.join(module_name[::-1])
|
|
||||||
|
|
||||||
|
|
||||||
def locate_app(script_info, module_name, app_name, raise_if_not_found=True):
|
|
||||||
__traceback_hide__ = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
__import__(module_name)
|
|
||||||
except ImportError:
|
|
||||||
# Reraise the ImportError if it occurred within the imported module.
|
|
||||||
# Determine this by checking whether the trace has a depth > 1.
|
|
||||||
if sys.exc_info()[-1].tb_next:
|
|
||||||
raise NoAppException(
|
|
||||||
'While importing "{name}", an ImportError was raised:'
|
|
||||||
'\n\n{tb}'.format(name=module_name, tb=traceback.format_exc())
|
|
||||||
)
|
|
||||||
elif raise_if_not_found:
|
|
||||||
raise NoAppException(
|
|
||||||
'Could not import "{name}".'.format(name=module_name)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
module = sys.modules[module_name]
|
|
||||||
|
|
||||||
if app_name is None:
|
|
||||||
return find_best_app(script_info, module)
|
|
||||||
else:
|
|
||||||
return find_app_by_string(script_info, module, app_name)
|
|
||||||
|
|
||||||
|
|
||||||
def get_version(ctx, param, value):
|
|
||||||
if not value or ctx.resilient_parsing:
|
|
||||||
return
|
|
||||||
message = 'Flask %(version)s\nPython %(python_version)s'
|
|
||||||
click.echo(message % {
|
|
||||||
'version': __version__,
|
|
||||||
'python_version': sys.version,
|
|
||||||
}, color=ctx.color)
|
|
||||||
ctx.exit()
|
|
||||||
|
|
||||||
|
|
||||||
version_option = click.Option(
|
|
||||||
['--version'],
|
|
||||||
help='Show the flask version',
|
|
||||||
expose_value=False,
|
|
||||||
callback=get_version,
|
|
||||||
is_flag=True,
|
|
||||||
is_eager=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DispatchingApp(object):
|
|
||||||
"""Special application that dispatches to a Flask application which
|
|
||||||
is imported by name in a background thread. If an error happens
|
|
||||||
it is recorded and shown as part of the WSGI handling which in case
|
|
||||||
of the Werkzeug debugger means that it shows up in the browser.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, loader, use_eager_loading=False):
|
|
||||||
self.loader = loader
|
|
||||||
self._app = None
|
|
||||||
self._lock = Lock()
|
|
||||||
self._bg_loading_exc_info = None
|
|
||||||
if use_eager_loading:
|
|
||||||
self._load_unlocked()
|
|
||||||
else:
|
|
||||||
self._load_in_background()
|
|
||||||
|
|
||||||
def _load_in_background(self):
|
|
||||||
def _load_app():
|
|
||||||
__traceback_hide__ = True
|
|
||||||
with self._lock:
|
|
||||||
try:
|
|
||||||
self._load_unlocked()
|
|
||||||
except Exception:
|
|
||||||
self._bg_loading_exc_info = sys.exc_info()
|
|
||||||
t = Thread(target=_load_app, args=())
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
def _flush_bg_loading_exception(self):
|
|
||||||
__traceback_hide__ = True
|
|
||||||
exc_info = self._bg_loading_exc_info
|
|
||||||
if exc_info is not None:
|
|
||||||
self._bg_loading_exc_info = None
|
|
||||||
reraise(*exc_info)
|
|
||||||
|
|
||||||
def _load_unlocked(self):
|
|
||||||
__traceback_hide__ = True
|
|
||||||
self._app = rv = self.loader()
|
|
||||||
self._bg_loading_exc_info = None
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
__traceback_hide__ = True
|
|
||||||
if self._app is not None:
|
|
||||||
return self._app(environ, start_response)
|
|
||||||
self._flush_bg_loading_exception()
|
|
||||||
with self._lock:
|
|
||||||
if self._app is not None:
|
|
||||||
rv = self._app
|
|
||||||
else:
|
|
||||||
rv = self._load_unlocked()
|
|
||||||
return rv(environ, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
class ScriptInfo(object):
|
|
||||||
"""Help object to deal with Flask applications. This is usually not
|
|
||||||
necessary to interface with as it's used internally in the dispatching
|
|
||||||
to click. In future versions of Flask this object will most likely play
|
|
||||||
a bigger role. Typically it's created automatically by the
|
|
||||||
:class:`FlaskGroup` but you can also manually create it and pass it
|
|
||||||
onwards as click object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app_import_path=None, create_app=None):
|
|
||||||
#: Optionally the import path for the Flask application.
|
|
||||||
self.app_import_path = app_import_path or os.environ.get('FLASK_APP')
|
|
||||||
#: Optionally a function that is passed the script info to create
|
|
||||||
#: the instance of the application.
|
|
||||||
self.create_app = create_app
|
|
||||||
#: A dictionary with arbitrary data that can be associated with
|
|
||||||
#: this script info.
|
|
||||||
self.data = {}
|
|
||||||
self._loaded_app = None
|
|
||||||
|
|
||||||
def load_app(self):
|
|
||||||
"""Loads the Flask app (if not yet loaded) and returns it. Calling
|
|
||||||
this multiple times will just result in the already loaded app to
|
|
||||||
be returned.
|
|
||||||
"""
|
|
||||||
__traceback_hide__ = True
|
|
||||||
|
|
||||||
if self._loaded_app is not None:
|
|
||||||
return self._loaded_app
|
|
||||||
|
|
||||||
app = None
|
|
||||||
|
|
||||||
if self.create_app is not None:
|
|
||||||
app = call_factory(self, self.create_app)
|
|
||||||
else:
|
|
||||||
if self.app_import_path:
|
|
||||||
path, name = (self.app_import_path.split(':', 1) + [None])[:2]
|
|
||||||
import_name = prepare_import(path)
|
|
||||||
app = locate_app(self, import_name, name)
|
|
||||||
else:
|
|
||||||
for path in ('wsgi.py', 'app.py'):
|
|
||||||
import_name = prepare_import(path)
|
|
||||||
app = locate_app(self, import_name, None,
|
|
||||||
raise_if_not_found=False)
|
|
||||||
|
|
||||||
if app:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not app:
|
|
||||||
raise NoAppException(
|
|
||||||
'Could not locate a Flask application. You did not provide '
|
|
||||||
'the "FLASK_APP" environment variable, and a "wsgi.py" or '
|
|
||||||
'"app.py" module was not found in the current directory.'
|
|
||||||
)
|
|
||||||
|
|
||||||
debug = get_debug_flag()
|
|
||||||
|
|
||||||
# Update the app's debug flag through the descriptor so that other
|
|
||||||
# values repopulate as well.
|
|
||||||
if debug is not None:
|
|
||||||
app.debug = debug
|
|
||||||
|
|
||||||
self._loaded_app = app
|
|
||||||
return app
|
|
||||||
|
|
||||||
|
|
||||||
pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
|
|
||||||
|
|
||||||
|
|
||||||
def with_appcontext(f):
|
|
||||||
"""Wraps a callback so that it's guaranteed to be executed with the
|
|
||||||
script's application context. If callbacks are registered directly
|
|
||||||
to the ``app.cli`` object then they are wrapped with this function
|
|
||||||
by default unless it's disabled.
|
|
||||||
"""
|
|
||||||
@click.pass_context
|
|
||||||
def decorator(__ctx, *args, **kwargs):
|
|
||||||
with __ctx.ensure_object(ScriptInfo).load_app().app_context():
|
|
||||||
return __ctx.invoke(f, *args, **kwargs)
|
|
||||||
return update_wrapper(decorator, f)
|
|
||||||
|
|
||||||
|
|
||||||
class AppGroup(click.Group):
|
|
||||||
"""This works similar to a regular click :class:`~click.Group` but it
|
|
||||||
changes the behavior of the :meth:`command` decorator so that it
|
|
||||||
automatically wraps the functions in :func:`with_appcontext`.
|
|
||||||
|
|
||||||
Not to be confused with :class:`FlaskGroup`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def command(self, *args, **kwargs):
|
|
||||||
"""This works exactly like the method of the same name on a regular
|
|
||||||
:class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
|
|
||||||
unless it's disabled by passing ``with_appcontext=False``.
|
|
||||||
"""
|
|
||||||
wrap_for_ctx = kwargs.pop('with_appcontext', True)
|
|
||||||
def decorator(f):
|
|
||||||
if wrap_for_ctx:
|
|
||||||
f = with_appcontext(f)
|
|
||||||
return click.Group.command(self, *args, **kwargs)(f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
def group(self, *args, **kwargs):
|
|
||||||
"""This works exactly like the method of the same name on a regular
|
|
||||||
:class:`click.Group` but it defaults the group class to
|
|
||||||
:class:`AppGroup`.
|
|
||||||
"""
|
|
||||||
kwargs.setdefault('cls', AppGroup)
|
|
||||||
return click.Group.group(self, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskGroup(AppGroup):
|
|
||||||
"""Special subclass of the :class:`AppGroup` group that supports
|
|
||||||
loading more commands from the configured Flask app. Normally a
|
|
||||||
developer does not have to interface with this class but there are
|
|
||||||
some very advanced use cases for which it makes sense to create an
|
|
||||||
instance of this.
|
|
||||||
|
|
||||||
For information as of why this is useful see :ref:`custom-scripts`.
|
|
||||||
|
|
||||||
:param add_default_commands: if this is True then the default run and
|
|
||||||
shell commands wil be added.
|
|
||||||
:param add_version_option: adds the ``--version`` option.
|
|
||||||
:param create_app: an optional callback that is passed the script info and
|
|
||||||
returns the loaded app.
|
|
||||||
:param load_dotenv: Load the nearest :file:`.env` and :file:`.flaskenv`
|
|
||||||
files to set environment variables. Will also change the working
|
|
||||||
directory to the directory containing the first file found.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
|
||||||
If installed, python-dotenv will be used to load environment variables
|
|
||||||
from :file:`.env` and :file:`.flaskenv` files.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, add_default_commands=True, create_app=None,
|
|
||||||
add_version_option=True, load_dotenv=True, **extra):
|
|
||||||
params = list(extra.pop('params', None) or ())
|
|
||||||
|
|
||||||
if add_version_option:
|
|
||||||
params.append(version_option)
|
|
||||||
|
|
||||||
AppGroup.__init__(self, params=params, **extra)
|
|
||||||
self.create_app = create_app
|
|
||||||
self.load_dotenv = load_dotenv
|
|
||||||
|
|
||||||
if add_default_commands:
|
|
||||||
self.add_command(run_command)
|
|
||||||
self.add_command(shell_command)
|
|
||||||
self.add_command(routes_command)
|
|
||||||
|
|
||||||
self._loaded_plugin_commands = False
|
|
||||||
|
|
||||||
def _load_plugin_commands(self):
|
|
||||||
if self._loaded_plugin_commands:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
except ImportError:
|
|
||||||
self._loaded_plugin_commands = True
|
|
||||||
return
|
|
||||||
|
|
||||||
for ep in pkg_resources.iter_entry_points('flask.commands'):
|
|
||||||
self.add_command(ep.load(), ep.name)
|
|
||||||
self._loaded_plugin_commands = True
|
|
||||||
|
|
||||||
def get_command(self, ctx, name):
|
|
||||||
self._load_plugin_commands()
|
|
||||||
|
|
||||||
# We load built-in commands first as these should always be the
|
|
||||||
# same no matter what the app does. If the app does want to
|
|
||||||
# override this it needs to make a custom instance of this group
|
|
||||||
# and not attach the default commands.
|
|
||||||
#
|
|
||||||
# This also means that the script stays functional in case the
|
|
||||||
# application completely fails.
|
|
||||||
rv = AppGroup.get_command(self, ctx, name)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
|
|
||||||
info = ctx.ensure_object(ScriptInfo)
|
|
||||||
try:
|
|
||||||
rv = info.load_app().cli.get_command(ctx, name)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
except NoAppException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def list_commands(self, ctx):
|
|
||||||
self._load_plugin_commands()
|
|
||||||
|
|
||||||
# The commands available is the list of both the application (if
|
|
||||||
# available) plus the builtin commands.
|
|
||||||
rv = set(click.Group.list_commands(self, ctx))
|
|
||||||
info = ctx.ensure_object(ScriptInfo)
|
|
||||||
try:
|
|
||||||
rv.update(info.load_app().cli.list_commands(ctx))
|
|
||||||
except Exception:
|
|
||||||
# Here we intentionally swallow all exceptions as we don't
|
|
||||||
# want the help page to break if the app does not exist.
|
|
||||||
# If someone attempts to use the command we try to create
|
|
||||||
# the app again and this will give us the error.
|
|
||||||
# However, we will not do so silently because that would confuse
|
|
||||||
# users.
|
|
||||||
traceback.print_exc()
|
|
||||||
return sorted(rv)
|
|
||||||
|
|
||||||
def main(self, *args, **kwargs):
|
|
||||||
# Set a global flag that indicates that we were invoked from the
|
|
||||||
# command line interface. This is detected by Flask.run to make the
|
|
||||||
# call into a no-op. This is necessary to avoid ugly errors when the
|
|
||||||
# script that is loaded here also attempts to start a server.
|
|
||||||
os.environ['FLASK_RUN_FROM_CLI'] = 'true'
|
|
||||||
|
|
||||||
if get_load_dotenv(self.load_dotenv):
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
obj = kwargs.get('obj')
|
|
||||||
|
|
||||||
if obj is None:
|
|
||||||
obj = ScriptInfo(create_app=self.create_app)
|
|
||||||
|
|
||||||
kwargs['obj'] = obj
|
|
||||||
kwargs.setdefault('auto_envvar_prefix', 'FLASK')
|
|
||||||
return super(FlaskGroup, self).main(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def _path_is_ancestor(path, other):
|
|
||||||
"""Take ``other`` and remove the length of ``path`` from it. Then join it
|
|
||||||
to ``path``. If it is the original value, ``path`` is an ancestor of
|
|
||||||
``other``."""
|
|
||||||
return os.path.join(path, other[len(path):].lstrip(os.sep)) == other
|
|
||||||
|
|
||||||
|
|
||||||
def load_dotenv(path=None):
|
|
||||||
"""Load "dotenv" files in order of precedence to set environment variables.
|
|
||||||
|
|
||||||
If an env var is already set it is not overwritten, so earlier files in the
|
|
||||||
list are preferred over later files.
|
|
||||||
|
|
||||||
Changes the current working directory to the location of the first file
|
|
||||||
found, with the assumption that it is in the top level project directory
|
|
||||||
and will be where the Python path should import local packages from.
|
|
||||||
|
|
||||||
This is a no-op if `python-dotenv`_ is not installed.
|
|
||||||
|
|
||||||
.. _python-dotenv: https://github.com/theskumar/python-dotenv#readme
|
|
||||||
|
|
||||||
:param path: Load the file at this location instead of searching.
|
|
||||||
:return: ``True`` if a file was loaded.
|
|
||||||
|
|
||||||
.. versionadded:: 1.0
|
|
||||||
"""
|
|
||||||
if dotenv is None:
|
|
||||||
if path or os.path.exists('.env') or os.path.exists('.flaskenv'):
|
|
||||||
click.secho(
|
|
||||||
' * Tip: There are .env files present.'
|
|
||||||
' Do "pip install python-dotenv" to use them.',
|
|
||||||
fg='yellow')
|
|
||||||
return
|
|
||||||
|
|
||||||
if path is not None:
|
|
||||||
return dotenv.load_dotenv(path)
|
|
||||||
|
|
||||||
new_dir = None
|
|
||||||
|
|
||||||
for name in ('.env', '.flaskenv'):
|
|
||||||
path = dotenv.find_dotenv(name, usecwd=True)
|
|
||||||
|
|
||||||
if not path:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if new_dir is None:
|
|
||||||
new_dir = os.path.dirname(path)
|
|
||||||
|
|
||||||
dotenv.load_dotenv(path)
|
|
||||||
|
|
||||||
if new_dir and os.getcwd() != new_dir:
|
|
||||||
os.chdir(new_dir)
|
|
||||||
|
|
||||||
return new_dir is not None # at least one file was located and loaded
|
|
||||||
|
|
||||||
|
|
||||||
def show_server_banner(env, debug, app_import_path, eager_loading):
|
|
||||||
"""Show extra startup messages the first time the server is run,
|
|
||||||
ignoring the reloader.
|
|
||||||
"""
|
|
||||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
|
||||||
return
|
|
||||||
|
|
||||||
if app_import_path is not None:
|
|
||||||
message = ' * Serving Flask app "{0}"'.format(app_import_path)
|
|
||||||
|
|
||||||
if not eager_loading:
|
|
||||||
message += ' (lazy loading)'
|
|
||||||
|
|
||||||
click.echo(message)
|
|
||||||
|
|
||||||
click.echo(' * Environment: {0}'.format(env))
|
|
||||||
|
|
||||||
if env == 'production':
|
|
||||||
click.secho(
|
|
||||||
' WARNING: Do not use the development server in a production'
|
|
||||||
' environment.', fg='red')
|
|
||||||
click.secho(' Use a production WSGI server instead.', dim=True)
|
|
||||||
|
|
||||||
if debug is not None:
|
|
||||||
click.echo(' * Debug mode: {0}'.format('on' if debug else 'off'))
|
|
||||||
|
|
||||||
|
|
||||||
class CertParamType(click.ParamType):
|
|
||||||
"""Click option type for the ``--cert`` option. Allows either an
|
|
||||||
existing file, the string ``'adhoc'``, or an import for a
|
|
||||||
:class:`~ssl.SSLContext` object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
name = 'path'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.path_type = click.Path(
|
|
||||||
exists=True, dir_okay=False, resolve_path=True)
|
|
||||||
|
|
||||||
def convert(self, value, param, ctx):
|
|
||||||
try:
|
|
||||||
return self.path_type(value, param, ctx)
|
|
||||||
except click.BadParameter:
|
|
||||||
value = click.STRING(value, param, ctx).lower()
|
|
||||||
|
|
||||||
if value == 'adhoc':
|
|
||||||
try:
|
|
||||||
import OpenSSL
|
|
||||||
except ImportError:
|
|
||||||
raise click.BadParameter(
|
|
||||||
'Using ad-hoc certificates requires pyOpenSSL.',
|
|
||||||
ctx, param)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
obj = import_string(value, silent=True)
|
|
||||||
|
|
||||||
if sys.version_info < (2, 7):
|
|
||||||
if obj:
|
|
||||||
return obj
|
|
||||||
else:
|
|
||||||
if isinstance(obj, ssl.SSLContext):
|
|
||||||
return obj
|
|
||||||
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_key(ctx, param, value):
|
|
||||||
"""The ``--key`` option must be specified when ``--cert`` is a file.
|
|
||||||
Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
|
|
||||||
"""
|
|
||||||
cert = ctx.params.get('cert')
|
|
||||||
is_adhoc = cert == 'adhoc'
|
|
||||||
|
|
||||||
if sys.version_info < (2, 7):
|
|
||||||
is_context = cert and not isinstance(cert, (text_type, bytes))
|
|
||||||
else:
|
|
||||||
is_context = isinstance(cert, ssl.SSLContext)
|
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
if is_adhoc:
|
|
||||||
raise click.BadParameter(
|
|
||||||
'When "--cert" is "adhoc", "--key" is not used.',
|
|
||||||
ctx, param)
|
|
||||||
|
|
||||||
if is_context:
|
|
||||||
raise click.BadParameter(
|
|
||||||
'When "--cert" is an SSLContext object, "--key is not used.',
|
|
||||||
ctx, param)
|
|
||||||
|
|
||||||
if not cert:
|
|
||||||
raise click.BadParameter(
|
|
||||||
'"--cert" must also be specified.',
|
|
||||||
ctx, param)
|
|
||||||
|
|
||||||
ctx.params['cert'] = cert, value
|
|
||||||
|
|
||||||
else:
|
|
||||||
if cert and not (is_adhoc or is_context):
|
|
||||||
raise click.BadParameter(
|
|
||||||
'Required when using "--cert".',
|
|
||||||
ctx, param)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
@click.command('run', short_help='Runs a development server.')
|
|
||||||
@click.option('--host', '-h', default='127.0.0.1',
|
|
||||||
help='The interface to bind to.')
|
|
||||||
@click.option('--port', '-p', default=5000,
|
|
||||||
help='The port to bind to.')
|
|
||||||
@click.option('--cert', type=CertParamType(),
|
|
||||||
help='Specify a certificate file to use HTTPS.')
|
|
||||||
@click.option('--key',
|
|
||||||
type=click.Path(exists=True, dir_okay=False, resolve_path=True),
|
|
||||||
callback=_validate_key, expose_value=False,
|
|
||||||
help='The key file to use when specifying a certificate.')
|
|
||||||
@click.option('--reload/--no-reload', default=None,
|
|
||||||
help='Enable or disable the reloader. By default the reloader '
|
|
||||||
'is active if debug is enabled.')
|
|
||||||
@click.option('--debugger/--no-debugger', default=None,
|
|
||||||
help='Enable or disable the debugger. By default the debugger '
|
|
||||||
'is active if debug is enabled.')
|
|
||||||
@click.option('--eager-loading/--lazy-loader', default=None,
|
|
||||||
help='Enable or disable eager loading. By default eager '
|
|
||||||
'loading is enabled if the reloader is disabled.')
|
|
||||||
@click.option('--with-threads/--without-threads', default=True,
|
|
||||||
help='Enable or disable multithreading.')
|
|
||||||
@pass_script_info
|
|
||||||
def run_command(info, host, port, reload, debugger, eager_loading,
|
|
||||||
with_threads, cert):
|
|
||||||
"""Run a local development server.
|
|
||||||
|
|
||||||
This server is for development purposes only. It does not provide
|
|
||||||
the stability, security, or performance of production WSGI servers.
|
|
||||||
|
|
||||||
The reloader and debugger are enabled by default if
|
|
||||||
FLASK_ENV=development or FLASK_DEBUG=1.
|
|
||||||
"""
|
|
||||||
debug = get_debug_flag()
|
|
||||||
|
|
||||||
if reload is None:
|
|
||||||
reload = debug
|
|
||||||
|
|
||||||
if debugger is None:
|
|
||||||
debugger = debug
|
|
||||||
|
|
||||||
if eager_loading is None:
|
|
||||||
eager_loading = not reload
|
|
||||||
|
|
||||||
show_server_banner(get_env(), debug, info.app_import_path, eager_loading)
|
|
||||||
app = DispatchingApp(info.load_app, use_eager_loading=eager_loading)
|
|
||||||
|
|
||||||
from werkzeug.serving import run_simple
|
|
||||||
run_simple(host, port, app, use_reloader=reload, use_debugger=debugger,
|
|
||||||
threaded=with_threads, ssl_context=cert)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command('shell', short_help='Runs a shell in the app context.')
|
|
||||||
@with_appcontext
|
|
||||||
def shell_command():
|
|
||||||
"""Runs an interactive Python shell in the context of a given
|
|
||||||
Flask application. The application will populate the default
|
|
||||||
namespace of this shell according to it's configuration.
|
|
||||||
|
|
||||||
This is useful for executing small snippets of management code
|
|
||||||
without having to manually configure the application.
|
|
||||||
"""
|
|
||||||
import code
|
|
||||||
from flask.globals import _app_ctx_stack
|
|
||||||
app = _app_ctx_stack.top.app
|
|
||||||
banner = 'Python %s on %s\nApp: %s [%s]\nInstance: %s' % (
|
|
||||||
sys.version,
|
|
||||||
sys.platform,
|
|
||||||
app.import_name,
|
|
||||||
app.env,
|
|
||||||
app.instance_path,
|
|
||||||
)
|
|
||||||
ctx = {}
|
|
||||||
|
|
||||||
# Support the regular Python interpreter startup script if someone
|
|
||||||
# is using it.
|
|
||||||
startup = os.environ.get('PYTHONSTARTUP')
|
|
||||||
if startup and os.path.isfile(startup):
|
|
||||||
with open(startup, 'r') as f:
|
|
||||||
eval(compile(f.read(), startup, 'exec'), ctx)
|
|
||||||
|
|
||||||
ctx.update(app.make_shell_context())
|
|
||||||
|
|
||||||
code.interact(banner=banner, local=ctx)
|
|
||||||
|
|
||||||
|
|
||||||
@click.command('routes', short_help='Show the routes for the app.')
|
|
||||||
@click.option(
|
|
||||||
'--sort', '-s',
|
|
||||||
type=click.Choice(('endpoint', 'methods', 'rule', 'match')),
|
|
||||||
default='endpoint',
|
|
||||||
help=(
|
|
||||||
'Method to sort routes by. "match" is the order that Flask will match '
|
|
||||||
'routes when dispatching a request.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@click.option(
|
|
||||||
'--all-methods',
|
|
||||||
is_flag=True,
|
|
||||||
help="Show HEAD and OPTIONS methods."
|
|
||||||
)
|
|
||||||
@with_appcontext
|
|
||||||
def routes_command(sort, all_methods):
|
|
||||||
"""Show all registered routes with endpoints and methods."""
|
|
||||||
|
|
||||||
rules = list(current_app.url_map.iter_rules())
|
|
||||||
if not rules:
|
|
||||||
click.echo('No routes were registered.')
|
|
||||||
return
|
|
||||||
|
|
||||||
ignored_methods = set(() if all_methods else ('HEAD', 'OPTIONS'))
|
|
||||||
|
|
||||||
if sort in ('endpoint', 'rule'):
|
|
||||||
rules = sorted(rules, key=attrgetter(sort))
|
|
||||||
elif sort == 'methods':
|
|
||||||
rules = sorted(rules, key=lambda rule: sorted(rule.methods))
|
|
||||||
|
|
||||||
rule_methods = [
|
|
||||||
', '.join(sorted(rule.methods - ignored_methods)) for rule in rules
|
|
||||||
]
|
|
||||||
|
|
||||||
headers = ('Endpoint', 'Methods', 'Rule')
|
|
||||||
widths = (
|
|
||||||
max(len(rule.endpoint) for rule in rules),
|
|
||||||
max(len(methods) for methods in rule_methods),
|
|
||||||
max(len(rule.rule) for rule in rules),
|
|
||||||
)
|
|
||||||
widths = [max(len(h), w) for h, w in zip(headers, widths)]
|
|
||||||
row = '{{0:<{0}}} {{1:<{1}}} {{2:<{2}}}'.format(*widths)
|
|
||||||
|
|
||||||
click.echo(row.format(*headers).strip())
|
|
||||||
click.echo(row.format(*('-' * width for width in widths)))
|
|
||||||
|
|
||||||
for rule, methods in zip(rules, rule_methods):
|
|
||||||
click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
|
|
||||||
|
|
||||||
|
|
||||||
cli = FlaskGroup(help="""\
|
|
||||||
A general utility script for Flask applications.
|
|
||||||
|
|
||||||
Provides commands from Flask, extensions, and the application. Loads the
|
|
||||||
application defined in the FLASK_APP environment variable, or from a wsgi.py
|
|
||||||
file. Setting the FLASK_ENV environment variable to 'development' will enable
|
|
||||||
debug mode.
|
|
||||||
|
|
||||||
\b
|
|
||||||
{prefix}{cmd} FLASK_APP=hello.py
|
|
||||||
{prefix}{cmd} FLASK_ENV=development
|
|
||||||
{prefix}flask run
|
|
||||||
""".format(
|
|
||||||
cmd='export' if os.name == 'posix' else 'set',
|
|
||||||
prefix='$ ' if os.name == 'posix' else '> '
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def main(as_module=False):
|
|
||||||
args = sys.argv[1:]
|
|
||||||
|
|
||||||
if as_module:
|
|
||||||
this_module = 'flask'
|
|
||||||
|
|
||||||
if sys.version_info < (2, 7):
|
|
||||||
this_module += '.cli'
|
|
||||||
|
|
||||||
name = 'python -m ' + this_module
|
|
||||||
|
|
||||||
# Python rewrites "python -m flask" to the path to the file in argv.
|
|
||||||
# Restore the original command so that the reloader works.
|
|
||||||
sys.argv = ['-m', this_module] + args
|
|
||||||
else:
|
|
||||||
name = None
|
|
||||||
|
|
||||||
cli.main(args=args, prog_name=name)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main(as_module=True)
|
|
|
@ -1,265 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.config
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the configuration related objects.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import types
|
|
||||||
import errno
|
|
||||||
|
|
||||||
from werkzeug.utils import import_string
|
|
||||||
from ._compat import string_types, iteritems
|
|
||||||
from . import json
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigAttribute(object):
|
|
||||||
"""Makes an attribute forward to the config"""
|
|
||||||
|
|
||||||
def __init__(self, name, get_converter=None):
|
|
||||||
self.__name__ = name
|
|
||||||
self.get_converter = get_converter
|
|
||||||
|
|
||||||
def __get__(self, obj, type=None):
|
|
||||||
if obj is None:
|
|
||||||
return self
|
|
||||||
rv = obj.config[self.__name__]
|
|
||||||
if self.get_converter is not None:
|
|
||||||
rv = self.get_converter(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def __set__(self, obj, value):
|
|
||||||
obj.config[self.__name__] = value
|
|
||||||
|
|
||||||
|
|
||||||
class Config(dict):
|
|
||||||
"""Works exactly like a dict but provides ways to fill it from files
|
|
||||||
or special dictionaries. There are two common patterns to populate the
|
|
||||||
config.
|
|
||||||
|
|
||||||
Either you can fill the config from a config file::
|
|
||||||
|
|
||||||
app.config.from_pyfile('yourconfig.cfg')
|
|
||||||
|
|
||||||
Or alternatively you can define the configuration options in the
|
|
||||||
module that calls :meth:`from_object` or provide an import path to
|
|
||||||
a module that should be loaded. It is also possible to tell it to
|
|
||||||
use the same module and with that provide the configuration values
|
|
||||||
just before the call::
|
|
||||||
|
|
||||||
DEBUG = True
|
|
||||||
SECRET_KEY = 'development key'
|
|
||||||
app.config.from_object(__name__)
|
|
||||||
|
|
||||||
In both cases (loading from any Python file or loading from modules),
|
|
||||||
only uppercase keys are added to the config. This makes it possible to use
|
|
||||||
lowercase values in the config file for temporary values that are not added
|
|
||||||
to the config or to define the config keys in the same file that implements
|
|
||||||
the application.
|
|
||||||
|
|
||||||
Probably the most interesting way to load configurations is from an
|
|
||||||
environment variable pointing to a file::
|
|
||||||
|
|
||||||
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
|
|
||||||
|
|
||||||
In this case before launching the application you have to set this
|
|
||||||
environment variable to the file you want to use. On Linux and OS X
|
|
||||||
use the export statement::
|
|
||||||
|
|
||||||
export YOURAPPLICATION_SETTINGS='/path/to/config/file'
|
|
||||||
|
|
||||||
On windows use `set` instead.
|
|
||||||
|
|
||||||
:param root_path: path to which files are read relative from. When the
|
|
||||||
config object is created by the application, this is
|
|
||||||
the application's :attr:`~flask.Flask.root_path`.
|
|
||||||
:param defaults: an optional dictionary of default values
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, root_path, defaults=None):
|
|
||||||
dict.__init__(self, defaults or {})
|
|
||||||
self.root_path = root_path
|
|
||||||
|
|
||||||
def from_envvar(self, variable_name, silent=False):
|
|
||||||
"""Loads a configuration from an environment variable pointing to
|
|
||||||
a configuration file. This is basically just a shortcut with nicer
|
|
||||||
error messages for this line of code::
|
|
||||||
|
|
||||||
app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
|
|
||||||
|
|
||||||
:param variable_name: name of the environment variable
|
|
||||||
:param silent: set to ``True`` if you want silent failure for missing
|
|
||||||
files.
|
|
||||||
:return: bool. ``True`` if able to load config, ``False`` otherwise.
|
|
||||||
"""
|
|
||||||
rv = os.environ.get(variable_name)
|
|
||||||
if not rv:
|
|
||||||
if silent:
|
|
||||||
return False
|
|
||||||
raise RuntimeError('The environment variable %r is not set '
|
|
||||||
'and as such configuration could not be '
|
|
||||||
'loaded. Set this variable and make it '
|
|
||||||
'point to a configuration file' %
|
|
||||||
variable_name)
|
|
||||||
return self.from_pyfile(rv, silent=silent)
|
|
||||||
|
|
||||||
def from_pyfile(self, filename, silent=False):
|
|
||||||
"""Updates the values in the config from a Python file. This function
|
|
||||||
behaves as if the file was imported as module with the
|
|
||||||
:meth:`from_object` function.
|
|
||||||
|
|
||||||
:param filename: the filename of the config. This can either be an
|
|
||||||
absolute filename or a filename relative to the
|
|
||||||
root path.
|
|
||||||
:param silent: set to ``True`` if you want silent failure for missing
|
|
||||||
files.
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
`silent` parameter.
|
|
||||||
"""
|
|
||||||
filename = os.path.join(self.root_path, filename)
|
|
||||||
d = types.ModuleType('config')
|
|
||||||
d.__file__ = filename
|
|
||||||
try:
|
|
||||||
with open(filename, mode='rb') as config_file:
|
|
||||||
exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
|
|
||||||
except IOError as e:
|
|
||||||
if silent and e.errno in (
|
|
||||||
errno.ENOENT, errno.EISDIR, errno.ENOTDIR
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
|
||||||
raise
|
|
||||||
self.from_object(d)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def from_object(self, obj):
|
|
||||||
"""Updates the values from the given object. An object can be of one
|
|
||||||
of the following two types:
|
|
||||||
|
|
||||||
- a string: in this case the object with that name will be imported
|
|
||||||
- an actual object reference: that object is used directly
|
|
||||||
|
|
||||||
Objects are usually either modules or classes. :meth:`from_object`
|
|
||||||
loads only the uppercase attributes of the module/class. A ``dict``
|
|
||||||
object will not work with :meth:`from_object` because the keys of a
|
|
||||||
``dict`` are not attributes of the ``dict`` class.
|
|
||||||
|
|
||||||
Example of module-based configuration::
|
|
||||||
|
|
||||||
app.config.from_object('yourapplication.default_config')
|
|
||||||
from yourapplication import default_config
|
|
||||||
app.config.from_object(default_config)
|
|
||||||
|
|
||||||
You should not use this function to load the actual configuration but
|
|
||||||
rather configuration defaults. The actual config should be loaded
|
|
||||||
with :meth:`from_pyfile` and ideally from a location not within the
|
|
||||||
package because the package might be installed system wide.
|
|
||||||
|
|
||||||
See :ref:`config-dev-prod` for an example of class-based configuration
|
|
||||||
using :meth:`from_object`.
|
|
||||||
|
|
||||||
:param obj: an import name or object
|
|
||||||
"""
|
|
||||||
if isinstance(obj, string_types):
|
|
||||||
obj = import_string(obj)
|
|
||||||
for key in dir(obj):
|
|
||||||
if key.isupper():
|
|
||||||
self[key] = getattr(obj, key)
|
|
||||||
|
|
||||||
def from_json(self, filename, silent=False):
|
|
||||||
"""Updates the values in the config from a JSON file. This function
|
|
||||||
behaves as if the JSON object was a dictionary and passed to the
|
|
||||||
:meth:`from_mapping` function.
|
|
||||||
|
|
||||||
:param filename: the filename of the JSON file. This can either be an
|
|
||||||
absolute filename or a filename relative to the
|
|
||||||
root path.
|
|
||||||
:param silent: set to ``True`` if you want silent failure for missing
|
|
||||||
files.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
filename = os.path.join(self.root_path, filename)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(filename) as json_file:
|
|
||||||
obj = json.loads(json_file.read())
|
|
||||||
except IOError as e:
|
|
||||||
if silent and e.errno in (errno.ENOENT, errno.EISDIR):
|
|
||||||
return False
|
|
||||||
e.strerror = 'Unable to load configuration file (%s)' % e.strerror
|
|
||||||
raise
|
|
||||||
return self.from_mapping(obj)
|
|
||||||
|
|
||||||
def from_mapping(self, *mapping, **kwargs):
|
|
||||||
"""Updates the config like :meth:`update` ignoring items with non-upper
|
|
||||||
keys.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
mappings = []
|
|
||||||
if len(mapping) == 1:
|
|
||||||
if hasattr(mapping[0], 'items'):
|
|
||||||
mappings.append(mapping[0].items())
|
|
||||||
else:
|
|
||||||
mappings.append(mapping[0])
|
|
||||||
elif len(mapping) > 1:
|
|
||||||
raise TypeError(
|
|
||||||
'expected at most 1 positional argument, got %d' % len(mapping)
|
|
||||||
)
|
|
||||||
mappings.append(kwargs.items())
|
|
||||||
for mapping in mappings:
|
|
||||||
for (key, value) in mapping:
|
|
||||||
if key.isupper():
|
|
||||||
self[key] = value
|
|
||||||
return True
|
|
||||||
|
|
||||||
def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
|
|
||||||
"""Returns a dictionary containing a subset of configuration options
|
|
||||||
that match the specified namespace/prefix. Example usage::
|
|
||||||
|
|
||||||
app.config['IMAGE_STORE_TYPE'] = 'fs'
|
|
||||||
app.config['IMAGE_STORE_PATH'] = '/var/app/images'
|
|
||||||
app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
|
|
||||||
image_store_config = app.config.get_namespace('IMAGE_STORE_')
|
|
||||||
|
|
||||||
The resulting dictionary `image_store_config` would look like::
|
|
||||||
|
|
||||||
{
|
|
||||||
'type': 'fs',
|
|
||||||
'path': '/var/app/images',
|
|
||||||
'base_url': 'http://img.website.com'
|
|
||||||
}
|
|
||||||
|
|
||||||
This is often useful when configuration options map directly to
|
|
||||||
keyword arguments in functions or class constructors.
|
|
||||||
|
|
||||||
:param namespace: a configuration namespace
|
|
||||||
:param lowercase: a flag indicating if the keys of the resulting
|
|
||||||
dictionary should be lowercase
|
|
||||||
:param trim_namespace: a flag indicating if the keys of the resulting
|
|
||||||
dictionary should not include the namespace
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
rv = {}
|
|
||||||
for k, v in iteritems(self):
|
|
||||||
if not k.startswith(namespace):
|
|
||||||
continue
|
|
||||||
if trim_namespace:
|
|
||||||
key = k[len(namespace):]
|
|
||||||
else:
|
|
||||||
key = k
|
|
||||||
if lowercase:
|
|
||||||
key = key.lower()
|
|
||||||
rv[key] = v
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
|
|
|
@ -1,457 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.ctx
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the objects required to keep the context.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
from werkzeug.exceptions import HTTPException
|
|
||||||
|
|
||||||
from .globals import _request_ctx_stack, _app_ctx_stack
|
|
||||||
from .signals import appcontext_pushed, appcontext_popped
|
|
||||||
from ._compat import BROKEN_PYPY_CTXMGR_EXIT, reraise
|
|
||||||
|
|
||||||
|
|
||||||
# a singleton sentinel value for parameter defaults
|
|
||||||
_sentinel = object()
|
|
||||||
|
|
||||||
|
|
||||||
class _AppCtxGlobals(object):
|
|
||||||
"""A plain object. Used as a namespace for storing data during an
|
|
||||||
application context.
|
|
||||||
|
|
||||||
Creating an app context automatically creates this object, which is
|
|
||||||
made available as the :data:`g` proxy.
|
|
||||||
|
|
||||||
.. describe:: 'key' in g
|
|
||||||
|
|
||||||
Check whether an attribute is present.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
|
|
||||||
.. describe:: iter(g)
|
|
||||||
|
|
||||||
Return an iterator over the attribute names.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get(self, name, default=None):
|
|
||||||
"""Get an attribute by name, or a default value. Like
|
|
||||||
:meth:`dict.get`.
|
|
||||||
|
|
||||||
:param name: Name of attribute to get.
|
|
||||||
:param default: Value to return if the attribute is not present.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
"""
|
|
||||||
return self.__dict__.get(name, default)
|
|
||||||
|
|
||||||
def pop(self, name, default=_sentinel):
|
|
||||||
"""Get and remove an attribute by name. Like :meth:`dict.pop`.
|
|
||||||
|
|
||||||
:param name: Name of attribute to pop.
|
|
||||||
:param default: Value to return if the attribute is not present,
|
|
||||||
instead of raise a ``KeyError``.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
if default is _sentinel:
|
|
||||||
return self.__dict__.pop(name)
|
|
||||||
else:
|
|
||||||
return self.__dict__.pop(name, default)
|
|
||||||
|
|
||||||
def setdefault(self, name, default=None):
|
|
||||||
"""Get the value of an attribute if it is present, otherwise
|
|
||||||
set and return a default value. Like :meth:`dict.setdefault`.
|
|
||||||
|
|
||||||
:param name: Name of attribute to get.
|
|
||||||
:param: default: Value to set and return if the attribute is not
|
|
||||||
present.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
return self.__dict__.setdefault(name, default)
|
|
||||||
|
|
||||||
def __contains__(self, item):
|
|
||||||
return item in self.__dict__
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return iter(self.__dict__)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
top = _app_ctx_stack.top
|
|
||||||
if top is not None:
|
|
||||||
return '<flask.g of %r>' % top.app.name
|
|
||||||
return object.__repr__(self)
|
|
||||||
|
|
||||||
|
|
||||||
def after_this_request(f):
|
|
||||||
"""Executes a function after this request. This is useful to modify
|
|
||||||
response objects. The function is passed the response object and has
|
|
||||||
to return the same or a new one.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
@after_this_request
|
|
||||||
def add_header(response):
|
|
||||||
response.headers['X-Foo'] = 'Parachute'
|
|
||||||
return response
|
|
||||||
return 'Hello World!'
|
|
||||||
|
|
||||||
This is more useful if a function other than the view function wants to
|
|
||||||
modify a response. For instance think of a decorator that wants to add
|
|
||||||
some headers without converting the return value into a response object.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
_request_ctx_stack.top._after_request_functions.append(f)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
def copy_current_request_context(f):
|
|
||||||
"""A helper function that decorates a function to retain the current
|
|
||||||
request context. This is useful when working with greenlets. The moment
|
|
||||||
the function is decorated a copy of the request context is created and
|
|
||||||
then pushed when the function is called.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
import gevent
|
|
||||||
from flask import copy_current_request_context
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
@copy_current_request_context
|
|
||||||
def do_some_work():
|
|
||||||
# do some work here, it can access flask.request like you
|
|
||||||
# would otherwise in the view function.
|
|
||||||
...
|
|
||||||
gevent.spawn(do_some_work)
|
|
||||||
return 'Regular response'
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
"""
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is None:
|
|
||||||
raise RuntimeError('This decorator can only be used at local scopes '
|
|
||||||
'when a request context is on the stack. For instance within '
|
|
||||||
'view functions.')
|
|
||||||
reqctx = top.copy()
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
with reqctx:
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
return update_wrapper(wrapper, f)
|
|
||||||
|
|
||||||
|
|
||||||
def has_request_context():
|
|
||||||
"""If you have code that wants to test if a request context is there or
|
|
||||||
not this function can be used. For instance, you may want to take advantage
|
|
||||||
of request information if the request object is available, but fail
|
|
||||||
silently if it is unavailable.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
class User(db.Model):
|
|
||||||
|
|
||||||
def __init__(self, username, remote_addr=None):
|
|
||||||
self.username = username
|
|
||||||
if remote_addr is None and has_request_context():
|
|
||||||
remote_addr = request.remote_addr
|
|
||||||
self.remote_addr = remote_addr
|
|
||||||
|
|
||||||
Alternatively you can also just test any of the context bound objects
|
|
||||||
(such as :class:`request` or :class:`g` for truthness)::
|
|
||||||
|
|
||||||
class User(db.Model):
|
|
||||||
|
|
||||||
def __init__(self, username, remote_addr=None):
|
|
||||||
self.username = username
|
|
||||||
if remote_addr is None and request:
|
|
||||||
remote_addr = request.remote_addr
|
|
||||||
self.remote_addr = remote_addr
|
|
||||||
|
|
||||||
.. versionadded:: 0.7
|
|
||||||
"""
|
|
||||||
return _request_ctx_stack.top is not None
|
|
||||||
|
|
||||||
|
|
||||||
def has_app_context():
|
|
||||||
"""Works like :func:`has_request_context` but for the application
|
|
||||||
context. You can also just do a boolean check on the
|
|
||||||
:data:`current_app` object instead.
|
|
||||||
|
|
||||||
.. versionadded:: 0.9
|
|
||||||
"""
|
|
||||||
return _app_ctx_stack.top is not None
|
|
||||||
|
|
||||||
|
|
||||||
class AppContext(object):
|
|
||||||
"""The application context binds an application object implicitly
|
|
||||||
to the current thread or greenlet, similar to how the
|
|
||||||
:class:`RequestContext` binds request information. The application
|
|
||||||
context is also implicitly created if a request context is created
|
|
||||||
but the application is not on top of the individual application
|
|
||||||
context.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
self.url_adapter = app.create_url_adapter(None)
|
|
||||||
self.g = app.app_ctx_globals_class()
|
|
||||||
|
|
||||||
# Like request context, app contexts can be pushed multiple times
|
|
||||||
# but there a basic "refcount" is enough to track them.
|
|
||||||
self._refcnt = 0
|
|
||||||
|
|
||||||
def push(self):
|
|
||||||
"""Binds the app context to the current context."""
|
|
||||||
self._refcnt += 1
|
|
||||||
if hasattr(sys, 'exc_clear'):
|
|
||||||
sys.exc_clear()
|
|
||||||
_app_ctx_stack.push(self)
|
|
||||||
appcontext_pushed.send(self.app)
|
|
||||||
|
|
||||||
def pop(self, exc=_sentinel):
|
|
||||||
"""Pops the app context."""
|
|
||||||
try:
|
|
||||||
self._refcnt -= 1
|
|
||||||
if self._refcnt <= 0:
|
|
||||||
if exc is _sentinel:
|
|
||||||
exc = sys.exc_info()[1]
|
|
||||||
self.app.do_teardown_appcontext(exc)
|
|
||||||
finally:
|
|
||||||
rv = _app_ctx_stack.pop()
|
|
||||||
assert rv is self, 'Popped wrong app context. (%r instead of %r)' \
|
|
||||||
% (rv, self)
|
|
||||||
appcontext_popped.send(self.app)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.push()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.pop(exc_value)
|
|
||||||
|
|
||||||
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
|
||||||
reraise(exc_type, exc_value, tb)
|
|
||||||
|
|
||||||
|
|
||||||
class RequestContext(object):
|
|
||||||
"""The request context contains all request relevant information. It is
|
|
||||||
created at the beginning of the request and pushed to the
|
|
||||||
`_request_ctx_stack` and removed at the end of it. It will create the
|
|
||||||
URL adapter and request object for the WSGI environment provided.
|
|
||||||
|
|
||||||
Do not attempt to use this class directly, instead use
|
|
||||||
:meth:`~flask.Flask.test_request_context` and
|
|
||||||
:meth:`~flask.Flask.request_context` to create this object.
|
|
||||||
|
|
||||||
When the request context is popped, it will evaluate all the
|
|
||||||
functions registered on the application for teardown execution
|
|
||||||
(:meth:`~flask.Flask.teardown_request`).
|
|
||||||
|
|
||||||
The request context is automatically popped at the end of the request
|
|
||||||
for you. In debug mode the request context is kept around if
|
|
||||||
exceptions happen so that interactive debuggers have a chance to
|
|
||||||
introspect the data. With 0.4 this can also be forced for requests
|
|
||||||
that did not fail and outside of ``DEBUG`` mode. By setting
|
|
||||||
``'flask._preserve_context'`` to ``True`` on the WSGI environment the
|
|
||||||
context will not pop itself at the end of the request. This is used by
|
|
||||||
the :meth:`~flask.Flask.test_client` for example to implement the
|
|
||||||
deferred cleanup functionality.
|
|
||||||
|
|
||||||
You might find this helpful for unittests where you need the
|
|
||||||
information from the context local around for a little longer. Make
|
|
||||||
sure to properly :meth:`~werkzeug.LocalStack.pop` the stack yourself in
|
|
||||||
that situation, otherwise your unittests will leak memory.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, environ, request=None):
|
|
||||||
self.app = app
|
|
||||||
if request is None:
|
|
||||||
request = app.request_class(environ)
|
|
||||||
self.request = request
|
|
||||||
self.url_adapter = app.create_url_adapter(self.request)
|
|
||||||
self.flashes = None
|
|
||||||
self.session = None
|
|
||||||
|
|
||||||
# Request contexts can be pushed multiple times and interleaved with
|
|
||||||
# other request contexts. Now only if the last level is popped we
|
|
||||||
# get rid of them. Additionally if an application context is missing
|
|
||||||
# one is created implicitly so for each level we add this information
|
|
||||||
self._implicit_app_ctx_stack = []
|
|
||||||
|
|
||||||
# indicator if the context was preserved. Next time another context
|
|
||||||
# is pushed the preserved context is popped.
|
|
||||||
self.preserved = False
|
|
||||||
|
|
||||||
# remembers the exception for pop if there is one in case the context
|
|
||||||
# preservation kicks in.
|
|
||||||
self._preserved_exc = None
|
|
||||||
|
|
||||||
# Functions that should be executed after the request on the response
|
|
||||||
# object. These will be called before the regular "after_request"
|
|
||||||
# functions.
|
|
||||||
self._after_request_functions = []
|
|
||||||
|
|
||||||
self.match_request()
|
|
||||||
|
|
||||||
def _get_g(self):
|
|
||||||
return _app_ctx_stack.top.g
|
|
||||||
def _set_g(self, value):
|
|
||||||
_app_ctx_stack.top.g = value
|
|
||||||
g = property(_get_g, _set_g)
|
|
||||||
del _get_g, _set_g
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
"""Creates a copy of this request context with the same request object.
|
|
||||||
This can be used to move a request context to a different greenlet.
|
|
||||||
Because the actual request object is the same this cannot be used to
|
|
||||||
move a request context to a different thread unless access to the
|
|
||||||
request object is locked.
|
|
||||||
|
|
||||||
.. versionadded:: 0.10
|
|
||||||
"""
|
|
||||||
return self.__class__(self.app,
|
|
||||||
environ=self.request.environ,
|
|
||||||
request=self.request
|
|
||||||
)
|
|
||||||
|
|
||||||
def match_request(self):
|
|
||||||
"""Can be overridden by a subclass to hook into the matching
|
|
||||||
of the request.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
url_rule, self.request.view_args = \
|
|
||||||
self.url_adapter.match(return_rule=True)
|
|
||||||
self.request.url_rule = url_rule
|
|
||||||
except HTTPException as e:
|
|
||||||
self.request.routing_exception = e
|
|
||||||
|
|
||||||
def push(self):
|
|
||||||
"""Binds the request context to the current context."""
|
|
||||||
# If an exception occurs in debug mode or if context preservation is
|
|
||||||
# activated under exception situations exactly one context stays
|
|
||||||
# on the stack. The rationale is that you want to access that
|
|
||||||
# information under debug situations. However if someone forgets to
|
|
||||||
# pop that context again we want to make sure that on the next push
|
|
||||||
# it's invalidated, otherwise we run at risk that something leaks
|
|
||||||
# memory. This is usually only a problem in test suite since this
|
|
||||||
# functionality is not active in production environments.
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is not None and top.preserved:
|
|
||||||
top.pop(top._preserved_exc)
|
|
||||||
|
|
||||||
# Before we push the request context we have to ensure that there
|
|
||||||
# is an application context.
|
|
||||||
app_ctx = _app_ctx_stack.top
|
|
||||||
if app_ctx is None or app_ctx.app != self.app:
|
|
||||||
app_ctx = self.app.app_context()
|
|
||||||
app_ctx.push()
|
|
||||||
self._implicit_app_ctx_stack.append(app_ctx)
|
|
||||||
else:
|
|
||||||
self._implicit_app_ctx_stack.append(None)
|
|
||||||
|
|
||||||
if hasattr(sys, 'exc_clear'):
|
|
||||||
sys.exc_clear()
|
|
||||||
|
|
||||||
_request_ctx_stack.push(self)
|
|
||||||
|
|
||||||
# Open the session at the moment that the request context is available.
|
|
||||||
# This allows a custom open_session method to use the request context.
|
|
||||||
# Only open a new session if this is the first time the request was
|
|
||||||
# pushed, otherwise stream_with_context loses the session.
|
|
||||||
if self.session is None:
|
|
||||||
session_interface = self.app.session_interface
|
|
||||||
self.session = session_interface.open_session(
|
|
||||||
self.app, self.request
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.session is None:
|
|
||||||
self.session = session_interface.make_null_session(self.app)
|
|
||||||
|
|
||||||
def pop(self, exc=_sentinel):
|
|
||||||
"""Pops the request context and unbinds it by doing that. This will
|
|
||||||
also trigger the execution of functions registered by the
|
|
||||||
:meth:`~flask.Flask.teardown_request` decorator.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.9
|
|
||||||
Added the `exc` argument.
|
|
||||||
"""
|
|
||||||
app_ctx = self._implicit_app_ctx_stack.pop()
|
|
||||||
|
|
||||||
try:
|
|
||||||
clear_request = False
|
|
||||||
if not self._implicit_app_ctx_stack:
|
|
||||||
self.preserved = False
|
|
||||||
self._preserved_exc = None
|
|
||||||
if exc is _sentinel:
|
|
||||||
exc = sys.exc_info()[1]
|
|
||||||
self.app.do_teardown_request(exc)
|
|
||||||
|
|
||||||
# If this interpreter supports clearing the exception information
|
|
||||||
# we do that now. This will only go into effect on Python 2.x,
|
|
||||||
# on 3.x it disappears automatically at the end of the exception
|
|
||||||
# stack.
|
|
||||||
if hasattr(sys, 'exc_clear'):
|
|
||||||
sys.exc_clear()
|
|
||||||
|
|
||||||
request_close = getattr(self.request, 'close', None)
|
|
||||||
if request_close is not None:
|
|
||||||
request_close()
|
|
||||||
clear_request = True
|
|
||||||
finally:
|
|
||||||
rv = _request_ctx_stack.pop()
|
|
||||||
|
|
||||||
# get rid of circular dependencies at the end of the request
|
|
||||||
# so that we don't require the GC to be active.
|
|
||||||
if clear_request:
|
|
||||||
rv.request.environ['werkzeug.request'] = None
|
|
||||||
|
|
||||||
# Get rid of the app as well if necessary.
|
|
||||||
if app_ctx is not None:
|
|
||||||
app_ctx.pop(exc)
|
|
||||||
|
|
||||||
assert rv is self, 'Popped wrong request context. ' \
|
|
||||||
'(%r instead of %r)' % (rv, self)
|
|
||||||
|
|
||||||
def auto_pop(self, exc):
|
|
||||||
if self.request.environ.get('flask._preserve_context') or \
|
|
||||||
(exc is not None and self.app.preserve_context_on_exception):
|
|
||||||
self.preserved = True
|
|
||||||
self._preserved_exc = exc
|
|
||||||
else:
|
|
||||||
self.pop(exc)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.push()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
# do not pop the request stack if we are in debug mode and an
|
|
||||||
# exception happened. This will allow the debugger to still
|
|
||||||
# access the request object in the interactive shell. Furthermore
|
|
||||||
# the context can be force kept alive for the test client.
|
|
||||||
# See flask.testing for how this works.
|
|
||||||
self.auto_pop(exc_value)
|
|
||||||
|
|
||||||
if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
|
|
||||||
reraise(exc_type, exc_value, tb)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<%s \'%s\' [%s] of %s>' % (
|
|
||||||
self.__class__.__name__,
|
|
||||||
self.request.url,
|
|
||||||
self.request.method,
|
|
||||||
self.app.name,
|
|
||||||
)
|
|
|
@ -1,168 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.debughelpers
|
|
||||||
~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Various helpers to make the development experience better.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
from warnings import warn
|
|
||||||
|
|
||||||
from ._compat import implements_to_string, text_type
|
|
||||||
from .app import Flask
|
|
||||||
from .blueprints import Blueprint
|
|
||||||
from .globals import _request_ctx_stack
|
|
||||||
|
|
||||||
|
|
||||||
class UnexpectedUnicodeError(AssertionError, UnicodeError):
|
|
||||||
"""Raised in places where we want some better error reporting for
|
|
||||||
unexpected unicode or binary data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@implements_to_string
|
|
||||||
class DebugFilesKeyError(KeyError, AssertionError):
|
|
||||||
"""Raised from request.files during debugging. The idea is that it can
|
|
||||||
provide a better error message than just a generic KeyError/BadRequest.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request, key):
|
|
||||||
form_matches = request.form.getlist(key)
|
|
||||||
buf = ['You tried to access the file "%s" in the request.files '
|
|
||||||
'dictionary but it does not exist. The mimetype for the request '
|
|
||||||
'is "%s" instead of "multipart/form-data" which means that no '
|
|
||||||
'file contents were transmitted. To fix this error you should '
|
|
||||||
'provide enctype="multipart/form-data" in your form.' %
|
|
||||||
(key, request.mimetype)]
|
|
||||||
if form_matches:
|
|
||||||
buf.append('\n\nThe browser instead transmitted some file names. '
|
|
||||||
'This was submitted: %s' % ', '.join('"%s"' % x
|
|
||||||
for x in form_matches))
|
|
||||||
self.msg = ''.join(buf)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.msg
|
|
||||||
|
|
||||||
|
|
||||||
class FormDataRoutingRedirect(AssertionError):
|
|
||||||
"""This exception is raised by Flask in debug mode if it detects a
|
|
||||||
redirect caused by the routing system when the request method is not
|
|
||||||
GET, HEAD or OPTIONS. Reasoning: form data will be dropped.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request):
|
|
||||||
exc = request.routing_exception
|
|
||||||
buf = ['A request was sent to this URL (%s) but a redirect was '
|
|
||||||
'issued automatically by the routing system to "%s".'
|
|
||||||
% (request.url, exc.new_url)]
|
|
||||||
|
|
||||||
# In case just a slash was appended we can be extra helpful
|
|
||||||
if request.base_url + '/' == exc.new_url.split('?')[0]:
|
|
||||||
buf.append(' The URL was defined with a trailing slash so '
|
|
||||||
'Flask will automatically redirect to the URL '
|
|
||||||
'with the trailing slash if it was accessed '
|
|
||||||
'without one.')
|
|
||||||
|
|
||||||
buf.append(' Make sure to directly send your %s-request to this URL '
|
|
||||||
'since we can\'t make browsers or HTTP clients redirect '
|
|
||||||
'with form data reliably or without user interaction.' %
|
|
||||||
request.method)
|
|
||||||
buf.append('\n\nNote: this exception is only raised in debug mode')
|
|
||||||
AssertionError.__init__(self, ''.join(buf).encode('utf-8'))
|
|
||||||
|
|
||||||
|
|
||||||
def attach_enctype_error_multidict(request):
|
|
||||||
"""Since Flask 0.8 we're monkeypatching the files object in case a
|
|
||||||
request is detected that does not use multipart form data but the files
|
|
||||||
object is accessed.
|
|
||||||
"""
|
|
||||||
oldcls = request.files.__class__
|
|
||||||
class newcls(oldcls):
|
|
||||||
def __getitem__(self, key):
|
|
||||||
try:
|
|
||||||
return oldcls.__getitem__(self, key)
|
|
||||||
except KeyError:
|
|
||||||
if key not in request.form:
|
|
||||||
raise
|
|
||||||
raise DebugFilesKeyError(request, key)
|
|
||||||
newcls.__name__ = oldcls.__name__
|
|
||||||
newcls.__module__ = oldcls.__module__
|
|
||||||
request.files.__class__ = newcls
|
|
||||||
|
|
||||||
|
|
||||||
def _dump_loader_info(loader):
|
|
||||||
yield 'class: %s.%s' % (type(loader).__module__, type(loader).__name__)
|
|
||||||
for key, value in sorted(loader.__dict__.items()):
|
|
||||||
if key.startswith('_'):
|
|
||||||
continue
|
|
||||||
if isinstance(value, (tuple, list)):
|
|
||||||
if not all(isinstance(x, (str, text_type)) for x in value):
|
|
||||||
continue
|
|
||||||
yield '%s:' % key
|
|
||||||
for item in value:
|
|
||||||
yield ' - %s' % item
|
|
||||||
continue
|
|
||||||
elif not isinstance(value, (str, text_type, int, float, bool)):
|
|
||||||
continue
|
|
||||||
yield '%s: %r' % (key, value)
|
|
||||||
|
|
||||||
|
|
||||||
def explain_template_loading_attempts(app, template, attempts):
|
|
||||||
"""This should help developers understand what failed"""
|
|
||||||
info = ['Locating template "%s":' % template]
|
|
||||||
total_found = 0
|
|
||||||
blueprint = None
|
|
||||||
reqctx = _request_ctx_stack.top
|
|
||||||
if reqctx is not None and reqctx.request.blueprint is not None:
|
|
||||||
blueprint = reqctx.request.blueprint
|
|
||||||
|
|
||||||
for idx, (loader, srcobj, triple) in enumerate(attempts):
|
|
||||||
if isinstance(srcobj, Flask):
|
|
||||||
src_info = 'application "%s"' % srcobj.import_name
|
|
||||||
elif isinstance(srcobj, Blueprint):
|
|
||||||
src_info = 'blueprint "%s" (%s)' % (srcobj.name,
|
|
||||||
srcobj.import_name)
|
|
||||||
else:
|
|
||||||
src_info = repr(srcobj)
|
|
||||||
|
|
||||||
info.append('% 5d: trying loader of %s' % (
|
|
||||||
idx + 1, src_info))
|
|
||||||
|
|
||||||
for line in _dump_loader_info(loader):
|
|
||||||
info.append(' %s' % line)
|
|
||||||
|
|
||||||
if triple is None:
|
|
||||||
detail = 'no match'
|
|
||||||
else:
|
|
||||||
detail = 'found (%r)' % (triple[1] or '<string>')
|
|
||||||
total_found += 1
|
|
||||||
info.append(' -> %s' % detail)
|
|
||||||
|
|
||||||
seems_fishy = False
|
|
||||||
if total_found == 0:
|
|
||||||
info.append('Error: the template could not be found.')
|
|
||||||
seems_fishy = True
|
|
||||||
elif total_found > 1:
|
|
||||||
info.append('Warning: multiple loaders returned a match for the template.')
|
|
||||||
seems_fishy = True
|
|
||||||
|
|
||||||
if blueprint is not None and seems_fishy:
|
|
||||||
info.append(' The template was looked up from an endpoint that '
|
|
||||||
'belongs to the blueprint "%s".' % blueprint)
|
|
||||||
info.append(' Maybe you did not place a template in the right folder?')
|
|
||||||
info.append(' See http://flask.pocoo.org/docs/blueprints/#templates')
|
|
||||||
|
|
||||||
app.logger.info('\n'.join(info))
|
|
||||||
|
|
||||||
|
|
||||||
def explain_ignored_app_run():
|
|
||||||
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
|
||||||
warn(Warning('Silently ignoring app.run() because the '
|
|
||||||
'application is run from the flask command line '
|
|
||||||
'executable. Consider putting app.run() behind an '
|
|
||||||
'if __name__ == "__main__" guard to silence this '
|
|
||||||
'warning.'), stacklevel=3)
|
|
|
@ -1,61 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.globals
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Defines all the global objects that are proxies to the current
|
|
||||||
active context.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
from werkzeug.local import LocalStack, LocalProxy
|
|
||||||
|
|
||||||
|
|
||||||
_request_ctx_err_msg = '''\
|
|
||||||
Working outside of request context.
|
|
||||||
|
|
||||||
This typically means that you attempted to use functionality that needed
|
|
||||||
an active HTTP request. Consult the documentation on testing for
|
|
||||||
information about how to avoid this problem.\
|
|
||||||
'''
|
|
||||||
_app_ctx_err_msg = '''\
|
|
||||||
Working outside of application context.
|
|
||||||
|
|
||||||
This typically means that you attempted to use functionality that needed
|
|
||||||
to interface with the current application object in some way. To solve
|
|
||||||
this, set up an application context with app.app_context(). See the
|
|
||||||
documentation for more information.\
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_req_object(name):
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is None:
|
|
||||||
raise RuntimeError(_request_ctx_err_msg)
|
|
||||||
return getattr(top, name)
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_app_object(name):
|
|
||||||
top = _app_ctx_stack.top
|
|
||||||
if top is None:
|
|
||||||
raise RuntimeError(_app_ctx_err_msg)
|
|
||||||
return getattr(top, name)
|
|
||||||
|
|
||||||
|
|
||||||
def _find_app():
|
|
||||||
top = _app_ctx_stack.top
|
|
||||||
if top is None:
|
|
||||||
raise RuntimeError(_app_ctx_err_msg)
|
|
||||||
return top.app
|
|
||||||
|
|
||||||
|
|
||||||
# context locals
|
|
||||||
_request_ctx_stack = LocalStack()
|
|
||||||
_app_ctx_stack = LocalStack()
|
|
||||||
current_app = LocalProxy(_find_app)
|
|
||||||
request = LocalProxy(partial(_lookup_req_object, 'request'))
|
|
||||||
session = LocalProxy(partial(_lookup_req_object, 'session'))
|
|
||||||
g = LocalProxy(partial(_lookup_app_object, 'g'))
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,327 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.json
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
import codecs
|
|
||||||
import io
|
|
||||||
import uuid
|
|
||||||
from datetime import date, datetime
|
|
||||||
from flask.globals import current_app, request
|
|
||||||
from flask._compat import text_type, PY2
|
|
||||||
|
|
||||||
from werkzeug.http import http_date
|
|
||||||
from jinja2 import Markup
|
|
||||||
|
|
||||||
# Use the same json implementation as itsdangerous on which we
|
|
||||||
# depend anyways.
|
|
||||||
from itsdangerous import json as _json
|
|
||||||
|
|
||||||
|
|
||||||
# Figure out if simplejson escapes slashes. This behavior was changed
|
|
||||||
# from one version to another without reason.
|
|
||||||
_slash_escape = '\\/' not in _json.dumps('/')
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['dump', 'dumps', 'load', 'loads', 'htmlsafe_dump',
|
|
||||||
'htmlsafe_dumps', 'JSONDecoder', 'JSONEncoder',
|
|
||||||
'jsonify']
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap_reader_for_text(fp, encoding):
|
|
||||||
if isinstance(fp.read(0), bytes):
|
|
||||||
fp = io.TextIOWrapper(io.BufferedReader(fp), encoding)
|
|
||||||
return fp
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap_writer_for_text(fp, encoding):
|
|
||||||
try:
|
|
||||||
fp.write('')
|
|
||||||
except TypeError:
|
|
||||||
fp = io.TextIOWrapper(fp, encoding)
|
|
||||||
return fp
|
|
||||||
|
|
||||||
|
|
||||||
class JSONEncoder(_json.JSONEncoder):
|
|
||||||
"""The default Flask JSON encoder. This one extends the default simplejson
|
|
||||||
encoder by also supporting ``datetime`` objects, ``UUID`` as well as
|
|
||||||
``Markup`` objects which are serialized as RFC 822 datetime strings (same
|
|
||||||
as the HTTP date format). In order to support more data types override the
|
|
||||||
:meth:`default` method.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def default(self, o):
|
|
||||||
"""Implement this method in a subclass such that it returns a
|
|
||||||
serializable object for ``o``, or calls the base implementation (to
|
|
||||||
raise a :exc:`TypeError`).
|
|
||||||
|
|
||||||
For example, to support arbitrary iterators, you could implement
|
|
||||||
default like this::
|
|
||||||
|
|
||||||
def default(self, o):
|
|
||||||
try:
|
|
||||||
iterable = iter(o)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
return list(iterable)
|
|
||||||
return JSONEncoder.default(self, o)
|
|
||||||
"""
|
|
||||||
if isinstance(o, datetime):
|
|
||||||
return http_date(o.utctimetuple())
|
|
||||||
if isinstance(o, date):
|
|
||||||
return http_date(o.timetuple())
|
|
||||||
if isinstance(o, uuid.UUID):
|
|
||||||
return str(o)
|
|
||||||
if hasattr(o, '__html__'):
|
|
||||||
return text_type(o.__html__())
|
|
||||||
return _json.JSONEncoder.default(self, o)
|
|
||||||
|
|
||||||
|
|
||||||
class JSONDecoder(_json.JSONDecoder):
|
|
||||||
"""The default JSON decoder. This one does not change the behavior from
|
|
||||||
the default simplejson decoder. Consult the :mod:`json` documentation
|
|
||||||
for more information. This decoder is not only used for the load
|
|
||||||
functions of this module but also :attr:`~flask.Request`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def _dump_arg_defaults(kwargs):
|
|
||||||
"""Inject default arguments for dump functions."""
|
|
||||||
if current_app:
|
|
||||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
|
||||||
kwargs.setdefault(
|
|
||||||
'cls',
|
|
||||||
bp.json_encoder if bp and bp.json_encoder
|
|
||||||
else current_app.json_encoder
|
|
||||||
)
|
|
||||||
|
|
||||||
if not current_app.config['JSON_AS_ASCII']:
|
|
||||||
kwargs.setdefault('ensure_ascii', False)
|
|
||||||
|
|
||||||
kwargs.setdefault('sort_keys', current_app.config['JSON_SORT_KEYS'])
|
|
||||||
else:
|
|
||||||
kwargs.setdefault('sort_keys', True)
|
|
||||||
kwargs.setdefault('cls', JSONEncoder)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_arg_defaults(kwargs):
|
|
||||||
"""Inject default arguments for load functions."""
|
|
||||||
if current_app:
|
|
||||||
bp = current_app.blueprints.get(request.blueprint) if request else None
|
|
||||||
kwargs.setdefault(
|
|
||||||
'cls',
|
|
||||||
bp.json_decoder if bp and bp.json_decoder
|
|
||||||
else current_app.json_decoder
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
kwargs.setdefault('cls', JSONDecoder)
|
|
||||||
|
|
||||||
|
|
||||||
def detect_encoding(data):
|
|
||||||
"""Detect which UTF codec was used to encode the given bytes.
|
|
||||||
|
|
||||||
The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is
|
|
||||||
accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big
|
|
||||||
or little endian. Some editors or libraries may prepend a BOM.
|
|
||||||
|
|
||||||
:param data: Bytes in unknown UTF encoding.
|
|
||||||
:return: UTF encoding name
|
|
||||||
"""
|
|
||||||
head = data[:4]
|
|
||||||
|
|
||||||
if head[:3] == codecs.BOM_UTF8:
|
|
||||||
return 'utf-8-sig'
|
|
||||||
|
|
||||||
if b'\x00' not in head:
|
|
||||||
return 'utf-8'
|
|
||||||
|
|
||||||
if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE):
|
|
||||||
return 'utf-32'
|
|
||||||
|
|
||||||
if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE):
|
|
||||||
return 'utf-16'
|
|
||||||
|
|
||||||
if len(head) == 4:
|
|
||||||
if head[:3] == b'\x00\x00\x00':
|
|
||||||
return 'utf-32-be'
|
|
||||||
|
|
||||||
if head[::2] == b'\x00\x00':
|
|
||||||
return 'utf-16-be'
|
|
||||||
|
|
||||||
if head[1:] == b'\x00\x00\x00':
|
|
||||||
return 'utf-32-le'
|
|
||||||
|
|
||||||
if head[1::2] == b'\x00\x00':
|
|
||||||
return 'utf-16-le'
|
|
||||||
|
|
||||||
if len(head) == 2:
|
|
||||||
return 'utf-16-be' if head.startswith(b'\x00') else 'utf-16-le'
|
|
||||||
|
|
||||||
return 'utf-8'
|
|
||||||
|
|
||||||
|
|
||||||
def dumps(obj, **kwargs):
|
|
||||||
"""Serialize ``obj`` to a JSON formatted ``str`` by using the application's
|
|
||||||
configured encoder (:attr:`~flask.Flask.json_encoder`) if there is an
|
|
||||||
application on the stack.
|
|
||||||
|
|
||||||
This function can return ``unicode`` strings or ascii-only bytestrings by
|
|
||||||
default which coerce into unicode strings automatically. That behavior by
|
|
||||||
default is controlled by the ``JSON_AS_ASCII`` configuration variable
|
|
||||||
and can be overridden by the simplejson ``ensure_ascii`` parameter.
|
|
||||||
"""
|
|
||||||
_dump_arg_defaults(kwargs)
|
|
||||||
encoding = kwargs.pop('encoding', None)
|
|
||||||
rv = _json.dumps(obj, **kwargs)
|
|
||||||
if encoding is not None and isinstance(rv, text_type):
|
|
||||||
rv = rv.encode(encoding)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def dump(obj, fp, **kwargs):
|
|
||||||
"""Like :func:`dumps` but writes into a file object."""
|
|
||||||
_dump_arg_defaults(kwargs)
|
|
||||||
encoding = kwargs.pop('encoding', None)
|
|
||||||
if encoding is not None:
|
|
||||||
fp = _wrap_writer_for_text(fp, encoding)
|
|
||||||
_json.dump(obj, fp, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def loads(s, **kwargs):
|
|
||||||
"""Unserialize a JSON object from a string ``s`` by using the application's
|
|
||||||
configured decoder (:attr:`~flask.Flask.json_decoder`) if there is an
|
|
||||||
application on the stack.
|
|
||||||
"""
|
|
||||||
_load_arg_defaults(kwargs)
|
|
||||||
if isinstance(s, bytes):
|
|
||||||
encoding = kwargs.pop('encoding', None)
|
|
||||||
if encoding is None:
|
|
||||||
encoding = detect_encoding(s)
|
|
||||||
s = s.decode(encoding)
|
|
||||||
return _json.loads(s, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def load(fp, **kwargs):
|
|
||||||
"""Like :func:`loads` but reads from a file object.
|
|
||||||
"""
|
|
||||||
_load_arg_defaults(kwargs)
|
|
||||||
if not PY2:
|
|
||||||
fp = _wrap_reader_for_text(fp, kwargs.pop('encoding', None) or 'utf-8')
|
|
||||||
return _json.load(fp, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def htmlsafe_dumps(obj, **kwargs):
|
|
||||||
"""Works exactly like :func:`dumps` but is safe for use in ``<script>``
|
|
||||||
tags. It accepts the same arguments and returns a JSON string. Note that
|
|
||||||
this is available in templates through the ``|tojson`` filter which will
|
|
||||||
also mark the result as safe. Due to how this function escapes certain
|
|
||||||
characters this is safe even if used outside of ``<script>`` tags.
|
|
||||||
|
|
||||||
The following characters are escaped in strings:
|
|
||||||
|
|
||||||
- ``<``
|
|
||||||
- ``>``
|
|
||||||
- ``&``
|
|
||||||
- ``'``
|
|
||||||
|
|
||||||
This makes it safe to embed such strings in any place in HTML with the
|
|
||||||
notable exception of double quoted attributes. In that case single
|
|
||||||
quote your attributes or HTML escape it in addition.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.10
|
|
||||||
This function's return value is now always safe for HTML usage, even
|
|
||||||
if outside of script tags or if used in XHTML. This rule does not
|
|
||||||
hold true when using this function in HTML attributes that are double
|
|
||||||
quoted. Always single quote attributes if you use the ``|tojson``
|
|
||||||
filter. Alternatively use ``|tojson|forceescape``.
|
|
||||||
"""
|
|
||||||
rv = dumps(obj, **kwargs) \
|
|
||||||
.replace(u'<', u'\\u003c') \
|
|
||||||
.replace(u'>', u'\\u003e') \
|
|
||||||
.replace(u'&', u'\\u0026') \
|
|
||||||
.replace(u"'", u'\\u0027')
|
|
||||||
if not _slash_escape:
|
|
||||||
rv = rv.replace('\\/', '/')
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def htmlsafe_dump(obj, fp, **kwargs):
|
|
||||||
"""Like :func:`htmlsafe_dumps` but writes into a file object."""
|
|
||||||
fp.write(text_type(htmlsafe_dumps(obj, **kwargs)))
|
|
||||||
|
|
||||||
|
|
||||||
def jsonify(*args, **kwargs):
|
|
||||||
"""This function wraps :func:`dumps` to add a few enhancements that make
|
|
||||||
life easier. It turns the JSON output into a :class:`~flask.Response`
|
|
||||||
object with the :mimetype:`application/json` mimetype. For convenience, it
|
|
||||||
also converts multiple arguments into an array or multiple keyword arguments
|
|
||||||
into a dict. This means that both ``jsonify(1,2,3)`` and
|
|
||||||
``jsonify([1,2,3])`` serialize to ``[1,2,3]``.
|
|
||||||
|
|
||||||
For clarity, the JSON serialization behavior has the following differences
|
|
||||||
from :func:`dumps`:
|
|
||||||
|
|
||||||
1. Single argument: Passed straight through to :func:`dumps`.
|
|
||||||
2. Multiple arguments: Converted to an array before being passed to
|
|
||||||
:func:`dumps`.
|
|
||||||
3. Multiple keyword arguments: Converted to a dict before being passed to
|
|
||||||
:func:`dumps`.
|
|
||||||
4. Both args and kwargs: Behavior undefined and will throw an exception.
|
|
||||||
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
from flask import jsonify
|
|
||||||
|
|
||||||
@app.route('/_get_current_user')
|
|
||||||
def get_current_user():
|
|
||||||
return jsonify(username=g.user.username,
|
|
||||||
email=g.user.email,
|
|
||||||
id=g.user.id)
|
|
||||||
|
|
||||||
This will send a JSON response like this to the browser::
|
|
||||||
|
|
||||||
{
|
|
||||||
"username": "admin",
|
|
||||||
"email": "admin@localhost",
|
|
||||||
"id": 42
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.. versionchanged:: 0.11
|
|
||||||
Added support for serializing top-level arrays. This introduces a
|
|
||||||
security risk in ancient browsers. See :ref:`json-security` for details.
|
|
||||||
|
|
||||||
This function's response will be pretty printed if the
|
|
||||||
``JSONIFY_PRETTYPRINT_REGULAR`` config parameter is set to True or the
|
|
||||||
Flask app is running in debug mode. Compressed (not pretty) formatting
|
|
||||||
currently means no indents and no spaces after separators.
|
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
|
||||||
"""
|
|
||||||
|
|
||||||
indent = None
|
|
||||||
separators = (',', ':')
|
|
||||||
|
|
||||||
if current_app.config['JSONIFY_PRETTYPRINT_REGULAR'] or current_app.debug:
|
|
||||||
indent = 2
|
|
||||||
separators = (', ', ': ')
|
|
||||||
|
|
||||||
if args and kwargs:
|
|
||||||
raise TypeError('jsonify() behavior undefined when passed both args and kwargs')
|
|
||||||
elif len(args) == 1: # single args are passed directly to dumps()
|
|
||||||
data = args[0]
|
|
||||||
else:
|
|
||||||
data = args or kwargs
|
|
||||||
|
|
||||||
return current_app.response_class(
|
|
||||||
dumps(data, indent=indent, separators=separators) + '\n',
|
|
||||||
mimetype=current_app.config['JSONIFY_MIMETYPE']
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def tojson_filter(obj, **kwargs):
|
|
||||||
return Markup(htmlsafe_dumps(obj, **kwargs))
|
|
|
@ -1,300 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Tagged JSON
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
A compact representation for lossless serialization of non-standard JSON types.
|
|
||||||
:class:`~flask.sessions.SecureCookieSessionInterface` uses this to serialize
|
|
||||||
the session data, but it may be useful in other places. It can be extended to
|
|
||||||
support other types.
|
|
||||||
|
|
||||||
.. autoclass:: TaggedJSONSerializer
|
|
||||||
:members:
|
|
||||||
|
|
||||||
.. autoclass:: JSONTag
|
|
||||||
:members:
|
|
||||||
|
|
||||||
Let's seen an example that adds support for :class:`~collections.OrderedDict`.
|
|
||||||
Dicts don't have an order in Python or JSON, so to handle this we will dump
|
|
||||||
the items as a list of ``[key, value]`` pairs. Subclass :class:`JSONTag` and
|
|
||||||
give it the new key ``' od'`` to identify the type. The session serializer
|
|
||||||
processes dicts first, so insert the new tag at the front of the order since
|
|
||||||
``OrderedDict`` must be processed before ``dict``. ::
|
|
||||||
|
|
||||||
from flask.json.tag import JSONTag
|
|
||||||
|
|
||||||
class TagOrderedDict(JSONTag):
|
|
||||||
__slots__ = ('serializer',)
|
|
||||||
key = ' od'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, OrderedDict)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return [[k, self.serializer.tag(v)] for k, v in iteritems(value)]
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return OrderedDict(value)
|
|
||||||
|
|
||||||
app.session_interface.serializer.register(TagOrderedDict, index=0)
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from base64 import b64decode, b64encode
|
|
||||||
from datetime import datetime
|
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from jinja2 import Markup
|
|
||||||
from werkzeug.http import http_date, parse_date
|
|
||||||
|
|
||||||
from flask._compat import iteritems, text_type
|
|
||||||
from flask.json import dumps, loads
|
|
||||||
|
|
||||||
|
|
||||||
class JSONTag(object):
|
|
||||||
"""Base class for defining type tags for :class:`TaggedJSONSerializer`."""
|
|
||||||
|
|
||||||
__slots__ = ('serializer',)
|
|
||||||
|
|
||||||
#: The tag to mark the serialized object with. If ``None``, this tag is
|
|
||||||
#: only used as an intermediate step during tagging.
|
|
||||||
key = None
|
|
||||||
|
|
||||||
def __init__(self, serializer):
|
|
||||||
"""Create a tagger for the given serializer."""
|
|
||||||
self.serializer = serializer
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
"""Check if the given value should be tagged by this tag."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
"""Convert the Python object to an object that is a valid JSON type.
|
|
||||||
The tag will be added later."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
"""Convert the JSON representation back to the correct type. The tag
|
|
||||||
will already be removed."""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def tag(self, value):
|
|
||||||
"""Convert the value to a valid JSON type and add the tag structure
|
|
||||||
around it."""
|
|
||||||
return {self.key: self.to_json(value)}
|
|
||||||
|
|
||||||
|
|
||||||
class TagDict(JSONTag):
|
|
||||||
"""Tag for 1-item dicts whose only key matches a registered tag.
|
|
||||||
|
|
||||||
Internally, the dict key is suffixed with `__`, and the suffix is removed
|
|
||||||
when deserializing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
key = ' di'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return (
|
|
||||||
isinstance(value, dict)
|
|
||||||
and len(value) == 1
|
|
||||||
and next(iter(value)) in self.serializer.tags
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
key = next(iter(value))
|
|
||||||
return {key + '__': self.serializer.tag(value[key])}
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
key = next(iter(value))
|
|
||||||
return {key[:-2]: value[key]}
|
|
||||||
|
|
||||||
|
|
||||||
class PassDict(JSONTag):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, dict)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
# JSON objects may only have string keys, so don't bother tagging the
|
|
||||||
# key here.
|
|
||||||
return dict((k, self.serializer.tag(v)) for k, v in iteritems(value))
|
|
||||||
|
|
||||||
tag = to_json
|
|
||||||
|
|
||||||
|
|
||||||
class TagTuple(JSONTag):
|
|
||||||
__slots__ = ()
|
|
||||||
key = ' t'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, tuple)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return [self.serializer.tag(item) for item in value]
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return tuple(value)
|
|
||||||
|
|
||||||
|
|
||||||
class PassList(JSONTag):
|
|
||||||
__slots__ = ()
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, list)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return [self.serializer.tag(item) for item in value]
|
|
||||||
|
|
||||||
tag = to_json
|
|
||||||
|
|
||||||
|
|
||||||
class TagBytes(JSONTag):
|
|
||||||
__slots__ = ()
|
|
||||||
key = ' b'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, bytes)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return b64encode(value).decode('ascii')
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return b64decode(value)
|
|
||||||
|
|
||||||
|
|
||||||
class TagMarkup(JSONTag):
|
|
||||||
"""Serialize anything matching the :class:`~flask.Markup` API by
|
|
||||||
having a ``__html__`` method to the result of that method. Always
|
|
||||||
deserializes to an instance of :class:`~flask.Markup`."""
|
|
||||||
|
|
||||||
__slots__ = ()
|
|
||||||
key = ' m'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return callable(getattr(value, '__html__', None))
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return text_type(value.__html__())
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return Markup(value)
|
|
||||||
|
|
||||||
|
|
||||||
class TagUUID(JSONTag):
|
|
||||||
__slots__ = ()
|
|
||||||
key = ' u'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, UUID)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return value.hex
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return UUID(value)
|
|
||||||
|
|
||||||
|
|
||||||
class TagDateTime(JSONTag):
|
|
||||||
__slots__ = ()
|
|
||||||
key = ' d'
|
|
||||||
|
|
||||||
def check(self, value):
|
|
||||||
return isinstance(value, datetime)
|
|
||||||
|
|
||||||
def to_json(self, value):
|
|
||||||
return http_date(value)
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
return parse_date(value)
|
|
||||||
|
|
||||||
|
|
||||||
class TaggedJSONSerializer(object):
|
|
||||||
"""Serializer that uses a tag system to compactly represent objects that
|
|
||||||
are not JSON types. Passed as the intermediate serializer to
|
|
||||||
:class:`itsdangerous.Serializer`.
|
|
||||||
|
|
||||||
The following extra types are supported:
|
|
||||||
|
|
||||||
* :class:`dict`
|
|
||||||
* :class:`tuple`
|
|
||||||
* :class:`bytes`
|
|
||||||
* :class:`~flask.Markup`
|
|
||||||
* :class:`~uuid.UUID`
|
|
||||||
* :class:`~datetime.datetime`
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ('tags', 'order')
|
|
||||||
|
|
||||||
#: Tag classes to bind when creating the serializer. Other tags can be
|
|
||||||
#: added later using :meth:`~register`.
|
|
||||||
default_tags = [
|
|
||||||
TagDict, PassDict, TagTuple, PassList, TagBytes, TagMarkup, TagUUID,
|
|
||||||
TagDateTime,
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.tags = {}
|
|
||||||
self.order = []
|
|
||||||
|
|
||||||
for cls in self.default_tags:
|
|
||||||
self.register(cls)
|
|
||||||
|
|
||||||
def register(self, tag_class, force=False, index=None):
|
|
||||||
"""Register a new tag with this serializer.
|
|
||||||
|
|
||||||
:param tag_class: tag class to register. Will be instantiated with this
|
|
||||||
serializer instance.
|
|
||||||
:param force: overwrite an existing tag. If false (default), a
|
|
||||||
:exc:`KeyError` is raised.
|
|
||||||
:param index: index to insert the new tag in the tag order. Useful when
|
|
||||||
the new tag is a special case of an existing tag. If ``None``
|
|
||||||
(default), the tag is appended to the end of the order.
|
|
||||||
|
|
||||||
:raise KeyError: if the tag key is already registered and ``force`` is
|
|
||||||
not true.
|
|
||||||
"""
|
|
||||||
tag = tag_class(self)
|
|
||||||
key = tag.key
|
|
||||||
|
|
||||||
if key is not None:
|
|
||||||
if not force and key in self.tags:
|
|
||||||
raise KeyError("Tag '{0}' is already registered.".format(key))
|
|
||||||
|
|
||||||
self.tags[key] = tag
|
|
||||||
|
|
||||||
if index is None:
|
|
||||||
self.order.append(tag)
|
|
||||||
else:
|
|
||||||
self.order.insert(index, tag)
|
|
||||||
|
|
||||||
def tag(self, value):
|
|
||||||
"""Convert a value to a tagged representation if necessary."""
|
|
||||||
for tag in self.order:
|
|
||||||
if tag.check(value):
|
|
||||||
return tag.tag(value)
|
|
||||||
|
|
||||||
return value
|
|
||||||
|
|
||||||
def untag(self, value):
|
|
||||||
"""Convert a tagged representation back to the original type."""
|
|
||||||
if len(value) != 1:
|
|
||||||
return value
|
|
||||||
|
|
||||||
key = next(iter(value))
|
|
||||||
|
|
||||||
if key not in self.tags:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return self.tags[key].to_python(value[key])
|
|
||||||
|
|
||||||
def dumps(self, value):
|
|
||||||
"""Tag the value and dump it to a compact JSON string."""
|
|
||||||
return dumps(self.tag(value), separators=(',', ':'))
|
|
||||||
|
|
||||||
def loads(self, value):
|
|
||||||
"""Load data from a JSON string and deserialized any tagged objects."""
|
|
||||||
return loads(value, object_hook=self.untag)
|
|
|
@ -1,78 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.logging
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from werkzeug.local import LocalProxy
|
|
||||||
|
|
||||||
from .globals import request
|
|
||||||
|
|
||||||
|
|
||||||
@LocalProxy
|
|
||||||
def wsgi_errors_stream():
|
|
||||||
"""Find the most appropriate error stream for the application. If a request
|
|
||||||
is active, log to ``wsgi.errors``, otherwise use ``sys.stderr``.
|
|
||||||
|
|
||||||
If you configure your own :class:`logging.StreamHandler`, you may want to
|
|
||||||
use this for the stream. If you are using file or dict configuration and
|
|
||||||
can't import this directly, you can refer to it as
|
|
||||||
``ext://flask.logging.wsgi_errors_stream``.
|
|
||||||
"""
|
|
||||||
return request.environ['wsgi.errors'] if request else sys.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def has_level_handler(logger):
|
|
||||||
"""Check if there is a handler in the logging chain that will handle the
|
|
||||||
given logger's :meth:`effective level <~logging.Logger.getEffectiveLevel>`.
|
|
||||||
"""
|
|
||||||
level = logger.getEffectiveLevel()
|
|
||||||
current = logger
|
|
||||||
|
|
||||||
while current:
|
|
||||||
if any(handler.level <= level for handler in current.handlers):
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not current.propagate:
|
|
||||||
break
|
|
||||||
|
|
||||||
current = current.parent
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
#: Log messages to :func:`~flask.logging.wsgi_errors_stream` with the format
|
|
||||||
#: ``[%(asctime)s] %(levelname)s in %(module)s: %(message)s``.
|
|
||||||
default_handler = logging.StreamHandler(wsgi_errors_stream)
|
|
||||||
default_handler.setFormatter(logging.Formatter(
|
|
||||||
'[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def create_logger(app):
|
|
||||||
"""Get the ``'flask.app'`` logger and configure it if needed.
|
|
||||||
|
|
||||||
When :attr:`~flask.Flask.debug` is enabled, set the logger level to
|
|
||||||
:data:`logging.DEBUG` if it is not set.
|
|
||||||
|
|
||||||
If there is no handler for the logger's effective level, add a
|
|
||||||
:class:`~logging.StreamHandler` for
|
|
||||||
:func:`~flask.logging.wsgi_errors_stream` with a basic format.
|
|
||||||
"""
|
|
||||||
logger = logging.getLogger('flask.app')
|
|
||||||
|
|
||||||
if app.debug and logger.level == logging.NOTSET:
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
if not has_level_handler(logger):
|
|
||||||
logger.addHandler(default_handler)
|
|
||||||
|
|
||||||
return logger
|
|
|
@ -1,385 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.sessions
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements cookie based sessions based on itsdangerous.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import warnings
|
|
||||||
from collections import MutableMapping
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from itsdangerous import BadSignature, URLSafeTimedSerializer
|
|
||||||
from werkzeug.datastructures import CallbackDict
|
|
||||||
|
|
||||||
from flask.helpers import is_ip, total_seconds
|
|
||||||
from flask.json.tag import TaggedJSONSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class SessionMixin(MutableMapping):
|
|
||||||
"""Expands a basic dictionary with session attributes."""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def permanent(self):
|
|
||||||
"""This reflects the ``'_permanent'`` key in the dict."""
|
|
||||||
return self.get('_permanent', False)
|
|
||||||
|
|
||||||
@permanent.setter
|
|
||||||
def permanent(self, value):
|
|
||||||
self['_permanent'] = bool(value)
|
|
||||||
|
|
||||||
#: Some implementations can detect whether a session is newly
|
|
||||||
#: created, but that is not guaranteed. Use with caution. The mixin
|
|
||||||
# default is hard-coded ``False``.
|
|
||||||
new = False
|
|
||||||
|
|
||||||
#: Some implementations can detect changes to the session and set
|
|
||||||
#: this when that happens. The mixin default is hard coded to
|
|
||||||
#: ``True``.
|
|
||||||
modified = True
|
|
||||||
|
|
||||||
#: Some implementations can detect when session data is read or
|
|
||||||
#: written and set this when that happens. The mixin default is hard
|
|
||||||
#: coded to ``True``.
|
|
||||||
accessed = True
|
|
||||||
|
|
||||||
|
|
||||||
class SecureCookieSession(CallbackDict, SessionMixin):
|
|
||||||
"""Base class for sessions based on signed cookies.
|
|
||||||
|
|
||||||
This session backend will set the :attr:`modified` and
|
|
||||||
:attr:`accessed` attributes. It cannot reliably track whether a
|
|
||||||
session is new (vs. empty), so :attr:`new` remains hard coded to
|
|
||||||
``False``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: When data is changed, this is set to ``True``. Only the session
|
|
||||||
#: dictionary itself is tracked; if the session contains mutable
|
|
||||||
#: data (for example a nested dict) then this must be set to
|
|
||||||
#: ``True`` manually when modifying that data. The session cookie
|
|
||||||
#: will only be written to the response if this is ``True``.
|
|
||||||
modified = False
|
|
||||||
|
|
||||||
#: When data is read or written, this is set to ``True``. Used by
|
|
||||||
# :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
|
|
||||||
#: header, which allows caching proxies to cache different pages for
|
|
||||||
#: different users.
|
|
||||||
accessed = False
|
|
||||||
|
|
||||||
def __init__(self, initial=None):
|
|
||||||
def on_update(self):
|
|
||||||
self.modified = True
|
|
||||||
self.accessed = True
|
|
||||||
|
|
||||||
super(SecureCookieSession, self).__init__(initial, on_update)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
self.accessed = True
|
|
||||||
return super(SecureCookieSession, self).__getitem__(key)
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
self.accessed = True
|
|
||||||
return super(SecureCookieSession, self).get(key, default)
|
|
||||||
|
|
||||||
def setdefault(self, key, default=None):
|
|
||||||
self.accessed = True
|
|
||||||
return super(SecureCookieSession, self).setdefault(key, default)
|
|
||||||
|
|
||||||
|
|
||||||
class NullSession(SecureCookieSession):
|
|
||||||
"""Class used to generate nicer error messages if sessions are not
|
|
||||||
available. Will still allow read-only access to the empty session
|
|
||||||
but fail on setting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _fail(self, *args, **kwargs):
|
|
||||||
raise RuntimeError('The session is unavailable because no secret '
|
|
||||||
'key was set. Set the secret_key on the '
|
|
||||||
'application to something unique and secret.')
|
|
||||||
__setitem__ = __delitem__ = clear = pop = popitem = \
|
|
||||||
update = setdefault = _fail
|
|
||||||
del _fail
|
|
||||||
|
|
||||||
|
|
||||||
class SessionInterface(object):
|
|
||||||
"""The basic interface you have to implement in order to replace the
|
|
||||||
default session interface which uses werkzeug's securecookie
|
|
||||||
implementation. The only methods you have to implement are
|
|
||||||
:meth:`open_session` and :meth:`save_session`, the others have
|
|
||||||
useful defaults which you don't need to change.
|
|
||||||
|
|
||||||
The session object returned by the :meth:`open_session` method has to
|
|
||||||
provide a dictionary like interface plus the properties and methods
|
|
||||||
from the :class:`SessionMixin`. We recommend just subclassing a dict
|
|
||||||
and adding that mixin::
|
|
||||||
|
|
||||||
class Session(dict, SessionMixin):
|
|
||||||
pass
|
|
||||||
|
|
||||||
If :meth:`open_session` returns ``None`` Flask will call into
|
|
||||||
:meth:`make_null_session` to create a session that acts as replacement
|
|
||||||
if the session support cannot work because some requirement is not
|
|
||||||
fulfilled. The default :class:`NullSession` class that is created
|
|
||||||
will complain that the secret key was not set.
|
|
||||||
|
|
||||||
To replace the session interface on an application all you have to do
|
|
||||||
is to assign :attr:`flask.Flask.session_interface`::
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.session_interface = MySessionInterface()
|
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: :meth:`make_null_session` will look here for the class that should
|
|
||||||
#: be created when a null session is requested. Likewise the
|
|
||||||
#: :meth:`is_null_session` method will perform a typecheck against
|
|
||||||
#: this type.
|
|
||||||
null_session_class = NullSession
|
|
||||||
|
|
||||||
#: A flag that indicates if the session interface is pickle based.
|
|
||||||
#: This can be used by Flask extensions to make a decision in regards
|
|
||||||
#: to how to deal with the session object.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 0.10
|
|
||||||
pickle_based = False
|
|
||||||
|
|
||||||
def make_null_session(self, app):
|
|
||||||
"""Creates a null session which acts as a replacement object if the
|
|
||||||
real session support could not be loaded due to a configuration
|
|
||||||
error. This mainly aids the user experience because the job of the
|
|
||||||
null session is to still support lookup without complaining but
|
|
||||||
modifications are answered with a helpful error message of what
|
|
||||||
failed.
|
|
||||||
|
|
||||||
This creates an instance of :attr:`null_session_class` by default.
|
|
||||||
"""
|
|
||||||
return self.null_session_class()
|
|
||||||
|
|
||||||
def is_null_session(self, obj):
|
|
||||||
"""Checks if a given object is a null session. Null sessions are
|
|
||||||
not asked to be saved.
|
|
||||||
|
|
||||||
This checks if the object is an instance of :attr:`null_session_class`
|
|
||||||
by default.
|
|
||||||
"""
|
|
||||||
return isinstance(obj, self.null_session_class)
|
|
||||||
|
|
||||||
def get_cookie_domain(self, app):
|
|
||||||
"""Returns the domain that should be set for the session cookie.
|
|
||||||
|
|
||||||
Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
|
|
||||||
falls back to detecting the domain based on ``SERVER_NAME``.
|
|
||||||
|
|
||||||
Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
|
|
||||||
updated to avoid re-running the logic.
|
|
||||||
"""
|
|
||||||
|
|
||||||
rv = app.config['SESSION_COOKIE_DOMAIN']
|
|
||||||
|
|
||||||
# set explicitly, or cached from SERVER_NAME detection
|
|
||||||
# if False, return None
|
|
||||||
if rv is not None:
|
|
||||||
return rv if rv else None
|
|
||||||
|
|
||||||
rv = app.config['SERVER_NAME']
|
|
||||||
|
|
||||||
# server name not set, cache False to return none next time
|
|
||||||
if not rv:
|
|
||||||
app.config['SESSION_COOKIE_DOMAIN'] = False
|
|
||||||
return None
|
|
||||||
|
|
||||||
# chop off the port which is usually not supported by browsers
|
|
||||||
# remove any leading '.' since we'll add that later
|
|
||||||
rv = rv.rsplit(':', 1)[0].lstrip('.')
|
|
||||||
|
|
||||||
if '.' not in rv:
|
|
||||||
# Chrome doesn't allow names without a '.'
|
|
||||||
# this should only come up with localhost
|
|
||||||
# hack around this by not setting the name, and show a warning
|
|
||||||
warnings.warn(
|
|
||||||
'"{rv}" is not a valid cookie domain, it must contain a ".".'
|
|
||||||
' Add an entry to your hosts file, for example'
|
|
||||||
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
|
|
||||||
)
|
|
||||||
app.config['SESSION_COOKIE_DOMAIN'] = False
|
|
||||||
return None
|
|
||||||
|
|
||||||
ip = is_ip(rv)
|
|
||||||
|
|
||||||
if ip:
|
|
||||||
warnings.warn(
|
|
||||||
'The session cookie domain is an IP address. This may not work'
|
|
||||||
' as intended in some browsers. Add an entry to your hosts'
|
|
||||||
' file, for example "localhost.localdomain", and use that'
|
|
||||||
' instead.'
|
|
||||||
)
|
|
||||||
|
|
||||||
# if this is not an ip and app is mounted at the root, allow subdomain
|
|
||||||
# matching by adding a '.' prefix
|
|
||||||
if self.get_cookie_path(app) == '/' and not ip:
|
|
||||||
rv = '.' + rv
|
|
||||||
|
|
||||||
app.config['SESSION_COOKIE_DOMAIN'] = rv
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def get_cookie_path(self, app):
|
|
||||||
"""Returns the path for which the cookie should be valid. The
|
|
||||||
default implementation uses the value from the ``SESSION_COOKIE_PATH``
|
|
||||||
config var if it's set, and falls back to ``APPLICATION_ROOT`` or
|
|
||||||
uses ``/`` if it's ``None``.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_PATH'] \
|
|
||||||
or app.config['APPLICATION_ROOT']
|
|
||||||
|
|
||||||
def get_cookie_httponly(self, app):
|
|
||||||
"""Returns True if the session cookie should be httponly. This
|
|
||||||
currently just returns the value of the ``SESSION_COOKIE_HTTPONLY``
|
|
||||||
config var.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_HTTPONLY']
|
|
||||||
|
|
||||||
def get_cookie_secure(self, app):
|
|
||||||
"""Returns True if the cookie should be secure. This currently
|
|
||||||
just returns the value of the ``SESSION_COOKIE_SECURE`` setting.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_SECURE']
|
|
||||||
|
|
||||||
def get_cookie_samesite(self, app):
|
|
||||||
"""Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
|
|
||||||
``SameSite`` attribute. This currently just returns the value of
|
|
||||||
the :data:`SESSION_COOKIE_SAMESITE` setting.
|
|
||||||
"""
|
|
||||||
return app.config['SESSION_COOKIE_SAMESITE']
|
|
||||||
|
|
||||||
def get_expiration_time(self, app, session):
|
|
||||||
"""A helper method that returns an expiration date for the session
|
|
||||||
or ``None`` if the session is linked to the browser session. The
|
|
||||||
default implementation returns now + the permanent session
|
|
||||||
lifetime configured on the application.
|
|
||||||
"""
|
|
||||||
if session.permanent:
|
|
||||||
return datetime.utcnow() + app.permanent_session_lifetime
|
|
||||||
|
|
||||||
def should_set_cookie(self, app, session):
|
|
||||||
"""Used by session backends to determine if a ``Set-Cookie`` header
|
|
||||||
should be set for this session cookie for this response. If the session
|
|
||||||
has been modified, the cookie is set. If the session is permanent and
|
|
||||||
the ``SESSION_REFRESH_EACH_REQUEST`` config is true, the cookie is
|
|
||||||
always set.
|
|
||||||
|
|
||||||
This check is usually skipped if the session was deleted.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
|
|
||||||
return session.modified or (
|
|
||||||
session.permanent and app.config['SESSION_REFRESH_EACH_REQUEST']
|
|
||||||
)
|
|
||||||
|
|
||||||
def open_session(self, app, request):
|
|
||||||
"""This method has to be implemented and must either return ``None``
|
|
||||||
in case the loading failed because of a configuration error or an
|
|
||||||
instance of a session object which implements a dictionary like
|
|
||||||
interface + the methods and attributes on :class:`SessionMixin`.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def save_session(self, app, session, response):
|
|
||||||
"""This is called for actual sessions returned by :meth:`open_session`
|
|
||||||
at the end of the request. This is still called during a request
|
|
||||||
context so if you absolutely need access to the request you can do
|
|
||||||
that.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
session_json_serializer = TaggedJSONSerializer()
|
|
||||||
|
|
||||||
|
|
||||||
class SecureCookieSessionInterface(SessionInterface):
|
|
||||||
"""The default session interface that stores sessions in signed cookies
|
|
||||||
through the :mod:`itsdangerous` module.
|
|
||||||
"""
|
|
||||||
#: the salt that should be applied on top of the secret key for the
|
|
||||||
#: signing of cookie based sessions.
|
|
||||||
salt = 'cookie-session'
|
|
||||||
#: the hash function to use for the signature. The default is sha1
|
|
||||||
digest_method = staticmethod(hashlib.sha1)
|
|
||||||
#: the name of the itsdangerous supported key derivation. The default
|
|
||||||
#: is hmac.
|
|
||||||
key_derivation = 'hmac'
|
|
||||||
#: A python serializer for the payload. The default is a compact
|
|
||||||
#: JSON derived serializer with support for some extra Python types
|
|
||||||
#: such as datetime objects or tuples.
|
|
||||||
serializer = session_json_serializer
|
|
||||||
session_class = SecureCookieSession
|
|
||||||
|
|
||||||
def get_signing_serializer(self, app):
|
|
||||||
if not app.secret_key:
|
|
||||||
return None
|
|
||||||
signer_kwargs = dict(
|
|
||||||
key_derivation=self.key_derivation,
|
|
||||||
digest_method=self.digest_method
|
|
||||||
)
|
|
||||||
return URLSafeTimedSerializer(app.secret_key, salt=self.salt,
|
|
||||||
serializer=self.serializer,
|
|
||||||
signer_kwargs=signer_kwargs)
|
|
||||||
|
|
||||||
def open_session(self, app, request):
|
|
||||||
s = self.get_signing_serializer(app)
|
|
||||||
if s is None:
|
|
||||||
return None
|
|
||||||
val = request.cookies.get(app.session_cookie_name)
|
|
||||||
if not val:
|
|
||||||
return self.session_class()
|
|
||||||
max_age = total_seconds(app.permanent_session_lifetime)
|
|
||||||
try:
|
|
||||||
data = s.loads(val, max_age=max_age)
|
|
||||||
return self.session_class(data)
|
|
||||||
except BadSignature:
|
|
||||||
return self.session_class()
|
|
||||||
|
|
||||||
def save_session(self, app, session, response):
|
|
||||||
domain = self.get_cookie_domain(app)
|
|
||||||
path = self.get_cookie_path(app)
|
|
||||||
|
|
||||||
# If the session is modified to be empty, remove the cookie.
|
|
||||||
# If the session is empty, return without setting the cookie.
|
|
||||||
if not session:
|
|
||||||
if session.modified:
|
|
||||||
response.delete_cookie(
|
|
||||||
app.session_cookie_name,
|
|
||||||
domain=domain,
|
|
||||||
path=path
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
# Add a "Vary: Cookie" header if the session was accessed at all.
|
|
||||||
if session.accessed:
|
|
||||||
response.vary.add('Cookie')
|
|
||||||
|
|
||||||
if not self.should_set_cookie(app, session):
|
|
||||||
return
|
|
||||||
|
|
||||||
httponly = self.get_cookie_httponly(app)
|
|
||||||
secure = self.get_cookie_secure(app)
|
|
||||||
samesite = self.get_cookie_samesite(app)
|
|
||||||
expires = self.get_expiration_time(app, session)
|
|
||||||
val = self.get_signing_serializer(app).dumps(dict(session))
|
|
||||||
response.set_cookie(
|
|
||||||
app.session_cookie_name,
|
|
||||||
val,
|
|
||||||
expires=expires,
|
|
||||||
httponly=httponly,
|
|
||||||
domain=domain,
|
|
||||||
path=path,
|
|
||||||
secure=secure,
|
|
||||||
samesite=samesite
|
|
||||||
)
|
|
|
@ -1,57 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.signals
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements signals based on blinker if available, otherwise
|
|
||||||
falls silently back to a noop.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
signals_available = False
|
|
||||||
try:
|
|
||||||
from blinker import Namespace
|
|
||||||
signals_available = True
|
|
||||||
except ImportError:
|
|
||||||
class Namespace(object):
|
|
||||||
def signal(self, name, doc=None):
|
|
||||||
return _FakeSignal(name, doc)
|
|
||||||
|
|
||||||
class _FakeSignal(object):
|
|
||||||
"""If blinker is unavailable, create a fake class with the same
|
|
||||||
interface that allows sending of signals but will fail with an
|
|
||||||
error on anything else. Instead of doing anything on send, it
|
|
||||||
will just ignore the arguments and do nothing instead.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name, doc=None):
|
|
||||||
self.name = name
|
|
||||||
self.__doc__ = doc
|
|
||||||
def _fail(self, *args, **kwargs):
|
|
||||||
raise RuntimeError('signalling support is unavailable '
|
|
||||||
'because the blinker library is '
|
|
||||||
'not installed.')
|
|
||||||
send = lambda *a, **kw: None
|
|
||||||
connect = disconnect = has_receivers_for = receivers_for = \
|
|
||||||
temporarily_connected_to = connected_to = _fail
|
|
||||||
del _fail
|
|
||||||
|
|
||||||
# The namespace for code signals. If you are not Flask code, do
|
|
||||||
# not put signals in here. Create your own namespace instead.
|
|
||||||
_signals = Namespace()
|
|
||||||
|
|
||||||
|
|
||||||
# Core signals. For usage examples grep the source code or consult
|
|
||||||
# the API documentation in docs/api.rst as well as docs/signals.rst
|
|
||||||
template_rendered = _signals.signal('template-rendered')
|
|
||||||
before_render_template = _signals.signal('before-render-template')
|
|
||||||
request_started = _signals.signal('request-started')
|
|
||||||
request_finished = _signals.signal('request-finished')
|
|
||||||
request_tearing_down = _signals.signal('request-tearing-down')
|
|
||||||
got_request_exception = _signals.signal('got-request-exception')
|
|
||||||
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')
|
|
||||||
appcontext_pushed = _signals.signal('appcontext-pushed')
|
|
||||||
appcontext_popped = _signals.signal('appcontext-popped')
|
|
||||||
message_flashed = _signals.signal('message-flashed')
|
|
|
@ -1,150 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.templating
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the bridge to Jinja2.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from jinja2 import BaseLoader, Environment as BaseEnvironment, \
|
|
||||||
TemplateNotFound
|
|
||||||
|
|
||||||
from .globals import _request_ctx_stack, _app_ctx_stack
|
|
||||||
from .signals import template_rendered, before_render_template
|
|
||||||
|
|
||||||
|
|
||||||
def _default_template_ctx_processor():
|
|
||||||
"""Default template context processor. Injects `request`,
|
|
||||||
`session` and `g`.
|
|
||||||
"""
|
|
||||||
reqctx = _request_ctx_stack.top
|
|
||||||
appctx = _app_ctx_stack.top
|
|
||||||
rv = {}
|
|
||||||
if appctx is not None:
|
|
||||||
rv['g'] = appctx.g
|
|
||||||
if reqctx is not None:
|
|
||||||
rv['request'] = reqctx.request
|
|
||||||
rv['session'] = reqctx.session
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class Environment(BaseEnvironment):
|
|
||||||
"""Works like a regular Jinja2 environment but has some additional
|
|
||||||
knowledge of how Flask's blueprint works so that it can prepend the
|
|
||||||
name of the blueprint to referenced templates if necessary.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, **options):
|
|
||||||
if 'loader' not in options:
|
|
||||||
options['loader'] = app.create_global_jinja_loader()
|
|
||||||
BaseEnvironment.__init__(self, **options)
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
|
|
||||||
class DispatchingJinjaLoader(BaseLoader):
|
|
||||||
"""A loader that looks for templates in the application and all
|
|
||||||
the blueprint folders.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def get_source(self, environment, template):
|
|
||||||
if self.app.config['EXPLAIN_TEMPLATE_LOADING']:
|
|
||||||
return self._get_source_explained(environment, template)
|
|
||||||
return self._get_source_fast(environment, template)
|
|
||||||
|
|
||||||
def _get_source_explained(self, environment, template):
|
|
||||||
attempts = []
|
|
||||||
trv = None
|
|
||||||
|
|
||||||
for srcobj, loader in self._iter_loaders(template):
|
|
||||||
try:
|
|
||||||
rv = loader.get_source(environment, template)
|
|
||||||
if trv is None:
|
|
||||||
trv = rv
|
|
||||||
except TemplateNotFound:
|
|
||||||
rv = None
|
|
||||||
attempts.append((loader, srcobj, rv))
|
|
||||||
|
|
||||||
from .debughelpers import explain_template_loading_attempts
|
|
||||||
explain_template_loading_attempts(self.app, template, attempts)
|
|
||||||
|
|
||||||
if trv is not None:
|
|
||||||
return trv
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def _get_source_fast(self, environment, template):
|
|
||||||
for srcobj, loader in self._iter_loaders(template):
|
|
||||||
try:
|
|
||||||
return loader.get_source(environment, template)
|
|
||||||
except TemplateNotFound:
|
|
||||||
continue
|
|
||||||
raise TemplateNotFound(template)
|
|
||||||
|
|
||||||
def _iter_loaders(self, template):
|
|
||||||
loader = self.app.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
yield self.app, loader
|
|
||||||
|
|
||||||
for blueprint in self.app.iter_blueprints():
|
|
||||||
loader = blueprint.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
yield blueprint, loader
|
|
||||||
|
|
||||||
def list_templates(self):
|
|
||||||
result = set()
|
|
||||||
loader = self.app.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
result.update(loader.list_templates())
|
|
||||||
|
|
||||||
for blueprint in self.app.iter_blueprints():
|
|
||||||
loader = blueprint.jinja_loader
|
|
||||||
if loader is not None:
|
|
||||||
for template in loader.list_templates():
|
|
||||||
result.add(template)
|
|
||||||
|
|
||||||
return list(result)
|
|
||||||
|
|
||||||
|
|
||||||
def _render(template, context, app):
|
|
||||||
"""Renders the template and fires the signal"""
|
|
||||||
|
|
||||||
before_render_template.send(app, template=template, context=context)
|
|
||||||
rv = template.render(context)
|
|
||||||
template_rendered.send(app, template=template, context=context)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
def render_template(template_name_or_list, **context):
|
|
||||||
"""Renders a template from the template folder with the given
|
|
||||||
context.
|
|
||||||
|
|
||||||
:param template_name_or_list: the name of the template to be
|
|
||||||
rendered, or an iterable with template names
|
|
||||||
the first one existing will be rendered
|
|
||||||
:param context: the variables that should be available in the
|
|
||||||
context of the template.
|
|
||||||
"""
|
|
||||||
ctx = _app_ctx_stack.top
|
|
||||||
ctx.app.update_template_context(context)
|
|
||||||
return _render(ctx.app.jinja_env.get_or_select_template(template_name_or_list),
|
|
||||||
context, ctx.app)
|
|
||||||
|
|
||||||
|
|
||||||
def render_template_string(source, **context):
|
|
||||||
"""Renders a template from the given template source string
|
|
||||||
with the given context. Template variables will be autoescaped.
|
|
||||||
|
|
||||||
:param source: the source code of the template to be
|
|
||||||
rendered
|
|
||||||
:param context: the variables that should be available in the
|
|
||||||
context of the template.
|
|
||||||
"""
|
|
||||||
ctx = _app_ctx_stack.top
|
|
||||||
ctx.app.update_template_context(context)
|
|
||||||
return _render(ctx.app.jinja_env.from_string(source),
|
|
||||||
context, ctx.app)
|
|
|
@ -1,250 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.testing
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements test support helpers. This module is lazily imported
|
|
||||||
and usually not used in production environments.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import werkzeug
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
from click.testing import CliRunner
|
|
||||||
from flask.cli import ScriptInfo
|
|
||||||
from werkzeug.test import Client, EnvironBuilder
|
|
||||||
from flask import _request_ctx_stack
|
|
||||||
from flask.json import dumps as json_dumps
|
|
||||||
from werkzeug.urls import url_parse
|
|
||||||
|
|
||||||
|
|
||||||
def make_test_environ_builder(
|
|
||||||
app, path='/', base_url=None, subdomain=None, url_scheme=None,
|
|
||||||
*args, **kwargs
|
|
||||||
):
|
|
||||||
"""Create a :class:`~werkzeug.test.EnvironBuilder`, taking some
|
|
||||||
defaults from the application.
|
|
||||||
|
|
||||||
:param app: The Flask application to configure the environment from.
|
|
||||||
:param path: URL path being requested.
|
|
||||||
:param base_url: Base URL where the app is being served, which
|
|
||||||
``path`` is relative to. If not given, built from
|
|
||||||
:data:`PREFERRED_URL_SCHEME`, ``subdomain``,
|
|
||||||
:data:`SERVER_NAME`, and :data:`APPLICATION_ROOT`.
|
|
||||||
:param subdomain: Subdomain name to append to :data:`SERVER_NAME`.
|
|
||||||
:param url_scheme: Scheme to use instead of
|
|
||||||
:data:`PREFERRED_URL_SCHEME`.
|
|
||||||
:param json: If given, this is serialized as JSON and passed as
|
|
||||||
``data``. Also defaults ``content_type`` to
|
|
||||||
``application/json``.
|
|
||||||
:param args: other positional arguments passed to
|
|
||||||
:class:`~werkzeug.test.EnvironBuilder`.
|
|
||||||
:param kwargs: other keyword arguments passed to
|
|
||||||
:class:`~werkzeug.test.EnvironBuilder`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert (
|
|
||||||
not (base_url or subdomain or url_scheme)
|
|
||||||
or (base_url is not None) != bool(subdomain or url_scheme)
|
|
||||||
), 'Cannot pass "subdomain" or "url_scheme" with "base_url".'
|
|
||||||
|
|
||||||
if base_url is None:
|
|
||||||
http_host = app.config.get('SERVER_NAME') or 'localhost'
|
|
||||||
app_root = app.config['APPLICATION_ROOT']
|
|
||||||
|
|
||||||
if subdomain:
|
|
||||||
http_host = '{0}.{1}'.format(subdomain, http_host)
|
|
||||||
|
|
||||||
if url_scheme is None:
|
|
||||||
url_scheme = app.config['PREFERRED_URL_SCHEME']
|
|
||||||
|
|
||||||
url = url_parse(path)
|
|
||||||
base_url = '{scheme}://{netloc}/{path}'.format(
|
|
||||||
scheme=url.scheme or url_scheme,
|
|
||||||
netloc=url.netloc or http_host,
|
|
||||||
path=app_root.lstrip('/')
|
|
||||||
)
|
|
||||||
path = url.path
|
|
||||||
|
|
||||||
if url.query:
|
|
||||||
sep = b'?' if isinstance(url.query, bytes) else '?'
|
|
||||||
path += sep + url.query
|
|
||||||
|
|
||||||
if 'json' in kwargs:
|
|
||||||
assert 'data' not in kwargs, (
|
|
||||||
"Client cannot provide both 'json' and 'data'."
|
|
||||||
)
|
|
||||||
|
|
||||||
# push a context so flask.json can use app's json attributes
|
|
||||||
with app.app_context():
|
|
||||||
kwargs['data'] = json_dumps(kwargs.pop('json'))
|
|
||||||
|
|
||||||
if 'content_type' not in kwargs:
|
|
||||||
kwargs['content_type'] = 'application/json'
|
|
||||||
|
|
||||||
return EnvironBuilder(path, base_url, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskClient(Client):
|
|
||||||
"""Works like a regular Werkzeug test client but has some knowledge about
|
|
||||||
how Flask works to defer the cleanup of the request context stack to the
|
|
||||||
end of a ``with`` body when used in a ``with`` statement. For general
|
|
||||||
information about how to use this class refer to
|
|
||||||
:class:`werkzeug.test.Client`.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.12
|
|
||||||
`app.test_client()` includes preset default environment, which can be
|
|
||||||
set after instantiation of the `app.test_client()` object in
|
|
||||||
`client.environ_base`.
|
|
||||||
|
|
||||||
Basic usage is outlined in the :ref:`testing` chapter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
preserve_context = False
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(FlaskClient, self).__init__(*args, **kwargs)
|
|
||||||
self.environ_base = {
|
|
||||||
"REMOTE_ADDR": "127.0.0.1",
|
|
||||||
"HTTP_USER_AGENT": "werkzeug/" + werkzeug.__version__
|
|
||||||
}
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def session_transaction(self, *args, **kwargs):
|
|
||||||
"""When used in combination with a ``with`` statement this opens a
|
|
||||||
session transaction. This can be used to modify the session that
|
|
||||||
the test client uses. Once the ``with`` block is left the session is
|
|
||||||
stored back.
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['value'] = 42
|
|
||||||
|
|
||||||
Internally this is implemented by going through a temporary test
|
|
||||||
request context and since session handling could depend on
|
|
||||||
request variables this function accepts the same arguments as
|
|
||||||
:meth:`~flask.Flask.test_request_context` which are directly
|
|
||||||
passed through.
|
|
||||||
"""
|
|
||||||
if self.cookie_jar is None:
|
|
||||||
raise RuntimeError('Session transactions only make sense '
|
|
||||||
'with cookies enabled.')
|
|
||||||
app = self.application
|
|
||||||
environ_overrides = kwargs.setdefault('environ_overrides', {})
|
|
||||||
self.cookie_jar.inject_wsgi(environ_overrides)
|
|
||||||
outer_reqctx = _request_ctx_stack.top
|
|
||||||
with app.test_request_context(*args, **kwargs) as c:
|
|
||||||
session_interface = app.session_interface
|
|
||||||
sess = session_interface.open_session(app, c.request)
|
|
||||||
if sess is None:
|
|
||||||
raise RuntimeError('Session backend did not open a session. '
|
|
||||||
'Check the configuration')
|
|
||||||
|
|
||||||
# Since we have to open a new request context for the session
|
|
||||||
# handling we want to make sure that we hide out own context
|
|
||||||
# from the caller. By pushing the original request context
|
|
||||||
# (or None) on top of this and popping it we get exactly that
|
|
||||||
# behavior. It's important to not use the push and pop
|
|
||||||
# methods of the actual request context object since that would
|
|
||||||
# mean that cleanup handlers are called
|
|
||||||
_request_ctx_stack.push(outer_reqctx)
|
|
||||||
try:
|
|
||||||
yield sess
|
|
||||||
finally:
|
|
||||||
_request_ctx_stack.pop()
|
|
||||||
|
|
||||||
resp = app.response_class()
|
|
||||||
if not session_interface.is_null_session(sess):
|
|
||||||
session_interface.save_session(app, sess, resp)
|
|
||||||
headers = resp.get_wsgi_headers(c.request.environ)
|
|
||||||
self.cookie_jar.extract_wsgi(c.request.environ, headers)
|
|
||||||
|
|
||||||
def open(self, *args, **kwargs):
|
|
||||||
as_tuple = kwargs.pop('as_tuple', False)
|
|
||||||
buffered = kwargs.pop('buffered', False)
|
|
||||||
follow_redirects = kwargs.pop('follow_redirects', False)
|
|
||||||
|
|
||||||
if (
|
|
||||||
not kwargs and len(args) == 1
|
|
||||||
and isinstance(args[0], (EnvironBuilder, dict))
|
|
||||||
):
|
|
||||||
environ = self.environ_base.copy()
|
|
||||||
|
|
||||||
if isinstance(args[0], EnvironBuilder):
|
|
||||||
environ.update(args[0].get_environ())
|
|
||||||
else:
|
|
||||||
environ.update(args[0])
|
|
||||||
|
|
||||||
environ['flask._preserve_context'] = self.preserve_context
|
|
||||||
else:
|
|
||||||
kwargs.setdefault('environ_overrides', {}) \
|
|
||||||
['flask._preserve_context'] = self.preserve_context
|
|
||||||
kwargs.setdefault('environ_base', self.environ_base)
|
|
||||||
builder = make_test_environ_builder(
|
|
||||||
self.application, *args, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
environ = builder.get_environ()
|
|
||||||
finally:
|
|
||||||
builder.close()
|
|
||||||
|
|
||||||
return Client.open(
|
|
||||||
self, environ,
|
|
||||||
as_tuple=as_tuple,
|
|
||||||
buffered=buffered,
|
|
||||||
follow_redirects=follow_redirects
|
|
||||||
)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
if self.preserve_context:
|
|
||||||
raise RuntimeError('Cannot nest client invocations')
|
|
||||||
self.preserve_context = True
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb):
|
|
||||||
self.preserve_context = False
|
|
||||||
|
|
||||||
# on exit we want to clean up earlier. Normally the request context
|
|
||||||
# stays preserved until the next request in the same thread comes
|
|
||||||
# in. See RequestGlobals.push() for the general behavior.
|
|
||||||
top = _request_ctx_stack.top
|
|
||||||
if top is not None and top.preserved:
|
|
||||||
top.pop()
|
|
||||||
|
|
||||||
|
|
||||||
class FlaskCliRunner(CliRunner):
|
|
||||||
"""A :class:`~click.testing.CliRunner` for testing a Flask app's
|
|
||||||
CLI commands. Typically created using
|
|
||||||
:meth:`~flask.Flask.test_cli_runner`. See :ref:`testing-cli`.
|
|
||||||
"""
|
|
||||||
def __init__(self, app, **kwargs):
|
|
||||||
self.app = app
|
|
||||||
super(FlaskCliRunner, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
def invoke(self, cli=None, args=None, **kwargs):
|
|
||||||
"""Invokes a CLI command in an isolated environment. See
|
|
||||||
:meth:`CliRunner.invoke <click.testing.CliRunner.invoke>` for
|
|
||||||
full method documentation. See :ref:`testing-cli` for examples.
|
|
||||||
|
|
||||||
If the ``obj`` argument is not given, passes an instance of
|
|
||||||
:class:`~flask.cli.ScriptInfo` that knows how to load the Flask
|
|
||||||
app being tested.
|
|
||||||
|
|
||||||
:param cli: Command object to invoke. Default is the app's
|
|
||||||
:attr:`~flask.app.Flask.cli` group.
|
|
||||||
:param args: List of strings to invoke the command with.
|
|
||||||
|
|
||||||
:return: a :class:`~click.testing.Result` object.
|
|
||||||
"""
|
|
||||||
if cli is None:
|
|
||||||
cli = self.app.cli
|
|
||||||
|
|
||||||
if 'obj' not in kwargs:
|
|
||||||
kwargs['obj'] = ScriptInfo(create_app=lambda: self.app)
|
|
||||||
|
|
||||||
return super(FlaskCliRunner, self).invoke(cli, args, **kwargs)
|
|
|
@ -1,158 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.views
|
|
||||||
~~~~~~~~~~~
|
|
||||||
|
|
||||||
This module provides class-based views inspired by the ones in Django.
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .globals import request
|
|
||||||
from ._compat import with_metaclass
|
|
||||||
|
|
||||||
|
|
||||||
http_method_funcs = frozenset(['get', 'post', 'head', 'options',
|
|
||||||
'delete', 'put', 'trace', 'patch'])
|
|
||||||
|
|
||||||
|
|
||||||
class View(object):
|
|
||||||
"""Alternative way to use view functions. A subclass has to implement
|
|
||||||
:meth:`dispatch_request` which is called with the view arguments from
|
|
||||||
the URL routing system. If :attr:`methods` is provided the methods
|
|
||||||
do not have to be passed to the :meth:`~flask.Flask.add_url_rule`
|
|
||||||
method explicitly::
|
|
||||||
|
|
||||||
class MyView(View):
|
|
||||||
methods = ['GET']
|
|
||||||
|
|
||||||
def dispatch_request(self, name):
|
|
||||||
return 'Hello %s!' % name
|
|
||||||
|
|
||||||
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
|
|
||||||
|
|
||||||
When you want to decorate a pluggable view you will have to either do that
|
|
||||||
when the view function is created (by wrapping the return value of
|
|
||||||
:meth:`as_view`) or you can use the :attr:`decorators` attribute::
|
|
||||||
|
|
||||||
class SecretView(View):
|
|
||||||
methods = ['GET']
|
|
||||||
decorators = [superuser_required]
|
|
||||||
|
|
||||||
def dispatch_request(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
The decorators stored in the decorators list are applied one after another
|
|
||||||
when the view function is created. Note that you can *not* use the class
|
|
||||||
based decorators since those would decorate the view class and not the
|
|
||||||
generated view function!
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: A list of methods this view can handle.
|
|
||||||
methods = None
|
|
||||||
|
|
||||||
#: Setting this disables or force-enables the automatic options handling.
|
|
||||||
provide_automatic_options = None
|
|
||||||
|
|
||||||
#: The canonical way to decorate class-based views is to decorate the
|
|
||||||
#: return value of as_view(). However since this moves parts of the
|
|
||||||
#: logic from the class declaration to the place where it's hooked
|
|
||||||
#: into the routing system.
|
|
||||||
#:
|
|
||||||
#: You can place one or more decorators in this list and whenever the
|
|
||||||
#: view function is created the result is automatically decorated.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 0.8
|
|
||||||
decorators = ()
|
|
||||||
|
|
||||||
def dispatch_request(self):
|
|
||||||
"""Subclasses have to override this method to implement the
|
|
||||||
actual view function code. This method is called with all
|
|
||||||
the arguments from the URL rule.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def as_view(cls, name, *class_args, **class_kwargs):
|
|
||||||
"""Converts the class into an actual view function that can be used
|
|
||||||
with the routing system. Internally this generates a function on the
|
|
||||||
fly which will instantiate the :class:`View` on each request and call
|
|
||||||
the :meth:`dispatch_request` method on it.
|
|
||||||
|
|
||||||
The arguments passed to :meth:`as_view` are forwarded to the
|
|
||||||
constructor of the class.
|
|
||||||
"""
|
|
||||||
def view(*args, **kwargs):
|
|
||||||
self = view.view_class(*class_args, **class_kwargs)
|
|
||||||
return self.dispatch_request(*args, **kwargs)
|
|
||||||
|
|
||||||
if cls.decorators:
|
|
||||||
view.__name__ = name
|
|
||||||
view.__module__ = cls.__module__
|
|
||||||
for decorator in cls.decorators:
|
|
||||||
view = decorator(view)
|
|
||||||
|
|
||||||
# We attach the view class to the view function for two reasons:
|
|
||||||
# first of all it allows us to easily figure out what class-based
|
|
||||||
# view this thing came from, secondly it's also used for instantiating
|
|
||||||
# the view class so you can actually replace it with something else
|
|
||||||
# for testing purposes and debugging.
|
|
||||||
view.view_class = cls
|
|
||||||
view.__name__ = name
|
|
||||||
view.__doc__ = cls.__doc__
|
|
||||||
view.__module__ = cls.__module__
|
|
||||||
view.methods = cls.methods
|
|
||||||
view.provide_automatic_options = cls.provide_automatic_options
|
|
||||||
return view
|
|
||||||
|
|
||||||
|
|
||||||
class MethodViewType(type):
|
|
||||||
"""Metaclass for :class:`MethodView` that determines what methods the view
|
|
||||||
defines.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(cls, name, bases, d):
|
|
||||||
super(MethodViewType, cls).__init__(name, bases, d)
|
|
||||||
|
|
||||||
if 'methods' not in d:
|
|
||||||
methods = set()
|
|
||||||
|
|
||||||
for key in http_method_funcs:
|
|
||||||
if hasattr(cls, key):
|
|
||||||
methods.add(key.upper())
|
|
||||||
|
|
||||||
# If we have no method at all in there we don't want to add a
|
|
||||||
# method list. This is for instance the case for the base class
|
|
||||||
# or another subclass of a base method view that does not introduce
|
|
||||||
# new methods.
|
|
||||||
if methods:
|
|
||||||
cls.methods = methods
|
|
||||||
|
|
||||||
|
|
||||||
class MethodView(with_metaclass(MethodViewType, View)):
|
|
||||||
"""A class-based view that dispatches request methods to the corresponding
|
|
||||||
class methods. For example, if you implement a ``get`` method, it will be
|
|
||||||
used to handle ``GET`` requests. ::
|
|
||||||
|
|
||||||
class CounterAPI(MethodView):
|
|
||||||
def get(self):
|
|
||||||
return session.get('counter', 0)
|
|
||||||
|
|
||||||
def post(self):
|
|
||||||
session['counter'] = session.get('counter', 0) + 1
|
|
||||||
return 'OK'
|
|
||||||
|
|
||||||
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
|
|
||||||
"""
|
|
||||||
|
|
||||||
def dispatch_request(self, *args, **kwargs):
|
|
||||||
meth = getattr(self, request.method.lower(), None)
|
|
||||||
|
|
||||||
# If the request method is HEAD and we don't have a handler for it
|
|
||||||
# retry with GET.
|
|
||||||
if meth is None and request.method == 'HEAD':
|
|
||||||
meth = getattr(self, 'get', None)
|
|
||||||
|
|
||||||
assert meth is not None, 'Unimplemented method %r' % request.method
|
|
||||||
return meth(*args, **kwargs)
|
|
|
@ -1,216 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
flask.wrappers
|
|
||||||
~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Implements the WSGI wrappers (request and response).
|
|
||||||
|
|
||||||
:copyright: © 2010 by the Pallets team.
|
|
||||||
:license: BSD, see LICENSE for more details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from werkzeug.exceptions import BadRequest
|
|
||||||
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
|
|
||||||
|
|
||||||
from flask import json
|
|
||||||
from flask.globals import current_app
|
|
||||||
|
|
||||||
|
|
||||||
class JSONMixin(object):
|
|
||||||
"""Common mixin for both request and response objects to provide JSON
|
|
||||||
parsing capabilities.
|
|
||||||
|
|
||||||
.. versionadded:: 1.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
_cached_json = (Ellipsis, Ellipsis)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_json(self):
|
|
||||||
"""Check if the mimetype indicates JSON data, either
|
|
||||||
:mimetype:`application/json` or :mimetype:`application/*+json`.
|
|
||||||
|
|
||||||
.. versionadded:: 0.11
|
|
||||||
"""
|
|
||||||
mt = self.mimetype
|
|
||||||
return (
|
|
||||||
mt == 'application/json'
|
|
||||||
or (mt.startswith('application/')) and mt.endswith('+json')
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def json(self):
|
|
||||||
"""This will contain the parsed JSON data if the mimetype indicates
|
|
||||||
JSON (:mimetype:`application/json`, see :meth:`is_json`), otherwise it
|
|
||||||
will be ``None``.
|
|
||||||
"""
|
|
||||||
return self.get_json()
|
|
||||||
|
|
||||||
def _get_data_for_json(self, cache):
|
|
||||||
return self.get_data(cache=cache)
|
|
||||||
|
|
||||||
def get_json(self, force=False, silent=False, cache=True):
|
|
||||||
"""Parse and return the data as JSON. If the mimetype does not
|
|
||||||
indicate JSON (:mimetype:`application/json`, see
|
|
||||||
:meth:`is_json`), this returns ``None`` unless ``force`` is
|
|
||||||
true. If parsing fails, :meth:`on_json_loading_failed` is called
|
|
||||||
and its return value is used as the return value.
|
|
||||||
|
|
||||||
:param force: Ignore the mimetype and always try to parse JSON.
|
|
||||||
:param silent: Silence parsing errors and return ``None``
|
|
||||||
instead.
|
|
||||||
:param cache: Store the parsed JSON to return for subsequent
|
|
||||||
calls.
|
|
||||||
"""
|
|
||||||
if cache and self._cached_json[silent] is not Ellipsis:
|
|
||||||
return self._cached_json[silent]
|
|
||||||
|
|
||||||
if not (force or self.is_json):
|
|
||||||
return None
|
|
||||||
|
|
||||||
data = self._get_data_for_json(cache=cache)
|
|
||||||
|
|
||||||
try:
|
|
||||||
rv = json.loads(data)
|
|
||||||
except ValueError as e:
|
|
||||||
if silent:
|
|
||||||
rv = None
|
|
||||||
if cache:
|
|
||||||
normal_rv, _ = self._cached_json
|
|
||||||
self._cached_json = (normal_rv, rv)
|
|
||||||
else:
|
|
||||||
rv = self.on_json_loading_failed(e)
|
|
||||||
if cache:
|
|
||||||
_, silent_rv = self._cached_json
|
|
||||||
self._cached_json = (rv, silent_rv)
|
|
||||||
else:
|
|
||||||
if cache:
|
|
||||||
self._cached_json = (rv, rv)
|
|
||||||
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def on_json_loading_failed(self, e):
|
|
||||||
"""Called if :meth:`get_json` parsing fails and isn't silenced. If
|
|
||||||
this method returns a value, it is used as the return value for
|
|
||||||
:meth:`get_json`. The default implementation raises a
|
|
||||||
:class:`BadRequest` exception.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.10
|
|
||||||
Raise a :exc:`BadRequest` error instead of returning an error
|
|
||||||
message as JSON. If you want that behavior you can add it by
|
|
||||||
subclassing.
|
|
||||||
|
|
||||||
.. versionadded:: 0.8
|
|
||||||
"""
|
|
||||||
if current_app is not None and current_app.debug:
|
|
||||||
raise BadRequest('Failed to decode JSON object: {0}'.format(e))
|
|
||||||
|
|
||||||
raise BadRequest()
|
|
||||||
|
|
||||||
|
|
||||||
class Request(RequestBase, JSONMixin):
|
|
||||||
"""The request object used by default in Flask. Remembers the
|
|
||||||
matched endpoint and view arguments.
|
|
||||||
|
|
||||||
It is what ends up as :class:`~flask.request`. If you want to replace
|
|
||||||
the request object used you can subclass this and set
|
|
||||||
:attr:`~flask.Flask.request_class` to your subclass.
|
|
||||||
|
|
||||||
The request object is a :class:`~werkzeug.wrappers.Request` subclass and
|
|
||||||
provides all of the attributes Werkzeug defines plus a few Flask
|
|
||||||
specific ones.
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: The internal URL rule that matched the request. This can be
|
|
||||||
#: useful to inspect which methods are allowed for the URL from
|
|
||||||
#: a before/after handler (``request.url_rule.methods``) etc.
|
|
||||||
#: Though if the request's method was invalid for the URL rule,
|
|
||||||
#: the valid list is available in ``routing_exception.valid_methods``
|
|
||||||
#: instead (an attribute of the Werkzeug exception :exc:`~werkzeug.exceptions.MethodNotAllowed`)
|
|
||||||
#: because the request was never internally bound.
|
|
||||||
#:
|
|
||||||
#: .. versionadded:: 0.6
|
|
||||||
url_rule = None
|
|
||||||
|
|
||||||
#: A dict of view arguments that matched the request. If an exception
|
|
||||||
#: happened when matching, this will be ``None``.
|
|
||||||
view_args = None
|
|
||||||
|
|
||||||
#: If matching the URL failed, this is the exception that will be
|
|
||||||
#: raised / was raised as part of the request handling. This is
|
|
||||||
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
|
|
||||||
#: something similar.
|
|
||||||
routing_exception = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_content_length(self):
|
|
||||||
"""Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
|
|
||||||
if current_app:
|
|
||||||
return current_app.config['MAX_CONTENT_LENGTH']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def endpoint(self):
|
|
||||||
"""The endpoint that matched the request. This in combination with
|
|
||||||
:attr:`view_args` can be used to reconstruct the same or a
|
|
||||||
modified URL. If an exception happened when matching, this will
|
|
||||||
be ``None``.
|
|
||||||
"""
|
|
||||||
if self.url_rule is not None:
|
|
||||||
return self.url_rule.endpoint
|
|
||||||
|
|
||||||
@property
|
|
||||||
def blueprint(self):
|
|
||||||
"""The name of the current blueprint"""
|
|
||||||
if self.url_rule and '.' in self.url_rule.endpoint:
|
|
||||||
return self.url_rule.endpoint.rsplit('.', 1)[0]
|
|
||||||
|
|
||||||
def _load_form_data(self):
|
|
||||||
RequestBase._load_form_data(self)
|
|
||||||
|
|
||||||
# In debug mode we're replacing the files multidict with an ad-hoc
|
|
||||||
# subclass that raises a different error for key errors.
|
|
||||||
if (
|
|
||||||
current_app
|
|
||||||
and current_app.debug
|
|
||||||
and self.mimetype != 'multipart/form-data'
|
|
||||||
and not self.files
|
|
||||||
):
|
|
||||||
from .debughelpers import attach_enctype_error_multidict
|
|
||||||
attach_enctype_error_multidict(self)
|
|
||||||
|
|
||||||
|
|
||||||
class Response(ResponseBase, JSONMixin):
|
|
||||||
"""The response object that is used by default in Flask. Works like the
|
|
||||||
response object from Werkzeug but is set to have an HTML mimetype by
|
|
||||||
default. Quite often you don't have to create this object yourself because
|
|
||||||
:meth:`~flask.Flask.make_response` will take care of that for you.
|
|
||||||
|
|
||||||
If you want to replace the response object used you can subclass this and
|
|
||||||
set :attr:`~flask.Flask.response_class` to your subclass.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
|
||||||
JSON support is added to the response, like the request. This is useful
|
|
||||||
when testing to get the test client response data as JSON.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.0
|
|
||||||
|
|
||||||
Added :attr:`max_cookie_size`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
default_mimetype = 'text/html'
|
|
||||||
|
|
||||||
def _get_data_for_json(self, cache):
|
|
||||||
return self.get_data()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_cookie_size(self):
|
|
||||||
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
|
|
||||||
|
|
||||||
See :attr:`~werkzeug.wrappers.BaseResponse.max_cookie_size` in
|
|
||||||
Werkzeug's docs.
|
|
||||||
"""
|
|
||||||
if current_app:
|
|
||||||
return current_app.config['MAX_COOKIE_SIZE']
|
|
||||||
|
|
||||||
# return Werkzeug's default when not in an app context
|
|
||||||
return super(Response, self).max_cookie_size
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,23 +0,0 @@
|
||||||
2009-2018 (c) Benoît Chesneau <benoitc@e-engura.org>
|
|
||||||
2009-2015 (c) Paul J. Davis <paul.joseph.davis@gmail.com>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
|
||||||
obtaining a copy of this software and associated documentation
|
|
||||||
files (the "Software"), to deal in the Software without
|
|
||||||
restriction, including without limitation the rights to use,
|
|
||||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following
|
|
||||||
conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
||||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
||||||
OTHER DEALINGS IN THE SOFTWARE.
|
|
|
@ -1,111 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: gunicorn
|
|
||||||
Version: 19.8.1
|
|
||||||
Summary: WSGI HTTP Server for UNIX
|
|
||||||
Home-page: http://gunicorn.org
|
|
||||||
Author: Benoit Chesneau
|
|
||||||
Author-email: benoitc@e-engura.com
|
|
||||||
License: MIT
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 4 - Beta
|
|
||||||
Classifier: Environment :: Other Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Operating System :: MacOS :: MacOS X
|
|
||||||
Classifier: Operating System :: POSIX
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 2
|
|
||||||
Classifier: Programming Language :: Python :: 2.6
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.2
|
|
||||||
Classifier: Programming Language :: Python :: 3.3
|
|
||||||
Classifier: Programming Language :: Python :: 3.4
|
|
||||||
Classifier: Programming Language :: Python :: 3.5
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Topic :: Internet
|
|
||||||
Classifier: Topic :: Utilities
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Requires-Python: >=2.6, !=3.0.*, !=3.1.*
|
|
||||||
Provides-Extra: gevent
|
|
||||||
Provides-Extra: tornado
|
|
||||||
Provides-Extra: eventlet
|
|
||||||
Provides-Extra: gthread
|
|
||||||
Provides-Extra: eventlet
|
|
||||||
Requires-Dist: eventlet (>=0.9.7); extra == 'eventlet'
|
|
||||||
Provides-Extra: gevent
|
|
||||||
Requires-Dist: gevent (>=0.13); extra == 'gevent'
|
|
||||||
Provides-Extra: gthread
|
|
||||||
Provides-Extra: tornado
|
|
||||||
Requires-Dist: tornado (>=0.2); extra == 'tornado'
|
|
||||||
|
|
||||||
Gunicorn
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/gunicorn.svg?style=flat
|
|
||||||
:alt: PyPI version
|
|
||||||
:target: https://pypi.python.org/pypi/gunicorn
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/gunicorn.svg
|
|
||||||
:alt: Supported Python versions
|
|
||||||
:target: https://pypi.python.org/pypi/gunicorn
|
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/benoitc/gunicorn.svg?branch=master
|
|
||||||
:alt: Build Status
|
|
||||||
:target: https://travis-ci.org/benoitc/gunicorn
|
|
||||||
|
|
||||||
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX. It's a pre-fork
|
|
||||||
worker model ported from Ruby's Unicorn_ project. The Gunicorn server is broadly
|
|
||||||
compatible with various web frameworks, simply implemented, light on server
|
|
||||||
resource usage, and fairly speedy.
|
|
||||||
|
|
||||||
Feel free to join us in `#gunicorn`_ on Freenode_.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The documentation is hosted at http://docs.gunicorn.org.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
Gunicorn requires **Python 2.x >= 2.6** or **Python 3.x >= 3.2**.
|
|
||||||
|
|
||||||
Install from PyPI::
|
|
||||||
|
|
||||||
$ pip install gunicorn
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Basic usage::
|
|
||||||
|
|
||||||
$ gunicorn [OPTIONS] APP_MODULE
|
|
||||||
|
|
||||||
Where ``APP_MODULE`` is of the pattern ``$(MODULE_NAME):$(VARIABLE_NAME)``. The
|
|
||||||
module name can be a full dotted path. The variable name refers to a WSGI
|
|
||||||
callable that should be found in the specified module.
|
|
||||||
|
|
||||||
Example with test app::
|
|
||||||
|
|
||||||
$ cd examples
|
|
||||||
$ gunicorn --workers=2 test:app
|
|
||||||
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
Gunicorn is released under the MIT License. See the LICENSE_ file for more
|
|
||||||
details.
|
|
||||||
|
|
||||||
.. _Unicorn: https://bogomips.org/unicorn/
|
|
||||||
.. _`#gunicorn`: https://webchat.freenode.net/?channels=gunicorn
|
|
||||||
.. _Freenode: https://freenode.net/
|
|
||||||
.. _LICENSE: https://github.com/benoitc/gunicorn/blob/master/LICENSE
|
|
||||||
|
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
gunicorn/__init__.py,sha256=Y1BlVaRVNjIKXqSzcqTNHo9dq2eG6kgPx5jWJ3h5GRE,255
|
|
||||||
gunicorn/_compat.py,sha256=5cXb6vMfVzInDq-AHNyZfsK-UG5NetDn62nPfqylHSU,9355
|
|
||||||
gunicorn/arbiter.py,sha256=AbJNSFnTmx9Qd-vZAqEH3y5fz8ydPmyli_BERNIwdyE,20158
|
|
||||||
gunicorn/argparse_compat.py,sha256=gsHDGwo4BSJWHdiaEXy0Emr96NKC0LDYmK5nB7PE8Qc,87791
|
|
||||||
gunicorn/config.py,sha256=HDoWZ0JyoPzl2WW6hlDatDevvhfah4oS_j7tOq8Pa7E,53417
|
|
||||||
gunicorn/debug.py,sha256=UUw-eteLEm_OQ98D6K3XtDjx4Dya2H35zdiu8z7F7uc,2289
|
|
||||||
gunicorn/errors.py,sha256=JlDBjag90gMiRwLHG3xzEJzDOntSl1iM32R277-U6j0,919
|
|
||||||
gunicorn/glogging.py,sha256=PMdoe6hdBQWKGlnP4lXdBph6b1ygD0kknxkDsmNIVSU,15554
|
|
||||||
gunicorn/pidfile.py,sha256=_69tsfF1aHklrMrJe2sHERovMduRByVTv99my7yQ874,2357
|
|
||||||
gunicorn/reloader.py,sha256=fh4J7w_DxWaFuFd3G4RyOgDFs1C1lrd0w7jOXItSu5g,3791
|
|
||||||
gunicorn/selectors.py,sha256=14_UESrpE3AQKXWKeeAUG9vBTzJ0yTYDGtEo6xOtlDY,18997
|
|
||||||
gunicorn/six.py,sha256=6N-6RCENPfBtMpN5UmgDfDKmJebbbuPu_Dk3Zf8ngww,27344
|
|
||||||
gunicorn/sock.py,sha256=gX2FsdsOGMCtSHbDXn7lsiYYYRc3roQklIJLip1oZQo,6019
|
|
||||||
gunicorn/systemd.py,sha256=ffhv17cdv-hDeFAJi1eAVtJskkVciV6cQU75Q2oplqg,1362
|
|
||||||
gunicorn/util.py,sha256=Ns_a8Pf7MkaEi0KbV3GsP9aVQ2a_S45EjSE6Iyg2tYU,16229
|
|
||||||
gunicorn/app/__init__.py,sha256=GuqstqdkizeV4HRbd8aGMBn0Q8IDOyRU1wMMNqNe5GY,127
|
|
||||||
gunicorn/app/base.py,sha256=LKxyziLMPNlK3qm6dPMieELBqfLfmwBFnn9SB-KBogE,6652
|
|
||||||
gunicorn/app/pasterapp.py,sha256=AGzZnUpcpw8O8KrizxTgdJBZ4lQdrHgsV0gdx7FVTs8,6046
|
|
||||||
gunicorn/app/wsgiapp.py,sha256=ny71qjegQHl_bGMjNfq_aemPrmGEpH2bMRIdph6bj4Q,1870
|
|
||||||
gunicorn/http/__init__.py,sha256=b4TF3x5F0VYOPTOeNYwRGR1EYHBaPMhZRMoNeuD5-n0,277
|
|
||||||
gunicorn/http/_sendfile.py,sha256=Eqd-s3HlvLuyfGjqaH_Jk72cAtEV8hQv5tb1M1AqcBU,2217
|
|
||||||
gunicorn/http/body.py,sha256=MmlZpj_6oRPj3oPVSMQZr0X3KH6ikntxDnVcLgfekZs,7345
|
|
||||||
gunicorn/http/errors.py,sha256=sNjF2lm4m2qyZ9l95_U33FRxPXpxXzjnZyYqWS-hxd4,2850
|
|
||||||
gunicorn/http/message.py,sha256=G5po0upwbrTyIggb_IEAItIjSi_aDoWYLPQ62o8pOI4,12257
|
|
||||||
gunicorn/http/parser.py,sha256=IRMvp0veP4wL8Z4vgNV72CPydCNPdNNIy9u-DlDvvSo,1294
|
|
||||||
gunicorn/http/unreader.py,sha256=s4kDW5euiJPsDuHzCqFXUtHCApqIxpShb9dtAyjJw9Y,2019
|
|
||||||
gunicorn/http/wsgi.py,sha256=_5zYFl5HGKrPpEMrEnsLyPreOJcTgaYzsEBuJkEOmko,12852
|
|
||||||
gunicorn/instrument/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
gunicorn/instrument/statsd.py,sha256=5xueDuTZMFtmS8ayGT4sU_OyB9qkEv4Agk-eJwAmhJM,4434
|
|
||||||
gunicorn/workers/__init__.py,sha256=Z57G1WjnZDCG52C8PgiXF4mKRKqlv81b2GHkhOJiO6A,774
|
|
||||||
gunicorn/workers/_gaiohttp.py,sha256=CFKiyLNqWqemhDvDovb-JqMRTMNz50gZUwwOpIjrpHw,5071
|
|
||||||
gunicorn/workers/async.py,sha256=54VkS3S_wrFD7v3jInhFfkeBhaPnV5UN-cu-i5MoXkc,5575
|
|
||||||
gunicorn/workers/base.py,sha256=eYdcy2EPlydcjyi2CpgeU0tqlJpz3_kt-RbGhoamvQ8,9126
|
|
||||||
gunicorn/workers/gaiohttp.py,sha256=3rhXky6APkhI0D9nwXlogLo_Jd9v98CiEuCy9inzCU4,823
|
|
||||||
gunicorn/workers/geventlet.py,sha256=L7N2bizKQw8VXb02teu1_wYVG5hwt9SSaQn7J0kSKyI,4253
|
|
||||||
gunicorn/workers/ggevent.py,sha256=hzx2kOZP13yVBz-EBthoTgjalOeRKfoUk5XchmRKzDM,7407
|
|
||||||
gunicorn/workers/gthread.py,sha256=HIoWuylHZfH1wlSh4eZ8wxo1kQ5abvdUaFfKfIsgQvI,12009
|
|
||||||
gunicorn/workers/gtornado.py,sha256=LtBWnEX7MNpeGX-YmlBoV1_OOhjkdytFmt1pzOlRPZk,5044
|
|
||||||
gunicorn/workers/sync.py,sha256=_vd1JATNLG4MgJppNJG5KWBIzLGYqRzhEAQVz9H11LI,7153
|
|
||||||
gunicorn/workers/workertmp.py,sha256=6QINPBrriLvezgkC_hclOOeXLi_owMt_SOA5KPEIN-A,1459
|
|
||||||
gunicorn-19.8.1.dist-info/LICENSE.txt,sha256=eJ_hG5Lhyr-890S1_MOSyb1cZ5hgOk6J-SW2M3mE0d8,1136
|
|
||||||
gunicorn-19.8.1.dist-info/METADATA,sha256=w9Od3TWYwIXsx_CAyLy5OZ1h5v9C_T0_mhK9psl1x5Q,3388
|
|
||||||
gunicorn-19.8.1.dist-info/RECORD,,
|
|
||||||
gunicorn-19.8.1.dist-info/WHEEL,sha256=J3CsTk7Mf2JNUyhImI-mjX-fmI4oDjyiXgWT4qgZiCE,110
|
|
||||||
gunicorn-19.8.1.dist-info/entry_points.txt,sha256=XeFINKRdSUKwJwaVSolO24PuV_YeO71IMF-rOra5JO8,184
|
|
||||||
gunicorn-19.8.1.dist-info/top_level.txt,sha256=cdMaa2yhxb8do-WioY9qRHUCfwf55YztjwQCncaInoE,9
|
|
||||||
../../../bin/gunicorn,sha256=3n02jiI_1KSuGRVnArdMBsMwXwNk9qYNxest7n5NCtA,226
|
|
||||||
../../../bin/gunicorn_paster,sha256=VpfTHNYpEn2RVEjyLdmq1-_lArYY2joHAgAlGrbIjeg,228
|
|
||||||
gunicorn-19.8.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
gunicorn/debug.pyc,,
|
|
||||||
gunicorn/workers/sync.pyc,,
|
|
||||||
gunicorn/http/wsgi.pyc,,
|
|
||||||
gunicorn/workers/async.pyc,,
|
|
||||||
gunicorn/workers/workertmp.pyc,,
|
|
||||||
gunicorn/http/message.pyc,,
|
|
||||||
gunicorn/instrument/__init__.pyc,,
|
|
||||||
gunicorn/http/unreader.pyc,,
|
|
||||||
gunicorn/app/__init__.pyc,,
|
|
||||||
gunicorn/systemd.pyc,,
|
|
||||||
gunicorn/config.pyc,,
|
|
||||||
gunicorn/workers/base.pyc,,
|
|
||||||
gunicorn/app/wsgiapp.pyc,,
|
|
||||||
gunicorn/workers/gaiohttp.pyc,,
|
|
||||||
gunicorn/app/base.pyc,,
|
|
||||||
gunicorn/arbiter.pyc,,
|
|
||||||
gunicorn/workers/ggevent.pyc,,
|
|
||||||
gunicorn/http/errors.pyc,,
|
|
||||||
gunicorn/workers/gtornado.pyc,,
|
|
||||||
gunicorn/six.pyc,,
|
|
||||||
gunicorn/http/__init__.pyc,,
|
|
||||||
gunicorn/pidfile.pyc,,
|
|
||||||
gunicorn/workers/gthread.pyc,,
|
|
||||||
gunicorn/_compat.pyc,,
|
|
||||||
gunicorn/errors.pyc,,
|
|
||||||
gunicorn/http/body.pyc,,
|
|
||||||
gunicorn/instrument/statsd.pyc,,
|
|
||||||
gunicorn/__init__.pyc,,
|
|
||||||
gunicorn/http/parser.pyc,,
|
|
||||||
gunicorn/selectors.pyc,,
|
|
||||||
gunicorn/sock.pyc,,
|
|
||||||
gunicorn/reloader.pyc,,
|
|
||||||
gunicorn/workers/__init__.pyc,,
|
|
||||||
gunicorn/argparse_compat.pyc,,
|
|
||||||
gunicorn/glogging.pyc,,
|
|
||||||
gunicorn/http/_sendfile.pyc,,
|
|
||||||
gunicorn/workers/geventlet.pyc,,
|
|
||||||
gunicorn/util.pyc,,
|
|
||||||
gunicorn/app/pasterapp.pyc,,
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue