diff --git a/frontend/templates/punches.html b/frontend/templates/punches.html
index 6eca07e..b483609 100644
--- a/frontend/templates/punches.html
+++ b/frontend/templates/punches.html
@@ -17,25 +17,25 @@
Punch Card
- | Breakfast |
- |
- |
- |
- |
+ Breakfast |
+  |
+  |
+  |
+ |
- | Lunch |
- |
- |
- |
- |
+ Lunch |
+  |
+  |
+  |
+ |
- | Dinner |
- |
- |
- |
- |
+ Dinner |
+  |
+  |
+  |
+ |
diff --git a/venv/Include/site/python3.12/greenlet/greenlet.h b/venv/Include/site/python3.12/greenlet/greenlet.h
new file mode 100644
index 0000000..d02a16e
--- /dev/null
+++ b/venv/Include/site/python3.12/greenlet/greenlet.h
@@ -0,0 +1,164 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+
+/* Greenlet object interface */
+
+#ifndef Py_GREENLETOBJECT_H
+#define Py_GREENLETOBJECT_H
+
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is deprecated and undocumented. It does not change. */
+#define GREENLET_VERSION "1.0.0"
+
+#ifndef GREENLET_MODULE
+#define implementation_ptr_t void*
+#endif
+
+typedef struct _greenlet {
+ PyObject_HEAD
+ PyObject* weakreflist;
+ PyObject* dict;
+ implementation_ptr_t pimpl;
+} PyGreenlet;
+
+#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
+
+
+/* C API functions */
+
+/* Total number of symbols that are exported */
+#define PyGreenlet_API_pointers 12
+
+#define PyGreenlet_Type_NUM 0
+#define PyExc_GreenletError_NUM 1
+#define PyExc_GreenletExit_NUM 2
+
+#define PyGreenlet_New_NUM 3
+#define PyGreenlet_GetCurrent_NUM 4
+#define PyGreenlet_Throw_NUM 5
+#define PyGreenlet_Switch_NUM 6
+#define PyGreenlet_SetParent_NUM 7
+
+#define PyGreenlet_MAIN_NUM 8
+#define PyGreenlet_STARTED_NUM 9
+#define PyGreenlet_ACTIVE_NUM 10
+#define PyGreenlet_GET_PARENT_NUM 11
+
+#ifndef GREENLET_MODULE
+/* This section is used by modules that uses the greenlet C API */
+static void** _PyGreenlet_API = NULL;
+
+# define PyGreenlet_Type \
+ (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
+
+# define PyExc_GreenletError \
+ ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
+
+# define PyExc_GreenletExit \
+ ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
+
+/*
+ * PyGreenlet_New(PyObject *args)
+ *
+ * greenlet.greenlet(run, parent=None)
+ */
+# define PyGreenlet_New \
+ (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
+ _PyGreenlet_API[PyGreenlet_New_NUM])
+
+/*
+ * PyGreenlet_GetCurrent(void)
+ *
+ * greenlet.getcurrent()
+ */
+# define PyGreenlet_GetCurrent \
+ (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
+
+/*
+ * PyGreenlet_Throw(
+ * PyGreenlet *greenlet,
+ * PyObject *typ,
+ * PyObject *val,
+ * PyObject *tb)
+ *
+ * g.throw(...)
+ */
+# define PyGreenlet_Throw \
+ (*(PyObject * (*)(PyGreenlet * self, \
+ PyObject * typ, \
+ PyObject * val, \
+ PyObject * tb)) \
+ _PyGreenlet_API[PyGreenlet_Throw_NUM])
+
+/*
+ * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
+ *
+ * g.switch(*args, **kwargs)
+ */
+# define PyGreenlet_Switch \
+ (*(PyObject * \
+ (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
+ _PyGreenlet_API[PyGreenlet_Switch_NUM])
+
+/*
+ * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
+ *
+ * g.parent = new_parent
+ */
+# define PyGreenlet_SetParent \
+ (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
+ _PyGreenlet_API[PyGreenlet_SetParent_NUM])
+
+/*
+ * PyGreenlet_GetParent(PyObject* greenlet)
+ *
+ * return greenlet.parent;
+ *
+ * This could return NULL even if there is no exception active.
+ * If it does not return NULL, you are responsible for decrementing the
+ * reference count.
+ */
+# define PyGreenlet_GetParent \
+ (*(PyGreenlet* (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
+
+/*
+ * deprecated, undocumented alias.
+ */
+# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
+
+# define PyGreenlet_MAIN \
+ (*(int (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_MAIN_NUM])
+
+# define PyGreenlet_STARTED \
+ (*(int (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_STARTED_NUM])
+
+# define PyGreenlet_ACTIVE \
+ (*(int (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
+
+
+
+
+/* Macro that imports greenlet and initializes C API */
+/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
+ keep the older definition to be sure older code that might have a copy of
+ the header still works. */
+# define PyGreenlet_Import() \
+ { \
+ _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
+ }
+
+#endif /* GREENLET_MODULE */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py_GREENLETOBJECT_H */
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/INSTALLER b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/LICENSE b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/LICENSE
new file mode 100644
index 0000000..0446381
--- /dev/null
+++ b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Matthew Frazier
+
+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.
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/METADATA b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/METADATA
new file mode 100644
index 0000000..445a0b2
--- /dev/null
+++ b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/METADATA
@@ -0,0 +1,183 @@
+Metadata-Version: 2.1
+Name: Flask-Login
+Version: 0.6.3
+Summary: User authentication and session management for Flask.
+Home-page: https://github.com/maxcountryman/flask-login
+Author: Matthew Frazier
+Author-email: leafstormrush@gmail.com
+Maintainer: Max Countryman
+License: MIT
+Project-URL: Documentation, https://flask-login.readthedocs.io/
+Project-URL: Changes, https://github.com/maxcountryman/flask-login/blob/main/CHANGES.md
+Project-URL: Source Code, https://github.com/maxcountryman/flask-login
+Project-URL: Issue Tracker, https://github.com/maxcountryman/flask-login/issues
+Classifier: Development Status :: 4 - Beta
+Classifier: Environment :: Web Environment
+Classifier: Framework :: Flask
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.7
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Requires-Dist: Flask >=1.0.4
+Requires-Dist: Werkzeug >=1.0.1
+
+# Flask-Login
+
+
+[](https://coveralls.io/github/maxcountryman/flask-login?branch=main)
+[](LICENSE)
+
+Flask-Login provides user session management for Flask. It handles the common
+tasks of logging in, logging out, and remembering your users' sessions over
+extended periods of time.
+
+Flask-Login is not bound to any particular database system or permissions
+model. The only requirement is that your user objects implement a few methods,
+and that you provide a callback to the extension capable of loading users from
+their ID.
+
+## Installation
+
+Install the extension with pip:
+
+```sh
+$ pip install flask-login
+```
+
+## Usage
+
+Once installed, the Flask-Login is easy to use. Let's walk through setting up
+a basic application. Also please note that this is a very basic guide: we will
+be taking shortcuts here that you should never take in a real application.
+
+To begin we'll set up a Flask app:
+
+```python
+import flask
+
+app = flask.Flask(__name__)
+app.secret_key = 'super secret string' # Change this!
+```
+
+Flask-Login works via a login manager. To kick things off, we'll set up the
+login manager by instantiating it and telling it about our Flask app:
+
+```python
+import flask_login
+
+login_manager = flask_login.LoginManager()
+
+login_manager.init_app(app)
+```
+
+To keep things simple we're going to use a dictionary to represent a database
+of users. In a real application, this would be an actual persistence layer.
+However it's important to point out this is a feature of Flask-Login: it
+doesn't care how your data is stored so long as you tell it how to retrieve it!
+
+```python
+# Our mock database.
+users = {'foo@bar.tld': {'password': 'secret'}}
+```
+
+We also need to tell Flask-Login how to load a user from a Flask request and
+from its session. To do this we need to define our user object, a
+`user_loader` callback, and a `request_loader` callback.
+
+```python
+class User(flask_login.UserMixin):
+ pass
+
+
+@login_manager.user_loader
+def user_loader(email):
+ if email not in users:
+ return
+
+ user = User()
+ user.id = email
+ return user
+
+
+@login_manager.request_loader
+def request_loader(request):
+ email = request.form.get('email')
+ if email not in users:
+ return
+
+ user = User()
+ user.id = email
+ return user
+```
+
+Now we're ready to define our views. We can start with a login view, which will
+populate the session with authentication bits. After that we can define a view
+that requires authentication.
+
+```python
+@app.route('/login', methods=['GET', 'POST'])
+def login():
+ if flask.request.method == 'GET':
+ return '''
+
+ '''
+
+ email = flask.request.form['email']
+ if email in users and flask.request.form['password'] == users[email]['password']:
+ user = User()
+ user.id = email
+ flask_login.login_user(user)
+ return flask.redirect(flask.url_for('protected'))
+
+ return 'Bad login'
+
+
+@app.route('/protected')
+@flask_login.login_required
+def protected():
+ return 'Logged in as: ' + flask_login.current_user.id
+```
+
+Finally we can define a view to clear the session and log users out:
+
+```python
+@app.route('/logout')
+def logout():
+ flask_login.logout_user()
+ return 'Logged out'
+```
+
+We now have a basic working application that makes use of session-based
+authentication. To round things off, we should provide a callback for login
+failures:
+
+```python
+@login_manager.unauthorized_handler
+def unauthorized_handler():
+ return 'Unauthorized', 401
+```
+
+Documentation for Flask-Login is available on [ReadTheDocs](https://flask-login.readthedocs.io/en/latest/).
+For complete understanding of available configuration, please refer to the [source code](https://github.com/maxcountryman/flask-login).
+
+
+## Contributing
+
+We welcome contributions! If you would like to hack on Flask-Login, please
+follow these steps:
+
+1. Fork this repository
+2. Make your changes
+3. Install the dev requirements with `pip install -r requirements/dev.txt`
+4. Submit a pull request after running `tox` (ensure it does not error!)
+
+Please give us adequate time to review your submission. Thanks!
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/RECORD b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/RECORD
new file mode 100644
index 0000000..d30aa6a
--- /dev/null
+++ b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/RECORD
@@ -0,0 +1,23 @@
+Flask_Login-0.6.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+Flask_Login-0.6.3.dist-info/LICENSE,sha256=ep37nF2iBO0TcPO2LBPimSoS2h2nB_R-FWiX7rQ0Tls,1059
+Flask_Login-0.6.3.dist-info/METADATA,sha256=AUSHR5Po6-Cwmz1KBrAZbTzR-iVVFvtb2NQKYl7UuAU,5799
+Flask_Login-0.6.3.dist-info/RECORD,,
+Flask_Login-0.6.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+Flask_Login-0.6.3.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
+Flask_Login-0.6.3.dist-info/top_level.txt,sha256=OuXmIpiFnXLvW-iBbW2km7ZIy5EZvwSBnYaOC3Kt7j8,12
+flask_login/__about__.py,sha256=Kkp5e9mV9G7vK_FqZof-g9RFmyyBzq1gge5aKXgvilE,389
+flask_login/__init__.py,sha256=wYQiQCikT_Ndp3PhOD-1gRTGCrUPIE-FrjQUrT9aVAg,2681
+flask_login/__pycache__/__about__.cpython-312.pyc,,
+flask_login/__pycache__/__init__.cpython-312.pyc,,
+flask_login/__pycache__/config.cpython-312.pyc,,
+flask_login/__pycache__/login_manager.cpython-312.pyc,,
+flask_login/__pycache__/mixins.cpython-312.pyc,,
+flask_login/__pycache__/signals.cpython-312.pyc,,
+flask_login/__pycache__/test_client.cpython-312.pyc,,
+flask_login/__pycache__/utils.cpython-312.pyc,,
+flask_login/config.py,sha256=YAocv18La7YGQyNY5aT7rU1GQIZnX6pvchwqx3kA9p8,1813
+flask_login/login_manager.py,sha256=h20F_iv3mqc6rIJ4-V6_XookzOUl8Rcpasua-dCByQY,20073
+flask_login/mixins.py,sha256=gPd7otMRljxw0eUhUMbHsnEBc_jK2cYdxg5KFLuJcoI,1528
+flask_login/signals.py,sha256=xCMoFHKU1RTVt1NY-Gfl0OiVKpiyNt6YJw_PsgkjY3w,2464
+flask_login/test_client.py,sha256=6mrjiBRLGJpgvvFlLypXPTBLiMp0BAN-Ft-uogqC81g,517
+flask_login/utils.py,sha256=Y1wxjCVxpYohBaQJ0ADLypQ-VvBNycwG-gVXFF7k99I,14021
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/REQUESTED b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/WHEEL b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/WHEEL
new file mode 100644
index 0000000..ba48cbc
--- /dev/null
+++ b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.41.3)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/top_level.txt b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/top_level.txt
new file mode 100644
index 0000000..31514bd
--- /dev/null
+++ b/venv/Lib/site-packages/Flask_Login-0.6.3.dist-info/top_level.txt
@@ -0,0 +1 @@
+flask_login
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/INSTALLER b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/LICENSE.rst b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/LICENSE.rst
new file mode 100644
index 0000000..c37cae4
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2007 Pallets
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/METADATA b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/METADATA
new file mode 100644
index 0000000..56e9429
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/METADATA
@@ -0,0 +1,105 @@
+Metadata-Version: 2.1
+Name: Jinja2
+Version: 3.1.3
+Summary: A very fast and expressive template engine.
+Home-page: https://palletsprojects.com/p/jinja/
+Maintainer: Pallets
+Maintainer-email: contact@palletsprojects.com
+License: BSD-3-Clause
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Documentation, https://jinja.palletsprojects.com/
+Project-URL: Changes, https://jinja.palletsprojects.com/changes/
+Project-URL: Source Code, https://github.com/pallets/jinja/
+Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
+Project-URL: Chat, https://discord.gg/pallets
+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: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Topic :: Text Processing :: Markup :: HTML
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE.rst
+Requires-Dist: MarkupSafe >=2.0
+Provides-Extra: i18n
+Requires-Dist: Babel >=2.7 ; extra == 'i18n'
+
+Jinja
+=====
+
+Jinja is a fast, expressive, extensible templating engine. Special
+placeholders in the template allow writing code similar to Python
+syntax. Then the template is passed data to render the final document.
+
+It includes:
+
+- Template inheritance and inclusion.
+- Define and import macros within templates.
+- HTML templates can use autoescaping to prevent XSS from untrusted
+ user input.
+- A sandboxed environment can safely render untrusted templates.
+- AsyncIO support for generating templates and calling async
+ functions.
+- I18N support with Babel.
+- Templates are compiled to optimized Python code just-in-time and
+ cached, or can be compiled ahead-of-time.
+- Exceptions point to the correct line in templates to make debugging
+ easier.
+- Extensible filters, tests, functions, and even syntax.
+
+Jinja's philosophy is that while application logic belongs in Python if
+possible, it shouldn't make the template designer's job difficult by
+restricting functionality too much.
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+ $ pip install -U Jinja2
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+In A Nutshell
+-------------
+
+.. code-block:: jinja
+
+ {% extends "base.html" %}
+ {% block title %}Members{% endblock %}
+ {% block content %}
+
+ {% endblock %}
+
+
+Donate
+------
+
+The Pallets organization develops and supports Jinja and other popular
+packages. 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://palletsprojects.com/donate
+
+
+Links
+-----
+
+- Documentation: https://jinja.palletsprojects.com/
+- Changes: https://jinja.palletsprojects.com/changes/
+- PyPI Releases: https://pypi.org/project/Jinja2/
+- Source Code: https://github.com/pallets/jinja/
+- Issue Tracker: https://github.com/pallets/jinja/issues/
+- Chat: https://discord.gg/pallets
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/RECORD b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/RECORD
new file mode 100644
index 0000000..781d655
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/RECORD
@@ -0,0 +1,59 @@
+Jinja2-3.1.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+Jinja2-3.1.3.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
+Jinja2-3.1.3.dist-info/METADATA,sha256=0cLNbRCI91jytc7Bzv3XAQfZzFDF2gxkJuH46eF5vew,3301
+Jinja2-3.1.3.dist-info/RECORD,,
+Jinja2-3.1.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+Jinja2-3.1.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
+Jinja2-3.1.3.dist-info/entry_points.txt,sha256=zRd62fbqIyfUpsRtU7EVIFyiu1tPwfgO7EvPErnxgTE,59
+Jinja2-3.1.3.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
+jinja2/__init__.py,sha256=NTBwMwsECrdHmxeXF7seusHLzrh6Ldn1A9qhS5cDuf0,1927
+jinja2/__pycache__/__init__.cpython-312.pyc,,
+jinja2/__pycache__/_identifier.cpython-312.pyc,,
+jinja2/__pycache__/async_utils.cpython-312.pyc,,
+jinja2/__pycache__/bccache.cpython-312.pyc,,
+jinja2/__pycache__/compiler.cpython-312.pyc,,
+jinja2/__pycache__/constants.cpython-312.pyc,,
+jinja2/__pycache__/debug.cpython-312.pyc,,
+jinja2/__pycache__/defaults.cpython-312.pyc,,
+jinja2/__pycache__/environment.cpython-312.pyc,,
+jinja2/__pycache__/exceptions.cpython-312.pyc,,
+jinja2/__pycache__/ext.cpython-312.pyc,,
+jinja2/__pycache__/filters.cpython-312.pyc,,
+jinja2/__pycache__/idtracking.cpython-312.pyc,,
+jinja2/__pycache__/lexer.cpython-312.pyc,,
+jinja2/__pycache__/loaders.cpython-312.pyc,,
+jinja2/__pycache__/meta.cpython-312.pyc,,
+jinja2/__pycache__/nativetypes.cpython-312.pyc,,
+jinja2/__pycache__/nodes.cpython-312.pyc,,
+jinja2/__pycache__/optimizer.cpython-312.pyc,,
+jinja2/__pycache__/parser.cpython-312.pyc,,
+jinja2/__pycache__/runtime.cpython-312.pyc,,
+jinja2/__pycache__/sandbox.cpython-312.pyc,,
+jinja2/__pycache__/tests.cpython-312.pyc,,
+jinja2/__pycache__/utils.cpython-312.pyc,,
+jinja2/__pycache__/visitor.cpython-312.pyc,,
+jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958
+jinja2/async_utils.py,sha256=dFcmh6lMNfbh7eLKrBio8JqAKLHdZbpCuurFN4OERtY,2447
+jinja2/bccache.py,sha256=mhz5xtLxCcHRAa56azOhphIAe19u1we0ojifNMClDio,14061
+jinja2/compiler.py,sha256=PJzYdRLStlEOqmnQs1YxlizPrJoj3jTZuUleREn6AIQ,72199
+jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433
+jinja2/debug.py,sha256=iWJ432RadxJNnaMOPrjIDInz50UEgni3_HKuFXi2vuQ,6299
+jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267
+jinja2/environment.py,sha256=0qldX3VQKZcm6lgn7zHz94oRFow7YPYERiqkquomNjU,61253
+jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071
+jinja2/ext.py,sha256=5fnMpllaXkfm2P_93RIvi-OnK7Tk8mCW8Du-GcD12Hc,31844
+jinja2/filters.py,sha256=vYjKb2zaPShvYtn_LpSmqfS8SScbrA_KOanNibsMDIE,53862
+jinja2/idtracking.py,sha256=GfNmadir4oDALVxzn3DL9YInhJDr69ebXeA2ygfuCGA,10704
+jinja2/lexer.py,sha256=DW2nX9zk-6MWp65YR2bqqj0xqCvLtD-u9NWT8AnFRxQ,29726
+jinja2/loaders.py,sha256=ayAwxfrA1SAffQta0nwSDm3TDT4KYiIGN_D9Z45B310,23085
+jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396
+jinja2/nativetypes.py,sha256=7GIGALVJgdyL80oZJdQUaUfwSt5q2lSSZbXt0dNf_M4,4210
+jinja2/nodes.py,sha256=i34GPRAZexXMT6bwuf5SEyvdmS-bRCy9KMjwN5O6pjk,34550
+jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650
+jinja2/parser.py,sha256=Y199wPL-G67gJoi5G_5sHuu9uEP1PJkjjLEW_xTH8-k,39736
+jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+jinja2/runtime.py,sha256=_6LkKIWFJjQdqlrgA3K39zBFQ-7Orm3wGDm96RwxQoE,33406
+jinja2/sandbox.py,sha256=Y0xZeXQnH6EX5VjaV2YixESxoepnRbW_3UeQosaBU3M,14584
+jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905
+jinja2/utils.py,sha256=IMwRIcN1SsTw2-jdQtlH2KzNABsXZBW_-tnFXafQBvY,23933
+jinja2/visitor.py,sha256=MH14C6yq24G_KVtWzjwaI7Wg14PCJIYlWW1kpkxYak0,3568
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/REQUESTED b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/WHEEL b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/WHEEL
new file mode 100644
index 0000000..98c0d20
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.42.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/entry_points.txt b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/entry_points.txt
new file mode 100644
index 0000000..7b9666c
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/entry_points.txt
@@ -0,0 +1,2 @@
+[babel.extractors]
+jinja2 = jinja2.ext:babel_extract[i18n]
diff --git a/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/top_level.txt b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/top_level.txt
new file mode 100644
index 0000000..7f7afbf
--- /dev/null
+++ b/venv/Lib/site-packages/Jinja2-3.1.3.dist-info/top_level.txt
@@ -0,0 +1 @@
+jinja2
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst
new file mode 100644
index 0000000..9d227a0
--- /dev/null
+++ b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2010 Pallets
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA
new file mode 100644
index 0000000..dfe37d5
--- /dev/null
+++ b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/METADATA
@@ -0,0 +1,93 @@
+Metadata-Version: 2.1
+Name: MarkupSafe
+Version: 2.1.5
+Summary: Safely add untrusted strings to HTML/XML markup.
+Home-page: https://palletsprojects.com/p/markupsafe/
+Maintainer: Pallets
+Maintainer-email: contact@palletsprojects.com
+License: BSD-3-Clause
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Documentation, https://markupsafe.palletsprojects.com/
+Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
+Project-URL: Source Code, https://github.com/pallets/markupsafe/
+Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
+Project-URL: Chat, https://discord.gg/pallets
+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: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Topic :: Text Processing :: Markup :: HTML
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE.rst
+
+MarkupSafe
+==========
+
+MarkupSafe implements a text object that escapes characters so it is
+safe to use in HTML and XML. Characters that have special meanings are
+replaced so that they display as the actual characters. This mitigates
+injection attacks, meaning untrusted user input can safely be displayed
+on a page.
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+ pip install -U MarkupSafe
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+Examples
+--------
+
+.. code-block:: pycon
+
+ >>> from markupsafe import Markup, escape
+
+ >>> # escape replaces special characters and wraps in Markup
+ >>> escape("")
+ Markup('<script>alert(document.cookie);</script>')
+
+ >>> # wrap in Markup to mark text "safe" and prevent escaping
+ >>> Markup("Hello")
+ Markup('hello')
+
+ >>> escape(Markup("Hello"))
+ Markup('hello')
+
+ >>> # Markup is a str subclass
+ >>> # methods and operators escape their arguments
+ >>> template = Markup("Hello {name}")
+ >>> template.format(name='"World"')
+ Markup('Hello "World"')
+
+
+Donate
+------
+
+The Pallets organization develops and supports MarkupSafe and other
+popular packages. 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://palletsprojects.com/donate
+
+
+Links
+-----
+
+- Documentation: https://markupsafe.palletsprojects.com/
+- Changes: https://markupsafe.palletsprojects.com/changes/
+- PyPI Releases: https://pypi.org/project/MarkupSafe/
+- Source Code: https://github.com/pallets/markupsafe/
+- Issue Tracker: https://github.com/pallets/markupsafe/issues/
+- Chat: https://discord.gg/pallets
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD
new file mode 100644
index 0000000..c71545b
--- /dev/null
+++ b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/RECORD
@@ -0,0 +1,15 @@
+MarkupSafe-2.1.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+MarkupSafe-2.1.5.dist-info/LICENSE.rst,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
+MarkupSafe-2.1.5.dist-info/METADATA,sha256=icNlaniV7YIQZ1BScCVqNaRtm7MAgfw8d3OBmoSVyAY,3096
+MarkupSafe-2.1.5.dist-info/RECORD,,
+MarkupSafe-2.1.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+MarkupSafe-2.1.5.dist-info/WHEEL,sha256=j9Aissza3750LQHFAQyYerNjmkEON1-8w_RaZNFtKSs,102
+MarkupSafe-2.1.5.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
+markupsafe/__init__.py,sha256=m1ysNeqf55zbEoJtaovca40ivrkEFolPlw5bGoC5Gi4,11290
+markupsafe/__pycache__/__init__.cpython-312.pyc,,
+markupsafe/__pycache__/_native.cpython-312.pyc,,
+markupsafe/_native.py,sha256=_Q7UsXCOvgdonCgqG3l5asANI6eo50EKnDM-mlwEC5M,1776
+markupsafe/_speedups.c,sha256=n3jzzaJwXcoN8nTFyA53f3vSqsWK2vujI-v6QYifjhQ,7403
+markupsafe/_speedups.cp312-win_amd64.pyd,sha256=CLz8k0mpvM-dgLP0eSHpGYHm8shlGxXoCinA12zgHsY,15872
+markupsafe/_speedups.pyi,sha256=f5QtwIOP0eLrxh2v5p6SmaYmlcHIGIfmz0DovaqL0OU,238
+markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/REQUESTED b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL
new file mode 100644
index 0000000..1c1a93d
--- /dev/null
+++ b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.42.0)
+Root-Is-Purelib: false
+Tag: cp312-cp312-win_amd64
+
diff --git a/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt
new file mode 100644
index 0000000..75bf729
--- /dev/null
+++ b/venv/Lib/site-packages/MarkupSafe-2.1.5.dist-info/top_level.txt
@@ -0,0 +1 @@
+markupsafe
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/INSTALLER b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/LICENSE b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/LICENSE
new file mode 100644
index 0000000..967cdc5
--- /dev/null
+++ b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/LICENSE
@@ -0,0 +1,19 @@
+Copyright 2005-2024 SQLAlchemy authors and contributors .
+
+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.
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/METADATA b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/METADATA
new file mode 100644
index 0000000..e43a459
--- /dev/null
+++ b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/METADATA
@@ -0,0 +1,242 @@
+Metadata-Version: 2.1
+Name: SQLAlchemy
+Version: 2.0.27
+Summary: Database Abstraction Library
+Home-page: https://www.sqlalchemy.org
+Author: Mike Bayer
+Author-email: mike_mp@zzzcomputing.com
+License: MIT
+Project-URL: Documentation, https://docs.sqlalchemy.org
+Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Database :: Front-Ends
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Requires-Dist: typing-extensions >=4.6.0
+Requires-Dist: greenlet !=0.4.17 ; platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32")))))
+Requires-Dist: importlib-metadata ; python_version < "3.8"
+Provides-Extra: aiomysql
+Requires-Dist: greenlet !=0.4.17 ; extra == 'aiomysql'
+Requires-Dist: aiomysql >=0.2.0 ; extra == 'aiomysql'
+Provides-Extra: aioodbc
+Requires-Dist: greenlet !=0.4.17 ; extra == 'aioodbc'
+Requires-Dist: aioodbc ; extra == 'aioodbc'
+Provides-Extra: aiosqlite
+Requires-Dist: greenlet !=0.4.17 ; extra == 'aiosqlite'
+Requires-Dist: aiosqlite ; extra == 'aiosqlite'
+Requires-Dist: typing-extensions !=3.10.0.1 ; extra == 'aiosqlite'
+Provides-Extra: asyncio
+Requires-Dist: greenlet !=0.4.17 ; extra == 'asyncio'
+Provides-Extra: asyncmy
+Requires-Dist: greenlet !=0.4.17 ; extra == 'asyncmy'
+Requires-Dist: asyncmy !=0.2.4,!=0.2.6,>=0.2.3 ; extra == 'asyncmy'
+Provides-Extra: mariadb_connector
+Requires-Dist: mariadb !=1.1.2,!=1.1.5,>=1.0.1 ; extra == 'mariadb_connector'
+Provides-Extra: mssql
+Requires-Dist: pyodbc ; extra == 'mssql'
+Provides-Extra: mssql_pymssql
+Requires-Dist: pymssql ; extra == 'mssql_pymssql'
+Provides-Extra: mssql_pyodbc
+Requires-Dist: pyodbc ; extra == 'mssql_pyodbc'
+Provides-Extra: mypy
+Requires-Dist: mypy >=0.910 ; extra == 'mypy'
+Provides-Extra: mysql
+Requires-Dist: mysqlclient >=1.4.0 ; extra == 'mysql'
+Provides-Extra: mysql_connector
+Requires-Dist: mysql-connector-python ; extra == 'mysql_connector'
+Provides-Extra: oracle
+Requires-Dist: cx-oracle >=8 ; extra == 'oracle'
+Provides-Extra: oracle_oracledb
+Requires-Dist: oracledb >=1.0.1 ; extra == 'oracle_oracledb'
+Provides-Extra: postgresql
+Requires-Dist: psycopg2 >=2.7 ; extra == 'postgresql'
+Provides-Extra: postgresql_asyncpg
+Requires-Dist: greenlet !=0.4.17 ; extra == 'postgresql_asyncpg'
+Requires-Dist: asyncpg ; extra == 'postgresql_asyncpg'
+Provides-Extra: postgresql_pg8000
+Requires-Dist: pg8000 >=1.29.1 ; extra == 'postgresql_pg8000'
+Provides-Extra: postgresql_psycopg
+Requires-Dist: psycopg >=3.0.7 ; extra == 'postgresql_psycopg'
+Provides-Extra: postgresql_psycopg2binary
+Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary'
+Provides-Extra: postgresql_psycopg2cffi
+Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi'
+Provides-Extra: postgresql_psycopgbinary
+Requires-Dist: psycopg[binary] >=3.0.7 ; extra == 'postgresql_psycopgbinary'
+Provides-Extra: pymysql
+Requires-Dist: pymysql ; extra == 'pymysql'
+Provides-Extra: sqlcipher
+Requires-Dist: sqlcipher3-binary ; extra == 'sqlcipher'
+
+SQLAlchemy
+==========
+
+|PyPI| |Python| |Downloads|
+
+.. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy
+ :target: https://pypi.org/project/sqlalchemy
+ :alt: PyPI
+
+.. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy
+ :target: https://pypi.org/project/sqlalchemy
+ :alt: PyPI - Python Version
+
+.. |Downloads| image:: https://static.pepy.tech/badge/sqlalchemy/month
+ :target: https://pepy.tech/project/sqlalchemy
+ :alt: PyPI - Downloads
+
+
+The Python SQL Toolkit and Object Relational Mapper
+
+Introduction
+-------------
+
+SQLAlchemy is the Python SQL toolkit and Object Relational Mapper
+that gives application developers the full power and
+flexibility of SQL. SQLAlchemy provides a full suite
+of well known enterprise-level persistence patterns,
+designed for efficient and high-performing database
+access, adapted into a simple and Pythonic domain
+language.
+
+Major SQLAlchemy features include:
+
+* An industrial strength ORM, built
+ from the core on the identity map, unit of work,
+ and data mapper patterns. These patterns
+ allow transparent persistence of objects
+ using a declarative configuration system.
+ Domain models
+ can be constructed and manipulated naturally,
+ and changes are synchronized with the
+ current transaction automatically.
+* A relationally-oriented query system, exposing
+ the full range of SQL's capabilities
+ explicitly, including joins, subqueries,
+ correlation, and most everything else,
+ in terms of the object model.
+ Writing queries with the ORM uses the same
+ techniques of relational composition you use
+ when writing SQL. While you can drop into
+ literal SQL at any time, it's virtually never
+ needed.
+* A comprehensive and flexible system
+ of eager loading for related collections and objects.
+ Collections are cached within a session,
+ and can be loaded on individual access, all
+ at once using joins, or by query per collection
+ across the full result set.
+* A Core SQL construction system and DBAPI
+ interaction layer. The SQLAlchemy Core is
+ separate from the ORM and is a full database
+ abstraction layer in its own right, and includes
+ an extensible Python-based SQL expression
+ language, schema metadata, connection pooling,
+ type coercion, and custom types.
+* All primary and foreign key constraints are
+ assumed to be composite and natural. Surrogate
+ integer primary keys are of course still the
+ norm, but SQLAlchemy never assumes or hardcodes
+ to this model.
+* Database introspection and generation. Database
+ schemas can be "reflected" in one step into
+ Python structures representing database metadata;
+ those same structures can then generate
+ CREATE statements right back out - all within
+ the Core, independent of the ORM.
+
+SQLAlchemy's philosophy:
+
+* SQL databases behave less and less like object
+ collections the more size and performance start to
+ matter; object collections behave less and less like
+ tables and rows the more abstraction starts to matter.
+ SQLAlchemy aims to accommodate both of these
+ principles.
+* An ORM doesn't need to hide the "R". A relational
+ database provides rich, set-based functionality
+ that should be fully exposed. SQLAlchemy's
+ ORM provides an open-ended set of patterns
+ that allow a developer to construct a custom
+ mediation layer between a domain model and
+ a relational schema, turning the so-called
+ "object relational impedance" issue into
+ a distant memory.
+* The developer, in all cases, makes all decisions
+ regarding the design, structure, and naming conventions
+ of both the object model as well as the relational
+ schema. SQLAlchemy only provides the means
+ to automate the execution of these decisions.
+* With SQLAlchemy, there's no such thing as
+ "the ORM generated a bad query" - you
+ retain full control over the structure of
+ queries, including how joins are organized,
+ how subqueries and correlation is used, what
+ columns are requested. Everything SQLAlchemy
+ does is ultimately the result of a developer-initiated
+ decision.
+* Don't use an ORM if the problem doesn't need one.
+ SQLAlchemy consists of a Core and separate ORM
+ component. The Core offers a full SQL expression
+ language that allows Pythonic construction
+ of SQL constructs that render directly to SQL
+ strings for a target database, returning
+ result sets that are essentially enhanced DBAPI
+ cursors.
+* Transactions should be the norm. With SQLAlchemy's
+ ORM, nothing goes to permanent storage until
+ commit() is called. SQLAlchemy encourages applications
+ to create a consistent means of delineating
+ the start and end of a series of operations.
+* Never render a literal value in a SQL statement.
+ Bound parameters are used to the greatest degree
+ possible, allowing query optimizers to cache
+ query plans effectively and making SQL injection
+ attacks a non-issue.
+
+Documentation
+-------------
+
+Latest documentation is at:
+
+https://www.sqlalchemy.org/docs/
+
+Installation / Requirements
+---------------------------
+
+Full documentation for installation is at
+`Installation `_.
+
+Getting Help / Development / Bug reporting
+------------------------------------------
+
+Please refer to the `SQLAlchemy Community Guide `_.
+
+Code of Conduct
+---------------
+
+Above all, SQLAlchemy places great emphasis on polite, thoughtful, and
+constructive communication between users and developers.
+Please see our current Code of Conduct at
+`Code of Conduct `_.
+
+License
+-------
+
+SQLAlchemy is distributed under the `MIT license
+`_.
+
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/RECORD b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/RECORD
new file mode 100644
index 0000000..7054348
--- /dev/null
+++ b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/RECORD
@@ -0,0 +1,530 @@
+SQLAlchemy-2.0.27.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+SQLAlchemy-2.0.27.dist-info/LICENSE,sha256=eYQKk6tEYK_iQW6ePf95YIdsg66dK-JwXoOhBNSXQOs,1119
+SQLAlchemy-2.0.27.dist-info/METADATA,sha256=ySjjHZ2y0chER0KC_8dWxR1tJNJ6ZFKSsKn5NpUGQ4c,9844
+SQLAlchemy-2.0.27.dist-info/RECORD,,
+SQLAlchemy-2.0.27.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+SQLAlchemy-2.0.27.dist-info/WHEEL,sha256=j9Aissza3750LQHFAQyYerNjmkEON1-8w_RaZNFtKSs,102
+SQLAlchemy-2.0.27.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11
+sqlalchemy/__init__.py,sha256=hU_2Jtxg7KtVE1AdYJaSNwubWJozVVoKzMBzda_lewc,13327
+sqlalchemy/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/__pycache__/events.cpython-312.pyc,,
+sqlalchemy/__pycache__/exc.cpython-312.pyc,,
+sqlalchemy/__pycache__/inspection.cpython-312.pyc,,
+sqlalchemy/__pycache__/log.cpython-312.pyc,,
+sqlalchemy/__pycache__/schema.cpython-312.pyc,,
+sqlalchemy/__pycache__/types.cpython-312.pyc,,
+sqlalchemy/connectors/__init__.py,sha256=A2AI8p63aT0jT5CsVX33xlTfiGWliOcGahlK0RyTLXg,494
+sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/connectors/__pycache__/aioodbc.cpython-312.pyc,,
+sqlalchemy/connectors/__pycache__/asyncio.cpython-312.pyc,,
+sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc,,
+sqlalchemy/connectors/aioodbc.py,sha256=fg3xfG-5gLsy-DSyVonNNKYhOf0_lzHmixRFa5edtWI,5462
+sqlalchemy/connectors/asyncio.py,sha256=DOy84rX4l0U5Nfn9dYLY8ETFE2tRiuSAZTJVPZpEl0E,6163
+sqlalchemy/connectors/pyodbc.py,sha256=IG5lLCyFbnv1wB85HQuMO3S5piWHaB660OBWvBIQhbg,8750
+sqlalchemy/cyextension/__init__.py,sha256=Hlfk91RinbOuNF_fybR5R2UtiIcTeUOXS66QOfSSCV0,250
+sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/cyextension/collections.cp312-win_amd64.pyd,sha256=hkBZZsdk0yKzTf_5zBJYNgfsJUBALcAWgIy_RsKxqCo,176128
+sqlalchemy/cyextension/collections.pyx,sha256=GXPkr9cHRLW3Vcu-ik3dVBZMR-zf0Q5_K4J-_8yV-gk,12980
+sqlalchemy/cyextension/immutabledict.cp312-win_amd64.pyd,sha256=79l5BL-uRU0KJ7HlWNraU9b4hU0mQVbvqiE0DHBiTLc,72704
+sqlalchemy/cyextension/immutabledict.pxd,sha256=5iGndSbJCgCkNmRbJ_z14RANs2dSSnAzyiRPUTBk58Y,299
+sqlalchemy/cyextension/immutabledict.pyx,sha256=IhB2pR49CrORXQ3LXMFpuCIRc6I08QNvIylE1cPQA5o,3668
+sqlalchemy/cyextension/processors.cp312-win_amd64.pyd,sha256=k6wGS1oqX2VHNKEu7K8xoVJisIVk9ry8AfslLL5cG2g,58880
+sqlalchemy/cyextension/processors.pyx,sha256=V9gzqXiNHWsa5DBgYl-3KzclFHY8kXGF_TD1xHFE7eM,1860
+sqlalchemy/cyextension/resultproxy.cp312-win_amd64.pyd,sha256=PAOBSs08EkGLbNa1_Kf9JQMiW-O6BdmN_969wdVmQFk,61952
+sqlalchemy/cyextension/resultproxy.pyx,sha256=h_RrKasbLtKK3LqUh6UiWtkumBlKtcN5eeB_1bZROMA,2827
+sqlalchemy/cyextension/util.cp312-win_amd64.pyd,sha256=cx_06HhGPhuQtw0b-mWWPy78Xk6Q3Zz2akRspDIrlYU,73216
+sqlalchemy/cyextension/util.pyx,sha256=50QYpSAKgLSUfhFEQgSN2e1qHWCMh_b6ZNlErDUS7ec,2621
+sqlalchemy/dialects/__init__.py,sha256=SJfQyxMhOL58EB-S6GQv_0jf2oP7MMfmVdlV2UxGWQo,1831
+sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc,,
+sqlalchemy/dialects/_typing.py,sha256=mN2r8mU8z-mRh4YS3VeK8Nv_IKJmE0Mb1CrJ-ptILas,913
+sqlalchemy/dialects/mssql/__init__.py,sha256=r3oTfX2LLbJAGhM57wdPLWxaZBzunkcmyaTbW0FjLuY,1968
+sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/json.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/provision.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-312.pyc,,
+sqlalchemy/dialects/mssql/aioodbc.py,sha256=b9bhUKcVj4NzoqJIDfECeE_Rmt51sRy8OOUFz_R3vpg,2086
+sqlalchemy/dialects/mssql/base.py,sha256=ERGOPdJHLCF4n9L9-nBeiVQ3X-nl8ryt1FEFLxUwj1o,137336
+sqlalchemy/dialects/mssql/information_schema.py,sha256=A1UJAoFb3UtE8YCY3heBgeTMkzWq3j7C2caZ3gcMGZk,8338
+sqlalchemy/dialects/mssql/json.py,sha256=nZVVsgmR4Z4dNn9cv5Gucq596gsQ0MvASPuEEtz-Gek,4949
+sqlalchemy/dialects/mssql/provision.py,sha256=pa-b74Xr2qsto3BFG1O0I_B25TUT3TOecg6cAKuRcf8,5517
+sqlalchemy/dialects/mssql/pymssql.py,sha256=f7xqRif9Dp64de9G8yuC4OyWArwXy_oVq5X0oiwIX4E,4163
+sqlalchemy/dialects/mssql/pyodbc.py,sha256=YVI19AnrqxPCBwDqcjrO_rqUUWbV2re7E8iLuV1ilqE,27801
+sqlalchemy/dialects/mysql/__init__.py,sha256=PPQDwNqcpxWMt3nFQ66KefX9T9iz7d8lybEwKlfXB1U,2254
+sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/provision.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc,,
+sqlalchemy/dialects/mysql/aiomysql.py,sha256=-YFqFQEx0M2pww3xvsOlaVKflTeoUfIsM8BgfhP1MP0,10296
+sqlalchemy/dialects/mysql/asyncmy.py,sha256=h9BBhGsqPG2LWdoh0lPieRLhaQ_BJrhxwsR_G5yPLDQ,10370
+sqlalchemy/dialects/mysql/base.py,sha256=LQ-nvj7HIQSntwAycof93th3kpMBatHl402HNQ8z9hc,124297
+sqlalchemy/dialects/mysql/cymysql.py,sha256=0mRP3gFe2t7iJYQqJz1Os_TztFwMAF34w2MmXe-4B_w,2384
+sqlalchemy/dialects/mysql/dml.py,sha256=n31-m4vfOIL0MdHpUdIfTLgaMzusfQ-yHYoJWO_ndEc,7864
+sqlalchemy/dialects/mysql/enumerated.py,sha256=Nz9Sv3ENX-1T18aEoOY8QfZlAcwRf65lIOse7vwjil8,8692
+sqlalchemy/dialects/mysql/expression.py,sha256=uxD1fICubfGh8BhAn6WoeS8AF6hAVEvreDShXqRZTqM,4238
+sqlalchemy/dialects/mysql/json.py,sha256=i0Lrd_7VKTd3fNm6kQKzrtPERuW0JeSw7XSUWnl1HQI,2350
+sqlalchemy/dialects/mysql/mariadb.py,sha256=WoNxkjiPfIbWAkrVEU9MTM7mePeLHZ2uiJsyfvcpv1s,885
+sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=_XZ60dSn8-iQP-qyn1Utk-lswGciuoDgpW7m7GguSVA,9016
+sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=1ga6IV7lVLH9BKsMh2M2wSz78l5a82BZnyRqJMaS5Qw,5854
+sqlalchemy/dialects/mysql/mysqldb.py,sha256=KdSkQXgCTHfOWzaM9dlQnmb77FR9X8Io6PVWTYRb1XQ,9966
+sqlalchemy/dialects/mysql/provision.py,sha256=2ecdVRnZSXy5GF3hpLtQp3T8QW-oFjtTSQgbEePDH1k,3581
+sqlalchemy/dialects/mysql/pymysql.py,sha256=Kxi_A34-nbQ5UEFSmy14TXc1v43-1SZ8gE628REGTFo,4220
+sqlalchemy/dialects/mysql/pyodbc.py,sha256=CZCEnhyLIgbuiAW32Cw7N1m1aiQv1eBB34pV-txOs70,4435
+sqlalchemy/dialects/mysql/reflection.py,sha256=wn8qKHxDb9Dnr8zC_uEgAVjk2lVuObvqPOEiad8568c,23499
+sqlalchemy/dialects/mysql/reserved_words.py,sha256=s59cfdGTmlXn3GFCnevugDsc3qiiZn8tL31lief0tvo,9721
+sqlalchemy/dialects/mysql/types.py,sha256=2du4p4PnuJgLHvdofFYP5dtkicIDJYeEdrFBhe7uotQ,25040
+sqlalchemy/dialects/oracle/__init__.py,sha256=_yFT_k0R6yc7MKQG-Al9QZt8wYZsiCtpkhNlba5xqn8,1560
+sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/__pycache__/provision.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/__pycache__/types.cpython-312.pyc,,
+sqlalchemy/dialects/oracle/base.py,sha256=haztCHFbuhnhpcBUr8h1PDLXAavdwtpIjgOg_PSDI_A,121486
+sqlalchemy/dialects/oracle/cx_oracle.py,sha256=3Tx3DKvqcCKyXupBuCiCL4B8D5TDO934Q7LYsLJjlkk,57058
+sqlalchemy/dialects/oracle/dictionary.py,sha256=tmAZLEACqBAPBE0SEV2jr1R4aPcpNOrbomJl-UmgiR4,20026
+sqlalchemy/dialects/oracle/oracledb.py,sha256=kuw08rp-tXKPOtGGutqcs5o2gvRptQXAzNBqPVZoLxg,9798
+sqlalchemy/dialects/oracle/provision.py,sha256=KKlXDQnC8n6BjLJWA7AJg3lwXluH1OyStqfP2Uf9rq0,8524
+sqlalchemy/dialects/oracle/types.py,sha256=U9EReFRcr0PiwOxT9vg2cA7WOix8LQ2sVp0gRkMHcPo,8518
+sqlalchemy/dialects/postgresql/__init__.py,sha256=nfP-oId_1wzvRKo5TM6f2xrXry-IxoMkVd4Dga7LDg8,4062
+sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc,,
+sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=fYFqLVxNxAqh3nOvzGOv3Pfpm2BsclHrk71MJZrpJKo,5883
+sqlalchemy/dialects/postgresql/array.py,sha256=E69zMO2h7NQFjnWEeFX4fGYDymTbqHAI-laLfy7WrEA,14137
+sqlalchemy/dialects/postgresql/asyncpg.py,sha256=CpFNMxOqJUvviWxbEyMiJKnBXTMcvN0IFwaka2gH4R0,41445
+sqlalchemy/dialects/postgresql/base.py,sha256=x3xx5650ZKYAq9ShqDGQNCkj5nt3HNQXv0-Xq0ZT1Ro,181406
+sqlalchemy/dialects/postgresql/dml.py,sha256=uMiqxEkji-UXqk8gO1ramQEvEfCugYmy8Cv1cnG7DQs,11522
+sqlalchemy/dialects/postgresql/ext.py,sha256=ct6NQfMAfBnLYhybpF2wPEq-p8-U0tEpy-aq8NwqJLw,16758
+sqlalchemy/dialects/postgresql/hstore.py,sha256=4jAZQMPWl3VE4weDRZrgrbVDRZJTM3X0Xj4twr5znYQ,11938
+sqlalchemy/dialects/postgresql/json.py,sha256=TCw4qYTeLhxjDrF1sq3k-yk7UFEhryweKEj8AelBfRc,11537
+sqlalchemy/dialects/postgresql/named_types.py,sha256=aaH5fNMZp8ZdmLI1ag9g0UgDGVvX0dpE0PbTu3XVUYc,17595
+sqlalchemy/dialects/postgresql/operators.py,sha256=iyZuyx_daRyJjiS5rw-XnZlaWj1bmRiHdy5MXzBrFZw,2937
+sqlalchemy/dialects/postgresql/pg8000.py,sha256=TPJXX078vW0FSwZ-DlWNkEOXg7Z4xk8IFwi1droMhPw,19302
+sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=rNWjbOOC3SB2jmFAwz5VkbdZub7BCTp60YUmJt3eeRI,9178
+sqlalchemy/dialects/postgresql/provision.py,sha256=Uo_6vxVzFryFjLqsrvesRO55VqHrnsAs_pBH_8JtFcA,5937
+sqlalchemy/dialects/postgresql/psycopg.py,sha256=zRoaG1ggAKnMBo6fxe6xJa52BxyU2OrOGX-_jDrH2Zk,23113
+sqlalchemy/dialects/postgresql/psycopg2.py,sha256=xau5nXatjvPNbzqTF8GF_wrwU4Q5T2zkIMGZkhHvrI0,32483
+sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=hFg-9GH08ApPy3foVPUdJKwCEzNSv2zD5l4nH97AqgI,1817
+sqlalchemy/dialects/postgresql/ranges.py,sha256=oiTmnZ-hd5WqqGNsXbuOJfoNxpbso_M_49gky8dlCrE,33978
+sqlalchemy/dialects/postgresql/types.py,sha256=pd1QmuGwJFLqpY2tK-Ql3FNjtT1Ha-lVvfaR9dimvHc,7603
+sqlalchemy/dialects/sqlite/__init__.py,sha256=MmQfjHun1U_4q-Dq_yhs9RzAX0VLixSwWeY5xWiDwag,1239
+sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/json.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-312.pyc,,
+sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=TgobCILLu2mjGvDgMTxX3-CPxkj_c5LDYRDJHo5W5qg,12701
+sqlalchemy/dialects/sqlite/base.py,sha256=8Ft5tZeT1lHiWliTwoJaNslf8PRITVKKWhjhhRCeVDk,99576
+sqlalchemy/dialects/sqlite/dml.py,sha256=8JV6Ise7WtmFniy590X5b19AYZcE51M6N5hef7d9JoA,8683
+sqlalchemy/dialects/sqlite/json.py,sha256=-9afZnBt07vInCX20CKzjlTG85wHTO5_cxhcYU4phDc,2869
+sqlalchemy/dialects/sqlite/provision.py,sha256=nAXZPEjXFrb6a1LxXZMqKmkQoXgl3MPsSHuMyBQ76NU,5830
+sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=p0KfzHBwANDMwKTKEJCjR5RxMYqQwS4E8KXjl3Bx6Fw,5511
+sqlalchemy/dialects/sqlite/pysqlite.py,sha256=s1eL04d6fqPeUNSTnpxXSjWR8OdCuYcy9ilk7716wzU,28653
+sqlalchemy/dialects/type_migration_guidelines.txt,sha256=gyh3JCauAIFi_9XEfqm3vYv_jb2Eqcz2HjpmC9ZEPMM,8384
+sqlalchemy/engine/__init__.py,sha256=93FWhb62dLCidc6e4FE65wq_P8GeoWQG1OG6RZMBqhM,2880
+sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/_py_row.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/_py_util.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/create.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/default.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/events.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/mock.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/processors.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/result.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/row.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/url.cpython-312.pyc,,
+sqlalchemy/engine/__pycache__/util.cpython-312.pyc,,
+sqlalchemy/engine/_py_processors.py,sha256=-jlAYPM6etmuKeViiI7BD41kqY0Pr8nzaox22TPqCCQ,3880
+sqlalchemy/engine/_py_row.py,sha256=UEGCjAeRsggcUn0QB0PdFC82kuykrOiOZ1KGq_Gf_qQ,3915
+sqlalchemy/engine/_py_util.py,sha256=nh1XoVq1b-eGgkdzbqFqzje0RNSmVWotoa6yaB7J5Sw,2558
+sqlalchemy/engine/base.py,sha256=0VnqL5IK8rTjs7wanSKgOjuzbKdNjs_8TvzJgUqfNCw,125393
+sqlalchemy/engine/characteristics.py,sha256=q_l1-KybkM5Y-5hG74NuQBxHRkidBstqpRBlFLxJPqs,2671
+sqlalchemy/engine/create.py,sha256=IC_D9X-t-MN78Ro3shJl4RL0fP9eTvskl4j1bAjagco,33736
+sqlalchemy/engine/cursor.py,sha256=nCEzaLslgar67EDE4mqowuJsuiBaCbs5ix7sgD9iWTI,76593
+sqlalchemy/engine/default.py,sha256=oB9T6i1k7rMxat1HkDGCf-_i_3eAOymV1yPU4k0tBeU,87011
+sqlalchemy/engine/events.py,sha256=e0VHj69fH20sB7gocBhr5Rs2FjR8ioY4iE8VQt70oJg,38332
+sqlalchemy/engine/interfaces.py,sha256=G29dfsTCNayoIiRnCQ64y9y9w9wtrGwxnm5t102ApvA,116079
+sqlalchemy/engine/mock.py,sha256=wInBRiHwydTc5ELQLivdezDd1ikbSMVXgLVzZrSC0iQ,4310
+sqlalchemy/engine/processors.py,sha256=w4MiVMlU6VvfhIW49nygbHcwX8FteGpz7g3IGEqtZb8,2440
+sqlalchemy/engine/reflection.py,sha256=TO-tymk7BsfAzc6Fi0GmwtYyOWjfMGkyvytJyMVz_oY,77216
+sqlalchemy/engine/result.py,sha256=U245Q3kGUOugqjmv-qSkx8eyDn27fLYV5agIoBHXQCA,79985
+sqlalchemy/engine/row.py,sha256=g7ZqmsqX_BtRUzY-zfXoZZ4-5xZ_KJEVbvqKHUIlqRg,12433
+sqlalchemy/engine/strategies.py,sha256=fD4DJn0AD371wlUa7s5Sy4j7QtgGyP7gMy_kUyqCLDQ,461
+sqlalchemy/engine/url.py,sha256=tOCRmKkqrpsIfNeSDoy6KKTLtQAMtoIn9xa5kmJQebk,31694
+sqlalchemy/engine/util.py,sha256=GDoT1xzoCL_CaBvWpBigzXIpUsFAFtjHH8v2e_au7qo,5833
+sqlalchemy/event/__init__.py,sha256=09qZzHwt0PkIDsPwuPUVJvNakjtCBjuUJeY0AEJ9j7k,1022
+sqlalchemy/event/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/event/__pycache__/api.cpython-312.pyc,,
+sqlalchemy/event/__pycache__/attr.cpython-312.pyc,,
+sqlalchemy/event/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/event/__pycache__/legacy.cpython-312.pyc,,
+sqlalchemy/event/__pycache__/registry.cpython-312.pyc,,
+sqlalchemy/event/api.py,sha256=74Z-EXtNsv8fvsf8m8bgCkVzJf7QzhMaVXJhCylegLo,8452
+sqlalchemy/event/attr.py,sha256=-SHjzXMOs7IICPSgNwpgRS3FIEeLIpB5PyvVlpw8Gp8,21406
+sqlalchemy/event/base.py,sha256=haAsH-KuvvY52A6OjwATfvCXy3hdYGgZxkDZiOqbMvI,15416
+sqlalchemy/event/legacy.py,sha256=a8VEvS83PvgbomNnaSa3okZmTkxl_buZ7Lfilechjh8,8473
+sqlalchemy/event/registry.py,sha256=f31k0FLqIlWpOK9tksiYXnv-yuZPPz9iLQqvKEYV7ko,11221
+sqlalchemy/events.py,sha256=OAy8TK21lWzSe8bDUnAbmsP82bsBYy0LL19hR6y3BrM,542
+sqlalchemy/exc.py,sha256=k01TD2xp2BM3DrXdo2U5r8yuRfsoqBND4kwvtD1SVN0,24806
+sqlalchemy/ext/__init__.py,sha256=YbMQmRS_9HxRyWM-KA_F76WOss1_Em1ZcrnQDIDXoOc,333
+sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/associationproxy.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/automap.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/baked.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/horizontal_shard.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/hybrid.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/indexable.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/instrumentation.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/mutable.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/orderinglist.cpython-312.pyc,,
+sqlalchemy/ext/__pycache__/serializer.cpython-312.pyc,,
+sqlalchemy/ext/associationproxy.py,sha256=MBtGwISA4wwT9i6op8jfY6u9lCUYn_4JCqtxZpjggok,67776
+sqlalchemy/ext/asyncio/__init__.py,sha256=tKYIrERYf8hov9m8DuKWRO_53qhrvj2jRmIYjSGQ2Po,1342
+sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/__pycache__/engine.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/__pycache__/exc.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/__pycache__/result.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/__pycache__/session.cpython-312.pyc,,
+sqlalchemy/ext/asyncio/base.py,sha256=slWQTFdgQQlkzrnx3m5a9xT8IRg4iM0gkEbypXr_YXQ,9184
+sqlalchemy/ext/asyncio/engine.py,sha256=VKp3nRnmzl7cEIWSxJNJQMK8CbQbigmrvyqpIiVUdgY,49398
+sqlalchemy/ext/asyncio/exc.py,sha256=0awLfUB4PhEPVVTKYluyor1tW91GPZZnvdQ-GGSOmJY,660
+sqlalchemy/ext/asyncio/result.py,sha256=MtKAqA7hwYIdkpRxlCgHNYYzlB7dvqCtEp-aoDdFjDA,31370
+sqlalchemy/ext/asyncio/scoping.py,sha256=CiMQ7ewPNsyEtl9aGOiEZOrUaNYOTP_LrR0_xkdV3r8,54211
+sqlalchemy/ext/asyncio/session.py,sha256=gVdecaNFy8fo4YXhfhgwmMpezGaNKsIKL5kSI_j4GoI,64821
+sqlalchemy/ext/automap.py,sha256=8mu3_-s4DUnWfmOAZsWFFaADZALSIe1J3NQS2BvUGi8,63041
+sqlalchemy/ext/baked.py,sha256=jc6vPocoXXsvdZsOsqgT4kG6guWSZD1TdPjoRBmkbRU,18381
+sqlalchemy/ext/compiler.py,sha256=PbvelWqZdzL6y1C6rEc8ledF79t_04MtYV26RUwNhik,20946
+sqlalchemy/ext/declarative/__init__.py,sha256=MHSOffOS4MWcqshAuLNQv0vDXpK_Z3lpGXTm1riyLls,1883
+sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc,,
+sqlalchemy/ext/declarative/extensions.py,sha256=aPpW0PvTKH3CoSMhsOY5GcUMZOVq-OFsV1hflxmb3Lw,20095
+sqlalchemy/ext/horizontal_shard.py,sha256=V8vXEt5ZQb_PM39agZD2IyoQNGSqVI1MhY-6mNV5MRY,17231
+sqlalchemy/ext/hybrid.py,sha256=Fc73iUTCJuHcz3McvD3FBbjEvDag1Jw8THY6rL-phA8,53946
+sqlalchemy/ext/indexable.py,sha256=aDlVpN4rilRrer9qKg3kO7fqnqB5NX4M5qzYuYM8pvw,11373
+sqlalchemy/ext/instrumentation.py,sha256=lFsJECWlN1oc1E0r9TaQDZcxAx4VOz6PSHYrl5fLk9Y,16157
+sqlalchemy/ext/mutable.py,sha256=nAz3_lF2xkYSARt7GAWQh-OUMcnpe6s1ocjvQGxCPkc,38428
+sqlalchemy/ext/mypy/__init__.py,sha256=aqT8_9sNwzC8PIaEZ4zkCYGBvYPaDD3eCgJtJuk3g6A,247
+sqlalchemy/ext/mypy/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/ext/mypy/__pycache__/apply.cpython-312.pyc,,
+sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-312.pyc,,
+sqlalchemy/ext/mypy/__pycache__/infer.cpython-312.pyc,,
+sqlalchemy/ext/mypy/__pycache__/names.cpython-312.pyc,,
+sqlalchemy/ext/mypy/__pycache__/plugin.cpython-312.pyc,,
+sqlalchemy/ext/mypy/__pycache__/util.cpython-312.pyc,,
+sqlalchemy/ext/mypy/apply.py,sha256=1Qb-_FpQ_0LVB2KFA5hVjfPv6DDMIcxXe86Ts1X9GBk,10870
+sqlalchemy/ext/mypy/decl_class.py,sha256=f2iWiFVlDFqGb_IoGGotI3IEOUErh25sLT7B_cMfx0g,17899
+sqlalchemy/ext/mypy/infer.py,sha256=O-3IjELDSBEAwGGxRM7lr0NWwGD0HMK4vda_iY6iwjs,19959
+sqlalchemy/ext/mypy/names.py,sha256=TWsrp5ftD5p0NeyAipgYIir9SUbA4U0BAk0W2FXA3VA,10972
+sqlalchemy/ext/mypy/plugin.py,sha256=TDTziLsYFRqyX8UcQMtBBa6TFR4z9N-XNO8wRkHlEOI,10053
+sqlalchemy/ext/mypy/util.py,sha256=3iQ1zVpXSUoj2aHa-Kkg4O83JOzqVd8TDEwpZj3SWWs,9786
+sqlalchemy/ext/orderinglist.py,sha256=r7La_3nZlGevIgsBL1IB30FvWO_tZHlTKo_FWwid-aY,14800
+sqlalchemy/ext/serializer.py,sha256=_7gottqRCI-qkW4Go4o2EnOSnieKDCQ8jQ6muHXw-RM,6363
+sqlalchemy/future/__init__.py,sha256=6-qPdjMHX-V-kAPjTQgNuHztmYiwKlJhKhhljuETvoQ,528
+sqlalchemy/future/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/future/__pycache__/engine.cpython-312.pyc,,
+sqlalchemy/future/engine.py,sha256=N_5W2ab5-ueedWzqNdgLPzTW9audT1IbxF6FCDLRZOc,510
+sqlalchemy/inspection.py,sha256=GpmMuSAZ53u4W__iGpvzQKCBMFnTxnHt4Lo7Nq1FSKM,5237
+sqlalchemy/log.py,sha256=Sg6PGR_wmseiCCpJfRDEkaMs08XTPPsf0X_iYJLvzS0,8895
+sqlalchemy/orm/__init__.py,sha256=I-XesvuyjkAAwnsiF5FnXRLNV6W2nW70EnGAIt2GAjU,8633
+sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/collections.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/context.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/events.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/exc.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/identity.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/loading.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/properties.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/query.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/session.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/state.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/sync.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/util.cpython-312.pyc,,
+sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc,,
+sqlalchemy/orm/_orm_constructors.py,sha256=qOQLU_Rly-AYjGDotwZv65PCjjLgdAwqHGPUeZrTYfE,101822
+sqlalchemy/orm/_typing.py,sha256=Z9GZT8Vb-wFwvHeOeVE37dvmCWdItLZnqI_pLin4cMc,5152
+sqlalchemy/orm/attributes.py,sha256=devlqbjcICNLnG0HEg4wSP2sgm2m8sW9RpoQZuaU0KM,95355
+sqlalchemy/orm/base.py,sha256=JWzk2w1k-xT5BW8T7K0DRhMg7lDKFf4T6KrFey_6c4A,28394
+sqlalchemy/orm/bulk_persistence.py,sha256=z7yTdor_Nea7R0UMu8kloKvN-t0z2AZ-P-q5FE-oabc,72070
+sqlalchemy/orm/clsregistry.py,sha256=ZumBI7I2O-l93LbA4eyMKm0w6al-nNS2QV1VDcJxGko,18528
+sqlalchemy/orm/collections.py,sha256=lHjP6uDz0WdwedTqyh_8R2_nzRAK_5ONCIoisHrsb94,53797
+sqlalchemy/orm/context.py,sha256=bBieTIPsM10lt5z5Feq4tDjBqpznbsssaiXh7OrTh18,115244
+sqlalchemy/orm/decl_api.py,sha256=Xd9s8A7V_jlypP-u-tnpQ5o6Oq-v1A4H1f3hSEj4-Bc,65549
+sqlalchemy/orm/decl_base.py,sha256=g6bhTg5M9hsf3JVnMaqn1NS7MTdBJY--L-qf9aQabgs,83753
+sqlalchemy/orm/dependency.py,sha256=glstmbB4t-PIRA47u9NgTyyxbENfyQuG9Uzj2iezB_s,48935
+sqlalchemy/orm/descriptor_props.py,sha256=PpDt83EX72AhCbBjixQDpYG1P1MqWqGdA-bJpXMSxSw,38254
+sqlalchemy/orm/dynamic.py,sha256=m7V2GPS5__4y_hP7BQjD66b6BVEre1pzPwaj2bmRFRM,10084
+sqlalchemy/orm/evaluator.py,sha256=gQIDxuoB5Ut2hiFdN768U67JusVkbFt-Bdna_T8npPA,12293
+sqlalchemy/orm/events.py,sha256=EeIAGfdSQX0XQLZyURz94KYOlsjwI-ANWjPfyN_-jcQ,130956
+sqlalchemy/orm/exc.py,sha256=90xWOIIAmzPguaVH6mxr2xUSGW44aGIPz5WytJSwmR4,7583
+sqlalchemy/orm/identity.py,sha256=fOpANTf73r12F_w9DhVoyjkAdh8ldgJcNnwxx0GY8YM,9551
+sqlalchemy/orm/instrumentation.py,sha256=a8vi3qEAyO7Z9PYksLkFi_YzxqQhzB-anblegiAtsFw,25075
+sqlalchemy/orm/interfaces.py,sha256=IKCWZFHamXIkpBCaEh7YIYy-Jz2hV4-SQt6kGbfOuE8,49842
+sqlalchemy/orm/loading.py,sha256=sp7VaIoc9gzrtAOekU_2EWYN3L_9lKuiKkfn6f4VlQQ,59202
+sqlalchemy/orm/mapped_collection.py,sha256=AeSzQwj56cLr1tVMC0B-3JsC74IAP7_gbr-EPc4_2uw,20250
+sqlalchemy/orm/mapper.py,sha256=13goncHEJueS73Z7EWovVdp1W-xBKSWB5cjqtpyIk5s,175479
+sqlalchemy/orm/path_registry.py,sha256=KqS4yYe__beUSpdEjL8qxzL_z7V-FJkolm4AMCBHhGg,26658
+sqlalchemy/orm/persistence.py,sha256=MKb7TuSLJUQpyqnHxf6uNmGXSznmZgkkFTD04nHbNUQ,63483
+sqlalchemy/orm/properties.py,sha256=xtSDAAeUgDAmZF_OTBd4F18q9h3h7JWnPtQnWU0-aU4,30007
+sqlalchemy/orm/query.py,sha256=d2aENAsXkiZuXOxZ5WNKMqsrNHT7_d-_BgXqNa2BtsA,120948
+sqlalchemy/orm/relationships.py,sha256=jtIA6Y7jhlSzee-MGu_0YMmtH8Kr4lwanjCBfB_eV_I,131177
+sqlalchemy/orm/scoping.py,sha256=aAQMIAAZ-M_m6UGndmkUiDazcphE-klw6wZjFT2Az7E,80842
+sqlalchemy/orm/session.py,sha256=QRC0WQSjNFIhyInWnZD-picMoKWS79oIlQAVAcRon_4,198419
+sqlalchemy/orm/state.py,sha256=9opH8AR6LnbCRmW1lN2RxEQyxnEi1rcDXlySqrDeUiw,38656
+sqlalchemy/orm/state_changes.py,sha256=4i90vDgBGvVGUzhlonlBkZBAZFOWaAXij2X8OEA3-BA,7013
+sqlalchemy/orm/strategies.py,sha256=GFppPikNxZJdsi4DW1HcU6pv-EvIeFvTI8K_LDY5nmk,117550
+sqlalchemy/orm/strategy_options.py,sha256=cBzotMlredMZ8vM6T04mTnskiS0qSoRkHEQKSLZNARA,86734
+sqlalchemy/orm/sync.py,sha256=aMEMhYTj2rtJZJvjqm-cUx2CoQxYl8P6YddCLpLelhM,5943
+sqlalchemy/orm/unitofwork.py,sha256=THggzzAaqmYh5PBDob5dHTP_YyHXYdscs3fIxtRV-gE,27829
+sqlalchemy/orm/util.py,sha256=xIaltctFxy-u4xdLk1mqdHYsq_bvg4xsaTRQTev7I80,82756
+sqlalchemy/orm/writeonly.py,sha256=j5DcpZKOv1tLGQLhKfk-Uw-B0yEG7LezwJWNTq0FtWQ,22983
+sqlalchemy/pool/__init__.py,sha256=ZKUPMKdBU57mhu677UsvRs5Aq9s9BwIbMmSNRoTRPoY,1848
+sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/pool/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/pool/__pycache__/events.cpython-312.pyc,,
+sqlalchemy/pool/__pycache__/impl.cpython-312.pyc,,
+sqlalchemy/pool/base.py,sha256=D0sKTRla6wpIFbELyGY2JEHUHR324rveIl93qjjmYr8,53751
+sqlalchemy/pool/events.py,sha256=ysyFh0mNDpL4N4rQ-o_BC6tpo_zt0_au_QLBgJqaKY8,13517
+sqlalchemy/pool/impl.py,sha256=8VcM4JSUnu4FcSrC5TUzTWT0FYFxfNouKyuXCKnD6KM,18264
+sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+sqlalchemy/schema.py,sha256=UFhZjGmYoqN3zkId7M4CbVCd8KaeZUfKUjdlk0sHQ_E,3264
+sqlalchemy/sql/__init__.py,sha256=T16ZB3Za0Tq1LQGXeJuuxDkyu2t-XHR2t-8QH1mE1Uw,5965
+sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/_py_util.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/crud.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/dml.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/elements.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/events.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/expression.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/functions.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/naming.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/operators.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/roles.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/schema.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/util.cpython-312.pyc,,
+sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc,,
+sqlalchemy/sql/_dml_constructors.py,sha256=1xMH5Kd6SLhlFwfIs_lOXGC8GTrqW8mQM7Kc3cKyLuw,4007
+sqlalchemy/sql/_elements_constructors.py,sha256=DN5B84YTp9K5cfp1qjEpg_2d5WpHujlNBJ-pG7bsKoI,64386
+sqlalchemy/sql/_orm_types.py,sha256=_bzlAh3-vTIZoLvAM2ry1SF7rsYRM3-jupfhGWZZn5Y,645
+sqlalchemy/sql/_py_util.py,sha256=VzThcXk7fKqT9_mZmXrkxePdwyyl_wIciCftzl2Z_-g,2248
+sqlalchemy/sql/_selectable_constructors.py,sha256=mRgtlGyctlb1LMBqFxgn0eGzIXMbyZtQafjUuJWhYjs,19415
+sqlalchemy/sql/_typing.py,sha256=i4COgky9Gv0ArXdOpp9xfxpvKqP3lj0I_vJqinvWEck,12940
+sqlalchemy/sql/annotation.py,sha256=PslN1KQV9hN8Ji4k8I3-W-cDuRMCCLwMmJcg-n86Yy4,18830
+sqlalchemy/sql/base.py,sha256=Twa1DYB1fBGGzU0kIVS0h30-0j67gfduuw9GU6RxGao,75935
+sqlalchemy/sql/cache_key.py,sha256=vUWB-pqAtgt8SBRMHEkzo8BQ_1yXjMeGt-aaDB1ieek,34605
+sqlalchemy/sql/coercions.py,sha256=O6PA7Gzhr9XQedJs3zIngCivN1vcrNyEhFueN5CqriI,41882
+sqlalchemy/sql/compiler.py,sha256=PslsNi8ND2FXVPcFIS3UBMpVthfM47MAu4c_rpBpMHE,278910
+sqlalchemy/sql/crud.py,sha256=I5nPPnujtNKHC5C2v1vW4A0mbyomwChT21IYOX3z5fw,58190
+sqlalchemy/sql/ddl.py,sha256=NbW8F3UT4BTnda5w5TNPGxXPtv0wHSNB51hhr4gBSJM,46980
+sqlalchemy/sql/default_comparator.py,sha256=lXmd8yAUzfyeP5w4vebrQG99oC0bTrmdGc0crBq1GKw,17259
+sqlalchemy/sql/dml.py,sha256=lt5FC6BbJNotE65U-fmvEovBxkADfKBnVcnkVYYQxUM,67431
+sqlalchemy/sql/elements.py,sha256=tIgio7vC-0gftEddMtUu35cXIWwPqqomat0ufAIFrMM,177377
+sqlalchemy/sql/events.py,sha256=pG3jqJbPX18N9Amp47aXiQYMYD_HL_lOXHk-0m8m7Hw,18745
+sqlalchemy/sql/expression.py,sha256=T-AgCPp30tgKQYLKeSyqQg_VoJFE69m2yDTz6fn-u1E,7748
+sqlalchemy/sql/functions.py,sha256=vxYsWwzQpYhfQ_EwfdA-lGlbh2pkQ30AXGjvHEvVBWo,65741
+sqlalchemy/sql/lambdas.py,sha256=i3F6TZEAHSPqTV704LAybfNOMyUJ52x2YE2eCHTlYi4,50738
+sqlalchemy/sql/naming.py,sha256=ERVjqo6fBHBw2BwNgpbb5cvsCkq1jjdztczP9BKzVt8,7070
+sqlalchemy/sql/operators.py,sha256=6rpSbuFon7iIUCT4SDowYctDyOmFpe2-FdLu2HIX3x8,78508
+sqlalchemy/sql/roles.py,sha256=8nO4y1hbP1cA8IzeOn6uPgNZNVILb3E-IMeJWOIScu8,7985
+sqlalchemy/sql/schema.py,sha256=iIurzYqmZNRi_wBN-tXFKIM-jt07DIvVuzV_IVsfsTo,234377
+sqlalchemy/sql/selectable.py,sha256=fWcddtd9UM3QMcS-3Pg6E98mK9uAZGkhmyOLWlNEigI,239761
+sqlalchemy/sql/sqltypes.py,sha256=AJvAe9Nt3Bweic9eC__NVnkVAbIgb_exoajEfTij1R4,130912
+sqlalchemy/sql/traversals.py,sha256=p2iXAQc0FvV-l1Q3NNMxIhRYTm8U3Ul630jG3Ys6qCI,34611
+sqlalchemy/sql/type_api.py,sha256=zRtzrf5sLjDWnSUvl_vAnG6X8fhY8vuln4jG_Jx4zKY,86164
+sqlalchemy/sql/util.py,sha256=ftTiyNGeJK0MIRMqWMV7Xf8iZuiRGocoJRp3MIO3F3Y,49563
+sqlalchemy/sql/visitors.py,sha256=oudlabsf9qleuC78GFe_iflRSAD8H-HjaM7T8Frc538,37482
+sqlalchemy/testing/__init__.py,sha256=8iT66v5k4J9RmquaH4GLI2DjEA7c_JZSTVig-uuBNw8,3221
+sqlalchemy/testing/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/assertions.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/assertsql.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/asyncio.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/config.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/engines.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/entities.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/exclusions.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/pickleable.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/profiling.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/provision.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/requirements.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/schema.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/util.cpython-312.pyc,,
+sqlalchemy/testing/__pycache__/warnings.cpython-312.pyc,,
+sqlalchemy/testing/assertions.py,sha256=bBn2Ep89FF-WBmzh0VkvnJ9gNMKuqk8OXq7ALpUwar4,32428
+sqlalchemy/testing/assertsql.py,sha256=gj4YRBR9cjOtS1WgR3nsyIze1tmqctsNs1uCV8N2Q4w,17333
+sqlalchemy/testing/asyncio.py,sha256=GvWrQFrL3xz7rub61oGOS2PXVvw7D9Id3gtkXQjZJLY,3858
+sqlalchemy/testing/config.py,sha256=jfFVUiAOm8im6SlqyAdZVSaA51kmADgfBDqrHnngH7c,12517
+sqlalchemy/testing/engines.py,sha256=U3FkWECbghiK2_Yv5uKMjco377xoFsi75WZgRZroGWA,13814
+sqlalchemy/testing/entities.py,sha256=Um-DFSz81p06DhTK899ZRUOZRw3FtUDeNMVHcIg3eLc,3471
+sqlalchemy/testing/exclusions.py,sha256=8kjsaFfjCvPlLsQLD_LIDwuqvVlIVbD5qTWBlKdtNkM,12895
+sqlalchemy/testing/fixtures/__init__.py,sha256=B1IFCzEVdCqhEvFrLmgxZ_Fr08jDus5FddSA-lnnAAU,1226
+sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/testing/fixtures/__pycache__/base.cpython-312.pyc,,
+sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-312.pyc,,
+sqlalchemy/testing/fixtures/__pycache__/orm.cpython-312.pyc,,
+sqlalchemy/testing/fixtures/__pycache__/sql.cpython-312.pyc,,
+sqlalchemy/testing/fixtures/base.py,sha256=S0ODuph0jA2Za4GN3NNhYVIqN9jAa3Q9Vd1N4O4rcTc,12622
+sqlalchemy/testing/fixtures/mypy.py,sha256=2H8QxvGvwsb_Z3alRtvCvfXeqGjOb8aemfoYxQiuGMc,12285
+sqlalchemy/testing/fixtures/orm.py,sha256=6JvQpIfmgmSTH3Hie4nhmUFfvH0pseujIFA9Lup2Dzw,6322
+sqlalchemy/testing/fixtures/sql.py,sha256=Joqh4q1vE3wCaE3eDZUnSobeLNUE-pabIy58ZMURFto,16196
+sqlalchemy/testing/pickleable.py,sha256=uYLl557iNep6jSOVl0vK1GwaLHUKidALoPJc-QIrC08,2988
+sqlalchemy/testing/plugin/__init__.py,sha256=bbtVIt7LzVnUCcVxHWRH2owOQD067bQwwhyMf_whqHs,253
+sqlalchemy/testing/plugin/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-312.pyc,,
+sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-312.pyc,,
+sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-312.pyc,,
+sqlalchemy/testing/plugin/bootstrap.py,sha256=USn6pE-JcE5pSmnEd2wad3goKLx2hdJS3AUUFpXHm-I,1736
+sqlalchemy/testing/plugin/plugin_base.py,sha256=CgrNj2wj9KNALu9YfnGSaHX2fXfTtiim_cfx0CPVoy8,22357
+sqlalchemy/testing/plugin/pytestplugin.py,sha256=xFbgBkv92U7_nYSyq87MG6OZSg_NR2HOo7CG7IC1cpY,28416
+sqlalchemy/testing/profiling.py,sha256=o8_V3TpF_WytudMQQLm1UxlfNDrLCWxUvkH-Kd0unKU,10472
+sqlalchemy/testing/provision.py,sha256=ciWoXf3P9ql4hh1yBp0RNEtPr5vyvPbd8RD_DYxNG9U,15115
+sqlalchemy/testing/requirements.py,sha256=L_DKVqVxVMbB3JveC_6UhD5oVry2KjBHPxfQd35hrWQ,53600
+sqlalchemy/testing/schema.py,sha256=z2Z5rm3iJ1-vgifUxwzxEjt1qu7QOyr3TeDnQdCHlWE,6737
+sqlalchemy/testing/suite/__init__.py,sha256=YvTEqUNHaBlgLgWDAWn79mQrUR4VBGUHtprywJlmDT8,741
+sqlalchemy/testing/suite/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_cte.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_insert.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_results.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_select.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_types.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-312.pyc,,
+sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-312.pyc,,
+sqlalchemy/testing/suite/test_cte.py,sha256=C_viXJKClFAm91rtPb42tiAA7gYJwKkqGYVJYap0cLM,6662
+sqlalchemy/testing/suite/test_ddl.py,sha256=k6D6RreLkDSSpRUM2hQz-_CA48qV2PYx_2LNyUSoZzE,12420
+sqlalchemy/testing/suite/test_deprecations.py,sha256=SKRFZDteBO1rw9-BQjDic5nh7fdyw2ypVOewR2pj7-Q,5490
+sqlalchemy/testing/suite/test_dialect.py,sha256=ftOWRXWOotB2_jMJJqwoH9f3X2ucc1HwwOiXp573GwM,23663
+sqlalchemy/testing/suite/test_insert.py,sha256=v3zrUZaGlke3cI4vabHg7xaI4gNqcHhtMPgYuf0mOxc,19454
+sqlalchemy/testing/suite/test_reflection.py,sha256=C6P9ccG5Eog5uiIHO4s6M7ThnBbEUZKh83CmOMn-KSo,109594
+sqlalchemy/testing/suite/test_results.py,sha256=1SlvhdioM1_ZrkQX2IJbJgXNHuleizwAge6-XvHtA0s,16405
+sqlalchemy/testing/suite/test_rowcount.py,sha256=DCEGxorDcrT5JCLd3_SNQeZmxT6sKIcuKxX1r6vK4Mg,8158
+sqlalchemy/testing/suite/test_select.py,sha256=NwHUSVc4UptVYMGjp3QVLr0OpGxpz2qJG4cNWZW8vTo,60462
+sqlalchemy/testing/suite/test_sequence.py,sha256=sIqkfgVqPIgl4lm75EPdag9gK-rTHfUm3pWX-JijPy4,10240
+sqlalchemy/testing/suite/test_types.py,sha256=i1fCIXERdtGABdp_T3l1vaPH9AhQ80DJvbjOPbeng1c,67748
+sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=juF_KTK1nGrSlsL8z0Ky0rFSNkPGheLB3e0Kq3yRqss,6330
+sqlalchemy/testing/suite/test_update_delete.py,sha256=TnJI5U_ZEuu3bni4sH-S6CENxvSZwDgZL-FKSV45bAo,4133
+sqlalchemy/testing/util.py,sha256=jX9jlUHSH-7_2OCypZUvitP8JkJbNdr5_ZxU6Aa8DPY,14599
+sqlalchemy/testing/warnings.py,sha256=3EhbTlPe4gJnoydj-OKueNOOtGwIRF2kV4XvlFwFYOA,1598
+sqlalchemy/types.py,sha256=unCm_O8qKxU3LjLbqeqSNQSsK5k5R5POsyEx2gH6CF4,3244
+sqlalchemy/util/__init__.py,sha256=3-O9j9qPk-gTx6hlyLsISc_JOW5MhjV0J_L5nV19qI8,8436
+sqlalchemy/util/__pycache__/__init__.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/_collections.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/_py_collections.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/compat.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/queue.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/tool_support.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/topological.cpython-312.pyc,,
+sqlalchemy/util/__pycache__/typing.cpython-312.pyc,,
+sqlalchemy/util/_collections.py,sha256=O3iqq0R9TbcXNyFk8nG4QLwkUzdWkCFmqBYcDrajvl8,20778
+sqlalchemy/util/_concurrency_py3k.py,sha256=HQ5tLleQd5cR4BOoXKFWVTK7p4fCDW105QxRLW52_ko,8841
+sqlalchemy/util/_has_cy.py,sha256=IHGc5hUFbXQuv1a1z2P8yVwz0yGbCYXyQM2qsdcBTyg,1287
+sqlalchemy/util/_py_collections.py,sha256=2PUqiKIsF8d-gNDAAqYI8WE6XPyRf1flRLkVsJeXuOo,17255
+sqlalchemy/util/compat.py,sha256=ojCAtKHlkqNdYB33PXbAP0zTH1ZXYdTZkJl32cqGnMQ,9014
+sqlalchemy/util/concurrency.py,sha256=AOLQUBm9Hm4jDArP8vBYL39FzckyH9S4NsKRvWaYzEE,2500
+sqlalchemy/util/deprecations.py,sha256=AnHpDWHi7g2gv_QUTGStQTnr0J94lIF-3aFLOsv9yzg,12372
+sqlalchemy/util/langhelpers.py,sha256=1meF9IffDMmz50uxNdUO15FUL0TARzwFcPjwbpOQRX8,67115
+sqlalchemy/util/preloaded.py,sha256=78Sl7VjzTOPajbovvARxNeuZb-iYRpEvL5k8m5Bz4vQ,6054
+sqlalchemy/util/queue.py,sha256=4SbSbVamUECjCDpMPR035N1ooVHt9W5GjbqkxfZmH5k,10507
+sqlalchemy/util/tool_support.py,sha256=DuurikYgDUIIxk3gubUKl6rs-etXt3eeHaZ4ZkIyJXQ,6336
+sqlalchemy/util/topological.py,sha256=_NdtAghZjhZ4e2fwWHmn25erP5cvtGgOUMplsCa_VCE,3578
+sqlalchemy/util/typing.py,sha256=DG9V94Mh63cqObr_G5X19wH4H3hhWMqZXufVEZ2wtiw,17221
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/REQUESTED b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/WHEEL b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/WHEEL
new file mode 100644
index 0000000..1c1a93d
--- /dev/null
+++ b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.42.0)
+Root-Is-Purelib: false
+Tag: cp312-cp312-win_amd64
+
diff --git a/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/top_level.txt b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/top_level.txt
new file mode 100644
index 0000000..39fb2be
--- /dev/null
+++ b/venv/Lib/site-packages/SQLAlchemy-2.0.27.dist-info/top_level.txt
@@ -0,0 +1 @@
+sqlalchemy
diff --git a/venv/Lib/site-packages/__pycache__/typing_extensions.cpython-312.pyc b/venv/Lib/site-packages/__pycache__/typing_extensions.cpython-312.pyc
new file mode 100644
index 0000000..e06b2cc
Binary files /dev/null and b/venv/Lib/site-packages/__pycache__/typing_extensions.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/INSTALLER b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/LICENSE b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/LICENSE
new file mode 100644
index 0000000..11069ed
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/METADATA b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/METADATA
new file mode 100644
index 0000000..77e35dd
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/METADATA
@@ -0,0 +1,308 @@
+Metadata-Version: 2.1
+Name: bcrypt
+Version: 4.1.2
+Summary: Modern password hashing for your software and your servers
+Author-email: The Python Cryptographic Authority developers
+License: Apache-2.0
+Project-URL: homepage, https://github.com/pyca/bcrypt/
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Provides-Extra: tests
+Requires-Dist: pytest !=3.3.0,>=3.2.1 ; extra == 'tests'
+Provides-Extra: typecheck
+Requires-Dist: mypy ; extra == 'typecheck'
+
+bcrypt
+======
+
+.. image:: https://img.shields.io/pypi/v/bcrypt.svg
+ :target: https://pypi.org/project/bcrypt/
+ :alt: Latest Version
+
+.. image:: https://github.com/pyca/bcrypt/workflows/CI/badge.svg?branch=main
+ :target: https://github.com/pyca/bcrypt/actions?query=workflow%3ACI+branch%3Amain
+
+Acceptable password hashing for your software and your servers (but you should
+really use argon2id or scrypt)
+
+
+Installation
+============
+
+To install bcrypt, simply:
+
+.. code:: bash
+
+ $ pip install bcrypt
+
+Note that bcrypt should build very easily on Linux provided you have a C
+compiler and a Rust compiler (the minimum supported Rust version is 1.56.0).
+
+For Debian and Ubuntu, the following command will ensure that the required dependencies are installed:
+
+.. code:: bash
+
+ $ sudo apt-get install build-essential cargo
+
+For Fedora and RHEL-derivatives, the following command will ensure that the required dependencies are installed:
+
+.. code:: bash
+
+ $ sudo yum install gcc cargo
+
+For Alpine, the following command will ensure that the required dependencies are installed:
+
+.. code:: bash
+
+ $ apk add --update musl-dev gcc cargo
+
+
+Alternatives
+============
+
+While bcrypt remains an acceptable choice for password storage, depending on your specific use case you may also want to consider using scrypt (either via `standard library`_ or `cryptography`_) or argon2id via `argon2_cffi`_.
+
+Changelog
+=========
+
+4.1.2
+-----
+
+* Publish both ``py37`` and ``py39`` wheels. This should resolve some errors
+ relating to initializing a module multiple times per process.
+
+4.1.1
+-----
+
+* Fixed the type signature on the ``kdf`` method.
+* Fixed packaging bug on Windows.
+* Fixed incompatibility with passlib package detection assumptions.
+
+4.1.0
+-----
+
+* Dropped support for Python 3.6.
+* Bumped MSRV to 1.64. (Note: Rust 1.63 can be used by setting the ``BCRYPT_ALLOW_RUST_163`` environment variable)
+
+4.0.1
+-----
+
+* We now build PyPy ``manylinux`` wheels.
+* Fixed a bug where passing an invalid ``salt`` to ``checkpw`` could result in
+ a ``pyo3_runtime.PanicException``. It now correctly raises a ``ValueError``.
+
+4.0.0
+-----
+
+* ``bcrypt`` is now implemented in Rust. Users building from source will need
+ to have a Rust compiler available. Nothing will change for users downloading
+ wheels.
+* We no longer ship ``manylinux2010`` wheels. Users should upgrade to the latest
+ ``pip`` to ensure this doesn’t cause issues downloading wheels on their
+ platform. We now ship ``manylinux_2_28`` wheels for users on new enough platforms.
+* ``NUL`` bytes are now allowed in inputs.
+
+
+3.2.2
+-----
+
+* Fixed packaging of ``py.typed`` files in wheels so that ``mypy`` works.
+
+3.2.1
+-----
+
+* Added support for compilation on z/OS
+* The next release of ``bcrypt`` with be 4.0 and it will require Rust at
+ compile time, for users building from source. There will be no additional
+ requirement for users who are installing from wheels. Users on most
+ platforms will be able to obtain a wheel by making sure they have an up to
+ date ``pip``. The minimum supported Rust version will be 1.56.0.
+* This will be the final release for which we ship ``manylinux2010`` wheels.
+ Going forward the minimum supported manylinux ABI for our wheels will be
+ ``manylinux2014``. The vast majority of users will continue to receive
+ ``manylinux`` wheels provided they have an up to date ``pip``.
+
+
+3.2.0
+-----
+
+* Added typehints for library functions.
+* Dropped support for Python versions less than 3.6 (2.7, 3.4, 3.5).
+* Shipped ``abi3`` Windows wheels (requires pip >= 20).
+
+3.1.7
+-----
+
+* Set a ``setuptools`` lower bound for PEP517 wheel building.
+* We no longer distribute 32-bit ``manylinux1`` wheels. Continuing to produce
+ them was a maintenance burden.
+
+3.1.6
+-----
+
+* Added support for compilation on Haiku.
+
+3.1.5
+-----
+
+* Added support for compilation on AIX.
+* Dropped Python 2.6 and 3.3 support.
+* Switched to using ``abi3`` wheels for Python 3. If you are not getting a
+ wheel on a compatible platform please upgrade your ``pip`` version.
+
+3.1.4
+-----
+
+* Fixed compilation with mingw and on illumos.
+
+3.1.3
+-----
+* Fixed a compilation issue on Solaris.
+* Added a warning when using too few rounds with ``kdf``.
+
+3.1.2
+-----
+* Fixed a compile issue affecting big endian platforms.
+* Fixed invalid escape sequence warnings on Python 3.6.
+* Fixed building in non-UTF8 environments on Python 2.
+
+3.1.1
+-----
+* Resolved a ``UserWarning`` when used with ``cffi`` 1.8.3.
+
+3.1.0
+-----
+* Added support for ``checkpw``, a convenience method for verifying a password.
+* Ensure that you get a ``$2y$`` hash when you input a ``$2y$`` salt.
+* Fixed a regression where ``$2a`` hashes were vulnerable to a wraparound bug.
+* Fixed compilation under Alpine Linux.
+
+3.0.0
+-----
+* Switched the C backend to code obtained from the OpenBSD project rather than
+ openwall.
+* Added support for ``bcrypt_pbkdf`` via the ``kdf`` function.
+
+2.0.0
+-----
+* Added support for an adjustible prefix when calling ``gensalt``.
+* Switched to CFFI 1.0+
+
+Usage
+-----
+
+Password Hashing
+~~~~~~~~~~~~~~~~
+
+Hashing and then later checking that a password matches the previous hashed
+password is very simple:
+
+.. code:: pycon
+
+ >>> import bcrypt
+ >>> password = b"super secret password"
+ >>> # Hash a password for the first time, with a randomly-generated salt
+ >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
+ >>> # Check that an unhashed password matches one that has previously been
+ >>> # hashed
+ >>> if bcrypt.checkpw(password, hashed):
+ ... print("It Matches!")
+ ... else:
+ ... print("It Does not Match :(")
+
+KDF
+~~~
+
+As of 3.0.0 ``bcrypt`` now offers a ``kdf`` function which does ``bcrypt_pbkdf``.
+This KDF is used in OpenSSH's newer encrypted private key format.
+
+.. code:: pycon
+
+ >>> import bcrypt
+ >>> key = bcrypt.kdf(
+ ... password=b'password',
+ ... salt=b'salt',
+ ... desired_key_bytes=32,
+ ... rounds=100)
+
+
+Adjustable Work Factor
+~~~~~~~~~~~~~~~~~~~~~~
+One of bcrypt's features is an adjustable logarithmic work factor. To adjust
+the work factor merely pass the desired number of rounds to
+``bcrypt.gensalt(rounds=12)`` which defaults to 12):
+
+.. code:: pycon
+
+ >>> import bcrypt
+ >>> password = b"super secret password"
+ >>> # Hash a password for the first time, with a certain number of rounds
+ >>> hashed = bcrypt.hashpw(password, bcrypt.gensalt(14))
+ >>> # Check that a unhashed password matches one that has previously been
+ >>> # hashed
+ >>> if bcrypt.checkpw(password, hashed):
+ ... print("It Matches!")
+ ... else:
+ ... print("It Does not Match :(")
+
+
+Adjustable Prefix
+~~~~~~~~~~~~~~~~~
+
+Another one of bcrypt's features is an adjustable prefix to let you define what
+libraries you'll remain compatible with. To adjust this, pass either ``2a`` or
+``2b`` (the default) to ``bcrypt.gensalt(prefix=b"2b")`` as a bytes object.
+
+As of 3.0.0 the ``$2y$`` prefix is still supported in ``hashpw`` but deprecated.
+
+Maximum Password Length
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The bcrypt algorithm only handles passwords up to 72 characters, any characters
+beyond that are ignored. To work around this, a common approach is to hash a
+password with a cryptographic hash (such as ``sha256``) and then base64
+encode it to prevent NULL byte problems before hashing the result with
+``bcrypt``:
+
+.. code:: pycon
+
+ >>> password = b"an incredibly long password" * 10
+ >>> hashed = bcrypt.hashpw(
+ ... base64.b64encode(hashlib.sha256(password).digest()),
+ ... bcrypt.gensalt()
+ ... )
+
+Compatibility
+-------------
+
+This library should be compatible with py-bcrypt and it will run on Python
+3.6+, and PyPy 3.
+
+C Code
+------
+
+This library uses code from OpenBSD.
+
+Security
+--------
+
+``bcrypt`` follows the `same security policy as cryptography`_, if you
+identify a vulnerability, we ask you to contact us privately.
+
+.. _`same security policy as cryptography`: https://cryptography.io/en/latest/security.html
+.. _`standard library`: https://docs.python.org/3/library/hashlib.html#hashlib.scrypt
+.. _`argon2_cffi`: https://argon2-cffi.readthedocs.io
+.. _`cryptography`: https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#cryptography.hazmat.primitives.kdf.scrypt.Scrypt
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/RECORD b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/RECORD
new file mode 100644
index 0000000..b8e794c
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/RECORD
@@ -0,0 +1,12 @@
+bcrypt-4.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+bcrypt-4.1.2.dist-info/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
+bcrypt-4.1.2.dist-info/METADATA,sha256=jmTyW8N73scUOhr5R9wD1rnKQpg-MmHlCJ26qbL8hfo,9783
+bcrypt-4.1.2.dist-info/RECORD,,
+bcrypt-4.1.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+bcrypt-4.1.2.dist-info/WHEEL,sha256=ZzJfItdlTwUbeh2SvWRPbrqgDfW_djikghnwfRmqFIQ,100
+bcrypt-4.1.2.dist-info/top_level.txt,sha256=BkR_qBzDbSuycMzHWE1vzXrfYecAzUVmQs6G2CukqNI,7
+bcrypt/__init__.py,sha256=zTtuqGGQxDgxcqm1f_0UbbPS6uCl-WxL98gSYDMSUbw,1000
+bcrypt/__init__.pyi,sha256=ITUCB9mPVU8sKUbJQMDUH5YfQXZb1O55F9qvKZR_o8I,333
+bcrypt/__pycache__/__init__.cpython-312.pyc,,
+bcrypt/_bcrypt.pyd,sha256=SQQzbl3dCNuL52lO7w0dg95nmdZBKVKoLcpIR6P0YlE,316928
+bcrypt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/REQUESTED b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/WHEEL b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/WHEEL
new file mode 100644
index 0000000..96dd453
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.42.0)
+Root-Is-Purelib: false
+Tag: cp39-abi3-win_amd64
+
diff --git a/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/top_level.txt b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/top_level.txt
new file mode 100644
index 0000000..7f0b6e7
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt-4.1.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+bcrypt
diff --git a/venv/Lib/site-packages/bcrypt/__init__.py b/venv/Lib/site-packages/bcrypt/__init__.py
new file mode 100644
index 0000000..c201934
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt/__init__.py
@@ -0,0 +1,43 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from ._bcrypt import (
+ __author__,
+ __copyright__,
+ __email__,
+ __license__,
+ __summary__,
+ __title__,
+ __uri__,
+ checkpw,
+ gensalt,
+ hashpw,
+ kdf,
+)
+from ._bcrypt import (
+ __version_ex__ as __version__,
+)
+
+__all__ = [
+ "gensalt",
+ "hashpw",
+ "checkpw",
+ "kdf",
+ "__title__",
+ "__summary__",
+ "__uri__",
+ "__version__",
+ "__author__",
+ "__email__",
+ "__license__",
+ "__copyright__",
+]
diff --git a/venv/Lib/site-packages/bcrypt/__init__.pyi b/venv/Lib/site-packages/bcrypt/__init__.pyi
new file mode 100644
index 0000000..12e4a2e
--- /dev/null
+++ b/venv/Lib/site-packages/bcrypt/__init__.pyi
@@ -0,0 +1,10 @@
+def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes: ...
+def hashpw(password: bytes, salt: bytes) -> bytes: ...
+def checkpw(password: bytes, hashed_password: bytes) -> bool: ...
+def kdf(
+ password: bytes,
+ salt: bytes,
+ desired_key_bytes: int,
+ rounds: int,
+ ignore_few_rounds: bool = False,
+) -> bytes: ...
diff --git a/venv/Lib/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..166a92c
Binary files /dev/null and b/venv/Lib/site-packages/bcrypt/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/bcrypt/_bcrypt.pyd b/venv/Lib/site-packages/bcrypt/_bcrypt.pyd
new file mode 100644
index 0000000..580c22c
Binary files /dev/null and b/venv/Lib/site-packages/bcrypt/_bcrypt.pyd differ
diff --git a/venv/Lib/site-packages/bcrypt/py.typed b/venv/Lib/site-packages/bcrypt/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/blinker-1.7.0.dist-info/INSTALLER b/venv/Lib/site-packages/blinker-1.7.0.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/blinker-1.7.0.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/blinker-1.7.0.dist-info/LICENSE.rst b/venv/Lib/site-packages/blinker-1.7.0.dist-info/LICENSE.rst
new file mode 100644
index 0000000..79c9825
--- /dev/null
+++ b/venv/Lib/site-packages/blinker-1.7.0.dist-info/LICENSE.rst
@@ -0,0 +1,20 @@
+Copyright 2010 Jason Kirtland
+
+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.
diff --git a/venv/Lib/site-packages/blinker-1.7.0.dist-info/METADATA b/venv/Lib/site-packages/blinker-1.7.0.dist-info/METADATA
new file mode 100644
index 0000000..f96613c
--- /dev/null
+++ b/venv/Lib/site-packages/blinker-1.7.0.dist-info/METADATA
@@ -0,0 +1,62 @@
+Metadata-Version: 2.1
+Name: blinker
+Version: 1.7.0
+Summary: Fast, simple object-to-object and broadcast signaling
+Keywords: signal,emit,events,broadcast
+Author-email: Jason Kirtland
+Maintainer-email: Pallets Ecosystem
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries
+Project-URL: Chat, https://discord.gg/pallets
+Project-URL: Documentation, https://blinker.readthedocs.io
+Project-URL: Homepage, https://blinker.readthedocs.io
+Project-URL: Issue Tracker, https://github.com/pallets-eco/blinker/issues/
+Project-URL: Source Code, https://github.com/pallets-eco/blinker/
+
+Blinker
+=======
+
+Blinker provides a fast dispatching system that allows any number of
+interested parties to subscribe to events, or "signals".
+
+Signal receivers can subscribe to specific senders or receive signals
+sent by any sender.
+
+.. code-block:: pycon
+
+ >>> from blinker import signal
+ >>> started = signal('round-started')
+ >>> def each(round):
+ ... print(f"Round {round}")
+ ...
+ >>> started.connect(each)
+
+ >>> def round_two(round):
+ ... print("This is round two.")
+ ...
+ >>> started.connect(round_two, sender=2)
+
+ >>> for round in range(1, 4):
+ ... started.send(round)
+ ...
+ Round 1!
+ Round 2!
+ This is round two.
+ Round 3!
+
+
+Links
+-----
+
+- Documentation: https://blinker.readthedocs.io/
+- Changes: https://blinker.readthedocs.io/#changes
+- PyPI Releases: https://pypi.org/project/blinker/
+- Source Code: https://github.com/pallets-eco/blinker/
+- Issue Tracker: https://github.com/pallets-eco/blinker/issues/
+
diff --git a/venv/Lib/site-packages/blinker-1.7.0.dist-info/RECORD b/venv/Lib/site-packages/blinker-1.7.0.dist-info/RECORD
new file mode 100644
index 0000000..5ce4209
--- /dev/null
+++ b/venv/Lib/site-packages/blinker-1.7.0.dist-info/RECORD
@@ -0,0 +1,15 @@
+blinker-1.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+blinker-1.7.0.dist-info/LICENSE.rst,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054
+blinker-1.7.0.dist-info/METADATA,sha256=kDgzPgrw4he78pEX88bSAqwYMVWrfUMk8QmNjekjg_U,1918
+blinker-1.7.0.dist-info/RECORD,,
+blinker-1.7.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+blinker-1.7.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+blinker/__init__.py,sha256=s75XaRDHwSDzZ21BZUOEkQDQIcQEyT8hT7vk3EhYFQU,408
+blinker/__pycache__/__init__.cpython-312.pyc,,
+blinker/__pycache__/_saferef.cpython-312.pyc,,
+blinker/__pycache__/_utilities.cpython-312.pyc,,
+blinker/__pycache__/base.cpython-312.pyc,,
+blinker/_saferef.py,sha256=kWOTIWnCY3kOb8lZP74Rbx7bR_BLVg4TjwzNCRLhKHs,9096
+blinker/_utilities.py,sha256=S2njKDmlBpK_yCK4RT8hq98hEj30I0TQCC5mNhtY22I,2856
+blinker/base.py,sha256=FqZmAI5YzuRrvRmye1Jb-utyVOjXtF5vUVP3-1u-HtU,20544
+blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
diff --git a/venv/Lib/site-packages/blinker-1.7.0.dist-info/REQUESTED b/venv/Lib/site-packages/blinker-1.7.0.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/blinker-1.7.0.dist-info/WHEEL b/venv/Lib/site-packages/blinker-1.7.0.dist-info/WHEEL
new file mode 100644
index 0000000..3b5e64b
--- /dev/null
+++ b/venv/Lib/site-packages/blinker-1.7.0.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/venv/Lib/site-packages/blinker/__init__.py b/venv/Lib/site-packages/blinker/__init__.py
new file mode 100644
index 0000000..d014caa
--- /dev/null
+++ b/venv/Lib/site-packages/blinker/__init__.py
@@ -0,0 +1,19 @@
+from blinker.base import ANY
+from blinker.base import NamedSignal
+from blinker.base import Namespace
+from blinker.base import receiver_connected
+from blinker.base import Signal
+from blinker.base import signal
+from blinker.base import WeakNamespace
+
+__all__ = [
+ "ANY",
+ "NamedSignal",
+ "Namespace",
+ "Signal",
+ "WeakNamespace",
+ "receiver_connected",
+ "signal",
+]
+
+__version__ = "1.7.0"
diff --git a/venv/Lib/site-packages/blinker/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/blinker/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..b299ad5
Binary files /dev/null and b/venv/Lib/site-packages/blinker/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/blinker/__pycache__/_saferef.cpython-312.pyc b/venv/Lib/site-packages/blinker/__pycache__/_saferef.cpython-312.pyc
new file mode 100644
index 0000000..9fd9e21
Binary files /dev/null and b/venv/Lib/site-packages/blinker/__pycache__/_saferef.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc b/venv/Lib/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc
new file mode 100644
index 0000000..59c523e
Binary files /dev/null and b/venv/Lib/site-packages/blinker/__pycache__/_utilities.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/blinker/__pycache__/base.cpython-312.pyc b/venv/Lib/site-packages/blinker/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000..ec7bac6
Binary files /dev/null and b/venv/Lib/site-packages/blinker/__pycache__/base.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/blinker/_saferef.py b/venv/Lib/site-packages/blinker/_saferef.py
new file mode 100644
index 0000000..dcb70c1
--- /dev/null
+++ b/venv/Lib/site-packages/blinker/_saferef.py
@@ -0,0 +1,230 @@
+# extracted from Louie, http://pylouie.org/
+# updated for Python 3
+#
+# Copyright (c) 2006 Patrick K. O'Brien, Mike C. Fletcher,
+# Matthew R. Scott
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# * Neither the name of the nor the names of its
+# contributors may be used to endorse or promote products derived
+# from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# 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.
+#
+"""Refactored 'safe reference from dispatcher.py"""
+import operator
+import sys
+import traceback
+import weakref
+
+
+get_self = operator.attrgetter("__self__")
+get_func = operator.attrgetter("__func__")
+
+
+def safe_ref(target, on_delete=None):
+ """Return a *safe* weak reference to a callable target.
+
+ - ``target``: The object to be weakly referenced, if it's a bound
+ method reference, will create a BoundMethodWeakref, otherwise
+ creates a simple weakref.
+
+ - ``on_delete``: If provided, will have a hard reference stored to
+ the callable to be called after the safe reference goes out of
+ scope with the reference object, (either a weakref or a
+ BoundMethodWeakref) as argument.
+ """
+ try:
+ im_self = get_self(target)
+ except AttributeError:
+ if callable(on_delete):
+ return weakref.ref(target, on_delete)
+ else:
+ return weakref.ref(target)
+ else:
+ if im_self is not None:
+ # Turn a bound method into a BoundMethodWeakref instance.
+ # Keep track of these instances for lookup by disconnect().
+ assert hasattr(target, "im_func") or hasattr(target, "__func__"), (
+ f"safe_ref target {target!r} has im_self, but no im_func, "
+ "don't know how to create reference"
+ )
+ reference = BoundMethodWeakref(target=target, on_delete=on_delete)
+ return reference
+
+
+class BoundMethodWeakref:
+ """'Safe' and reusable weak references to instance methods.
+
+ BoundMethodWeakref objects provide a mechanism for referencing a
+ bound method without requiring that the method object itself
+ (which is normally a transient object) is kept alive. Instead,
+ the BoundMethodWeakref object keeps weak references to both the
+ object and the function which together define the instance method.
+
+ Attributes:
+
+ - ``key``: The identity key for the reference, calculated by the
+ class's calculate_key method applied to the target instance method.
+
+ - ``deletion_methods``: Sequence of callable objects taking single
+ argument, a reference to this object which will be called when
+ *either* the target object or target function is garbage
+ collected (i.e. when this object becomes invalid). These are
+ specified as the on_delete parameters of safe_ref calls.
+
+ - ``weak_self``: Weak reference to the target object.
+
+ - ``weak_func``: Weak reference to the target function.
+
+ Class Attributes:
+
+ - ``_all_instances``: Class attribute pointing to all live
+ BoundMethodWeakref objects indexed by the class's
+ calculate_key(target) method applied to the target objects.
+ This weak value dictionary is used to short-circuit creation so
+ that multiple references to the same (object, function) pair
+ produce the same BoundMethodWeakref instance.
+ """
+
+ _all_instances = weakref.WeakValueDictionary() # type: ignore[var-annotated]
+
+ def __new__(cls, target, on_delete=None, *arguments, **named):
+ """Create new instance or return current instance.
+
+ Basically this method of construction allows us to
+ short-circuit creation of references to already-referenced
+ instance methods. The key corresponding to the target is
+ calculated, and if there is already an existing reference,
+ that is returned, with its deletion_methods attribute updated.
+ Otherwise the new instance is created and registered in the
+ table of already-referenced methods.
+ """
+ key = cls.calculate_key(target)
+ current = cls._all_instances.get(key)
+ if current is not None:
+ current.deletion_methods.append(on_delete)
+ return current
+ else:
+ base = super().__new__(cls)
+ cls._all_instances[key] = base
+ base.__init__(target, on_delete, *arguments, **named)
+ return base
+
+ def __init__(self, target, on_delete=None):
+ """Return a weak-reference-like instance for a bound method.
+
+ - ``target``: The instance-method target for the weak reference,
+ must have im_self and im_func attributes and be
+ reconstructable via the following, which is true of built-in
+ instance methods::
+
+ target.im_func.__get__( target.im_self )
+
+ - ``on_delete``: Optional callback which will be called when
+ this weak reference ceases to be valid (i.e. either the
+ object or the function is garbage collected). Should take a
+ single argument, which will be passed a pointer to this
+ object.
+ """
+
+ def remove(weak, self=self):
+ """Set self.isDead to True when method or instance is destroyed."""
+ methods = self.deletion_methods[:]
+ del self.deletion_methods[:]
+ try:
+ del self.__class__._all_instances[self.key]
+ except KeyError:
+ pass
+ for function in methods:
+ try:
+ if callable(function):
+ function(self)
+ except Exception:
+ try:
+ traceback.print_exc()
+ except AttributeError:
+ e = sys.exc_info()[1]
+ print(
+ f"Exception during saferef {self} "
+ f"cleanup function {function}: {e}"
+ )
+
+ self.deletion_methods = [on_delete]
+ self.key = self.calculate_key(target)
+ im_self = get_self(target)
+ im_func = get_func(target)
+ self.weak_self = weakref.ref(im_self, remove)
+ self.weak_func = weakref.ref(im_func, remove)
+ self.self_name = str(im_self)
+ self.func_name = str(im_func.__name__)
+
+ @classmethod
+ def calculate_key(cls, target):
+ """Calculate the reference key for this reference.
+
+ Currently this is a two-tuple of the id()'s of the target
+ object and the target function respectively.
+ """
+ return (id(get_self(target)), id(get_func(target)))
+
+ def __str__(self):
+ """Give a friendly representation of the object."""
+ return "{}({}.{})".format(
+ self.__class__.__name__,
+ self.self_name,
+ self.func_name,
+ )
+
+ __repr__ = __str__
+
+ def __hash__(self):
+ return hash((self.self_name, self.key))
+
+ def __nonzero__(self):
+ """Whether we are still a valid reference."""
+ return self() is not None
+
+ def __eq__(self, other):
+ """Compare with another reference."""
+ if not isinstance(other, self.__class__):
+ return operator.eq(self.__class__, type(other))
+ return operator.eq(self.key, other.key)
+
+ def __call__(self):
+ """Return a strong reference to the bound method.
+
+ If the target cannot be retrieved, then will return None,
+ otherwise returns a bound instance method for our object and
+ function.
+
+ Note: You may call this method any number of times, as it does
+ not invalidate the reference.
+ """
+ target = self.weak_self()
+ if target is not None:
+ function = self.weak_func()
+ if function is not None:
+ return function.__get__(target)
+ return None
diff --git a/venv/Lib/site-packages/blinker/_utilities.py b/venv/Lib/site-packages/blinker/_utilities.py
new file mode 100644
index 0000000..4b711c6
--- /dev/null
+++ b/venv/Lib/site-packages/blinker/_utilities.py
@@ -0,0 +1,105 @@
+from __future__ import annotations
+
+import typing as t
+from weakref import ref
+
+from blinker._saferef import BoundMethodWeakref
+
+IdentityType = t.Union[t.Tuple[int, int], str, int]
+
+
+class _symbol:
+ def __init__(self, name):
+ """Construct a new named symbol."""
+ self.__name__ = self.name = name
+
+ def __reduce__(self):
+ return symbol, (self.name,)
+
+ def __repr__(self):
+ return self.name
+
+
+_symbol.__name__ = "symbol"
+
+
+class symbol:
+ """A constant symbol.
+
+ >>> symbol('foo') is symbol('foo')
+ True
+ >>> symbol('foo')
+ foo
+
+ A slight refinement of the MAGICCOOKIE=object() pattern. The primary
+ advantage of symbol() is its repr(). They are also singletons.
+
+ Repeated calls of symbol('name') will all return the same instance.
+
+ """
+
+ symbols = {} # type: ignore[var-annotated]
+
+ def __new__(cls, name):
+ try:
+ return cls.symbols[name]
+ except KeyError:
+ return cls.symbols.setdefault(name, _symbol(name))
+
+
+def hashable_identity(obj: object) -> IdentityType:
+ if hasattr(obj, "__func__"):
+ return (id(obj.__func__), id(obj.__self__)) # type: ignore[attr-defined]
+ elif hasattr(obj, "im_func"):
+ return (id(obj.im_func), id(obj.im_self)) # type: ignore[attr-defined]
+ elif isinstance(obj, (int, str)):
+ return obj
+ else:
+ return id(obj)
+
+
+WeakTypes = (ref, BoundMethodWeakref)
+
+
+class annotatable_weakref(ref):
+ """A weakref.ref that supports custom instance attributes."""
+
+ receiver_id: t.Optional[IdentityType]
+ sender_id: t.Optional[IdentityType]
+
+
+def reference( # type: ignore[no-untyped-def]
+ object, callback=None, **annotations
+) -> annotatable_weakref:
+ """Return an annotated weak ref."""
+ if callable(object):
+ weak = callable_reference(object, callback)
+ else:
+ weak = annotatable_weakref(object, callback)
+ for key, value in annotations.items():
+ setattr(weak, key, value)
+ return weak # type: ignore[no-any-return]
+
+
+def callable_reference(object, callback=None):
+ """Return an annotated weak ref, supporting bound instance methods."""
+ if hasattr(object, "im_self") and object.im_self is not None:
+ return BoundMethodWeakref(target=object, on_delete=callback)
+ elif hasattr(object, "__self__") and object.__self__ is not None:
+ return BoundMethodWeakref(target=object, on_delete=callback)
+ return annotatable_weakref(object, callback)
+
+
+class lazy_property:
+ """A @property that is only evaluated once."""
+
+ def __init__(self, deferred):
+ self._deferred = deferred
+ self.__doc__ = deferred.__doc__
+
+ def __get__(self, obj, cls):
+ if obj is None:
+ return self
+ value = self._deferred(obj)
+ setattr(obj, self._deferred.__name__, value)
+ return value
diff --git a/venv/Lib/site-packages/blinker/base.py b/venv/Lib/site-packages/blinker/base.py
new file mode 100644
index 0000000..b9d7035
--- /dev/null
+++ b/venv/Lib/site-packages/blinker/base.py
@@ -0,0 +1,558 @@
+"""Signals and events.
+
+A small implementation of signals, inspired by a snippet of Django signal
+API client code seen in a blog post. Signals are first-class objects and
+each manages its own receivers and message emission.
+
+The :func:`signal` function provides singleton behavior for named signals.
+
+"""
+from __future__ import annotations
+
+import typing as t
+from collections import defaultdict
+from contextlib import contextmanager
+from inspect import iscoroutinefunction
+from warnings import warn
+from weakref import WeakValueDictionary
+
+from blinker._utilities import annotatable_weakref
+from blinker._utilities import hashable_identity
+from blinker._utilities import IdentityType
+from blinker._utilities import lazy_property
+from blinker._utilities import reference
+from blinker._utilities import symbol
+from blinker._utilities import WeakTypes
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+
+ T_callable = t.TypeVar("T_callable", bound=t.Callable[..., t.Any])
+
+ T = t.TypeVar("T")
+ P = te.ParamSpec("P")
+
+ AsyncWrapperType = t.Callable[[t.Callable[P, t.Awaitable[T]]], t.Callable[P, T]]
+ SyncWrapperType = t.Callable[[t.Callable[P, T]], t.Callable[P, t.Awaitable[T]]]
+
+ANY = symbol("ANY")
+ANY.__doc__ = 'Token for "any sender".'
+ANY_ID = 0
+
+# NOTE: We need a reference to cast for use in weakref callbacks otherwise
+# t.cast may have already been set to None during finalization.
+cast = t.cast
+
+
+class Signal:
+ """A notification emitter."""
+
+ #: An :obj:`ANY` convenience synonym, allows ``Signal.ANY``
+ #: without an additional import.
+ ANY = ANY
+
+ set_class: type[set] = set
+
+ @lazy_property
+ def receiver_connected(self) -> Signal:
+ """Emitted after each :meth:`connect`.
+
+ The signal sender is the signal instance, and the :meth:`connect`
+ arguments are passed through: *receiver*, *sender*, and *weak*.
+
+ .. versionadded:: 1.2
+
+ """
+ return Signal(doc="Emitted after a receiver connects.")
+
+ @lazy_property
+ def receiver_disconnected(self) -> Signal:
+ """Emitted after :meth:`disconnect`.
+
+ The sender is the signal instance, and the :meth:`disconnect` arguments
+ are passed through: *receiver* and *sender*.
+
+ Note, this signal is emitted **only** when :meth:`disconnect` is
+ called explicitly.
+
+ The disconnect signal can not be emitted by an automatic disconnect
+ (due to a weakly referenced receiver or sender going out of scope),
+ as the receiver and/or sender instances are no longer available for
+ use at the time this signal would be emitted.
+
+ An alternative approach is available by subscribing to
+ :attr:`receiver_connected` and setting up a custom weakref cleanup
+ callback on weak receivers and senders.
+
+ .. versionadded:: 1.2
+
+ """
+ return Signal(doc="Emitted after a receiver disconnects.")
+
+ def __init__(self, doc: str | None = None) -> None:
+ """
+ :param doc: optional. If provided, will be assigned to the signal's
+ __doc__ attribute.
+
+ """
+ if doc:
+ self.__doc__ = doc
+ #: A mapping of connected receivers.
+ #:
+ #: The values of this mapping are not meaningful outside of the
+ #: internal :class:`Signal` implementation, however the boolean value
+ #: of the mapping is useful as an extremely efficient check to see if
+ #: any receivers are connected to the signal.
+ self.receivers: dict[IdentityType, t.Callable | annotatable_weakref] = {}
+ self.is_muted = False
+ self._by_receiver: dict[IdentityType, set[IdentityType]] = defaultdict(
+ self.set_class
+ )
+ self._by_sender: dict[IdentityType, set[IdentityType]] = defaultdict(
+ self.set_class
+ )
+ self._weak_senders: dict[IdentityType, annotatable_weakref] = {}
+
+ def connect(
+ self, receiver: T_callable, sender: t.Any = ANY, weak: bool = True
+ ) -> T_callable:
+ """Connect *receiver* to signal events sent by *sender*.
+
+ :param receiver: A callable. Will be invoked by :meth:`send` with
+ `sender=` as a single positional argument and any ``kwargs`` that
+ were provided to a call to :meth:`send`.
+
+ :param sender: Any object or :obj:`ANY`, defaults to ``ANY``.
+ Restricts notifications delivered to *receiver* to only those
+ :meth:`send` emissions sent by *sender*. If ``ANY``, the receiver
+ will always be notified. A *receiver* may be connected to
+ multiple *sender* values on the same Signal through multiple calls
+ to :meth:`connect`.
+
+ :param weak: If true, the Signal will hold a weakref to *receiver*
+ and automatically disconnect when *receiver* goes out of scope or
+ is garbage collected. Defaults to True.
+
+ """
+ receiver_id = hashable_identity(receiver)
+ receiver_ref: T_callable | annotatable_weakref
+
+ if weak:
+ receiver_ref = reference(receiver, self._cleanup_receiver)
+ receiver_ref.receiver_id = receiver_id
+ else:
+ receiver_ref = receiver
+ sender_id: IdentityType
+ if sender is ANY:
+ sender_id = ANY_ID
+ else:
+ sender_id = hashable_identity(sender)
+
+ self.receivers.setdefault(receiver_id, receiver_ref)
+ self._by_sender[sender_id].add(receiver_id)
+ self._by_receiver[receiver_id].add(sender_id)
+ del receiver_ref
+
+ if sender is not ANY and sender_id not in self._weak_senders:
+ # wire together a cleanup for weakref-able senders
+ try:
+ sender_ref = reference(sender, self._cleanup_sender)
+ sender_ref.sender_id = sender_id
+ except TypeError:
+ pass
+ else:
+ self._weak_senders.setdefault(sender_id, sender_ref)
+ del sender_ref
+
+ # broadcast this connection. if receivers raise, disconnect.
+ if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers:
+ try:
+ self.receiver_connected.send(
+ self, receiver=receiver, sender=sender, weak=weak
+ )
+ except TypeError as e:
+ self.disconnect(receiver, sender)
+ raise e
+ if receiver_connected.receivers and self is not receiver_connected:
+ try:
+ receiver_connected.send(
+ self, receiver_arg=receiver, sender_arg=sender, weak_arg=weak
+ )
+ except TypeError as e:
+ self.disconnect(receiver, sender)
+ raise e
+ return receiver
+
+ def connect_via(
+ self, sender: t.Any, weak: bool = False
+ ) -> t.Callable[[T_callable], T_callable]:
+ """Connect the decorated function as a receiver for *sender*.
+
+ :param sender: Any object or :obj:`ANY`. The decorated function
+ will only receive :meth:`send` emissions sent by *sender*. If
+ ``ANY``, the receiver will always be notified. A function may be
+ decorated multiple times with differing *sender* values.
+
+ :param weak: If true, the Signal will hold a weakref to the
+ decorated function and automatically disconnect when *receiver*
+ goes out of scope or is garbage collected. Unlike
+ :meth:`connect`, this defaults to False.
+
+ The decorated function will be invoked by :meth:`send` with
+ `sender=` as a single positional argument and any ``kwargs`` that
+ were provided to the call to :meth:`send`.
+
+
+ .. versionadded:: 1.1
+
+ """
+
+ def decorator(fn: T_callable) -> T_callable:
+ self.connect(fn, sender, weak)
+ return fn
+
+ return decorator
+
+ @contextmanager
+ def connected_to(
+ self, receiver: t.Callable, sender: t.Any = ANY
+ ) -> t.Generator[None, None, None]:
+ """Execute a block with the signal temporarily connected to *receiver*.
+
+ :param receiver: a receiver callable
+ :param sender: optional, a sender to filter on
+
+ This is a context manager for use in the ``with`` statement. It can
+ be useful in unit tests. *receiver* is connected to the signal for
+ the duration of the ``with`` block, and will be disconnected
+ automatically when exiting the block:
+
+ .. code-block:: python
+
+ with on_ready.connected_to(receiver):
+ # do stuff
+ on_ready.send(123)
+
+ .. versionadded:: 1.1
+
+ """
+ self.connect(receiver, sender=sender, weak=False)
+ try:
+ yield None
+ finally:
+ self.disconnect(receiver)
+
+ @contextmanager
+ def muted(self) -> t.Generator[None, None, None]:
+ """Context manager for temporarily disabling signal.
+ Useful for test purposes.
+ """
+ self.is_muted = True
+ try:
+ yield None
+ except Exception as e:
+ raise e
+ finally:
+ self.is_muted = False
+
+ def temporarily_connected_to(
+ self, receiver: t.Callable, sender: t.Any = ANY
+ ) -> t.ContextManager[None]:
+ """An alias for :meth:`connected_to`.
+
+ :param receiver: a receiver callable
+ :param sender: optional, a sender to filter on
+
+ .. versionadded:: 0.9
+
+ .. versionchanged:: 1.1
+ Renamed to :meth:`connected_to`. ``temporarily_connected_to`` was
+ deprecated in 1.2 and will be removed in a subsequent version.
+
+ """
+ warn(
+ "temporarily_connected_to is deprecated; use connected_to instead.",
+ DeprecationWarning,
+ )
+ return self.connected_to(receiver, sender)
+
+ def send(
+ self,
+ *sender: t.Any,
+ _async_wrapper: AsyncWrapperType | None = None,
+ **kwargs: t.Any,
+ ) -> list[tuple[t.Callable, t.Any]]:
+ """Emit this signal on behalf of *sender*, passing on ``kwargs``.
+
+ Returns a list of 2-tuples, pairing receivers with their return
+ value. The ordering of receiver notification is undefined.
+
+ :param sender: Any object or ``None``. If omitted, synonymous
+ with ``None``. Only accepts one positional argument.
+ :param _async_wrapper: A callable that should wrap a coroutine
+ receiver and run it when called synchronously.
+
+ :param kwargs: Data to be sent to receivers.
+ """
+ if self.is_muted:
+ return []
+
+ sender = self._extract_sender(sender)
+ results = []
+ for receiver in self.receivers_for(sender):
+ if iscoroutinefunction(receiver):
+ if _async_wrapper is None:
+ raise RuntimeError("Cannot send to a coroutine function")
+ receiver = _async_wrapper(receiver)
+ result = receiver(sender, **kwargs)
+ results.append((receiver, result))
+ return results
+
+ async def send_async(
+ self,
+ *sender: t.Any,
+ _sync_wrapper: SyncWrapperType | None = None,
+ **kwargs: t.Any,
+ ) -> list[tuple[t.Callable, t.Any]]:
+ """Emit this signal on behalf of *sender*, passing on ``kwargs``.
+
+ Returns a list of 2-tuples, pairing receivers with their return
+ value. The ordering of receiver notification is undefined.
+
+ :param sender: Any object or ``None``. If omitted, synonymous
+ with ``None``. Only accepts one positional argument.
+ :param _sync_wrapper: A callable that should wrap a synchronous
+ receiver and run it when awaited.
+
+ :param kwargs: Data to be sent to receivers.
+ """
+ if self.is_muted:
+ return []
+
+ sender = self._extract_sender(sender)
+ results = []
+ for receiver in self.receivers_for(sender):
+ if not iscoroutinefunction(receiver):
+ if _sync_wrapper is None:
+ raise RuntimeError("Cannot send to a non-coroutine function")
+ receiver = _sync_wrapper(receiver)
+ result = await receiver(sender, **kwargs)
+ results.append((receiver, result))
+ return results
+
+ def _extract_sender(self, sender: t.Any) -> t.Any:
+ if not self.receivers:
+ # Ensure correct signature even on no-op sends, disable with -O
+ # for lowest possible cost.
+ if __debug__ and sender and len(sender) > 1:
+ raise TypeError(
+ f"send() accepts only one positional argument, {len(sender)} given"
+ )
+ return []
+
+ # Using '*sender' rather than 'sender=None' allows 'sender' to be
+ # used as a keyword argument- i.e. it's an invisible name in the
+ # function signature.
+ if len(sender) == 0:
+ sender = None
+ elif len(sender) > 1:
+ raise TypeError(
+ f"send() accepts only one positional argument, {len(sender)} given"
+ )
+ else:
+ sender = sender[0]
+ return sender
+
+ def has_receivers_for(self, sender: t.Any) -> bool:
+ """True if there is probably a receiver for *sender*.
+
+ Performs an optimistic check only. Does not guarantee that all
+ weakly referenced receivers are still alive. See
+ :meth:`receivers_for` for a stronger search.
+
+ """
+ if not self.receivers:
+ return False
+ if self._by_sender[ANY_ID]:
+ return True
+ if sender is ANY:
+ return False
+ return hashable_identity(sender) in self._by_sender
+
+ def receivers_for(
+ self, sender: t.Any
+ ) -> t.Generator[t.Callable[[t.Any], t.Any], None, None]:
+ """Iterate all live receivers listening for *sender*."""
+ # TODO: test receivers_for(ANY)
+ if self.receivers:
+ sender_id = hashable_identity(sender)
+ if sender_id in self._by_sender:
+ ids = self._by_sender[ANY_ID] | self._by_sender[sender_id]
+ else:
+ ids = self._by_sender[ANY_ID].copy()
+ for receiver_id in ids:
+ receiver = self.receivers.get(receiver_id)
+ if receiver is None:
+ continue
+ if isinstance(receiver, WeakTypes):
+ strong = receiver()
+ if strong is None:
+ self._disconnect(receiver_id, ANY_ID)
+ continue
+ receiver = strong
+ yield receiver # type: ignore[misc]
+
+ def disconnect(self, receiver: t.Callable, sender: t.Any = ANY) -> None:
+ """Disconnect *receiver* from this signal's events.
+
+ :param receiver: a previously :meth:`connected` callable
+
+ :param sender: a specific sender to disconnect from, or :obj:`ANY`
+ to disconnect from all senders. Defaults to ``ANY``.
+
+ """
+ sender_id: IdentityType
+ if sender is ANY:
+ sender_id = ANY_ID
+ else:
+ sender_id = hashable_identity(sender)
+ receiver_id = hashable_identity(receiver)
+ self._disconnect(receiver_id, sender_id)
+
+ if (
+ "receiver_disconnected" in self.__dict__
+ and self.receiver_disconnected.receivers
+ ):
+ self.receiver_disconnected.send(self, receiver=receiver, sender=sender)
+
+ def _disconnect(self, receiver_id: IdentityType, sender_id: IdentityType) -> None:
+ if sender_id == ANY_ID:
+ if self._by_receiver.pop(receiver_id, False):
+ for bucket in self._by_sender.values():
+ bucket.discard(receiver_id)
+ self.receivers.pop(receiver_id, None)
+ else:
+ self._by_sender[sender_id].discard(receiver_id)
+ self._by_receiver[receiver_id].discard(sender_id)
+
+ def _cleanup_receiver(self, receiver_ref: annotatable_weakref) -> None:
+ """Disconnect a receiver from all senders."""
+ self._disconnect(cast(IdentityType, receiver_ref.receiver_id), ANY_ID)
+
+ def _cleanup_sender(self, sender_ref: annotatable_weakref) -> None:
+ """Disconnect all receivers from a sender."""
+ sender_id = cast(IdentityType, sender_ref.sender_id)
+ assert sender_id != ANY_ID
+ self._weak_senders.pop(sender_id, None)
+ for receiver_id in self._by_sender.pop(sender_id, ()):
+ self._by_receiver[receiver_id].discard(sender_id)
+
+ def _cleanup_bookkeeping(self) -> None:
+ """Prune unused sender/receiver bookkeeping. Not threadsafe.
+
+ Connecting & disconnecting leave behind a small amount of bookkeeping
+ for the receiver and sender values. Typical workloads using Blinker,
+ for example in most web apps, Flask, CLI scripts, etc., are not
+ adversely affected by this bookkeeping.
+
+ With a long-running Python process performing dynamic signal routing
+ with high volume- e.g. connecting to function closures, "senders" are
+ all unique object instances, and doing all of this over and over- you
+ may see memory usage will grow due to extraneous bookkeeping. (An empty
+ set() for each stale sender/receiver pair.)
+
+ This method will prune that bookkeeping away, with the caveat that such
+ pruning is not threadsafe. The risk is that cleanup of a fully
+ disconnected receiver/sender pair occurs while another thread is
+ connecting that same pair. If you are in the highly dynamic, unique
+ receiver/sender situation that has lead you to this method, that
+ failure mode is perhaps not a big deal for you.
+ """
+ for mapping in (self._by_sender, self._by_receiver):
+ for _id, bucket in list(mapping.items()):
+ if not bucket:
+ mapping.pop(_id, None)
+
+ def _clear_state(self) -> None:
+ """Throw away all signal state. Useful for unit tests."""
+ self._weak_senders.clear()
+ self.receivers.clear()
+ self._by_sender.clear()
+ self._by_receiver.clear()
+
+
+receiver_connected = Signal(
+ """\
+Sent by a :class:`Signal` after a receiver connects.
+
+:argument: the Signal that was connected to
+:keyword receiver_arg: the connected receiver
+:keyword sender_arg: the sender to connect to
+:keyword weak_arg: true if the connection to receiver_arg is a weak reference
+
+.. deprecated:: 1.2
+
+As of 1.2, individual signals have their own private
+:attr:`~Signal.receiver_connected` and
+:attr:`~Signal.receiver_disconnected` signals with a slightly simplified
+call signature. This global signal is planned to be removed in 1.6.
+
+"""
+)
+
+
+class NamedSignal(Signal):
+ """A named generic notification emitter."""
+
+ def __init__(self, name: str, doc: str | None = None) -> None:
+ Signal.__init__(self, doc)
+
+ #: The name of this signal.
+ self.name = name
+
+ def __repr__(self) -> str:
+ base = Signal.__repr__(self)
+ return f"{base[:-1]}; {self.name!r}>" # noqa: E702
+
+
+class Namespace(dict):
+ """A mapping of signal names to signals."""
+
+ def signal(self, name: str, doc: str | None = None) -> NamedSignal:
+ """Return the :class:`NamedSignal` *name*, creating it if required.
+
+ Repeated calls to this function will return the same signal object.
+
+ """
+ try:
+ return self[name] # type: ignore[no-any-return]
+ except KeyError:
+ result = self.setdefault(name, NamedSignal(name, doc))
+ return result # type: ignore[no-any-return]
+
+
+class WeakNamespace(WeakValueDictionary):
+ """A weak mapping of signal names to signals.
+
+ Automatically cleans up unused Signals when the last reference goes out
+ of scope. This namespace implementation exists for a measure of legacy
+ compatibility with Blinker <= 1.2, and may be dropped in the future.
+
+ .. versionadded:: 1.3
+
+ """
+
+ def signal(self, name: str, doc: str | None = None) -> NamedSignal:
+ """Return the :class:`NamedSignal` *name*, creating it if required.
+
+ Repeated calls to this function will return the same signal object.
+
+ """
+ try:
+ return self[name] # type: ignore[no-any-return]
+ except KeyError:
+ result = self.setdefault(name, NamedSignal(name, doc))
+ return result # type: ignore[no-any-return]
+
+
+signal = Namespace().signal
diff --git a/venv/Lib/site-packages/blinker/py.typed b/venv/Lib/site-packages/blinker/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/INSTALLER b/venv/Lib/site-packages/click-8.1.7.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/click-8.1.7.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/LICENSE.rst b/venv/Lib/site-packages/click-8.1.7.dist-info/LICENSE.rst
new file mode 100644
index 0000000..d12a849
--- /dev/null
+++ b/venv/Lib/site-packages/click-8.1.7.dist-info/LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2014 Pallets
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/METADATA b/venv/Lib/site-packages/click-8.1.7.dist-info/METADATA
new file mode 100644
index 0000000..7a6bbb2
--- /dev/null
+++ b/venv/Lib/site-packages/click-8.1.7.dist-info/METADATA
@@ -0,0 +1,103 @@
+Metadata-Version: 2.1
+Name: click
+Version: 8.1.7
+Summary: Composable command line interface toolkit
+Home-page: https://palletsprojects.com/p/click/
+Maintainer: Pallets
+Maintainer-email: contact@palletsprojects.com
+License: BSD-3-Clause
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Documentation, https://click.palletsprojects.com/
+Project-URL: Changes, https://click.palletsprojects.com/changes/
+Project-URL: Source Code, https://github.com/pallets/click/
+Project-URL: Issue Tracker, https://github.com/pallets/click/issues/
+Project-URL: Chat, https://discord.gg/pallets
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE.rst
+Requires-Dist: colorama ; platform_system == "Windows"
+Requires-Dist: importlib-metadata ; python_version < "3.8"
+
+\$ click\_
+==========
+
+Click is a Python package for creating beautiful command line interfaces
+in a composable way with as little code as necessary. It's the "Command
+Line Interface Creation Kit". It's highly configurable but comes with
+sensible defaults out of the box.
+
+It aims to make the process of writing command line tools quick and fun
+while also preventing any frustration caused by the inability to
+implement an intended CLI API.
+
+Click in three points:
+
+- Arbitrary nesting of commands
+- Automatic help page generation
+- Supports lazy loading of subcommands at runtime
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+ $ pip install -U click
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+A Simple Example
+----------------
+
+.. code-block:: python
+
+ import click
+
+ @click.command()
+ @click.option("--count", default=1, help="Number of greetings.")
+ @click.option("--name", prompt="Your name", help="The person to greet.")
+ def hello(count, name):
+ """Simple program that greets NAME for a total of COUNT times."""
+ for _ in range(count):
+ click.echo(f"Hello, {name}!")
+
+ if __name__ == '__main__':
+ hello()
+
+.. code-block:: text
+
+ $ python hello.py --count=3
+ Your name: Click
+ Hello, Click!
+ Hello, Click!
+ Hello, Click!
+
+
+Donate
+------
+
+The Pallets organization develops and supports Click and other popular
+packages. 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://palletsprojects.com/donate
+
+
+Links
+-----
+
+- Documentation: https://click.palletsprojects.com/
+- Changes: https://click.palletsprojects.com/changes/
+- PyPI Releases: https://pypi.org/project/click/
+- Source Code: https://github.com/pallets/click
+- Issue Tracker: https://github.com/pallets/click/issues
+- Chat: https://discord.gg/pallets
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/RECORD b/venv/Lib/site-packages/click-8.1.7.dist-info/RECORD
new file mode 100644
index 0000000..a477e47
--- /dev/null
+++ b/venv/Lib/site-packages/click-8.1.7.dist-info/RECORD
@@ -0,0 +1,40 @@
+click-8.1.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+click-8.1.7.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
+click-8.1.7.dist-info/METADATA,sha256=qIMevCxGA9yEmJOM_4WHuUJCwWpsIEVbCPOhs45YPN4,3014
+click-8.1.7.dist-info/RECORD,,
+click-8.1.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+click-8.1.7.dist-info/WHEEL,sha256=5sUXSg9e4bi7lTLOHcm6QEYwO5TIF1TNbTSVFVjcJcc,92
+click-8.1.7.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
+click/__init__.py,sha256=YDDbjm406dTOA0V8bTtdGnhN7zj5j-_dFRewZF_pLvw,3138
+click/__pycache__/__init__.cpython-312.pyc,,
+click/__pycache__/_compat.cpython-312.pyc,,
+click/__pycache__/_termui_impl.cpython-312.pyc,,
+click/__pycache__/_textwrap.cpython-312.pyc,,
+click/__pycache__/_winconsole.cpython-312.pyc,,
+click/__pycache__/core.cpython-312.pyc,,
+click/__pycache__/decorators.cpython-312.pyc,,
+click/__pycache__/exceptions.cpython-312.pyc,,
+click/__pycache__/formatting.cpython-312.pyc,,
+click/__pycache__/globals.cpython-312.pyc,,
+click/__pycache__/parser.cpython-312.pyc,,
+click/__pycache__/shell_completion.cpython-312.pyc,,
+click/__pycache__/termui.cpython-312.pyc,,
+click/__pycache__/testing.cpython-312.pyc,,
+click/__pycache__/types.cpython-312.pyc,,
+click/__pycache__/utils.cpython-312.pyc,,
+click/_compat.py,sha256=5318agQpbt4kroKsbqDOYpTSWzL_YCZVUQiTT04yXmc,18744
+click/_termui_impl.py,sha256=3dFYv4445Nw-rFvZOTBMBPYwB1bxnmNk9Du6Dm_oBSU,24069
+click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353
+click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860
+click/core.py,sha256=j6oEWtGgGna8JarD6WxhXmNnxLnfRjwXglbBc-8jr7U,114086
+click/decorators.py,sha256=-ZlbGYgV-oI8jr_oH4RpuL1PFS-5QmeuEAsLDAYgxtw,18719
+click/exceptions.py,sha256=fyROO-47HWFDjt2qupo7A3J32VlpM-ovJnfowu92K3s,9273
+click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706
+click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961
+click/parser.py,sha256=LKyYQE9ZLj5KgIDXkrcTHQRXIggfoivX14_UVIn56YA,19067
+click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+click/shell_completion.py,sha256=Ty3VM_ts0sQhj6u7eFTiLwHPoTgcXTGEAUg2OpLqYKw,18460
+click/termui.py,sha256=H7Q8FpmPelhJ2ovOhfCRhjMtCpNyjFXryAMLZODqsdc,28324
+click/testing.py,sha256=1Qd4kS5bucn1hsNIRryd0WtTMuCpkA93grkWxT8POsU,16084
+click/types.py,sha256=TZvz3hKvBztf-Hpa2enOmP4eznSPLzijjig5b_0XMxE,36391
+click/utils.py,sha256=1476UduUNY6UePGU4m18uzVHLt1sKM2PP3yWsQhbItM,20298
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/REQUESTED b/venv/Lib/site-packages/click-8.1.7.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/WHEEL b/venv/Lib/site-packages/click-8.1.7.dist-info/WHEEL
new file mode 100644
index 0000000..2c08da0
--- /dev/null
+++ b/venv/Lib/site-packages/click-8.1.7.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.41.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/venv/Lib/site-packages/click-8.1.7.dist-info/top_level.txt b/venv/Lib/site-packages/click-8.1.7.dist-info/top_level.txt
new file mode 100644
index 0000000..dca9a90
--- /dev/null
+++ b/venv/Lib/site-packages/click-8.1.7.dist-info/top_level.txt
@@ -0,0 +1 @@
+click
diff --git a/venv/Lib/site-packages/click/__init__.py b/venv/Lib/site-packages/click/__init__.py
new file mode 100644
index 0000000..9a1dab0
--- /dev/null
+++ b/venv/Lib/site-packages/click/__init__.py
@@ -0,0 +1,73 @@
+"""
+Click is a simple Python module inspired by the stdlib 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.
+"""
+from .core import Argument as Argument
+from .core import BaseCommand as BaseCommand
+from .core import Command as Command
+from .core import CommandCollection as CommandCollection
+from .core import Context as Context
+from .core import Group as Group
+from .core import MultiCommand as MultiCommand
+from .core import Option as Option
+from .core import Parameter as Parameter
+from .decorators import argument as argument
+from .decorators import command as command
+from .decorators import confirmation_option as confirmation_option
+from .decorators import group as group
+from .decorators import help_option as help_option
+from .decorators import make_pass_decorator as make_pass_decorator
+from .decorators import option as option
+from .decorators import pass_context as pass_context
+from .decorators import pass_obj as pass_obj
+from .decorators import password_option as password_option
+from .decorators import version_option as version_option
+from .exceptions import Abort as Abort
+from .exceptions import BadArgumentUsage as BadArgumentUsage
+from .exceptions import BadOptionUsage as BadOptionUsage
+from .exceptions import BadParameter as BadParameter
+from .exceptions import ClickException as ClickException
+from .exceptions import FileError as FileError
+from .exceptions import MissingParameter as MissingParameter
+from .exceptions import NoSuchOption as NoSuchOption
+from .exceptions import UsageError as UsageError
+from .formatting import HelpFormatter as HelpFormatter
+from .formatting import wrap_text as wrap_text
+from .globals import get_current_context as get_current_context
+from .parser import OptionParser as OptionParser
+from .termui import clear as clear
+from .termui import confirm as confirm
+from .termui import echo_via_pager as echo_via_pager
+from .termui import edit as edit
+from .termui import getchar as getchar
+from .termui import launch as launch
+from .termui import pause as pause
+from .termui import progressbar as progressbar
+from .termui import prompt as prompt
+from .termui import secho as secho
+from .termui import style as style
+from .termui import unstyle as unstyle
+from .types import BOOL as BOOL
+from .types import Choice as Choice
+from .types import DateTime as DateTime
+from .types import File as File
+from .types import FLOAT as FLOAT
+from .types import FloatRange as FloatRange
+from .types import INT as INT
+from .types import IntRange as IntRange
+from .types import ParamType as ParamType
+from .types import Path as Path
+from .types import STRING as STRING
+from .types import Tuple as Tuple
+from .types import UNPROCESSED as UNPROCESSED
+from .types import UUID as UUID
+from .utils import echo as echo
+from .utils import format_filename as format_filename
+from .utils import get_app_dir as get_app_dir
+from .utils import get_binary_stream as get_binary_stream
+from .utils import get_text_stream as get_text_stream
+from .utils import open_file as open_file
+
+__version__ = "8.1.7"
diff --git a/venv/Lib/site-packages/click/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..2adee0f
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_compat.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/_compat.cpython-312.pyc
new file mode 100644
index 0000000..fc2a782
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_compat.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc
new file mode 100644
index 0000000..a732f18
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_termui_impl.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-312.pyc
new file mode 100644
index 0000000..309df84
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_textwrap.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-312.pyc
new file mode 100644
index 0000000..cf58acf
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/_winconsole.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/core.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/core.cpython-312.pyc
new file mode 100644
index 0000000..f36c160
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/core.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/decorators.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/decorators.cpython-312.pyc
new file mode 100644
index 0000000..e7f9c72
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/decorators.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/exceptions.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/exceptions.cpython-312.pyc
new file mode 100644
index 0000000..261f9dd
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/exceptions.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/formatting.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/formatting.cpython-312.pyc
new file mode 100644
index 0000000..0e1a634
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/formatting.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/globals.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/globals.cpython-312.pyc
new file mode 100644
index 0000000..0ab251a
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/globals.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/parser.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/parser.cpython-312.pyc
new file mode 100644
index 0000000..4fb19c5
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/parser.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-312.pyc
new file mode 100644
index 0000000..65483d4
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/shell_completion.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/termui.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/termui.cpython-312.pyc
new file mode 100644
index 0000000..7bd7c6f
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/termui.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/testing.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/testing.cpython-312.pyc
new file mode 100644
index 0000000..40e17f1
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/testing.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/types.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/types.cpython-312.pyc
new file mode 100644
index 0000000..728ecad
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/types.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/__pycache__/utils.cpython-312.pyc b/venv/Lib/site-packages/click/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000..eebaae0
Binary files /dev/null and b/venv/Lib/site-packages/click/__pycache__/utils.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/click/_compat.py b/venv/Lib/site-packages/click/_compat.py
new file mode 100644
index 0000000..23f8866
--- /dev/null
+++ b/venv/Lib/site-packages/click/_compat.py
@@ -0,0 +1,623 @@
+import codecs
+import io
+import os
+import re
+import sys
+import typing as t
+from weakref import WeakKeyDictionary
+
+CYGWIN = sys.platform.startswith("cygwin")
+WIN = sys.platform.startswith("win")
+auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None
+_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
+
+
+def _make_text_stream(
+ stream: t.BinaryIO,
+ encoding: t.Optional[str],
+ errors: t.Optional[str],
+ force_readable: bool = False,
+ force_writable: bool = False,
+) -> t.TextIO:
+ if encoding is None:
+ encoding = get_best_encoding(stream)
+ if errors is None:
+ errors = "replace"
+ return _NonClosingTextIOWrapper(
+ stream,
+ encoding,
+ errors,
+ line_buffering=True,
+ force_readable=force_readable,
+ force_writable=force_writable,
+ )
+
+
+def is_ascii_encoding(encoding: str) -> bool:
+ """Checks if a given encoding is ascii."""
+ try:
+ return codecs.lookup(encoding).name == "ascii"
+ except LookupError:
+ return False
+
+
+def get_best_encoding(stream: t.IO[t.Any]) -> str:
+ """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: t.BinaryIO,
+ encoding: t.Optional[str],
+ errors: t.Optional[str],
+ force_readable: bool = False,
+ force_writable: bool = False,
+ **extra: t.Any,
+ ) -> None:
+ self._stream = stream = t.cast(
+ t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
+ )
+ super().__init__(stream, encoding, errors, **extra)
+
+ def __del__(self) -> None:
+ try:
+ self.detach()
+ except Exception:
+ pass
+
+ def isatty(self) -> bool:
+ # https://bitbucket.org/pypy/pypy/issue/1803
+ return self._stream.isatty()
+
+
+class _FixupStream:
+ """The new io interface needs more from streams than streams
+ traditionally implement. As such, this fix-up code is necessary in
+ some circumstances.
+
+ The forcing of readable and writable flags are there because some tools
+ put badly patched objects on sys (one such offender are certain version
+ of jupyter notebook).
+ """
+
+ def __init__(
+ self,
+ stream: t.BinaryIO,
+ force_readable: bool = False,
+ force_writable: bool = False,
+ ):
+ self._stream = stream
+ self._force_readable = force_readable
+ self._force_writable = force_writable
+
+ def __getattr__(self, name: str) -> t.Any:
+ return getattr(self._stream, name)
+
+ def read1(self, size: int) -> bytes:
+ f = getattr(self._stream, "read1", None)
+
+ if f is not None:
+ return t.cast(bytes, f(size))
+
+ return self._stream.read(size)
+
+ def readable(self) -> bool:
+ if self._force_readable:
+ return True
+ x = getattr(self._stream, "readable", None)
+ if x is not None:
+ return t.cast(bool, x())
+ try:
+ self._stream.read(0)
+ except Exception:
+ return False
+ return True
+
+ def writable(self) -> bool:
+ if self._force_writable:
+ return True
+ x = getattr(self._stream, "writable", None)
+ if x is not None:
+ return t.cast(bool, x())
+ try:
+ self._stream.write("") # type: ignore
+ except Exception:
+ try:
+ self._stream.write(b"")
+ except Exception:
+ return False
+ return True
+
+ def seekable(self) -> bool:
+ x = getattr(self._stream, "seekable", None)
+ if x is not None:
+ return t.cast(bool, x())
+ try:
+ self._stream.seek(self._stream.tell())
+ except Exception:
+ return False
+ return True
+
+
+def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool:
+ 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: t.IO[t.Any], default: bool = False) -> bool:
+ try:
+ stream.write(b"")
+ except Exception:
+ try:
+ stream.write("")
+ return False
+ except Exception:
+ pass
+ return default
+ return True
+
+
+def _find_binary_reader(stream: t.IO[t.Any]) -> t.Optional[t.BinaryIO]:
+ # 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 t.cast(t.BinaryIO, 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 t.cast(t.BinaryIO, buf)
+
+ return None
+
+
+def _find_binary_writer(stream: t.IO[t.Any]) -> t.Optional[t.BinaryIO]:
+ # 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_writer(stream, False):
+ return t.cast(t.BinaryIO, 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 t.cast(t.BinaryIO, buf)
+
+ return None
+
+
+def _stream_is_misconfigured(stream: t.TextIO) -> bool:
+ """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_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool:
+ """A stream attribute is compatible if it is equal to the
+ desired value or the desired value is unset and the attribute
+ has a value.
+ """
+ stream_value = getattr(stream, attr, None)
+ return stream_value == value or (value is None and stream_value is not None)
+
+
+def _is_compatible_text_stream(
+ stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
+) -> bool:
+ """Check if a stream's encoding and errors attributes are
+ compatible with the desired values.
+ """
+ return _is_compat_stream_attr(
+ stream, "encoding", encoding
+ ) and _is_compat_stream_attr(stream, "errors", errors)
+
+
+def _force_correct_text_stream(
+ text_stream: t.IO[t.Any],
+ encoding: t.Optional[str],
+ errors: t.Optional[str],
+ is_binary: t.Callable[[t.IO[t.Any], bool], bool],
+ find_binary: t.Callable[[t.IO[t.Any]], t.Optional[t.BinaryIO]],
+ force_readable: bool = False,
+ force_writable: bool = False,
+) -> t.TextIO:
+ if is_binary(text_stream, False):
+ binary_reader = t.cast(t.BinaryIO, text_stream)
+ else:
+ text_stream = t.cast(t.TextIO, text_stream)
+ # If the stream looks compatible, and won't default to a
+ # misconfigured ascii encoding, return it as-is.
+ if _is_compatible_text_stream(text_stream, encoding, errors) and not (
+ encoding is None and _stream_is_misconfigured(text_stream)
+ ):
+ return text_stream
+
+ # Otherwise, get the underlying binary reader.
+ possible_binary_reader = find_binary(text_stream)
+
+ # If that's not possible, silently use the original reader
+ # and get mojibake instead of exceptions.
+ if possible_binary_reader is None:
+ return text_stream
+
+ binary_reader = possible_binary_reader
+
+ # Default errors to replace instead of strict in order to get
+ # something that works.
+ if errors is None:
+ errors = "replace"
+
+ # Wrap the binary stream in a text stream with the correct
+ # encoding parameters.
+ return _make_text_stream(
+ binary_reader,
+ encoding,
+ errors,
+ force_readable=force_readable,
+ force_writable=force_writable,
+ )
+
+
+def _force_correct_text_reader(
+ text_reader: t.IO[t.Any],
+ encoding: t.Optional[str],
+ errors: t.Optional[str],
+ force_readable: bool = False,
+) -> t.TextIO:
+ return _force_correct_text_stream(
+ text_reader,
+ encoding,
+ errors,
+ _is_binary_reader,
+ _find_binary_reader,
+ force_readable=force_readable,
+ )
+
+
+def _force_correct_text_writer(
+ text_writer: t.IO[t.Any],
+ encoding: t.Optional[str],
+ errors: t.Optional[str],
+ force_writable: bool = False,
+) -> t.TextIO:
+ return _force_correct_text_stream(
+ text_writer,
+ encoding,
+ errors,
+ _is_binary_writer,
+ _find_binary_writer,
+ force_writable=force_writable,
+ )
+
+
+def get_binary_stdin() -> t.BinaryIO:
+ 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() -> t.BinaryIO:
+ 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() -> t.BinaryIO:
+ 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: t.Optional[str] = None, errors: t.Optional[str] = None
+) -> t.TextIO:
+ 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, force_readable=True)
+
+
+def get_text_stdout(
+ encoding: t.Optional[str] = None, errors: t.Optional[str] = None
+) -> t.TextIO:
+ 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, force_writable=True)
+
+
+def get_text_stderr(
+ encoding: t.Optional[str] = None, errors: t.Optional[str] = None
+) -> t.TextIO:
+ 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, force_writable=True)
+
+
+def _wrap_io_open(
+ file: t.Union[str, "os.PathLike[str]", int],
+ mode: str,
+ encoding: t.Optional[str],
+ errors: t.Optional[str],
+) -> t.IO[t.Any]:
+ """Handles not passing ``encoding`` and ``errors`` in binary mode."""
+ if "b" in mode:
+ return open(file, mode)
+
+ return open(file, mode, encoding=encoding, errors=errors)
+
+
+def open_stream(
+ filename: "t.Union[str, os.PathLike[str]]",
+ mode: str = "r",
+ encoding: t.Optional[str] = None,
+ errors: t.Optional[str] = "strict",
+ atomic: bool = False,
+) -> t.Tuple[t.IO[t.Any], bool]:
+ binary = "b" in mode
+ filename = os.fspath(filename)
+
+ # Standard streams first. These are simple because they ignore the
+ # atomic flag. Use fsdecode to handle Path("-").
+ if os.fsdecode(filename) == "-":
+ if any(m in mode for m in ["w", "a", "x"]):
+ if binary:
+ return get_binary_stdout(), False
+ return get_text_stdout(encoding=encoding, errors=errors), False
+ if binary:
+ 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:
+ return _wrap_io_open(filename, mode, encoding, 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 errno
+ import random
+
+ try:
+ perm: t.Optional[int] = os.stat(filename).st_mode
+ except OSError:
+ perm = None
+
+ flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
+
+ if binary:
+ flags |= getattr(os, "O_BINARY", 0)
+
+ while True:
+ tmp_filename = os.path.join(
+ os.path.dirname(filename),
+ f".__atomic-write{random.randrange(1 << 32):08x}",
+ )
+ try:
+ fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
+ break
+ except OSError as e:
+ if e.errno == errno.EEXIST or (
+ os.name == "nt"
+ and e.errno == errno.EACCES
+ and os.path.isdir(e.filename)
+ and os.access(e.filename, os.W_OK)
+ ):
+ continue
+ raise
+
+ if perm is not None:
+ os.chmod(tmp_filename, perm) # in case perm includes bits in umask
+
+ f = _wrap_io_open(fd, mode, encoding, errors)
+ af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
+ return t.cast(t.IO[t.Any], af), True
+
+
+class _AtomicFile:
+ def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None:
+ self._f = f
+ self._tmp_filename = tmp_filename
+ self._real_filename = real_filename
+ self.closed = False
+
+ @property
+ def name(self) -> str:
+ return self._real_filename
+
+ def close(self, delete: bool = False) -> None:
+ if self.closed:
+ return
+ self._f.close()
+ os.replace(self._tmp_filename, self._real_filename)
+ self.closed = True
+
+ def __getattr__(self, name: str) -> t.Any:
+ return getattr(self._f, name)
+
+ def __enter__(self) -> "_AtomicFile":
+ return self
+
+ def __exit__(self, exc_type: t.Optional[t.Type[BaseException]], *_: t.Any) -> None:
+ self.close(delete=exc_type is not None)
+
+ def __repr__(self) -> str:
+ return repr(self._f)
+
+
+def strip_ansi(value: str) -> str:
+ return _ansi_re.sub("", value)
+
+
+def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool:
+ while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
+ stream = stream._stream
+
+ return stream.__class__.__module__.startswith("ipykernel.")
+
+
+def should_strip_ansi(
+ stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None
+) -> bool:
+ if color is None:
+ if stream is None:
+ stream = sys.stdin
+ return not isatty(stream) and not _is_jupyter_kernel_output(stream)
+ return not color
+
+
+# On Windows, wrap the output streams with colorama to support ANSI
+# color codes.
+# NOTE: double check is needed so mypy does not analyze this on Linux
+if sys.platform.startswith("win") and WIN:
+ from ._winconsole import _get_windows_console_stream
+
+ def _get_argv_encoding() -> str:
+ import locale
+
+ return locale.getpreferredencoding()
+
+ _ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
+
+ def auto_wrap_for_ansi( # noqa: F811
+ stream: t.TextIO, color: t.Optional[bool] = None
+ ) -> t.TextIO:
+ """Support ANSI color and style codes on Windows by wrapping a
+ stream with colorama.
+ """
+ try:
+ cached = _ansi_stream_wrappers.get(stream)
+ except Exception:
+ cached = None
+
+ if cached is not None:
+ return cached
+
+ import colorama
+
+ strip = should_strip_ansi(stream, color)
+ ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
+ rv = t.cast(t.TextIO, ansi_wrapper.stream)
+ _write = rv.write
+
+ def _safe_write(s):
+ try:
+ return _write(s)
+ except BaseException:
+ ansi_wrapper.reset_all()
+ raise
+
+ rv.write = _safe_write
+
+ try:
+ _ansi_stream_wrappers[stream] = rv
+ except Exception:
+ pass
+
+ return rv
+
+else:
+
+ def _get_argv_encoding() -> str:
+ return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding()
+
+ def _get_windows_console_stream(
+ f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
+ ) -> t.Optional[t.TextIO]:
+ return None
+
+
+def term_len(x: str) -> int:
+ return len(strip_ansi(x))
+
+
+def isatty(stream: t.IO[t.Any]) -> bool:
+ try:
+ return stream.isatty()
+ except Exception:
+ return False
+
+
+def _make_cached_stream_func(
+ src_func: t.Callable[[], t.Optional[t.TextIO]],
+ wrapper_func: t.Callable[[], t.TextIO],
+) -> t.Callable[[], t.Optional[t.TextIO]]:
+ cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
+
+ def func() -> t.Optional[t.TextIO]:
+ stream = src_func()
+
+ if stream is None:
+ return None
+
+ 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: t.Mapping[str, t.Callable[[], t.BinaryIO]] = {
+ "stdin": get_binary_stdin,
+ "stdout": get_binary_stdout,
+ "stderr": get_binary_stderr,
+}
+
+text_streams: t.Mapping[
+ str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO]
+] = {
+ "stdin": get_text_stdin,
+ "stdout": get_text_stdout,
+ "stderr": get_text_stderr,
+}
diff --git a/venv/Lib/site-packages/click/_termui_impl.py b/venv/Lib/site-packages/click/_termui_impl.py
new file mode 100644
index 0000000..f744657
--- /dev/null
+++ b/venv/Lib/site-packages/click/_termui_impl.py
@@ -0,0 +1,739 @@
+"""
+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.
+"""
+import contextlib
+import math
+import os
+import sys
+import time
+import typing as t
+from gettext import gettext as _
+from io import StringIO
+from types import TracebackType
+
+from ._compat import _default_text_stdout
+from ._compat import CYGWIN
+from ._compat import get_best_encoding
+from ._compat import isatty
+from ._compat import open_stream
+from ._compat import strip_ansi
+from ._compat import term_len
+from ._compat import WIN
+from .exceptions import ClickException
+from .utils import echo
+
+V = t.TypeVar("V")
+
+if os.name == "nt":
+ BEFORE_BAR = "\r"
+ AFTER_BAR = "\n"
+else:
+ BEFORE_BAR = "\r\033[?25l"
+ AFTER_BAR = "\033[?25h\n"
+
+
+class ProgressBar(t.Generic[V]):
+ def __init__(
+ self,
+ iterable: t.Optional[t.Iterable[V]],
+ length: t.Optional[int] = None,
+ fill_char: str = "#",
+ empty_char: str = " ",
+ bar_template: str = "%(bar)s",
+ info_sep: str = " ",
+ show_eta: bool = True,
+ show_percent: t.Optional[bool] = None,
+ show_pos: bool = False,
+ item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
+ label: t.Optional[str] = None,
+ file: t.Optional[t.TextIO] = None,
+ color: t.Optional[bool] = None,
+ update_min_steps: int = 1,
+ width: int = 30,
+ ) -> None:
+ 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: str = label or ""
+
+ if file is None:
+ file = _default_text_stdout()
+
+ # There are no standard streams attached to write to. For example,
+ # pythonw on Windows.
+ if file is None:
+ file = StringIO()
+
+ self.file = file
+ self.color = color
+ self.update_min_steps = update_min_steps
+ self._completed_intervals = 0
+ self.width: int = width
+ self.autowidth: bool = width == 0
+
+ if length is None:
+ from operator import length_hint
+
+ length = length_hint(iterable, -1)
+
+ if length == -1:
+ length = None
+ if iterable is None:
+ if length is None:
+ raise TypeError("iterable or length is required")
+ iterable = t.cast(t.Iterable[V], range(length))
+ self.iter: t.Iterable[V] = iter(iterable)
+ self.length = length
+ self.pos = 0
+ self.avg: t.List[float] = []
+ self.last_eta: float
+ self.start: float
+ self.start = self.last_eta = time.time()
+ self.eta_known: bool = False
+ self.finished: bool = False
+ self.max_width: t.Optional[int] = None
+ self.entered: bool = False
+ self.current_item: t.Optional[V] = None
+ self.is_hidden: bool = not isatty(self.file)
+ self._last_line: t.Optional[str] = None
+
+ def __enter__(self) -> "ProgressBar[V]":
+ self.entered = True
+ self.render_progress()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: t.Optional[t.Type[BaseException]],
+ exc_value: t.Optional[BaseException],
+ tb: t.Optional[TracebackType],
+ ) -> None:
+ self.render_finish()
+
+ def __iter__(self) -> t.Iterator[V]:
+ if not self.entered:
+ raise RuntimeError("You need to use progress bars in a with block.")
+ self.render_progress()
+ return self.generator()
+
+ def __next__(self) -> V:
+ # Iteration is defined in terms of a generator function,
+ # returned by iter(self); use that to define next(). This works
+ # because `self.iter` is an iterable consumed by that generator,
+ # so it is re-entry safe. Calling `next(self.generator())`
+ # twice works and does "what you want".
+ return next(iter(self))
+
+ def render_finish(self) -> None:
+ if self.is_hidden:
+ return
+ self.file.write(AFTER_BAR)
+ self.file.flush()
+
+ @property
+ def pct(self) -> float:
+ if self.finished:
+ return 1.0
+ return min(self.pos / (float(self.length or 1) or 1), 1.0)
+
+ @property
+ def time_per_iteration(self) -> float:
+ if not self.avg:
+ return 0.0
+ return sum(self.avg) / float(len(self.avg))
+
+ @property
+ def eta(self) -> float:
+ if self.length is not None and not self.finished:
+ return self.time_per_iteration * (self.length - self.pos)
+ return 0.0
+
+ def format_eta(self) -> str:
+ if self.eta_known:
+ t = int(self.eta)
+ seconds = t % 60
+ t //= 60
+ minutes = t % 60
+ t //= 60
+ hours = t % 24
+ t //= 24
+ if t > 0:
+ return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
+ else:
+ return f"{hours:02}:{minutes:02}:{seconds:02}"
+ return ""
+
+ def format_pos(self) -> str:
+ pos = str(self.pos)
+ if self.length is not None:
+ pos += f"/{self.length}"
+ return pos
+
+ def format_pct(self) -> str:
+ return f"{int(self.pct * 100): 4}%"[1:]
+
+ def format_bar(self) -> str:
+ if self.length is not None:
+ bar_length = int(self.pct * self.width)
+ bar = self.fill_char * bar_length
+ bar += self.empty_char * (self.width - bar_length)
+ elif self.finished:
+ bar = self.fill_char * self.width
+ else:
+ chars = list(self.empty_char * (self.width or 1))
+ if self.time_per_iteration != 0:
+ chars[
+ int(
+ (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
+ * self.width
+ )
+ ] = self.fill_char
+ bar = "".join(chars)
+ return bar
+
+ def format_progress_line(self) -> str:
+ show_percent = self.show_percent
+
+ info_bits = []
+ if self.length is not None and show_percent is None:
+ show_percent = not self.show_pos
+
+ 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": self.format_bar(),
+ "info": self.info_sep.join(info_bits),
+ }
+ ).rstrip()
+
+ def render_progress(self) -> None:
+ import shutil
+
+ if self.is_hidden:
+ # Only output the label as it changes if the output is not a
+ # TTY. Use file=stderr if you expect to be piping stdout.
+ if self._last_line != self.label:
+ self._last_line = self.label
+ echo(self.label, file=self.file, color=self.color)
+
+ return
+
+ 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, shutil.get_terminal_size().columns - clutter_length)
+ if new_width < old_width:
+ buf.append(BEFORE_BAR)
+ buf.append(" " * self.max_width) # type: ignore
+ 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=False)
+ self.file.flush()
+
+ def make_step(self, n_steps: int) -> None:
+ self.pos += n_steps
+ if self.length is not None and self.pos >= self.length:
+ self.finished = True
+
+ if (time.time() - self.last_eta) < 1.0:
+ return
+
+ self.last_eta = time.time()
+
+ # self.avg is a rolling list of length <= 7 of steps where steps are
+ # defined as time elapsed divided by the total progress through
+ # self.length.
+ if self.pos:
+ step = (time.time() - self.start) / self.pos
+ else:
+ step = time.time() - self.start
+
+ self.avg = self.avg[-6:] + [step]
+
+ self.eta_known = self.length is not None
+
+ def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None:
+ """Update the progress bar by advancing a specified number of
+ steps, and optionally set the ``current_item`` for this new
+ position.
+
+ :param n_steps: Number of steps to advance.
+ :param current_item: Optional item to set as ``current_item``
+ for the updated position.
+
+ .. versionchanged:: 8.0
+ Added the ``current_item`` optional parameter.
+
+ .. versionchanged:: 8.0
+ Only render when the number of steps meets the
+ ``update_min_steps`` threshold.
+ """
+ if current_item is not None:
+ self.current_item = current_item
+
+ self._completed_intervals += n_steps
+
+ if self._completed_intervals >= self.update_min_steps:
+ self.make_step(self._completed_intervals)
+ self.render_progress()
+ self._completed_intervals = 0
+
+ def finish(self) -> None:
+ self.eta_known = False
+ self.current_item = None
+ self.finished = True
+
+ def generator(self) -> t.Iterator[V]:
+ """Return a generator which yields the items added to the bar
+ during construction, and updates the progress bar *after* the
+ yielded block returns.
+ """
+ # WARNING: the iterator interface for `ProgressBar` relies on
+ # this and only works because this is a simple generator which
+ # doesn't create or manage additional state. If this function
+ # changes, the impact should be evaluated both against
+ # `iter(bar)` and `next(bar)`. `next()` in particular may call
+ # `self.generator()` repeatedly, and this must remain safe in
+ # order for that interface to work.
+ if not self.entered:
+ raise RuntimeError("You need to use progress bars in a with block.")
+
+ if self.is_hidden:
+ yield from self.iter
+ else:
+ for rv in self.iter:
+ self.current_item = rv
+
+ # This allows show_item_func to be updated before the
+ # item is processed. Only trigger at the beginning of
+ # the update interval.
+ if self._completed_intervals == 0:
+ self.render_progress()
+
+ yield rv
+ self.update(1)
+
+ self.finish()
+ self.render_progress()
+
+
+def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None:
+ """Decide what method to use for paging through text."""
+ stdout = _default_text_stdout()
+
+ # There are no standard streams attached to write to. For example,
+ # pythonw on Windows.
+ if stdout is None:
+ stdout = StringIO()
+
+ if not isatty(sys.stdin) or not isatty(stdout):
+ return _nullpager(stdout, generator, color)
+ pager_cmd = (os.environ.get("PAGER", None) or "").strip()
+ if pager_cmd:
+ if WIN:
+ return _tempfilepager(generator, pager_cmd, color)
+ return _pipepager(generator, pager_cmd, color)
+ if os.environ.get("TERM") in ("dumb", "emacs"):
+ return _nullpager(stdout, generator, color)
+ if WIN or sys.platform.startswith("os2"):
+ return _tempfilepager(generator, "more <", color)
+ if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0:
+ return _pipepager(generator, "less", color)
+
+ import tempfile
+
+ fd, filename = tempfile.mkstemp()
+ os.close(fd)
+ try:
+ if hasattr(os, "system") and os.system(f'more "{filename}"') == 0:
+ return _pipepager(generator, "more", color)
+ return _nullpager(stdout, generator, color)
+ finally:
+ os.unlink(filename)
+
+
+def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None:
+ """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 = f"{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
+
+ c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env)
+ stdin = t.cast(t.BinaryIO, c.stdin)
+ encoding = get_best_encoding(stdin)
+ try:
+ for text in generator:
+ if not color:
+ text = strip_ansi(text)
+
+ stdin.write(text.encode(encoding, "replace"))
+ except (OSError, KeyboardInterrupt):
+ pass
+ else:
+ stdin.close()
+
+ # 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(
+ generator: t.Iterable[str], cmd: str, color: t.Optional[bool]
+) -> None:
+ """Page through text by invoking a program on a temporary file."""
+ import tempfile
+
+ fd, filename = tempfile.mkstemp()
+ # TODO: This never terminates if the passed generator never terminates.
+ text = "".join(generator)
+ 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(f'{cmd} "{filename}"')
+ finally:
+ os.close(fd)
+ os.unlink(filename)
+
+
+def _nullpager(
+ stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool]
+) -> None:
+ """Simply print unformatted text. This is the ultimate fallback."""
+ for text in generator:
+ if not color:
+ text = strip_ansi(text)
+ stream.write(text)
+
+
+class Editor:
+ def __init__(
+ self,
+ editor: t.Optional[str] = None,
+ env: t.Optional[t.Mapping[str, str]] = None,
+ require_save: bool = True,
+ extension: str = ".txt",
+ ) -> None:
+ self.editor = editor
+ self.env = env
+ self.require_save = require_save
+ self.extension = extension
+
+ def get_editor(self) -> str:
+ 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 "sensible-editor", "vim", "nano":
+ if os.system(f"which {editor} >/dev/null 2>&1") == 0:
+ return editor
+ return "vi"
+
+ def edit_file(self, filename: str) -> None:
+ import subprocess
+
+ editor = self.get_editor()
+ environ: t.Optional[t.Dict[str, str]] = None
+
+ if self.env:
+ environ = os.environ.copy()
+ environ.update(self.env)
+
+ try:
+ c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True)
+ exit_code = c.wait()
+ if exit_code != 0:
+ raise ClickException(
+ _("{editor}: Editing failed").format(editor=editor)
+ )
+ except OSError as e:
+ raise ClickException(
+ _("{editor}: Editing failed: {e}").format(editor=editor, e=e)
+ ) from e
+
+ def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]:
+ import tempfile
+
+ if not text:
+ data = b""
+ elif isinstance(text, (bytes, bytearray)):
+ data = text
+ else:
+ if text and not text.endswith("\n"):
+ text += "\n"
+
+ if WIN:
+ data = text.replace("\n", "\r\n").encode("utf-8-sig")
+ else:
+ data = text.encode("utf-8")
+
+ fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
+ f: t.BinaryIO
+
+ try:
+ with os.fdopen(fd, "wb") as f:
+ f.write(data)
+
+ # If the filesystem resolution is 1 second, like Mac OS
+ # 10.12 Extended, or 2 seconds, like FAT32, and the editor
+ # closes very fast, require_save can fail. Set the modified
+ # time to be 2 seconds in the past to work around this.
+ os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
+ # Depending on the resolution, the exact value might not be
+ # recorded, so get the new recorded value.
+ timestamp = os.path.getmtime(name)
+
+ self.edit_file(name)
+
+ if self.require_save and os.path.getmtime(name) == timestamp:
+ return None
+
+ with open(name, "rb") as f:
+ rv = f.read()
+
+ if isinstance(text, (bytes, bytearray)):
+ return rv
+
+ return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore
+ finally:
+ os.unlink(name)
+
+
+def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
+ import subprocess
+
+ def _unquote_file(url: str) -> str:
+ from urllib.parse import unquote
+
+ if url.startswith("file://"):
+ url = 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.replace('"', ""))
+ args = f'explorer /select,"{url}"'
+ else:
+ url = url.replace('"', "")
+ wait_str = "/WAIT" if wait else ""
+ args = f'start {wait_str} "" "{url}"'
+ return os.system(args)
+ elif CYGWIN:
+ if locate:
+ url = os.path.dirname(_unquote_file(url).replace('"', ""))
+ args = f'cygstart "{url}"'
+ else:
+ url = url.replace('"', "")
+ wait_str = "-w" if wait else ""
+ args = f'cygstart {wait_str} "{url}"'
+ 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: str) -> t.Optional[BaseException]:
+ if ch == "\x03":
+ raise KeyboardInterrupt()
+
+ if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
+ raise EOFError()
+
+ if ch == "\x1a" and WIN: # Windows, Ctrl+Z
+ raise EOFError()
+
+ return None
+
+
+if WIN:
+ import msvcrt
+
+ @contextlib.contextmanager
+ def raw_terminal() -> t.Iterator[int]:
+ yield -1
+
+ def getchar(echo: bool) -> str:
+ # The function `getch` will return a bytes object corresponding to
+ # the pressed character. Since Windows 10 build 1803, it will also
+ # return \x00 when called a second time after pressing a regular key.
+ #
+ # `getwch` does not share this probably-bugged behavior. Moreover, it
+ # returns a Unicode object by default, which is what we want.
+ #
+ # Either of these functions will return \x00 or \xe0 to indicate
+ # a special key, and you need to call the same function again to get
+ # the "rest" of the code. The fun part is that \u00e0 is
+ # "latin small letter a with grave", so if you type that on a French
+ # keyboard, you _also_ get a \xe0.
+ # E.g., consider the Up arrow. This returns \xe0 and then \x48. The
+ # resulting Unicode string reads as "a with grave" + "capital H".
+ # This is indistinguishable from when the user actually types
+ # "a with grave" and then "capital H".
+ #
+ # When \xe0 is returned, we assume it's part of a special-key sequence
+ # and call `getwch` again, but that means that when the user types
+ # the \u00e0 character, `getchar` doesn't return until a second
+ # character is typed.
+ # The alternative is returning immediately, but that would mess up
+ # cross-platform handling of arrow keys and others that start with
+ # \xe0. Another option is using `getch`, but then we can't reliably
+ # read non-ASCII characters, because return values of `getch` are
+ # limited to the current 8-bit codepage.
+ #
+ # Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
+ # is doing the right thing in more situations than with `getch`.
+ func: t.Callable[[], str]
+
+ if echo:
+ func = msvcrt.getwche # type: ignore
+ else:
+ func = msvcrt.getwch # type: ignore
+
+ rv = func()
+
+ if rv in ("\x00", "\xe0"):
+ # \x00 and \xe0 are control characters that indicate special key,
+ # see above.
+ rv += func()
+
+ _translate_ch_to_exc(rv)
+ return rv
+
+else:
+ import tty
+ import termios
+
+ @contextlib.contextmanager
+ def raw_terminal() -> t.Iterator[int]:
+ f: t.Optional[t.TextIO]
+ fd: int
+
+ 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)
+ yield fd
+ finally:
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
+ sys.stdout.flush()
+
+ if f is not None:
+ f.close()
+ except termios.error:
+ pass
+
+ def getchar(echo: bool) -> str:
+ with raw_terminal() as fd:
+ ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
+
+ if echo and isatty(sys.stdout):
+ sys.stdout.write(ch)
+
+ _translate_ch_to_exc(ch)
+ return ch
diff --git a/venv/Lib/site-packages/click/_textwrap.py b/venv/Lib/site-packages/click/_textwrap.py
new file mode 100644
index 0000000..b47dcbd
--- /dev/null
+++ b/venv/Lib/site-packages/click/_textwrap.py
@@ -0,0 +1,49 @@
+import textwrap
+import typing as t
+from contextlib import contextmanager
+
+
+class TextWrapper(textwrap.TextWrapper):
+ def _handle_long_word(
+ self,
+ reversed_chunks: t.List[str],
+ cur_line: t.List[str],
+ cur_len: int,
+ width: int,
+ ) -> None:
+ 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: str) -> t.Iterator[None]:
+ 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: str) -> str:
+ rv = []
+
+ for idx, line in enumerate(text.splitlines()):
+ indent = self.initial_indent
+
+ if idx > 0:
+ indent = self.subsequent_indent
+
+ rv.append(f"{indent}{line}")
+
+ return "\n".join(rv)
diff --git a/venv/Lib/site-packages/click/_winconsole.py b/venv/Lib/site-packages/click/_winconsole.py
new file mode 100644
index 0000000..6b20df3
--- /dev/null
+++ b/venv/Lib/site-packages/click/_winconsole.py
@@ -0,0 +1,279 @@
+# 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 prompt.
+import io
+import sys
+import time
+import typing as t
+from ctypes import byref
+from ctypes import c_char
+from ctypes import c_char_p
+from ctypes import c_int
+from ctypes import c_ssize_t
+from ctypes import c_ulong
+from ctypes import c_void_p
+from ctypes import POINTER
+from ctypes import py_object
+from ctypes import Structure
+from ctypes.wintypes import DWORD
+from ctypes.wintypes import HANDLE
+from ctypes.wintypes import LPCWSTR
+from ctypes.wintypes import LPWSTR
+
+from ._compat import _NonClosingTextIOWrapper
+
+assert sys.platform == "win32"
+import msvcrt # noqa: E402
+from ctypes import windll # noqa: E402
+from ctypes import WINFUNCTYPE # noqa: E402
+
+c_ssize_p = POINTER(c_ssize_t)
+
+kernel32 = windll.kernel32
+GetStdHandle = kernel32.GetStdHandle
+ReadConsoleW = kernel32.ReadConsoleW
+WriteConsoleW = kernel32.WriteConsoleW
+GetConsoleMode = kernel32.GetConsoleMode
+GetLastError = kernel32.GetLastError
+GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
+CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
+ ("CommandLineToArgvW", windll.shell32)
+)
+LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
+
+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
+
+try:
+ from ctypes import pythonapi
+except ImportError:
+ # On PyPy we cannot get buffers so our ability to operate here is
+ # severely limited.
+ get_buffer = None
+else:
+
+ class Py_buffer(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),
+ ]
+
+ PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
+ PyBuffer_Release = pythonapi.PyBuffer_Release
+
+ 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):
+ super().isatty()
+ 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(
+ HANDLE(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(f"Windows error: {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 f"Windows error {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(
+ HANDLE(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:
+ def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
+ self._text_stream = text_stream
+ self.buffer = byte_stream
+
+ @property
+ def name(self) -> str:
+ return self.buffer.name
+
+ def write(self, x: t.AnyStr) -> int:
+ if isinstance(x, str):
+ return self._text_stream.write(x)
+ try:
+ self.flush()
+ except Exception:
+ pass
+ return self.buffer.write(x)
+
+ def writelines(self, lines: t.Iterable[t.AnyStr]) -> None:
+ for line in lines:
+ self.write(line)
+
+ def __getattr__(self, name: str) -> t.Any:
+ return getattr(self._text_stream, name)
+
+ def isatty(self) -> bool:
+ return self.buffer.isatty()
+
+ def __repr__(self):
+ return f""
+
+
+def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
+ text_stream = _NonClosingTextIOWrapper(
+ io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
+ "utf-16-le",
+ "strict",
+ line_buffering=True,
+ )
+ return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
+
+
+def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
+ text_stream = _NonClosingTextIOWrapper(
+ io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
+ "utf-16-le",
+ "strict",
+ line_buffering=True,
+ )
+ return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
+
+
+def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
+ text_stream = _NonClosingTextIOWrapper(
+ io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
+ "utf-16-le",
+ "strict",
+ line_buffering=True,
+ )
+ return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
+
+
+_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
+ 0: _get_text_stdin,
+ 1: _get_text_stdout,
+ 2: _get_text_stderr,
+}
+
+
+def _is_console(f: t.TextIO) -> bool:
+ if not hasattr(f, "fileno"):
+ return False
+
+ try:
+ fileno = f.fileno()
+ except (OSError, io.UnsupportedOperation):
+ return False
+
+ handle = msvcrt.get_osfhandle(fileno)
+ return bool(GetConsoleMode(handle, byref(DWORD())))
+
+
+def _get_windows_console_stream(
+ f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
+) -> t.Optional[t.TextIO]:
+ if (
+ get_buffer is not None
+ and encoding in {"utf-16-le", None}
+ and errors in {"strict", None}
+ and _is_console(f)
+ ):
+ func = _stream_factories.get(f.fileno())
+ if func is not None:
+ b = getattr(f, "buffer", None)
+
+ if b is None:
+ return None
+
+ return func(b)
diff --git a/venv/Lib/site-packages/click/core.py b/venv/Lib/site-packages/click/core.py
new file mode 100644
index 0000000..cc65e89
--- /dev/null
+++ b/venv/Lib/site-packages/click/core.py
@@ -0,0 +1,3042 @@
+import enum
+import errno
+import inspect
+import os
+import sys
+import typing as t
+from collections import abc
+from contextlib import contextmanager
+from contextlib import ExitStack
+from functools import update_wrapper
+from gettext import gettext as _
+from gettext import ngettext
+from itertools import repeat
+from types import TracebackType
+
+from . import types
+from .exceptions import Abort
+from .exceptions import BadParameter
+from .exceptions import ClickException
+from .exceptions import Exit
+from .exceptions import MissingParameter
+from .exceptions import UsageError
+from .formatting import HelpFormatter
+from .formatting import join_options
+from .globals import pop_context
+from .globals import push_context
+from .parser import _flag_needs_value
+from .parser import OptionParser
+from .parser import split_opt
+from .termui import confirm
+from .termui import prompt
+from .termui import style
+from .utils import _detect_program_name
+from .utils import _expand_args
+from .utils import echo
+from .utils import make_default_short_help
+from .utils import make_str
+from .utils import PacifyFlushWrapper
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+ from .shell_completion import CompletionItem
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+V = t.TypeVar("V")
+
+
+def _complete_visible_commands(
+ ctx: "Context", incomplete: str
+) -> t.Iterator[t.Tuple[str, "Command"]]:
+ """List all the subcommands of a group that start with the
+ incomplete value and aren't hidden.
+
+ :param ctx: Invocation context for the group.
+ :param incomplete: Value being completed. May be empty.
+ """
+ multi = t.cast(MultiCommand, ctx.command)
+
+ for name in multi.list_commands(ctx):
+ if name.startswith(incomplete):
+ command = multi.get_command(ctx, name)
+
+ if command is not None and not command.hidden:
+ yield name, command
+
+
+def _check_multicommand(
+ base_command: "MultiCommand", cmd_name: str, cmd: "Command", register: bool = False
+) -> None:
+ if not base_command.chain or not isinstance(cmd, MultiCommand):
+ return
+ if register:
+ hint = (
+ "It is not possible to add multi commands as children to"
+ " another multi command that is in chain mode."
+ )
+ else:
+ hint = (
+ "Found a multi command as subcommand to a multi command"
+ " that is in chain mode. This is not supported."
+ )
+ raise RuntimeError(
+ f"{hint}. Command {base_command.name!r} is set to chain and"
+ f" {cmd_name!r} was added as a subcommand but it in itself is a"
+ f" multi command. ({cmd_name!r} is a {type(cmd).__name__}"
+ f" within a chained {type(base_command).__name__} named"
+ f" {base_command.name!r})."
+ )
+
+
+def batch(iterable: t.Iterable[V], batch_size: int) -> t.List[t.Tuple[V, ...]]:
+ return list(zip(*repeat(iter(iterable), batch_size)))
+
+
+@contextmanager
+def augment_usage_errors(
+ ctx: "Context", param: t.Optional["Parameter"] = None
+) -> t.Iterator[None]:
+ """Context manager that attaches extra information to exceptions."""
+ try:
+ yield
+ except BadParameter as e:
+ if e.ctx is None:
+ e.ctx = ctx
+ if param is not None and e.param is None:
+ e.param = param
+ raise
+ except UsageError as e:
+ if e.ctx is None:
+ e.ctx = ctx
+ raise
+
+
+def iter_params_for_processing(
+ invocation_order: t.Sequence["Parameter"],
+ declaration_order: t.Sequence["Parameter"],
+) -> t.List["Parameter"]:
+ """Given a sequence of parameters in the order as should be considered
+ for processing and an iterable of parameters that exist, this returns
+ a list in the correct order as they should be processed.
+ """
+
+ def sort_key(item: "Parameter") -> t.Tuple[bool, float]:
+ try:
+ idx: float = invocation_order.index(item)
+ except ValueError:
+ idx = float("inf")
+
+ return not item.is_eager, idx
+
+ return sorted(declaration_order, key=sort_key)
+
+
+class ParameterSource(enum.Enum):
+ """This is an :class:`~enum.Enum` that indicates the source of a
+ parameter's value.
+
+ Use :meth:`click.Context.get_parameter_source` to get the
+ source for a parameter by name.
+
+ .. versionchanged:: 8.0
+ Use :class:`~enum.Enum` and drop the ``validate`` method.
+
+ .. versionchanged:: 8.0
+ Added the ``PROMPT`` value.
+ """
+
+ COMMANDLINE = enum.auto()
+ """The value was provided by the command line args."""
+ ENVIRONMENT = enum.auto()
+ """The value was provided with an environment variable."""
+ DEFAULT = enum.auto()
+ """Used the default specified by the parameter."""
+ DEFAULT_MAP = enum.auto()
+ """Used a default provided by :attr:`Context.default_map`."""
+ PROMPT = enum.auto()
+ """Used a prompt to confirm a default or provide a value."""
+
+
+class Context:
+ """The context is a special internal object that holds state relevant
+ for the script execution at every single level. It's normally invisible
+ to commands unless they opt-in to getting access to it.
+
+ The context is useful as it can pass internal objects around and can
+ control special execution features such as reading data from
+ environment variables.
+
+ A context can be used as context manager in which case it will call
+ :meth:`close` on teardown.
+
+ :param command: the command class for this context.
+ :param parent: the parent context.
+ :param info_name: the info name for this invocation. Generally this
+ is the most descriptive name for the script or
+ command. For the toplevel script it is usually
+ the name of the script, for commands below it it's
+ the name of the script.
+ :param obj: an arbitrary object of user data.
+ :param auto_envvar_prefix: the prefix to use for automatic environment
+ variables. If this is `None` then reading
+ from environment variables is disabled. This
+ does not affect manually set environment
+ variables which are always read.
+ :param default_map: a dictionary (like object) with default values
+ for parameters.
+ :param terminal_width: the width of the terminal. The default is
+ inherit from parent context. If no context
+ defines the terminal width then auto
+ detection will be applied.
+ :param max_content_width: the maximum width for content rendered by
+ Click (this currently only affects help
+ pages). This defaults to 80 characters if
+ not overridden. In other words: even if the
+ terminal is larger than that, Click will not
+ format things wider than 80 characters by
+ default. In addition to that, formatters might
+ add some safety mapping on the right.
+ :param resilient_parsing: if this flag is enabled then Click will
+ parse without any interactivity or callback
+ invocation. Default values will also be
+ ignored. This is useful for implementing
+ things such as completion support.
+ :param allow_extra_args: if this is set to `True` then extra arguments
+ at the end will not raise an error and will be
+ kept on the context. The default is to inherit
+ from the command.
+ :param allow_interspersed_args: if this is set to `False` then options
+ and arguments cannot be mixed. The
+ default is to inherit from the command.
+ :param ignore_unknown_options: instructs click to ignore options it does
+ not know and keeps them for later
+ processing.
+ :param help_option_names: optionally a list of strings that define how
+ the default help parameter is named. The
+ default is ``['--help']``.
+ :param token_normalize_func: an optional function that is used to
+ normalize tokens (options, choices,
+ etc.). This for instance can be used to
+ implement case insensitive behavior.
+ :param color: controls if the terminal supports ANSI colors or not. The
+ default is autodetection. This is only needed if ANSI
+ codes are used in texts that Click prints which is by
+ default not the case. This for instance would affect
+ help output.
+ :param show_default: Show the default value for commands. If this
+ value is not set, it defaults to the value from the parent
+ context. ``Command.show_default`` overrides this default for the
+ specific command.
+
+ .. versionchanged:: 8.1
+ The ``show_default`` parameter is overridden by
+ ``Command.show_default``, instead of the other way around.
+
+ .. versionchanged:: 8.0
+ The ``show_default`` parameter defaults to the value from the
+ parent context.
+
+ .. versionchanged:: 7.1
+ Added the ``show_default`` parameter.
+
+ .. versionchanged:: 4.0
+ Added the ``color``, ``ignore_unknown_options``, and
+ ``max_content_width`` parameters.
+
+ .. versionchanged:: 3.0
+ Added the ``allow_extra_args`` and ``allow_interspersed_args``
+ parameters.
+
+ .. versionchanged:: 2.0
+ Added the ``resilient_parsing``, ``help_option_names``, and
+ ``token_normalize_func`` parameters.
+ """
+
+ #: The formatter class to create with :meth:`make_formatter`.
+ #:
+ #: .. versionadded:: 8.0
+ formatter_class: t.Type["HelpFormatter"] = HelpFormatter
+
+ def __init__(
+ self,
+ command: "Command",
+ parent: t.Optional["Context"] = None,
+ info_name: t.Optional[str] = None,
+ obj: t.Optional[t.Any] = None,
+ auto_envvar_prefix: t.Optional[str] = None,
+ default_map: t.Optional[t.MutableMapping[str, t.Any]] = None,
+ terminal_width: t.Optional[int] = None,
+ max_content_width: t.Optional[int] = None,
+ resilient_parsing: bool = False,
+ allow_extra_args: t.Optional[bool] = None,
+ allow_interspersed_args: t.Optional[bool] = None,
+ ignore_unknown_options: t.Optional[bool] = None,
+ help_option_names: t.Optional[t.List[str]] = None,
+ token_normalize_func: t.Optional[t.Callable[[str], str]] = None,
+ color: t.Optional[bool] = None,
+ show_default: t.Optional[bool] = None,
+ ) -> None:
+ #: the parent context or `None` if none exists.
+ self.parent = parent
+ #: the :class:`Command` for this context.
+ self.command = command
+ #: the descriptive information name
+ self.info_name = info_name
+ #: Map of parameter names to their parsed values. Parameters
+ #: with ``expose_value=False`` are not stored.
+ self.params: t.Dict[str, t.Any] = {}
+ #: the leftover arguments.
+ self.args: t.List[str] = []
+ #: protected arguments. These are arguments that are prepended
+ #: to `args` when certain parsing scenarios are encountered but
+ #: must be never propagated to another arguments. This is used
+ #: to implement nested parsing.
+ self.protected_args: t.List[str] = []
+ #: the collected prefixes of the command's options.
+ self._opt_prefixes: t.Set[str] = set(parent._opt_prefixes) if parent else set()
+
+ if obj is None and parent is not None:
+ obj = parent.obj
+
+ #: the user object stored.
+ self.obj: t.Any = obj
+ self._meta: t.Dict[str, t.Any] = getattr(parent, "meta", {})
+
+ #: A dictionary (-like object) with defaults for parameters.
+ if (
+ default_map is None
+ and info_name is not None
+ and parent is not None
+ and parent.default_map is not None
+ ):
+ default_map = parent.default_map.get(info_name)
+
+ self.default_map: t.Optional[t.MutableMapping[str, t.Any]] = default_map
+
+ #: This flag indicates if a subcommand is going to be executed. A
+ #: group callback can use this information to figure out if it's
+ #: being executed directly or because the execution flow passes
+ #: onwards to a subcommand. By default it's None, but it can be
+ #: the name of the subcommand to execute.
+ #:
+ #: If chaining is enabled this will be set to ``'*'`` in case
+ #: any commands are executed. It is however not possible to
+ #: figure out which ones. If you require this knowledge you
+ #: should use a :func:`result_callback`.
+ self.invoked_subcommand: t.Optional[str] = None
+
+ if terminal_width is None and parent is not None:
+ terminal_width = parent.terminal_width
+
+ #: The width of the terminal (None is autodetection).
+ self.terminal_width: t.Optional[int] = terminal_width
+
+ if max_content_width is None and parent is not None:
+ max_content_width = parent.max_content_width
+
+ #: The maximum width of formatted content (None implies a sensible
+ #: default which is 80 for most things).
+ self.max_content_width: t.Optional[int] = max_content_width
+
+ if allow_extra_args is None:
+ allow_extra_args = command.allow_extra_args
+
+ #: Indicates if the context allows extra args or if it should
+ #: fail on parsing.
+ #:
+ #: .. versionadded:: 3.0
+ self.allow_extra_args = allow_extra_args
+
+ if allow_interspersed_args is None:
+ allow_interspersed_args = command.allow_interspersed_args
+
+ #: Indicates if the context allows mixing of arguments and
+ #: options or not.
+ #:
+ #: .. versionadded:: 3.0
+ self.allow_interspersed_args: bool = allow_interspersed_args
+
+ if ignore_unknown_options is None:
+ ignore_unknown_options = command.ignore_unknown_options
+
+ #: Instructs click to ignore options that a command does not
+ #: understand and will store it on the context for later
+ #: processing. This is primarily useful for situations where you
+ #: want to call into external programs. Generally this pattern is
+ #: strongly discouraged because it's not possibly to losslessly
+ #: forward all arguments.
+ #:
+ #: .. versionadded:: 4.0
+ self.ignore_unknown_options: bool = ignore_unknown_options
+
+ if help_option_names is None:
+ if parent is not None:
+ help_option_names = parent.help_option_names
+ else:
+ help_option_names = ["--help"]
+
+ #: The names for the help options.
+ self.help_option_names: t.List[str] = help_option_names
+
+ if token_normalize_func is None and parent is not None:
+ token_normalize_func = parent.token_normalize_func
+
+ #: An optional normalization function for tokens. This is
+ #: options, choices, commands etc.
+ self.token_normalize_func: t.Optional[
+ t.Callable[[str], str]
+ ] = token_normalize_func
+
+ #: Indicates if resilient parsing is enabled. In that case Click
+ #: will do its best to not cause any failures and default values
+ #: will be ignored. Useful for completion.
+ self.resilient_parsing: bool = resilient_parsing
+
+ # If there is no envvar prefix yet, but the parent has one and
+ # the command on this level has a name, we can expand the envvar
+ # prefix automatically.
+ if auto_envvar_prefix is None:
+ if (
+ parent is not None
+ and parent.auto_envvar_prefix is not None
+ and self.info_name is not None
+ ):
+ auto_envvar_prefix = (
+ f"{parent.auto_envvar_prefix}_{self.info_name.upper()}"
+ )
+ else:
+ auto_envvar_prefix = auto_envvar_prefix.upper()
+
+ if auto_envvar_prefix is not None:
+ auto_envvar_prefix = auto_envvar_prefix.replace("-", "_")
+
+ self.auto_envvar_prefix: t.Optional[str] = auto_envvar_prefix
+
+ if color is None and parent is not None:
+ color = parent.color
+
+ #: Controls if styling output is wanted or not.
+ self.color: t.Optional[bool] = color
+
+ if show_default is None and parent is not None:
+ show_default = parent.show_default
+
+ #: Show option default values when formatting help text.
+ self.show_default: t.Optional[bool] = show_default
+
+ self._close_callbacks: t.List[t.Callable[[], t.Any]] = []
+ self._depth = 0
+ self._parameter_source: t.Dict[str, ParameterSource] = {}
+ self._exit_stack = ExitStack()
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ """Gather information that could be useful for a tool generating
+ user-facing documentation. This traverses the entire CLI
+ structure.
+
+ .. code-block:: python
+
+ with Context(cli) as ctx:
+ info = ctx.to_info_dict()
+
+ .. versionadded:: 8.0
+ """
+ return {
+ "command": self.command.to_info_dict(self),
+ "info_name": self.info_name,
+ "allow_extra_args": self.allow_extra_args,
+ "allow_interspersed_args": self.allow_interspersed_args,
+ "ignore_unknown_options": self.ignore_unknown_options,
+ "auto_envvar_prefix": self.auto_envvar_prefix,
+ }
+
+ def __enter__(self) -> "Context":
+ self._depth += 1
+ push_context(self)
+ return self
+
+ def __exit__(
+ self,
+ exc_type: t.Optional[t.Type[BaseException]],
+ exc_value: t.Optional[BaseException],
+ tb: t.Optional[TracebackType],
+ ) -> None:
+ self._depth -= 1
+ if self._depth == 0:
+ self.close()
+ pop_context()
+
+ @contextmanager
+ def scope(self, cleanup: bool = True) -> t.Iterator["Context"]:
+ """This helper method can be used with the context object to promote
+ it to the current thread local (see :func:`get_current_context`).
+ The default behavior of this is to invoke the cleanup functions which
+ can be disabled by setting `cleanup` to `False`. The cleanup
+ functions are typically used for things such as closing file handles.
+
+ If the cleanup is intended the context object can also be directly
+ used as a context manager.
+
+ Example usage::
+
+ with ctx.scope():
+ assert get_current_context() is ctx
+
+ This is equivalent::
+
+ with ctx:
+ assert get_current_context() is ctx
+
+ .. versionadded:: 5.0
+
+ :param cleanup: controls if the cleanup functions should be run or
+ not. The default is to run these functions. In
+ some situations the context only wants to be
+ temporarily pushed in which case this can be disabled.
+ Nested pushes automatically defer the cleanup.
+ """
+ if not cleanup:
+ self._depth += 1
+ try:
+ with self as rv:
+ yield rv
+ finally:
+ if not cleanup:
+ self._depth -= 1
+
+ @property
+ def meta(self) -> t.Dict[str, t.Any]:
+ """This is a dictionary which is shared with all the contexts
+ that are nested. It exists so that click utilities can store some
+ state here if they need to. It is however the responsibility of
+ that code to manage this dictionary well.
+
+ The keys are supposed to be unique dotted strings. For instance
+ module paths are a good choice for it. What is stored in there is
+ irrelevant for the operation of click. However what is important is
+ that code that places data here adheres to the general semantics of
+ the system.
+
+ Example usage::
+
+ LANG_KEY = f'{__name__}.lang'
+
+ def set_language(value):
+ ctx = get_current_context()
+ ctx.meta[LANG_KEY] = value
+
+ def get_language():
+ return get_current_context().meta.get(LANG_KEY, 'en_US')
+
+ .. versionadded:: 5.0
+ """
+ return self._meta
+
+ def make_formatter(self) -> HelpFormatter:
+ """Creates the :class:`~click.HelpFormatter` for the help and
+ usage output.
+
+ To quickly customize the formatter class used without overriding
+ this method, set the :attr:`formatter_class` attribute.
+
+ .. versionchanged:: 8.0
+ Added the :attr:`formatter_class` attribute.
+ """
+ return self.formatter_class(
+ width=self.terminal_width, max_width=self.max_content_width
+ )
+
+ def with_resource(self, context_manager: t.ContextManager[V]) -> V:
+ """Register a resource as if it were used in a ``with``
+ statement. The resource will be cleaned up when the context is
+ popped.
+
+ Uses :meth:`contextlib.ExitStack.enter_context`. It calls the
+ resource's ``__enter__()`` method and returns the result. When
+ the context is popped, it closes the stack, which calls the
+ resource's ``__exit__()`` method.
+
+ To register a cleanup function for something that isn't a
+ context manager, use :meth:`call_on_close`. Or use something
+ from :mod:`contextlib` to turn it into a context manager first.
+
+ .. code-block:: python
+
+ @click.group()
+ @click.option("--name")
+ @click.pass_context
+ def cli(ctx):
+ ctx.obj = ctx.with_resource(connect_db(name))
+
+ :param context_manager: The context manager to enter.
+ :return: Whatever ``context_manager.__enter__()`` returns.
+
+ .. versionadded:: 8.0
+ """
+ return self._exit_stack.enter_context(context_manager)
+
+ def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
+ """Register a function to be called when the context tears down.
+
+ This can be used to close resources opened during the script
+ execution. Resources that support Python's context manager
+ protocol which would be used in a ``with`` statement should be
+ registered with :meth:`with_resource` instead.
+
+ :param f: The function to execute on teardown.
+ """
+ return self._exit_stack.callback(f)
+
+ def close(self) -> None:
+ """Invoke all close callbacks registered with
+ :meth:`call_on_close`, and exit all context managers entered
+ with :meth:`with_resource`.
+ """
+ self._exit_stack.close()
+ # In case the context is reused, create a new exit stack.
+ self._exit_stack = ExitStack()
+
+ @property
+ def command_path(self) -> str:
+ """The computed command path. This is used for the ``usage``
+ information on the help page. It's automatically created by
+ combining the info names of the chain of contexts to the root.
+ """
+ rv = ""
+ if self.info_name is not None:
+ rv = self.info_name
+ if self.parent is not None:
+ parent_command_path = [self.parent.command_path]
+
+ if isinstance(self.parent.command, Command):
+ for param in self.parent.command.get_params(self):
+ parent_command_path.extend(param.get_usage_pieces(self))
+
+ rv = f"{' '.join(parent_command_path)} {rv}"
+ return rv.lstrip()
+
+ def find_root(self) -> "Context":
+ """Finds the outermost context."""
+ node = self
+ while node.parent is not None:
+ node = node.parent
+ return node
+
+ def find_object(self, object_type: t.Type[V]) -> t.Optional[V]:
+ """Finds the closest object of a given type."""
+ node: t.Optional["Context"] = self
+
+ while node is not None:
+ if isinstance(node.obj, object_type):
+ return node.obj
+
+ node = node.parent
+
+ return None
+
+ def ensure_object(self, object_type: t.Type[V]) -> V:
+ """Like :meth:`find_object` but sets the innermost object to a
+ new instance of `object_type` if it does not exist.
+ """
+ rv = self.find_object(object_type)
+ if rv is None:
+ self.obj = rv = object_type()
+ return rv
+
+ @t.overload
+ def lookup_default(
+ self, name: str, call: "te.Literal[True]" = True
+ ) -> t.Optional[t.Any]:
+ ...
+
+ @t.overload
+ def lookup_default(
+ self, name: str, call: "te.Literal[False]" = ...
+ ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
+ ...
+
+ def lookup_default(self, name: str, call: bool = True) -> t.Optional[t.Any]:
+ """Get the default for a parameter from :attr:`default_map`.
+
+ :param name: Name of the parameter.
+ :param call: If the default is a callable, call it. Disable to
+ return the callable instead.
+
+ .. versionchanged:: 8.0
+ Added the ``call`` parameter.
+ """
+ if self.default_map is not None:
+ value = self.default_map.get(name)
+
+ if call and callable(value):
+ return value()
+
+ return value
+
+ return None
+
+ def fail(self, message: str) -> "te.NoReturn":
+ """Aborts the execution of the program with a specific error
+ message.
+
+ :param message: the error message to fail with.
+ """
+ raise UsageError(message, self)
+
+ def abort(self) -> "te.NoReturn":
+ """Aborts the script."""
+ raise Abort()
+
+ def exit(self, code: int = 0) -> "te.NoReturn":
+ """Exits the application with a given exit code."""
+ raise Exit(code)
+
+ def get_usage(self) -> str:
+ """Helper method to get formatted usage string for the current
+ context and command.
+ """
+ return self.command.get_usage(self)
+
+ def get_help(self) -> str:
+ """Helper method to get formatted help page for the current
+ context and command.
+ """
+ return self.command.get_help(self)
+
+ def _make_sub_context(self, command: "Command") -> "Context":
+ """Create a new context of the same type as this context, but
+ for a new command.
+
+ :meta private:
+ """
+ return type(self)(command, info_name=command.name, parent=self)
+
+ @t.overload
+ def invoke(
+ __self, # noqa: B902
+ __callback: "t.Callable[..., V]",
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> V:
+ ...
+
+ @t.overload
+ def invoke(
+ __self, # noqa: B902
+ __callback: "Command",
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> t.Any:
+ ...
+
+ def invoke(
+ __self, # noqa: B902
+ __callback: t.Union["Command", "t.Callable[..., V]"],
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> t.Union[t.Any, V]:
+ """Invokes a command callback in exactly the way it expects. There
+ are two ways to invoke this method:
+
+ 1. the first argument can be a callback and all other arguments and
+ keyword arguments are forwarded directly to the function.
+ 2. the first argument is a click command object. In that case all
+ arguments are forwarded as well but proper click parameters
+ (options and click arguments) must be keyword arguments and Click
+ will fill in defaults.
+
+ Note that before Click 3.2 keyword arguments were not properly filled
+ in against the intention of this code and no context was created. For
+ more information about this change and why it was done in a bugfix
+ release see :ref:`upgrade-to-3.2`.
+
+ .. versionchanged:: 8.0
+ All ``kwargs`` are tracked in :attr:`params` so they will be
+ passed if :meth:`forward` is called at multiple levels.
+ """
+ if isinstance(__callback, Command):
+ other_cmd = __callback
+
+ if other_cmd.callback is None:
+ raise TypeError(
+ "The given command does not have a callback that can be invoked."
+ )
+ else:
+ __callback = t.cast("t.Callable[..., V]", other_cmd.callback)
+
+ ctx = __self._make_sub_context(other_cmd)
+
+ for param in other_cmd.params:
+ if param.name not in kwargs and param.expose_value:
+ kwargs[param.name] = param.type_cast_value( # type: ignore
+ ctx, param.get_default(ctx)
+ )
+
+ # Track all kwargs as params, so that forward() will pass
+ # them on in subsequent calls.
+ ctx.params.update(kwargs)
+ else:
+ ctx = __self
+
+ with augment_usage_errors(__self):
+ with ctx:
+ return __callback(*args, **kwargs)
+
+ def forward(
+ __self, __cmd: "Command", *args: t.Any, **kwargs: t.Any # noqa: B902
+ ) -> t.Any:
+ """Similar to :meth:`invoke` but fills in default keyword
+ arguments from the current context if the other command expects
+ it. This cannot invoke callbacks directly, only other commands.
+
+ .. versionchanged:: 8.0
+ All ``kwargs`` are tracked in :attr:`params` so they will be
+ passed if ``forward`` is called at multiple levels.
+ """
+ # Can only forward to other commands, not direct callbacks.
+ if not isinstance(__cmd, Command):
+ raise TypeError("Callback is not a command.")
+
+ for param in __self.params:
+ if param not in kwargs:
+ kwargs[param] = __self.params[param]
+
+ return __self.invoke(__cmd, *args, **kwargs)
+
+ def set_parameter_source(self, name: str, source: ParameterSource) -> None:
+ """Set the source of a parameter. This indicates the location
+ from which the value of the parameter was obtained.
+
+ :param name: The name of the parameter.
+ :param source: A member of :class:`~click.core.ParameterSource`.
+ """
+ self._parameter_source[name] = source
+
+ def get_parameter_source(self, name: str) -> t.Optional[ParameterSource]:
+ """Get the source of a parameter. This indicates the location
+ from which the value of the parameter was obtained.
+
+ This can be useful for determining when a user specified a value
+ on the command line that is the same as the default value. It
+ will be :attr:`~click.core.ParameterSource.DEFAULT` only if the
+ value was actually taken from the default.
+
+ :param name: The name of the parameter.
+ :rtype: ParameterSource
+
+ .. versionchanged:: 8.0
+ Returns ``None`` if the parameter was not provided from any
+ source.
+ """
+ return self._parameter_source.get(name)
+
+
+class BaseCommand:
+ """The base command implements the minimal API contract of commands.
+ Most code will never use this as it does not implement a lot of useful
+ functionality but it can act as the direct subclass of alternative
+ parsing methods that do not depend on the Click parser.
+
+ For instance, this can be used to bridge Click and other systems like
+ argparse or docopt.
+
+ Because base commands do not implement a lot of the API that other
+ parts of Click take for granted, they are not supported for all
+ operations. For instance, they cannot be used with the decorators
+ usually and they have no built-in callback system.
+
+ .. versionchanged:: 2.0
+ Added the `context_settings` parameter.
+
+ :param name: the name of the command to use unless a group overrides it.
+ :param context_settings: an optional dictionary with defaults that are
+ passed to the context object.
+ """
+
+ #: The context class to create with :meth:`make_context`.
+ #:
+ #: .. versionadded:: 8.0
+ context_class: t.Type[Context] = Context
+ #: the default for the :attr:`Context.allow_extra_args` flag.
+ allow_extra_args = False
+ #: the default for the :attr:`Context.allow_interspersed_args` flag.
+ allow_interspersed_args = True
+ #: the default for the :attr:`Context.ignore_unknown_options` flag.
+ ignore_unknown_options = False
+
+ def __init__(
+ self,
+ name: t.Optional[str],
+ context_settings: t.Optional[t.MutableMapping[str, t.Any]] = None,
+ ) -> None:
+ #: the name the command thinks it has. Upon registering a command
+ #: on a :class:`Group` the group will default the command name
+ #: with this information. You should instead use the
+ #: :class:`Context`\'s :attr:`~Context.info_name` attribute.
+ self.name = name
+
+ if context_settings is None:
+ context_settings = {}
+
+ #: an optional dictionary with defaults passed to the context.
+ self.context_settings: t.MutableMapping[str, t.Any] = context_settings
+
+ def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]:
+ """Gather information that could be useful for a tool generating
+ user-facing documentation. This traverses the entire structure
+ below this command.
+
+ Use :meth:`click.Context.to_info_dict` to traverse the entire
+ CLI structure.
+
+ :param ctx: A :class:`Context` representing this command.
+
+ .. versionadded:: 8.0
+ """
+ return {"name": self.name}
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} {self.name}>"
+
+ def get_usage(self, ctx: Context) -> str:
+ raise NotImplementedError("Base commands cannot get usage")
+
+ def get_help(self, ctx: Context) -> str:
+ raise NotImplementedError("Base commands cannot get help")
+
+ def make_context(
+ self,
+ info_name: t.Optional[str],
+ args: t.List[str],
+ parent: t.Optional[Context] = None,
+ **extra: t.Any,
+ ) -> Context:
+ """This function when given an info name and arguments will kick
+ off the parsing and create a new :class:`Context`. It does not
+ invoke the actual command callback though.
+
+ To quickly customize the context class used without overriding
+ this method, set the :attr:`context_class` attribute.
+
+ :param info_name: the info name for this invocation. Generally this
+ is the most descriptive name for the script or
+ command. For the toplevel script it's usually
+ the name of the script, for commands below it's
+ the name of the command.
+ :param args: the arguments to parse as list of strings.
+ :param parent: the parent context if available.
+ :param extra: extra keyword arguments forwarded to the context
+ constructor.
+
+ .. versionchanged:: 8.0
+ Added the :attr:`context_class` attribute.
+ """
+ for key, value in self.context_settings.items():
+ if key not in extra:
+ extra[key] = value
+
+ ctx = self.context_class(
+ self, info_name=info_name, parent=parent, **extra # type: ignore
+ )
+
+ with ctx.scope(cleanup=False):
+ self.parse_args(ctx, args)
+ return ctx
+
+ def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]:
+ """Given a context and a list of arguments this creates the parser
+ and parses the arguments, then modifies the context as necessary.
+ This is automatically invoked by :meth:`make_context`.
+ """
+ raise NotImplementedError("Base commands do not know how to parse arguments.")
+
+ def invoke(self, ctx: Context) -> t.Any:
+ """Given a context, this invokes the command. The default
+ implementation is raising a not implemented error.
+ """
+ raise NotImplementedError("Base commands are not invocable by default")
+
+ def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]:
+ """Return a list of completions for the incomplete value. Looks
+ at the names of chained multi-commands.
+
+ Any command could be part of a chained multi-command, so sibling
+ commands are valid at any point during command completion. Other
+ command classes will return more completions.
+
+ :param ctx: Invocation context for this command.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ from click.shell_completion import CompletionItem
+
+ results: t.List["CompletionItem"] = []
+
+ while ctx.parent is not None:
+ ctx = ctx.parent
+
+ if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
+ results.extend(
+ CompletionItem(name, help=command.get_short_help_str())
+ for name, command in _complete_visible_commands(ctx, incomplete)
+ if name not in ctx.protected_args
+ )
+
+ return results
+
+ @t.overload
+ def main(
+ self,
+ args: t.Optional[t.Sequence[str]] = None,
+ prog_name: t.Optional[str] = None,
+ complete_var: t.Optional[str] = None,
+ standalone_mode: "te.Literal[True]" = True,
+ **extra: t.Any,
+ ) -> "te.NoReturn":
+ ...
+
+ @t.overload
+ def main(
+ self,
+ args: t.Optional[t.Sequence[str]] = None,
+ prog_name: t.Optional[str] = None,
+ complete_var: t.Optional[str] = None,
+ standalone_mode: bool = ...,
+ **extra: t.Any,
+ ) -> t.Any:
+ ...
+
+ def main(
+ self,
+ args: t.Optional[t.Sequence[str]] = None,
+ prog_name: t.Optional[str] = None,
+ complete_var: t.Optional[str] = None,
+ standalone_mode: bool = True,
+ windows_expand_args: bool = True,
+ **extra: t.Any,
+ ) -> t.Any:
+ """This is the way to invoke a script with all the bells and
+ whistles as a command line application. This will always terminate
+ the application after a call. If this is not wanted, ``SystemExit``
+ needs to be caught.
+
+ This method is also available by directly calling the instance of
+ a :class:`Command`.
+
+ :param args: the arguments that should be used for parsing. If not
+ provided, ``sys.argv[1:]`` is used.
+ :param prog_name: the program name that should be used. By default
+ the program name is constructed by taking the file
+ name from ``sys.argv[0]``.
+ :param complete_var: the environment variable that controls the
+ bash completion support. The default is
+ ``"__COMPLETE"`` with prog_name in
+ uppercase.
+ :param standalone_mode: the default behavior is to invoke the script
+ in standalone mode. Click will then
+ handle exceptions and convert them into
+ error messages and the function will never
+ return but shut down the interpreter. If
+ this is set to `False` they will be
+ propagated to the caller and the return
+ value of this function is the return value
+ of :meth:`invoke`.
+ :param windows_expand_args: Expand glob patterns, user dir, and
+ env vars in command line args on Windows.
+ :param extra: extra keyword arguments are forwarded to the context
+ constructor. See :class:`Context` for more information.
+
+ .. versionchanged:: 8.0.1
+ Added the ``windows_expand_args`` parameter to allow
+ disabling command line arg expansion on Windows.
+
+ .. versionchanged:: 8.0
+ When taking arguments from ``sys.argv`` on Windows, glob
+ patterns, user dir, and env vars are expanded.
+
+ .. versionchanged:: 3.0
+ Added the ``standalone_mode`` parameter.
+ """
+ if args is None:
+ args = sys.argv[1:]
+
+ if os.name == "nt" and windows_expand_args:
+ args = _expand_args(args)
+ else:
+ args = list(args)
+
+ if prog_name is None:
+ prog_name = _detect_program_name()
+
+ # Process shell completion requests and exit early.
+ self._main_shell_completion(extra, prog_name, complete_var)
+
+ try:
+ try:
+ with self.make_context(prog_name, args, **extra) as ctx:
+ rv = self.invoke(ctx)
+ if not standalone_mode:
+ return rv
+ # it's not safe to `ctx.exit(rv)` here!
+ # note that `rv` may actually contain data like "1" which
+ # has obvious effects
+ # more subtle case: `rv=[None, None]` can come out of
+ # chained commands which all returned `None` -- so it's not
+ # even always obvious that `rv` indicates success/failure
+ # by its truthiness/falsiness
+ ctx.exit()
+ except (EOFError, KeyboardInterrupt) as e:
+ echo(file=sys.stderr)
+ raise Abort() from e
+ except ClickException as e:
+ if not standalone_mode:
+ raise
+ e.show()
+ sys.exit(e.exit_code)
+ except OSError as e:
+ if e.errno == errno.EPIPE:
+ sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout))
+ sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr))
+ sys.exit(1)
+ else:
+ raise
+ except Exit as e:
+ if standalone_mode:
+ sys.exit(e.exit_code)
+ else:
+ # in non-standalone mode, return the exit code
+ # note that this is only reached if `self.invoke` above raises
+ # an Exit explicitly -- thus bypassing the check there which
+ # would return its result
+ # the results of non-standalone execution may therefore be
+ # somewhat ambiguous: if there are codepaths which lead to
+ # `ctx.exit(1)` and to `return 1`, the caller won't be able to
+ # tell the difference between the two
+ return e.exit_code
+ except Abort:
+ if not standalone_mode:
+ raise
+ echo(_("Aborted!"), file=sys.stderr)
+ sys.exit(1)
+
+ def _main_shell_completion(
+ self,
+ ctx_args: t.MutableMapping[str, t.Any],
+ prog_name: str,
+ complete_var: t.Optional[str] = None,
+ ) -> None:
+ """Check if the shell is asking for tab completion, process
+ that, then exit early. Called from :meth:`main` before the
+ program is invoked.
+
+ :param prog_name: Name of the executable in the shell.
+ :param complete_var: Name of the environment variable that holds
+ the completion instruction. Defaults to
+ ``_{PROG_NAME}_COMPLETE``.
+
+ .. versionchanged:: 8.2.0
+ Dots (``.``) in ``prog_name`` are replaced with underscores (``_``).
+ """
+ if complete_var is None:
+ complete_name = prog_name.replace("-", "_").replace(".", "_")
+ complete_var = f"_{complete_name}_COMPLETE".upper()
+
+ instruction = os.environ.get(complete_var)
+
+ if not instruction:
+ return
+
+ from .shell_completion import shell_complete
+
+ rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction)
+ sys.exit(rv)
+
+ def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
+ """Alias for :meth:`main`."""
+ return self.main(*args, **kwargs)
+
+
+class Command(BaseCommand):
+ """Commands are the basic building block of command line interfaces in
+ Click. A basic command handles command line parsing and might dispatch
+ more parsing to commands nested below it.
+
+ :param name: the name of the command to use unless a group overrides it.
+ :param context_settings: an optional dictionary with defaults that are
+ passed to the context object.
+ :param callback: the callback to invoke. This is optional.
+ :param params: the parameters to register with this command. This can
+ be either :class:`Option` or :class:`Argument` objects.
+ :param help: the help string to use for this command.
+ :param epilog: like the help string but it's printed at the end of the
+ help page after everything else.
+ :param short_help: the short help to use for this command. This is
+ shown on the command listing of the parent command.
+ :param add_help_option: by default each command registers a ``--help``
+ option. This can be disabled by this parameter.
+ :param no_args_is_help: this controls what happens if no arguments are
+ provided. This option is disabled by default.
+ If enabled this will add ``--help`` as argument
+ if no arguments are passed
+ :param hidden: hide this command from help outputs.
+
+ :param deprecated: issues a message indicating that
+ the command is deprecated.
+
+ .. versionchanged:: 8.1
+ ``help``, ``epilog``, and ``short_help`` are stored unprocessed,
+ all formatting is done when outputting help text, not at init,
+ and is done even if not using the ``@command`` decorator.
+
+ .. versionchanged:: 8.0
+ Added a ``repr`` showing the command name.
+
+ .. versionchanged:: 7.1
+ Added the ``no_args_is_help`` parameter.
+
+ .. versionchanged:: 2.0
+ Added the ``context_settings`` parameter.
+ """
+
+ def __init__(
+ self,
+ name: t.Optional[str],
+ context_settings: t.Optional[t.MutableMapping[str, t.Any]] = None,
+ callback: t.Optional[t.Callable[..., t.Any]] = None,
+ params: t.Optional[t.List["Parameter"]] = None,
+ help: t.Optional[str] = None,
+ epilog: t.Optional[str] = None,
+ short_help: t.Optional[str] = None,
+ options_metavar: t.Optional[str] = "[OPTIONS]",
+ add_help_option: bool = True,
+ no_args_is_help: bool = False,
+ hidden: bool = False,
+ deprecated: bool = False,
+ ) -> None:
+ super().__init__(name, context_settings)
+ #: the callback to execute when the command fires. This might be
+ #: `None` in which case nothing happens.
+ self.callback = callback
+ #: the list of parameters for this command in the order they
+ #: should show up in the help page and execute. Eager parameters
+ #: will automatically be handled before non eager ones.
+ self.params: t.List["Parameter"] = params or []
+ self.help = help
+ self.epilog = epilog
+ self.options_metavar = options_metavar
+ self.short_help = short_help
+ self.add_help_option = add_help_option
+ self.no_args_is_help = no_args_is_help
+ self.hidden = hidden
+ self.deprecated = deprecated
+
+ def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict(ctx)
+ info_dict.update(
+ params=[param.to_info_dict() for param in self.get_params(ctx)],
+ help=self.help,
+ epilog=self.epilog,
+ short_help=self.short_help,
+ hidden=self.hidden,
+ deprecated=self.deprecated,
+ )
+ return info_dict
+
+ def get_usage(self, ctx: Context) -> str:
+ """Formats the usage line into a string and returns it.
+
+ Calls :meth:`format_usage` internally.
+ """
+ formatter = ctx.make_formatter()
+ self.format_usage(ctx, formatter)
+ return formatter.getvalue().rstrip("\n")
+
+ def get_params(self, ctx: Context) -> t.List["Parameter"]:
+ rv = self.params
+ help_option = self.get_help_option(ctx)
+
+ if help_option is not None:
+ rv = [*rv, help_option]
+
+ return rv
+
+ def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None:
+ """Writes the usage line into the formatter.
+
+ This is a low-level method called by :meth:`get_usage`.
+ """
+ pieces = self.collect_usage_pieces(ctx)
+ formatter.write_usage(ctx.command_path, " ".join(pieces))
+
+ def collect_usage_pieces(self, ctx: Context) -> t.List[str]:
+ """Returns all the pieces that go into the usage line and returns
+ it as a list of strings.
+ """
+ rv = [self.options_metavar] if self.options_metavar else []
+
+ for param in self.get_params(ctx):
+ rv.extend(param.get_usage_pieces(ctx))
+
+ return rv
+
+ def get_help_option_names(self, ctx: Context) -> t.List[str]:
+ """Returns the names for the help option."""
+ all_names = set(ctx.help_option_names)
+ for param in self.params:
+ all_names.difference_update(param.opts)
+ all_names.difference_update(param.secondary_opts)
+ return list(all_names)
+
+ def get_help_option(self, ctx: Context) -> t.Optional["Option"]:
+ """Returns the help option object."""
+ help_options = self.get_help_option_names(ctx)
+
+ if not help_options or not self.add_help_option:
+ return None
+
+ def show_help(ctx: Context, param: "Parameter", value: str) -> None:
+ if value and not ctx.resilient_parsing:
+ echo(ctx.get_help(), color=ctx.color)
+ ctx.exit()
+
+ return Option(
+ help_options,
+ is_flag=True,
+ is_eager=True,
+ expose_value=False,
+ callback=show_help,
+ help=_("Show this message and exit."),
+ )
+
+ def make_parser(self, ctx: Context) -> OptionParser:
+ """Creates the underlying option parser for this command."""
+ parser = OptionParser(ctx)
+ for param in self.get_params(ctx):
+ param.add_to_parser(parser, ctx)
+ return parser
+
+ def get_help(self, ctx: Context) -> str:
+ """Formats the help into a string and returns it.
+
+ Calls :meth:`format_help` internally.
+ """
+ formatter = ctx.make_formatter()
+ self.format_help(ctx, formatter)
+ return formatter.getvalue().rstrip("\n")
+
+ def get_short_help_str(self, limit: int = 45) -> str:
+ """Gets short help for the command or makes it by shortening the
+ long help string.
+ """
+ if self.short_help:
+ text = inspect.cleandoc(self.short_help)
+ elif self.help:
+ text = make_default_short_help(self.help, limit)
+ else:
+ text = ""
+
+ if self.deprecated:
+ text = _("(Deprecated) {text}").format(text=text)
+
+ return text.strip()
+
+ def format_help(self, ctx: Context, formatter: HelpFormatter) -> None:
+ """Writes the help into the formatter if it exists.
+
+ This is a low-level method called by :meth:`get_help`.
+
+ This calls the following methods:
+
+ - :meth:`format_usage`
+ - :meth:`format_help_text`
+ - :meth:`format_options`
+ - :meth:`format_epilog`
+ """
+ self.format_usage(ctx, formatter)
+ self.format_help_text(ctx, formatter)
+ self.format_options(ctx, formatter)
+ self.format_epilog(ctx, formatter)
+
+ def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None:
+ """Writes the help text to the formatter if it exists."""
+ if self.help is not None:
+ # truncate the help text to the first form feed
+ text = inspect.cleandoc(self.help).partition("\f")[0]
+ else:
+ text = ""
+
+ if self.deprecated:
+ text = _("(Deprecated) {text}").format(text=text)
+
+ if text:
+ formatter.write_paragraph()
+
+ with formatter.indentation():
+ formatter.write_text(text)
+
+ def format_options(self, ctx: Context, formatter: HelpFormatter) -> None:
+ """Writes all the options into the formatter if they exist."""
+ opts = []
+ for param in self.get_params(ctx):
+ rv = param.get_help_record(ctx)
+ if rv is not None:
+ opts.append(rv)
+
+ if opts:
+ with formatter.section(_("Options")):
+ formatter.write_dl(opts)
+
+ def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None:
+ """Writes the epilog into the formatter if it exists."""
+ if self.epilog:
+ epilog = inspect.cleandoc(self.epilog)
+ formatter.write_paragraph()
+
+ with formatter.indentation():
+ formatter.write_text(epilog)
+
+ def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]:
+ if not args and self.no_args_is_help and not ctx.resilient_parsing:
+ echo(ctx.get_help(), color=ctx.color)
+ ctx.exit()
+
+ parser = self.make_parser(ctx)
+ opts, args, param_order = parser.parse_args(args=args)
+
+ for param in iter_params_for_processing(param_order, self.get_params(ctx)):
+ value, args = param.handle_parse_result(ctx, opts, args)
+
+ if args and not ctx.allow_extra_args and not ctx.resilient_parsing:
+ ctx.fail(
+ ngettext(
+ "Got unexpected extra argument ({args})",
+ "Got unexpected extra arguments ({args})",
+ len(args),
+ ).format(args=" ".join(map(str, args)))
+ )
+
+ ctx.args = args
+ ctx._opt_prefixes.update(parser._opt_prefixes)
+ return args
+
+ def invoke(self, ctx: Context) -> t.Any:
+ """Given a context, this invokes the attached callback (if it exists)
+ in the right way.
+ """
+ if self.deprecated:
+ message = _(
+ "DeprecationWarning: The command {name!r} is deprecated."
+ ).format(name=self.name)
+ echo(style(message, fg="red"), err=True)
+
+ if self.callback is not None:
+ return ctx.invoke(self.callback, **ctx.params)
+
+ def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]:
+ """Return a list of completions for the incomplete value. Looks
+ at the names of options and chained multi-commands.
+
+ :param ctx: Invocation context for this command.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ from click.shell_completion import CompletionItem
+
+ results: t.List["CompletionItem"] = []
+
+ if incomplete and not incomplete[0].isalnum():
+ for param in self.get_params(ctx):
+ if (
+ not isinstance(param, Option)
+ or param.hidden
+ or (
+ not param.multiple
+ and ctx.get_parameter_source(param.name) # type: ignore
+ is ParameterSource.COMMANDLINE
+ )
+ ):
+ continue
+
+ results.extend(
+ CompletionItem(name, help=param.help)
+ for name in [*param.opts, *param.secondary_opts]
+ if name.startswith(incomplete)
+ )
+
+ results.extend(super().shell_complete(ctx, incomplete))
+ return results
+
+
+class MultiCommand(Command):
+ """A multi command is the basic implementation of a command that
+ dispatches to subcommands. The most common version is the
+ :class:`Group`.
+
+ :param invoke_without_command: this controls how the multi command itself
+ is invoked. By default it's only invoked
+ if a subcommand is provided.
+ :param no_args_is_help: this controls what happens if no arguments are
+ provided. This option is enabled by default if
+ `invoke_without_command` is disabled or disabled
+ if it's enabled. If enabled this will add
+ ``--help`` as argument if no arguments are
+ passed.
+ :param subcommand_metavar: the string that is used in the documentation
+ to indicate the subcommand place.
+ :param chain: if this is set to `True` chaining of multiple subcommands
+ is enabled. This restricts the form of commands in that
+ they cannot have optional arguments but it allows
+ multiple commands to be chained together.
+ :param result_callback: The result callback to attach to this multi
+ command. This can be set or changed later with the
+ :meth:`result_callback` decorator.
+ :param attrs: Other command arguments described in :class:`Command`.
+ """
+
+ allow_extra_args = True
+ allow_interspersed_args = False
+
+ def __init__(
+ self,
+ name: t.Optional[str] = None,
+ invoke_without_command: bool = False,
+ no_args_is_help: t.Optional[bool] = None,
+ subcommand_metavar: t.Optional[str] = None,
+ chain: bool = False,
+ result_callback: t.Optional[t.Callable[..., t.Any]] = None,
+ **attrs: t.Any,
+ ) -> None:
+ super().__init__(name, **attrs)
+
+ if no_args_is_help is None:
+ no_args_is_help = not invoke_without_command
+
+ self.no_args_is_help = no_args_is_help
+ self.invoke_without_command = invoke_without_command
+
+ if subcommand_metavar is None:
+ if chain:
+ subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..."
+ else:
+ subcommand_metavar = "COMMAND [ARGS]..."
+
+ self.subcommand_metavar = subcommand_metavar
+ self.chain = chain
+ # The result callback that is stored. This can be set or
+ # overridden with the :func:`result_callback` decorator.
+ self._result_callback = result_callback
+
+ if self.chain:
+ for param in self.params:
+ if isinstance(param, Argument) and not param.required:
+ raise RuntimeError(
+ "Multi commands in chain mode cannot have"
+ " optional arguments."
+ )
+
+ def to_info_dict(self, ctx: Context) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict(ctx)
+ commands = {}
+
+ for name in self.list_commands(ctx):
+ command = self.get_command(ctx, name)
+
+ if command is None:
+ continue
+
+ sub_ctx = ctx._make_sub_context(command)
+
+ with sub_ctx.scope(cleanup=False):
+ commands[name] = command.to_info_dict(sub_ctx)
+
+ info_dict.update(commands=commands, chain=self.chain)
+ return info_dict
+
+ def collect_usage_pieces(self, ctx: Context) -> t.List[str]:
+ rv = super().collect_usage_pieces(ctx)
+ rv.append(self.subcommand_metavar)
+ return rv
+
+ def format_options(self, ctx: Context, formatter: HelpFormatter) -> None:
+ super().format_options(ctx, formatter)
+ self.format_commands(ctx, formatter)
+
+ def result_callback(self, replace: bool = False) -> t.Callable[[F], F]:
+ """Adds a result callback to the command. By default if a
+ result callback is already registered this will chain them but
+ this can be disabled with the `replace` parameter. The result
+ callback is invoked with the return value of the subcommand
+ (or the list of return values from all subcommands if chaining
+ is enabled) as well as the parameters as they would be passed
+ to the main callback.
+
+ Example::
+
+ @click.group()
+ @click.option('-i', '--input', default=23)
+ def cli(input):
+ return 42
+
+ @cli.result_callback()
+ def process_result(result, input):
+ return result + input
+
+ :param replace: if set to `True` an already existing result
+ callback will be removed.
+
+ .. versionchanged:: 8.0
+ Renamed from ``resultcallback``.
+
+ .. versionadded:: 3.0
+ """
+
+ def decorator(f: F) -> F:
+ old_callback = self._result_callback
+
+ if old_callback is None or replace:
+ self._result_callback = f
+ return f
+
+ def function(__value, *args, **kwargs): # type: ignore
+ inner = old_callback(__value, *args, **kwargs)
+ return f(inner, *args, **kwargs)
+
+ self._result_callback = rv = update_wrapper(t.cast(F, function), f)
+ return rv
+
+ return decorator
+
+ def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None:
+ """Extra format methods for multi methods that adds all the commands
+ after the options.
+ """
+ commands = []
+ for subcommand in self.list_commands(ctx):
+ cmd = self.get_command(ctx, subcommand)
+ # What is this, the tool lied about a command. Ignore it
+ if cmd is None:
+ continue
+ if cmd.hidden:
+ continue
+
+ commands.append((subcommand, cmd))
+
+ # allow for 3 times the default spacing
+ if len(commands):
+ limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
+
+ rows = []
+ for subcommand, cmd in commands:
+ help = cmd.get_short_help_str(limit)
+ rows.append((subcommand, help))
+
+ if rows:
+ with formatter.section(_("Commands")):
+ formatter.write_dl(rows)
+
+ def parse_args(self, ctx: Context, args: t.List[str]) -> t.List[str]:
+ if not args and self.no_args_is_help and not ctx.resilient_parsing:
+ echo(ctx.get_help(), color=ctx.color)
+ ctx.exit()
+
+ rest = super().parse_args(ctx, args)
+
+ if self.chain:
+ ctx.protected_args = rest
+ ctx.args = []
+ elif rest:
+ ctx.protected_args, ctx.args = rest[:1], rest[1:]
+
+ return ctx.args
+
+ def invoke(self, ctx: Context) -> t.Any:
+ def _process_result(value: t.Any) -> t.Any:
+ if self._result_callback is not None:
+ value = ctx.invoke(self._result_callback, value, **ctx.params)
+ return value
+
+ if not ctx.protected_args:
+ if self.invoke_without_command:
+ # No subcommand was invoked, so the result callback is
+ # invoked with the group return value for regular
+ # groups, or an empty list for chained groups.
+ with ctx:
+ rv = super().invoke(ctx)
+ return _process_result([] if self.chain else rv)
+ ctx.fail(_("Missing command."))
+
+ # Fetch args back out
+ args = [*ctx.protected_args, *ctx.args]
+ ctx.args = []
+ ctx.protected_args = []
+
+ # If we're not in chain mode, we only allow the invocation of a
+ # single command but we also inform the current context about the
+ # name of the command to invoke.
+ if not self.chain:
+ # Make sure the context is entered so we do not clean up
+ # resources until the result processor has worked.
+ with ctx:
+ cmd_name, cmd, args = self.resolve_command(ctx, args)
+ assert cmd is not None
+ ctx.invoked_subcommand = cmd_name
+ super().invoke(ctx)
+ sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
+ with sub_ctx:
+ return _process_result(sub_ctx.command.invoke(sub_ctx))
+
+ # In chain mode we create the contexts step by step, but after the
+ # base command has been invoked. Because at that point we do not
+ # know the subcommands yet, the invoked subcommand attribute is
+ # set to ``*`` to inform the command that subcommands are executed
+ # but nothing else.
+ with ctx:
+ ctx.invoked_subcommand = "*" if args else None
+ super().invoke(ctx)
+
+ # Otherwise we make every single context and invoke them in a
+ # chain. In that case the return value to the result processor
+ # is the list of all invoked subcommand's results.
+ contexts = []
+ while args:
+ cmd_name, cmd, args = self.resolve_command(ctx, args)
+ assert cmd is not None
+ sub_ctx = cmd.make_context(
+ cmd_name,
+ args,
+ parent=ctx,
+ allow_extra_args=True,
+ allow_interspersed_args=False,
+ )
+ contexts.append(sub_ctx)
+ args, sub_ctx.args = sub_ctx.args, []
+
+ rv = []
+ for sub_ctx in contexts:
+ with sub_ctx:
+ rv.append(sub_ctx.command.invoke(sub_ctx))
+ return _process_result(rv)
+
+ def resolve_command(
+ self, ctx: Context, args: t.List[str]
+ ) -> t.Tuple[t.Optional[str], t.Optional[Command], t.List[str]]:
+ cmd_name = make_str(args[0])
+ original_cmd_name = cmd_name
+
+ # Get the command
+ cmd = self.get_command(ctx, cmd_name)
+
+ # If we can't find the command but there is a normalization
+ # function available, we try with that one.
+ if cmd is None and ctx.token_normalize_func is not None:
+ cmd_name = ctx.token_normalize_func(cmd_name)
+ cmd = self.get_command(ctx, cmd_name)
+
+ # If we don't find the command we want to show an error message
+ # to the user that it was not provided. However, there is
+ # something else we should do: if the first argument looks like
+ # an option we want to kick off parsing again for arguments to
+ # resolve things like --help which now should go to the main
+ # place.
+ if cmd is None and not ctx.resilient_parsing:
+ if split_opt(cmd_name)[0]:
+ self.parse_args(ctx, ctx.args)
+ ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name))
+ return cmd_name if cmd else None, cmd, args[1:]
+
+ def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
+ """Given a context and a command name, this returns a
+ :class:`Command` object if it exists or returns `None`.
+ """
+ raise NotImplementedError
+
+ def list_commands(self, ctx: Context) -> t.List[str]:
+ """Returns a list of subcommand names in the order they should
+ appear.
+ """
+ return []
+
+ def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]:
+ """Return a list of completions for the incomplete value. Looks
+ at the names of options, subcommands, and chained
+ multi-commands.
+
+ :param ctx: Invocation context for this command.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ from click.shell_completion import CompletionItem
+
+ results = [
+ CompletionItem(name, help=command.get_short_help_str())
+ for name, command in _complete_visible_commands(ctx, incomplete)
+ ]
+ results.extend(super().shell_complete(ctx, incomplete))
+ return results
+
+
+class Group(MultiCommand):
+ """A group allows a command to have subcommands attached. This is
+ the most common way to implement nesting in Click.
+
+ :param name: The name of the group command.
+ :param commands: A dict mapping names to :class:`Command` objects.
+ Can also be a list of :class:`Command`, which will use
+ :attr:`Command.name` to create the dict.
+ :param attrs: Other command arguments described in
+ :class:`MultiCommand`, :class:`Command`, and
+ :class:`BaseCommand`.
+
+ .. versionchanged:: 8.0
+ The ``commands`` argument can be a list of command objects.
+ """
+
+ #: If set, this is used by the group's :meth:`command` decorator
+ #: as the default :class:`Command` class. This is useful to make all
+ #: subcommands use a custom command class.
+ #:
+ #: .. versionadded:: 8.0
+ command_class: t.Optional[t.Type[Command]] = None
+
+ #: If set, this is used by the group's :meth:`group` decorator
+ #: as the default :class:`Group` class. This is useful to make all
+ #: subgroups use a custom group class.
+ #:
+ #: If set to the special value :class:`type` (literally
+ #: ``group_class = type``), this group's class will be used as the
+ #: default class. This makes a custom group class continue to make
+ #: custom groups.
+ #:
+ #: .. versionadded:: 8.0
+ group_class: t.Optional[t.Union[t.Type["Group"], t.Type[type]]] = None
+ # Literal[type] isn't valid, so use Type[type]
+
+ def __init__(
+ self,
+ name: t.Optional[str] = None,
+ commands: t.Optional[
+ t.Union[t.MutableMapping[str, Command], t.Sequence[Command]]
+ ] = None,
+ **attrs: t.Any,
+ ) -> None:
+ super().__init__(name, **attrs)
+
+ if commands is None:
+ commands = {}
+ elif isinstance(commands, abc.Sequence):
+ commands = {c.name: c for c in commands if c.name is not None}
+
+ #: The registered subcommands by their exported names.
+ self.commands: t.MutableMapping[str, Command] = commands
+
+ def add_command(self, cmd: Command, name: t.Optional[str] = None) -> None:
+ """Registers another :class:`Command` with this group. If the name
+ is not provided, the name of the command is used.
+ """
+ name = name or cmd.name
+ if name is None:
+ raise TypeError("Command has no name.")
+ _check_multicommand(self, name, cmd, register=True)
+ self.commands[name] = cmd
+
+ @t.overload
+ def command(self, __func: t.Callable[..., t.Any]) -> Command:
+ ...
+
+ @t.overload
+ def command(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Callable[[t.Callable[..., t.Any]], Command]:
+ ...
+
+ def command(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], Command], Command]:
+ """A shortcut decorator for declaring and attaching a command to
+ the group. This takes the same arguments as :func:`command` and
+ immediately registers the created command with this group by
+ calling :meth:`add_command`.
+
+ To customize the command class used, set the
+ :attr:`command_class` attribute.
+
+ .. versionchanged:: 8.1
+ This decorator can be applied without parentheses.
+
+ .. versionchanged:: 8.0
+ Added the :attr:`command_class` attribute.
+ """
+ from .decorators import command
+
+ func: t.Optional[t.Callable[..., t.Any]] = None
+
+ if args and callable(args[0]):
+ assert (
+ len(args) == 1 and not kwargs
+ ), "Use 'command(**kwargs)(callable)' to provide arguments."
+ (func,) = args
+ args = ()
+
+ if self.command_class and kwargs.get("cls") is None:
+ kwargs["cls"] = self.command_class
+
+ def decorator(f: t.Callable[..., t.Any]) -> Command:
+ cmd: Command = command(*args, **kwargs)(f)
+ self.add_command(cmd)
+ return cmd
+
+ if func is not None:
+ return decorator(func)
+
+ return decorator
+
+ @t.overload
+ def group(self, __func: t.Callable[..., t.Any]) -> "Group":
+ ...
+
+ @t.overload
+ def group(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Callable[[t.Callable[..., t.Any]], "Group"]:
+ ...
+
+ def group(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Union[t.Callable[[t.Callable[..., t.Any]], "Group"], "Group"]:
+ """A shortcut decorator for declaring and attaching a group to
+ the group. This takes the same arguments as :func:`group` and
+ immediately registers the created group with this group by
+ calling :meth:`add_command`.
+
+ To customize the group class used, set the :attr:`group_class`
+ attribute.
+
+ .. versionchanged:: 8.1
+ This decorator can be applied without parentheses.
+
+ .. versionchanged:: 8.0
+ Added the :attr:`group_class` attribute.
+ """
+ from .decorators import group
+
+ func: t.Optional[t.Callable[..., t.Any]] = None
+
+ if args and callable(args[0]):
+ assert (
+ len(args) == 1 and not kwargs
+ ), "Use 'group(**kwargs)(callable)' to provide arguments."
+ (func,) = args
+ args = ()
+
+ if self.group_class is not None and kwargs.get("cls") is None:
+ if self.group_class is type:
+ kwargs["cls"] = type(self)
+ else:
+ kwargs["cls"] = self.group_class
+
+ def decorator(f: t.Callable[..., t.Any]) -> "Group":
+ cmd: Group = group(*args, **kwargs)(f)
+ self.add_command(cmd)
+ return cmd
+
+ if func is not None:
+ return decorator(func)
+
+ return decorator
+
+ def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
+ return self.commands.get(cmd_name)
+
+ def list_commands(self, ctx: Context) -> t.List[str]:
+ return sorted(self.commands)
+
+
+class CommandCollection(MultiCommand):
+ """A command collection is a multi command that merges multiple multi
+ commands together into one. This is a straightforward implementation
+ that accepts a list of different multi commands as sources and
+ provides all the commands for each of them.
+
+ See :class:`MultiCommand` and :class:`Command` for the description of
+ ``name`` and ``attrs``.
+ """
+
+ def __init__(
+ self,
+ name: t.Optional[str] = None,
+ sources: t.Optional[t.List[MultiCommand]] = None,
+ **attrs: t.Any,
+ ) -> None:
+ super().__init__(name, **attrs)
+ #: The list of registered multi commands.
+ self.sources: t.List[MultiCommand] = sources or []
+
+ def add_source(self, multi_cmd: MultiCommand) -> None:
+ """Adds a new multi command to the chain dispatcher."""
+ self.sources.append(multi_cmd)
+
+ def get_command(self, ctx: Context, cmd_name: str) -> t.Optional[Command]:
+ for source in self.sources:
+ rv = source.get_command(ctx, cmd_name)
+
+ if rv is not None:
+ if self.chain:
+ _check_multicommand(self, cmd_name, rv)
+
+ return rv
+
+ return None
+
+ def list_commands(self, ctx: Context) -> t.List[str]:
+ rv: t.Set[str] = set()
+
+ for source in self.sources:
+ rv.update(source.list_commands(ctx))
+
+ return sorted(rv)
+
+
+def _check_iter(value: t.Any) -> t.Iterator[t.Any]:
+ """Check if the value is iterable but not a string. Raises a type
+ error, or return an iterator over the value.
+ """
+ if isinstance(value, str):
+ raise TypeError
+
+ return iter(value)
+
+
+class Parameter:
+ r"""A parameter to a command comes in two versions: they are either
+ :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently
+ not supported by design as some of the internals for parsing are
+ intentionally not finalized.
+
+ Some settings are supported by both options and arguments.
+
+ :param param_decls: the parameter declarations for this option or
+ argument. This is a list of flags or argument
+ names.
+ :param type: the type that should be used. Either a :class:`ParamType`
+ or a Python type. The latter is converted into the former
+ automatically if supported.
+ :param required: controls if this is optional or not.
+ :param default: the default value if omitted. This can also be a callable,
+ in which case it's invoked when the default is needed
+ without any arguments.
+ :param callback: A function to further process or validate the value
+ after type conversion. It is called as ``f(ctx, param, value)``
+ and must return the value. It is called for all sources,
+ including prompts.
+ :param nargs: the number of arguments to match. If not ``1`` the return
+ value is a tuple instead of single value. The default for
+ nargs is ``1`` (except if the type is a tuple, then it's
+ the arity of the tuple). If ``nargs=-1``, all remaining
+ parameters are collected.
+ :param metavar: how the value is represented in the help page.
+ :param expose_value: if this is `True` then the value is passed onwards
+ to the command callback and stored on the context,
+ otherwise it's skipped.
+ :param is_eager: eager values are processed before non eager ones. This
+ should not be set for arguments or it will inverse the
+ order of processing.
+ :param envvar: a string or list of strings that are environment variables
+ that should be checked.
+ :param shell_complete: A function that returns custom shell
+ completions. Used instead of the param's type completion if
+ given. Takes ``ctx, param, incomplete`` and must return a list
+ of :class:`~click.shell_completion.CompletionItem` or a list of
+ strings.
+
+ .. versionchanged:: 8.0
+ ``process_value`` validates required parameters and bounded
+ ``nargs``, and invokes the parameter callback before returning
+ the value. This allows the callback to validate prompts.
+ ``full_process_value`` is removed.
+
+ .. versionchanged:: 8.0
+ ``autocompletion`` is renamed to ``shell_complete`` and has new
+ semantics described above. The old name is deprecated and will
+ be removed in 8.1, until then it will be wrapped to match the
+ new requirements.
+
+ .. versionchanged:: 8.0
+ For ``multiple=True, nargs>1``, the default must be a list of
+ tuples.
+
+ .. versionchanged:: 8.0
+ Setting a default is no longer required for ``nargs>1``, it will
+ default to ``None``. ``multiple=True`` or ``nargs=-1`` will
+ default to ``()``.
+
+ .. versionchanged:: 7.1
+ Empty environment variables are ignored rather than taking the
+ empty string value. This makes it possible for scripts to clear
+ variables if they can't unset them.
+
+ .. versionchanged:: 2.0
+ Changed signature for parameter callback to also be passed the
+ parameter. The old callback format will still work, but it will
+ raise a warning to give you a chance to migrate the code easier.
+ """
+
+ param_type_name = "parameter"
+
+ def __init__(
+ self,
+ param_decls: t.Optional[t.Sequence[str]] = None,
+ type: t.Optional[t.Union[types.ParamType, t.Any]] = None,
+ required: bool = False,
+ default: t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]] = None,
+ callback: t.Optional[t.Callable[[Context, "Parameter", t.Any], t.Any]] = None,
+ nargs: t.Optional[int] = None,
+ multiple: bool = False,
+ metavar: t.Optional[str] = None,
+ expose_value: bool = True,
+ is_eager: bool = False,
+ envvar: t.Optional[t.Union[str, t.Sequence[str]]] = None,
+ shell_complete: t.Optional[
+ t.Callable[
+ [Context, "Parameter", str],
+ t.Union[t.List["CompletionItem"], t.List[str]],
+ ]
+ ] = None,
+ ) -> None:
+ self.name: t.Optional[str]
+ self.opts: t.List[str]
+ self.secondary_opts: t.List[str]
+ self.name, self.opts, self.secondary_opts = self._parse_decls(
+ param_decls or (), expose_value
+ )
+ self.type: types.ParamType = types.convert_type(type, default)
+
+ # Default nargs to what the type tells us if we have that
+ # information available.
+ if nargs is None:
+ if self.type.is_composite:
+ nargs = self.type.arity
+ else:
+ nargs = 1
+
+ self.required = required
+ self.callback = callback
+ self.nargs = nargs
+ self.multiple = multiple
+ self.expose_value = expose_value
+ self.default = default
+ self.is_eager = is_eager
+ self.metavar = metavar
+ self.envvar = envvar
+ self._custom_shell_complete = shell_complete
+
+ if __debug__:
+ if self.type.is_composite and nargs != self.type.arity:
+ raise ValueError(
+ f"'nargs' must be {self.type.arity} (or None) for"
+ f" type {self.type!r}, but it was {nargs}."
+ )
+
+ # Skip no default or callable default.
+ check_default = default if not callable(default) else None
+
+ if check_default is not None:
+ if multiple:
+ try:
+ # Only check the first value against nargs.
+ check_default = next(_check_iter(check_default), None)
+ except TypeError:
+ raise ValueError(
+ "'default' must be a list when 'multiple' is true."
+ ) from None
+
+ # Can be None for multiple with empty default.
+ if nargs != 1 and check_default is not None:
+ try:
+ _check_iter(check_default)
+ except TypeError:
+ if multiple:
+ message = (
+ "'default' must be a list of lists when 'multiple' is"
+ " true and 'nargs' != 1."
+ )
+ else:
+ message = "'default' must be a list when 'nargs' != 1."
+
+ raise ValueError(message) from None
+
+ if nargs > 1 and len(check_default) != nargs:
+ subject = "item length" if multiple else "length"
+ raise ValueError(
+ f"'default' {subject} must match nargs={nargs}."
+ )
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ """Gather information that could be useful for a tool generating
+ user-facing documentation.
+
+ Use :meth:`click.Context.to_info_dict` to traverse the entire
+ CLI structure.
+
+ .. versionadded:: 8.0
+ """
+ return {
+ "name": self.name,
+ "param_type_name": self.param_type_name,
+ "opts": self.opts,
+ "secondary_opts": self.secondary_opts,
+ "type": self.type.to_info_dict(),
+ "required": self.required,
+ "nargs": self.nargs,
+ "multiple": self.multiple,
+ "default": self.default,
+ "envvar": self.envvar,
+ }
+
+ def __repr__(self) -> str:
+ return f"<{self.__class__.__name__} {self.name}>"
+
+ def _parse_decls(
+ self, decls: t.Sequence[str], expose_value: bool
+ ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]:
+ raise NotImplementedError()
+
+ @property
+ def human_readable_name(self) -> str:
+ """Returns the human readable name of this parameter. This is the
+ same as the name for options, but the metavar for arguments.
+ """
+ return self.name # type: ignore
+
+ def make_metavar(self) -> str:
+ if self.metavar is not None:
+ return self.metavar
+
+ metavar = self.type.get_metavar(self)
+
+ if metavar is None:
+ metavar = self.type.name.upper()
+
+ if self.nargs != 1:
+ metavar += "..."
+
+ return metavar
+
+ @t.overload
+ def get_default(
+ self, ctx: Context, call: "te.Literal[True]" = True
+ ) -> t.Optional[t.Any]:
+ ...
+
+ @t.overload
+ def get_default(
+ self, ctx: Context, call: bool = ...
+ ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
+ ...
+
+ def get_default(
+ self, ctx: Context, call: bool = True
+ ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
+ """Get the default for the parameter. Tries
+ :meth:`Context.lookup_default` first, then the local default.
+
+ :param ctx: Current context.
+ :param call: If the default is a callable, call it. Disable to
+ return the callable instead.
+
+ .. versionchanged:: 8.0.2
+ Type casting is no longer performed when getting a default.
+
+ .. versionchanged:: 8.0.1
+ Type casting can fail in resilient parsing mode. Invalid
+ defaults will not prevent showing help text.
+
+ .. versionchanged:: 8.0
+ Looks at ``ctx.default_map`` first.
+
+ .. versionchanged:: 8.0
+ Added the ``call`` parameter.
+ """
+ value = ctx.lookup_default(self.name, call=False) # type: ignore
+
+ if value is None:
+ value = self.default
+
+ if call and callable(value):
+ value = value()
+
+ return value
+
+ def add_to_parser(self, parser: OptionParser, ctx: Context) -> None:
+ raise NotImplementedError()
+
+ def consume_value(
+ self, ctx: Context, opts: t.Mapping[str, t.Any]
+ ) -> t.Tuple[t.Any, ParameterSource]:
+ value = opts.get(self.name) # type: ignore
+ source = ParameterSource.COMMANDLINE
+
+ if value is None:
+ value = self.value_from_envvar(ctx)
+ source = ParameterSource.ENVIRONMENT
+
+ if value is None:
+ value = ctx.lookup_default(self.name) # type: ignore
+ source = ParameterSource.DEFAULT_MAP
+
+ if value is None:
+ value = self.get_default(ctx)
+ source = ParameterSource.DEFAULT
+
+ return value, source
+
+ def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any:
+ """Convert and validate a value against the option's
+ :attr:`type`, :attr:`multiple`, and :attr:`nargs`.
+ """
+ if value is None:
+ return () if self.multiple or self.nargs == -1 else None
+
+ def check_iter(value: t.Any) -> t.Iterator[t.Any]:
+ try:
+ return _check_iter(value)
+ except TypeError:
+ # This should only happen when passing in args manually,
+ # the parser should construct an iterable when parsing
+ # the command line.
+ raise BadParameter(
+ _("Value must be an iterable."), ctx=ctx, param=self
+ ) from None
+
+ if self.nargs == 1 or self.type.is_composite:
+
+ def convert(value: t.Any) -> t.Any:
+ return self.type(value, param=self, ctx=ctx)
+
+ elif self.nargs == -1:
+
+ def convert(value: t.Any) -> t.Any: # t.Tuple[t.Any, ...]
+ return tuple(self.type(x, self, ctx) for x in check_iter(value))
+
+ else: # nargs > 1
+
+ def convert(value: t.Any) -> t.Any: # t.Tuple[t.Any, ...]
+ value = tuple(check_iter(value))
+
+ if len(value) != self.nargs:
+ raise BadParameter(
+ ngettext(
+ "Takes {nargs} values but 1 was given.",
+ "Takes {nargs} values but {len} were given.",
+ len(value),
+ ).format(nargs=self.nargs, len=len(value)),
+ ctx=ctx,
+ param=self,
+ )
+
+ return tuple(self.type(x, self, ctx) for x in value)
+
+ if self.multiple:
+ return tuple(convert(x) for x in check_iter(value))
+
+ return convert(value)
+
+ def value_is_missing(self, value: t.Any) -> bool:
+ if value is None:
+ return True
+
+ if (self.nargs != 1 or self.multiple) and value == ():
+ return True
+
+ return False
+
+ def process_value(self, ctx: Context, value: t.Any) -> t.Any:
+ value = self.type_cast_value(ctx, value)
+
+ if self.required and self.value_is_missing(value):
+ raise MissingParameter(ctx=ctx, param=self)
+
+ if self.callback is not None:
+ value = self.callback(ctx, self, value)
+
+ return value
+
+ def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]:
+ if self.envvar is None:
+ return None
+
+ if isinstance(self.envvar, str):
+ rv = os.environ.get(self.envvar)
+
+ if rv:
+ return rv
+ else:
+ for envvar in self.envvar:
+ rv = os.environ.get(envvar)
+
+ if rv:
+ return rv
+
+ return None
+
+ def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]:
+ rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx)
+
+ if rv is not None and self.nargs != 1:
+ rv = self.type.split_envvar_value(rv)
+
+ return rv
+
+ def handle_parse_result(
+ self, ctx: Context, opts: t.Mapping[str, t.Any], args: t.List[str]
+ ) -> t.Tuple[t.Any, t.List[str]]:
+ with augment_usage_errors(ctx, param=self):
+ value, source = self.consume_value(ctx, opts)
+ ctx.set_parameter_source(self.name, source) # type: ignore
+
+ try:
+ value = self.process_value(ctx, value)
+ except Exception:
+ if not ctx.resilient_parsing:
+ raise
+
+ value = None
+
+ if self.expose_value:
+ ctx.params[self.name] = value # type: ignore
+
+ return value, args
+
+ def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]:
+ pass
+
+ def get_usage_pieces(self, ctx: Context) -> t.List[str]:
+ return []
+
+ def get_error_hint(self, ctx: Context) -> str:
+ """Get a stringified version of the param for use in error messages to
+ indicate which param caused the error.
+ """
+ hint_list = self.opts or [self.human_readable_name]
+ return " / ".join(f"'{x}'" for x in hint_list)
+
+ def shell_complete(self, ctx: Context, incomplete: str) -> t.List["CompletionItem"]:
+ """Return a list of completions for the incomplete value. If a
+ ``shell_complete`` function was given during init, it is used.
+ Otherwise, the :attr:`type`
+ :meth:`~click.types.ParamType.shell_complete` function is used.
+
+ :param ctx: Invocation context for this command.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ if self._custom_shell_complete is not None:
+ results = self._custom_shell_complete(ctx, self, incomplete)
+
+ if results and isinstance(results[0], str):
+ from click.shell_completion import CompletionItem
+
+ results = [CompletionItem(c) for c in results]
+
+ return t.cast(t.List["CompletionItem"], results)
+
+ return self.type.shell_complete(ctx, self, incomplete)
+
+
+class Option(Parameter):
+ """Options are usually optional values on the command line and
+ have some extra features that arguments don't have.
+
+ All other parameters are passed onwards to the parameter constructor.
+
+ :param show_default: Show the default value for this option in its
+ help text. Values are not shown by default, unless
+ :attr:`Context.show_default` is ``True``. If this value is a
+ string, it shows that string in parentheses instead of the
+ actual value. This is particularly useful for dynamic options.
+ For single option boolean flags, the default remains hidden if
+ its value is ``False``.
+ :param show_envvar: Controls if an environment variable should be
+ shown on the help page. Normally, environment variables are not
+ shown.
+ :param prompt: If set to ``True`` or a non empty string then the
+ user will be prompted for input. If set to ``True`` the prompt
+ will be the option name capitalized.
+ :param confirmation_prompt: Prompt a second time to confirm the
+ value if it was prompted for. Can be set to a string instead of
+ ``True`` to customize the message.
+ :param prompt_required: If set to ``False``, the user will be
+ prompted for input only when the option was specified as a flag
+ without a value.
+ :param hide_input: If this is ``True`` then the input on the prompt
+ will be hidden from the user. This is useful for password input.
+ :param is_flag: forces this option to act as a flag. The default is
+ auto detection.
+ :param flag_value: which value should be used for this flag if it's
+ enabled. This is set to a boolean automatically if
+ the option string contains a slash to mark two options.
+ :param multiple: if this is set to `True` then the argument is accepted
+ multiple times and recorded. This is similar to ``nargs``
+ in how it works but supports arbitrary number of
+ arguments.
+ :param count: this flag makes an option increment an integer.
+ :param allow_from_autoenv: if this is enabled then the value of this
+ parameter will be pulled from an environment
+ variable in case a prefix is defined on the
+ context.
+ :param help: the help string.
+ :param hidden: hide this option from help outputs.
+ :param attrs: Other command arguments described in :class:`Parameter`.
+
+ .. versionchanged:: 8.1.0
+ Help text indentation is cleaned here instead of only in the
+ ``@option`` decorator.
+
+ .. versionchanged:: 8.1.0
+ The ``show_default`` parameter overrides
+ ``Context.show_default``.
+
+ .. versionchanged:: 8.1.0
+ The default of a single option boolean flag is not shown if the
+ default value is ``False``.
+
+ .. versionchanged:: 8.0.1
+ ``type`` is detected from ``flag_value`` if given.
+ """
+
+ param_type_name = "option"
+
+ def __init__(
+ self,
+ param_decls: t.Optional[t.Sequence[str]] = None,
+ show_default: t.Union[bool, str, None] = None,
+ prompt: t.Union[bool, str] = False,
+ confirmation_prompt: t.Union[bool, str] = False,
+ prompt_required: bool = True,
+ hide_input: bool = False,
+ is_flag: t.Optional[bool] = None,
+ flag_value: t.Optional[t.Any] = None,
+ multiple: bool = False,
+ count: bool = False,
+ allow_from_autoenv: bool = True,
+ type: t.Optional[t.Union[types.ParamType, t.Any]] = None,
+ help: t.Optional[str] = None,
+ hidden: bool = False,
+ show_choices: bool = True,
+ show_envvar: bool = False,
+ **attrs: t.Any,
+ ) -> None:
+ if help:
+ help = inspect.cleandoc(help)
+
+ default_is_missing = "default" not in attrs
+ super().__init__(param_decls, type=type, multiple=multiple, **attrs)
+
+ if prompt is True:
+ if self.name is None:
+ raise TypeError("'name' is required with 'prompt=True'.")
+
+ prompt_text: t.Optional[str] = self.name.replace("_", " ").capitalize()
+ elif prompt is False:
+ prompt_text = None
+ else:
+ prompt_text = prompt
+
+ self.prompt = prompt_text
+ self.confirmation_prompt = confirmation_prompt
+ self.prompt_required = prompt_required
+ self.hide_input = hide_input
+ self.hidden = hidden
+
+ # If prompt is enabled but not required, then the option can be
+ # used as a flag to indicate using prompt or flag_value.
+ self._flag_needs_value = self.prompt is not None and not self.prompt_required
+
+ if is_flag is None:
+ if flag_value is not None:
+ # Implicitly a flag because flag_value was set.
+ is_flag = True
+ elif self._flag_needs_value:
+ # Not a flag, but when used as a flag it shows a prompt.
+ is_flag = False
+ else:
+ # Implicitly a flag because flag options were given.
+ is_flag = bool(self.secondary_opts)
+ elif is_flag is False and not self._flag_needs_value:
+ # Not a flag, and prompt is not enabled, can be used as a
+ # flag if flag_value is set.
+ self._flag_needs_value = flag_value is not None
+
+ self.default: t.Union[t.Any, t.Callable[[], t.Any]]
+
+ if is_flag and default_is_missing and not self.required:
+ if multiple:
+ self.default = ()
+ else:
+ self.default = False
+
+ if flag_value is None:
+ flag_value = not self.default
+
+ self.type: types.ParamType
+ if is_flag and type is None:
+ # Re-guess the type from the flag value instead of the
+ # default.
+ self.type = types.convert_type(None, flag_value)
+
+ self.is_flag: bool = is_flag
+ self.is_bool_flag: bool = is_flag and isinstance(self.type, types.BoolParamType)
+ self.flag_value: t.Any = flag_value
+
+ # Counting
+ self.count = count
+ if count:
+ if type is None:
+ self.type = types.IntRange(min=0)
+ if default_is_missing:
+ self.default = 0
+
+ self.allow_from_autoenv = allow_from_autoenv
+ self.help = help
+ self.show_default = show_default
+ self.show_choices = show_choices
+ self.show_envvar = show_envvar
+
+ if __debug__:
+ if self.nargs == -1:
+ raise TypeError("nargs=-1 is not supported for options.")
+
+ if self.prompt and self.is_flag and not self.is_bool_flag:
+ raise TypeError("'prompt' is not valid for non-boolean flag.")
+
+ if not self.is_bool_flag and self.secondary_opts:
+ raise TypeError("Secondary flag is not valid for non-boolean flag.")
+
+ if self.is_bool_flag and self.hide_input and self.prompt is not None:
+ raise TypeError(
+ "'prompt' with 'hide_input' is not valid for boolean flag."
+ )
+
+ if self.count:
+ if self.multiple:
+ raise TypeError("'count' is not valid with 'multiple'.")
+
+ if self.is_flag:
+ raise TypeError("'count' is not valid with 'is_flag'.")
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict.update(
+ help=self.help,
+ prompt=self.prompt,
+ is_flag=self.is_flag,
+ flag_value=self.flag_value,
+ count=self.count,
+ hidden=self.hidden,
+ )
+ return info_dict
+
+ def _parse_decls(
+ self, decls: t.Sequence[str], expose_value: bool
+ ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]:
+ opts = []
+ secondary_opts = []
+ name = None
+ possible_names = []
+
+ for decl in decls:
+ if decl.isidentifier():
+ if name is not None:
+ raise TypeError(f"Name '{name}' defined twice")
+ name = decl
+ else:
+ split_char = ";" if decl[:1] == "/" else "/"
+ if split_char in decl:
+ first, second = decl.split(split_char, 1)
+ first = first.rstrip()
+ if first:
+ possible_names.append(split_opt(first))
+ opts.append(first)
+ second = second.lstrip()
+ if second:
+ secondary_opts.append(second.lstrip())
+ if first == second:
+ raise ValueError(
+ f"Boolean option {decl!r} cannot use the"
+ " same flag for true/false."
+ )
+ else:
+ possible_names.append(split_opt(decl))
+ opts.append(decl)
+
+ if name is None and possible_names:
+ possible_names.sort(key=lambda x: -len(x[0])) # group long options first
+ name = possible_names[0][1].replace("-", "_").lower()
+ if not name.isidentifier():
+ name = None
+
+ if name is None:
+ if not expose_value:
+ return None, opts, secondary_opts
+ raise TypeError("Could not determine name for option")
+
+ if not opts and not secondary_opts:
+ raise TypeError(
+ f"No options defined but a name was passed ({name})."
+ " Did you mean to declare an argument instead? Did"
+ f" you mean to pass '--{name}'?"
+ )
+
+ return name, opts, secondary_opts
+
+ def add_to_parser(self, parser: OptionParser, ctx: Context) -> None:
+ if self.multiple:
+ action = "append"
+ elif self.count:
+ action = "count"
+ else:
+ action = "store"
+
+ if self.is_flag:
+ action = f"{action}_const"
+
+ if self.is_bool_flag and self.secondary_opts:
+ parser.add_option(
+ obj=self, opts=self.opts, dest=self.name, action=action, const=True
+ )
+ parser.add_option(
+ obj=self,
+ opts=self.secondary_opts,
+ dest=self.name,
+ action=action,
+ const=False,
+ )
+ else:
+ parser.add_option(
+ obj=self,
+ opts=self.opts,
+ dest=self.name,
+ action=action,
+ const=self.flag_value,
+ )
+ else:
+ parser.add_option(
+ obj=self,
+ opts=self.opts,
+ dest=self.name,
+ action=action,
+ nargs=self.nargs,
+ )
+
+ def get_help_record(self, ctx: Context) -> t.Optional[t.Tuple[str, str]]:
+ if self.hidden:
+ return None
+
+ any_prefix_is_slash = False
+
+ def _write_opts(opts: t.Sequence[str]) -> str:
+ nonlocal any_prefix_is_slash
+
+ rv, any_slashes = join_options(opts)
+
+ if any_slashes:
+ any_prefix_is_slash = True
+
+ if not self.is_flag and not self.count:
+ rv += f" {self.make_metavar()}"
+
+ return rv
+
+ rv = [_write_opts(self.opts)]
+
+ if self.secondary_opts:
+ rv.append(_write_opts(self.secondary_opts))
+
+ help = self.help or ""
+ extra = []
+
+ if self.show_envvar:
+ envvar = self.envvar
+
+ if envvar is None:
+ if (
+ self.allow_from_autoenv
+ and ctx.auto_envvar_prefix is not None
+ and self.name is not None
+ ):
+ envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
+
+ if envvar is not None:
+ var_str = (
+ envvar
+ if isinstance(envvar, str)
+ else ", ".join(str(d) for d in envvar)
+ )
+ extra.append(_("env var: {var}").format(var=var_str))
+
+ # Temporarily enable resilient parsing to avoid type casting
+ # failing for the default. Might be possible to extend this to
+ # help formatting in general.
+ resilient = ctx.resilient_parsing
+ ctx.resilient_parsing = True
+
+ try:
+ default_value = self.get_default(ctx, call=False)
+ finally:
+ ctx.resilient_parsing = resilient
+
+ show_default = False
+ show_default_is_str = False
+
+ if self.show_default is not None:
+ if isinstance(self.show_default, str):
+ show_default_is_str = show_default = True
+ else:
+ show_default = self.show_default
+ elif ctx.show_default is not None:
+ show_default = ctx.show_default
+
+ if show_default_is_str or (show_default and (default_value is not None)):
+ if show_default_is_str:
+ default_string = f"({self.show_default})"
+ elif isinstance(default_value, (list, tuple)):
+ default_string = ", ".join(str(d) for d in default_value)
+ elif inspect.isfunction(default_value):
+ default_string = _("(dynamic)")
+ elif self.is_bool_flag and self.secondary_opts:
+ # For boolean flags that have distinct True/False opts,
+ # use the opt without prefix instead of the value.
+ default_string = split_opt(
+ (self.opts if self.default else self.secondary_opts)[0]
+ )[1]
+ elif self.is_bool_flag and not self.secondary_opts and not default_value:
+ default_string = ""
+ else:
+ default_string = str(default_value)
+
+ if default_string:
+ extra.append(_("default: {default}").format(default=default_string))
+
+ if (
+ isinstance(self.type, types._NumberRangeBase)
+ # skip count with default range type
+ and not (self.count and self.type.min == 0 and self.type.max is None)
+ ):
+ range_str = self.type._describe_range()
+
+ if range_str:
+ extra.append(range_str)
+
+ if self.required:
+ extra.append(_("required"))
+
+ if extra:
+ extra_str = "; ".join(extra)
+ help = f"{help} [{extra_str}]" if help else f"[{extra_str}]"
+
+ return ("; " if any_prefix_is_slash else " / ").join(rv), help
+
+ @t.overload
+ def get_default(
+ self, ctx: Context, call: "te.Literal[True]" = True
+ ) -> t.Optional[t.Any]:
+ ...
+
+ @t.overload
+ def get_default(
+ self, ctx: Context, call: bool = ...
+ ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
+ ...
+
+ def get_default(
+ self, ctx: Context, call: bool = True
+ ) -> t.Optional[t.Union[t.Any, t.Callable[[], t.Any]]]:
+ # If we're a non boolean flag our default is more complex because
+ # we need to look at all flags in the same group to figure out
+ # if we're the default one in which case we return the flag
+ # value as default.
+ if self.is_flag and not self.is_bool_flag:
+ for param in ctx.command.params:
+ if param.name == self.name and param.default:
+ return t.cast(Option, param).flag_value
+
+ return None
+
+ return super().get_default(ctx, call=call)
+
+ def prompt_for_value(self, ctx: Context) -> t.Any:
+ """This is an alternative flow that can be activated in the full
+ value processing if a value does not exist. It will prompt the
+ user until a valid value exists and then returns the processed
+ value as result.
+ """
+ assert self.prompt is not None
+
+ # Calculate the default before prompting anything to be stable.
+ default = self.get_default(ctx)
+
+ # If this is a prompt for a flag we need to handle this
+ # differently.
+ if self.is_bool_flag:
+ return confirm(self.prompt, default)
+
+ return prompt(
+ self.prompt,
+ default=default,
+ type=self.type,
+ hide_input=self.hide_input,
+ show_choices=self.show_choices,
+ confirmation_prompt=self.confirmation_prompt,
+ value_proc=lambda x: self.process_value(ctx, x),
+ )
+
+ def resolve_envvar_value(self, ctx: Context) -> t.Optional[str]:
+ rv = super().resolve_envvar_value(ctx)
+
+ if rv is not None:
+ return rv
+
+ if (
+ self.allow_from_autoenv
+ and ctx.auto_envvar_prefix is not None
+ and self.name is not None
+ ):
+ envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}"
+ rv = os.environ.get(envvar)
+
+ if rv:
+ return rv
+
+ return None
+
+ def value_from_envvar(self, ctx: Context) -> t.Optional[t.Any]:
+ rv: t.Optional[t.Any] = self.resolve_envvar_value(ctx)
+
+ if rv is None:
+ return None
+
+ value_depth = (self.nargs != 1) + bool(self.multiple)
+
+ if value_depth > 0:
+ rv = self.type.split_envvar_value(rv)
+
+ if self.multiple and self.nargs != 1:
+ rv = batch(rv, self.nargs)
+
+ return rv
+
+ def consume_value(
+ self, ctx: Context, opts: t.Mapping[str, "Parameter"]
+ ) -> t.Tuple[t.Any, ParameterSource]:
+ value, source = super().consume_value(ctx, opts)
+
+ # The parser will emit a sentinel value if the option can be
+ # given as a flag without a value. This is different from None
+ # to distinguish from the flag not being given at all.
+ if value is _flag_needs_value:
+ if self.prompt is not None and not ctx.resilient_parsing:
+ value = self.prompt_for_value(ctx)
+ source = ParameterSource.PROMPT
+ else:
+ value = self.flag_value
+ source = ParameterSource.COMMANDLINE
+
+ elif (
+ self.multiple
+ and value is not None
+ and any(v is _flag_needs_value for v in value)
+ ):
+ value = [self.flag_value if v is _flag_needs_value else v for v in value]
+ source = ParameterSource.COMMANDLINE
+
+ # The value wasn't set, or used the param's default, prompt if
+ # prompting is enabled.
+ elif (
+ source in {None, ParameterSource.DEFAULT}
+ and self.prompt is not None
+ and (self.required or self.prompt_required)
+ and not ctx.resilient_parsing
+ ):
+ value = self.prompt_for_value(ctx)
+ source = ParameterSource.PROMPT
+
+ return value, source
+
+
+class Argument(Parameter):
+ """Arguments are positional parameters to a command. They generally
+ provide fewer features than options but can have infinite ``nargs``
+ and are required by default.
+
+ All parameters are passed onwards to the constructor of :class:`Parameter`.
+ """
+
+ param_type_name = "argument"
+
+ def __init__(
+ self,
+ param_decls: t.Sequence[str],
+ required: t.Optional[bool] = None,
+ **attrs: t.Any,
+ ) -> None:
+ if required is None:
+ if attrs.get("default") is not None:
+ required = False
+ else:
+ required = attrs.get("nargs", 1) > 0
+
+ if "multiple" in attrs:
+ raise TypeError("__init__() got an unexpected keyword argument 'multiple'.")
+
+ super().__init__(param_decls, required=required, **attrs)
+
+ if __debug__:
+ if self.default is not None and self.nargs == -1:
+ raise TypeError("'default' is not supported for nargs=-1.")
+
+ @property
+ def human_readable_name(self) -> str:
+ if self.metavar is not None:
+ return self.metavar
+ return self.name.upper() # type: ignore
+
+ def make_metavar(self) -> str:
+ if self.metavar is not None:
+ return self.metavar
+ var = self.type.get_metavar(self)
+ if not var:
+ var = self.name.upper() # type: ignore
+ if not self.required:
+ var = f"[{var}]"
+ if self.nargs != 1:
+ var += "..."
+ return var
+
+ def _parse_decls(
+ self, decls: t.Sequence[str], expose_value: bool
+ ) -> t.Tuple[t.Optional[str], t.List[str], t.List[str]]:
+ if not decls:
+ if not expose_value:
+ return None, [], []
+ raise TypeError("Could not determine name for argument")
+ if len(decls) == 1:
+ name = arg = decls[0]
+ name = name.replace("-", "_").lower()
+ else:
+ raise TypeError(
+ "Arguments take exactly one parameter declaration, got"
+ f" {len(decls)}."
+ )
+ return name, [arg], []
+
+ def get_usage_pieces(self, ctx: Context) -> t.List[str]:
+ return [self.make_metavar()]
+
+ def get_error_hint(self, ctx: Context) -> str:
+ return f"'{self.make_metavar()}'"
+
+ def add_to_parser(self, parser: OptionParser, ctx: Context) -> None:
+ parser.add_argument(dest=self.name, nargs=self.nargs, obj=self)
diff --git a/venv/Lib/site-packages/click/decorators.py b/venv/Lib/site-packages/click/decorators.py
new file mode 100644
index 0000000..d9bba95
--- /dev/null
+++ b/venv/Lib/site-packages/click/decorators.py
@@ -0,0 +1,561 @@
+import inspect
+import types
+import typing as t
+from functools import update_wrapper
+from gettext import gettext as _
+
+from .core import Argument
+from .core import Command
+from .core import Context
+from .core import Group
+from .core import Option
+from .core import Parameter
+from .globals import get_current_context
+from .utils import echo
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+
+ P = te.ParamSpec("P")
+
+R = t.TypeVar("R")
+T = t.TypeVar("T")
+_AnyCallable = t.Callable[..., t.Any]
+FC = t.TypeVar("FC", bound=t.Union[_AnyCallable, Command])
+
+
+def pass_context(f: "t.Callable[te.Concatenate[Context, P], R]") -> "t.Callable[P, R]":
+ """Marks a callback as wanting to receive the current context
+ object as first argument.
+ """
+
+ def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R":
+ return f(get_current_context(), *args, **kwargs)
+
+ return update_wrapper(new_func, f)
+
+
+def pass_obj(f: "t.Callable[te.Concatenate[t.Any, P], R]") -> "t.Callable[P, R]":
+ """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: "P.args", **kwargs: "P.kwargs") -> "R":
+ return f(get_current_context().obj, *args, **kwargs)
+
+ return update_wrapper(new_func, f)
+
+
+def make_pass_decorator(
+ object_type: t.Type[T], ensure: bool = False
+) -> t.Callable[["t.Callable[te.Concatenate[T, P], R]"], "t.Callable[P, R]"]:
+ """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: "t.Callable[te.Concatenate[T, P], R]") -> "t.Callable[P, R]":
+ def new_func(*args: "P.args", **kwargs: "P.kwargs") -> "R":
+ ctx = get_current_context()
+
+ obj: t.Optional[T]
+ 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"
+ f" object of type {object_type.__name__!r}"
+ " existing."
+ )
+
+ return ctx.invoke(f, obj, *args, **kwargs)
+
+ return update_wrapper(new_func, f)
+
+ return decorator # type: ignore[return-value]
+
+
+def pass_meta_key(
+ key: str, *, doc_description: t.Optional[str] = None
+) -> "t.Callable[[t.Callable[te.Concatenate[t.Any, P], R]], t.Callable[P, R]]":
+ """Create a decorator that passes a key from
+ :attr:`click.Context.meta` as the first argument to the decorated
+ function.
+
+ :param key: Key in ``Context.meta`` to pass.
+ :param doc_description: Description of the object being passed,
+ inserted into the decorator's docstring. Defaults to "the 'key'
+ key from Context.meta".
+
+ .. versionadded:: 8.0
+ """
+
+ def decorator(f: "t.Callable[te.Concatenate[t.Any, P], R]") -> "t.Callable[P, R]":
+ def new_func(*args: "P.args", **kwargs: "P.kwargs") -> R:
+ ctx = get_current_context()
+ obj = ctx.meta[key]
+ return ctx.invoke(f, obj, *args, **kwargs)
+
+ return update_wrapper(new_func, f)
+
+ if doc_description is None:
+ doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
+
+ decorator.__doc__ = (
+ f"Decorator that passes {doc_description} as the first argument"
+ " to the decorated function."
+ )
+ return decorator # type: ignore[return-value]
+
+
+CmdType = t.TypeVar("CmdType", bound=Command)
+
+
+# variant: no call, directly as decorator for a function.
+@t.overload
+def command(name: _AnyCallable) -> Command:
+ ...
+
+
+# variant: with positional name and with positional or keyword cls argument:
+# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...)
+@t.overload
+def command(
+ name: t.Optional[str],
+ cls: t.Type[CmdType],
+ **attrs: t.Any,
+) -> t.Callable[[_AnyCallable], CmdType]:
+ ...
+
+
+# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...)
+@t.overload
+def command(
+ name: None = None,
+ *,
+ cls: t.Type[CmdType],
+ **attrs: t.Any,
+) -> t.Callable[[_AnyCallable], CmdType]:
+ ...
+
+
+# variant: with optional string name, no cls argument provided.
+@t.overload
+def command(
+ name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any
+) -> t.Callable[[_AnyCallable], Command]:
+ ...
+
+
+def command(
+ name: t.Union[t.Optional[str], _AnyCallable] = None,
+ cls: t.Optional[t.Type[CmdType]] = None,
+ **attrs: t.Any,
+) -> t.Union[Command, t.Callable[[_AnyCallable], t.Union[Command, CmdType]]]:
+ r"""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 with
+ underscores replaced by dashes. 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.
+ For the ``params`` argument, any decorated params are appended to
+ the end of the list.
+
+ 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 with underscores replaced by dashes.
+ :param cls: the command class to instantiate. This defaults to
+ :class:`Command`.
+
+ .. versionchanged:: 8.1
+ This decorator can be applied without parentheses.
+
+ .. versionchanged:: 8.1
+ The ``params`` argument can be used. Decorated params are
+ appended to the end of the list.
+ """
+
+ func: t.Optional[t.Callable[[_AnyCallable], t.Any]] = None
+
+ if callable(name):
+ func = name
+ name = None
+ assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class."
+ assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments."
+
+ if cls is None:
+ cls = t.cast(t.Type[CmdType], Command)
+
+ def decorator(f: _AnyCallable) -> CmdType:
+ if isinstance(f, Command):
+ raise TypeError("Attempted to convert a callback into a command twice.")
+
+ attr_params = attrs.pop("params", None)
+ params = attr_params if attr_params is not None else []
+
+ try:
+ decorator_params = f.__click_params__ # type: ignore
+ except AttributeError:
+ pass
+ else:
+ del f.__click_params__ # type: ignore
+ params.extend(reversed(decorator_params))
+
+ if attrs.get("help") is None:
+ attrs["help"] = f.__doc__
+
+ if t.TYPE_CHECKING:
+ assert cls is not None
+ assert not callable(name)
+
+ cmd = cls(
+ name=name or f.__name__.lower().replace("_", "-"),
+ callback=f,
+ params=params,
+ **attrs,
+ )
+ cmd.__doc__ = f.__doc__
+ return cmd
+
+ if func is not None:
+ return decorator(func)
+
+ return decorator
+
+
+GrpType = t.TypeVar("GrpType", bound=Group)
+
+
+# variant: no call, directly as decorator for a function.
+@t.overload
+def group(name: _AnyCallable) -> Group:
+ ...
+
+
+# variant: with positional name and with positional or keyword cls argument:
+# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...)
+@t.overload
+def group(
+ name: t.Optional[str],
+ cls: t.Type[GrpType],
+ **attrs: t.Any,
+) -> t.Callable[[_AnyCallable], GrpType]:
+ ...
+
+
+# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...)
+@t.overload
+def group(
+ name: None = None,
+ *,
+ cls: t.Type[GrpType],
+ **attrs: t.Any,
+) -> t.Callable[[_AnyCallable], GrpType]:
+ ...
+
+
+# variant: with optional string name, no cls argument provided.
+@t.overload
+def group(
+ name: t.Optional[str] = ..., cls: None = None, **attrs: t.Any
+) -> t.Callable[[_AnyCallable], Group]:
+ ...
+
+
+def group(
+ name: t.Union[str, _AnyCallable, None] = None,
+ cls: t.Optional[t.Type[GrpType]] = None,
+ **attrs: t.Any,
+) -> t.Union[Group, t.Callable[[_AnyCallable], t.Union[Group, GrpType]]]:
+ """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`.
+
+ .. versionchanged:: 8.1
+ This decorator can be applied without parentheses.
+ """
+ if cls is None:
+ cls = t.cast(t.Type[GrpType], Group)
+
+ if callable(name):
+ return command(cls=cls, **attrs)(name)
+
+ return command(name, cls, **attrs)
+
+
+def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None:
+ if isinstance(f, Command):
+ f.params.append(param)
+ else:
+ if not hasattr(f, "__click_params__"):
+ f.__click_params__ = [] # type: ignore
+
+ f.__click_params__.append(param) # type: ignore
+
+
+def argument(
+ *param_decls: str, cls: t.Optional[t.Type[Argument]] = None, **attrs: t.Any
+) -> t.Callable[[FC], FC]:
+ """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.
+
+ For the default argument class, refer to :class:`Argument` and
+ :class:`Parameter` for descriptions of parameters.
+
+ :param cls: the argument class to instantiate. This defaults to
+ :class:`Argument`.
+ :param param_decls: Passed as positional arguments to the constructor of
+ ``cls``.
+ :param attrs: Passed as keyword arguments to the constructor of ``cls``.
+ """
+ if cls is None:
+ cls = Argument
+
+ def decorator(f: FC) -> FC:
+ _param_memo(f, cls(param_decls, **attrs))
+ return f
+
+ return decorator
+
+
+def option(
+ *param_decls: str, cls: t.Optional[t.Type[Option]] = None, **attrs: t.Any
+) -> t.Callable[[FC], FC]:
+ """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.
+
+ For the default option class, refer to :class:`Option` and
+ :class:`Parameter` for descriptions of parameters.
+
+ :param cls: the option class to instantiate. This defaults to
+ :class:`Option`.
+ :param param_decls: Passed as positional arguments to the constructor of
+ ``cls``.
+ :param attrs: Passed as keyword arguments to the constructor of ``cls``.
+ """
+ if cls is None:
+ cls = Option
+
+ def decorator(f: FC) -> FC:
+ _param_memo(f, cls(param_decls, **attrs))
+ return f
+
+ return decorator
+
+
+def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
+ """Add a ``--yes`` option which shows a prompt before continuing if
+ not passed. If the prompt is declined, the program will exit.
+
+ :param param_decls: One or more option names. Defaults to the single
+ value ``"--yes"``.
+ :param kwargs: Extra arguments are passed to :func:`option`.
+ """
+
+ def callback(ctx: Context, param: Parameter, value: bool) -> None:
+ if not value:
+ ctx.abort()
+
+ if not param_decls:
+ param_decls = ("--yes",)
+
+ kwargs.setdefault("is_flag", True)
+ kwargs.setdefault("callback", callback)
+ kwargs.setdefault("expose_value", False)
+ kwargs.setdefault("prompt", "Do you want to continue?")
+ kwargs.setdefault("help", "Confirm the action without prompting.")
+ return option(*param_decls, **kwargs)
+
+
+def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
+ """Add a ``--password`` option which prompts for a password, hiding
+ input and asking to enter the value again for confirmation.
+
+ :param param_decls: One or more option names. Defaults to the single
+ value ``"--password"``.
+ :param kwargs: Extra arguments are passed to :func:`option`.
+ """
+ if not param_decls:
+ param_decls = ("--password",)
+
+ kwargs.setdefault("prompt", True)
+ kwargs.setdefault("confirmation_prompt", True)
+ kwargs.setdefault("hide_input", True)
+ return option(*param_decls, **kwargs)
+
+
+def version_option(
+ version: t.Optional[str] = None,
+ *param_decls: str,
+ package_name: t.Optional[str] = None,
+ prog_name: t.Optional[str] = None,
+ message: t.Optional[str] = None,
+ **kwargs: t.Any,
+) -> t.Callable[[FC], FC]:
+ """Add a ``--version`` option which immediately prints the version
+ number and exits the program.
+
+ If ``version`` is not provided, Click will try to detect it using
+ :func:`importlib.metadata.version` to get the version for the
+ ``package_name``. On Python < 3.8, the ``importlib_metadata``
+ backport must be installed.
+
+ If ``package_name`` is not provided, Click will try to detect it by
+ inspecting the stack frames. This will be used to detect the
+ version, so it must match the name of the installed package.
+
+ :param version: The version number to show. If not provided, Click
+ will try to detect it.
+ :param param_decls: One or more option names. Defaults to the single
+ value ``"--version"``.
+ :param package_name: The package name to detect the version from. If
+ not provided, Click will try to detect it.
+ :param prog_name: The name of the CLI to show in the message. If not
+ provided, it will be detected from the command.
+ :param message: The message to show. The values ``%(prog)s``,
+ ``%(package)s``, and ``%(version)s`` are available. Defaults to
+ ``"%(prog)s, version %(version)s"``.
+ :param kwargs: Extra arguments are passed to :func:`option`.
+ :raise RuntimeError: ``version`` could not be detected.
+
+ .. versionchanged:: 8.0
+ Add the ``package_name`` parameter, and the ``%(package)s``
+ value for messages.
+
+ .. versionchanged:: 8.0
+ Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
+ version is detected based on the package name, not the entry
+ point name. The Python package name must match the installed
+ package name, or be passed with ``package_name=``.
+ """
+ if message is None:
+ message = _("%(prog)s, version %(version)s")
+
+ if version is None and package_name is None:
+ frame = inspect.currentframe()
+ f_back = frame.f_back if frame is not None else None
+ f_globals = f_back.f_globals if f_back is not None else None
+ # break reference cycle
+ # https://docs.python.org/3/library/inspect.html#the-interpreter-stack
+ del frame
+
+ if f_globals is not None:
+ package_name = f_globals.get("__name__")
+
+ if package_name == "__main__":
+ package_name = f_globals.get("__package__")
+
+ if package_name:
+ package_name = package_name.partition(".")[0]
+
+ def callback(ctx: Context, param: Parameter, value: bool) -> None:
+ if not value or ctx.resilient_parsing:
+ return
+
+ nonlocal prog_name
+ nonlocal version
+
+ if prog_name is None:
+ prog_name = ctx.find_root().info_name
+
+ if version is None and package_name is not None:
+ metadata: t.Optional[types.ModuleType]
+
+ try:
+ from importlib import metadata # type: ignore
+ except ImportError:
+ # Python < 3.8
+ import importlib_metadata as metadata # type: ignore
+
+ try:
+ version = metadata.version(package_name) # type: ignore
+ except metadata.PackageNotFoundError: # type: ignore
+ raise RuntimeError(
+ f"{package_name!r} is not installed. Try passing"
+ " 'package_name' instead."
+ ) from None
+
+ if version is None:
+ raise RuntimeError(
+ f"Could not determine the version for {package_name!r} automatically."
+ )
+
+ echo(
+ message % {"prog": prog_name, "package": package_name, "version": version},
+ color=ctx.color,
+ )
+ ctx.exit()
+
+ if not param_decls:
+ param_decls = ("--version",)
+
+ kwargs.setdefault("is_flag", True)
+ kwargs.setdefault("expose_value", False)
+ kwargs.setdefault("is_eager", True)
+ kwargs.setdefault("help", _("Show the version and exit."))
+ kwargs["callback"] = callback
+ return option(*param_decls, **kwargs)
+
+
+def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
+ """Add a ``--help`` option which immediately prints the help page
+ and exits the program.
+
+ This is usually unnecessary, as the ``--help`` option is added to
+ each command automatically unless ``add_help_option=False`` is
+ passed.
+
+ :param param_decls: One or more option names. Defaults to the single
+ value ``"--help"``.
+ :param kwargs: Extra arguments are passed to :func:`option`.
+ """
+
+ def callback(ctx: Context, param: Parameter, value: bool) -> None:
+ if not value or ctx.resilient_parsing:
+ return
+
+ echo(ctx.get_help(), color=ctx.color)
+ ctx.exit()
+
+ if not param_decls:
+ param_decls = ("--help",)
+
+ kwargs.setdefault("is_flag", True)
+ kwargs.setdefault("expose_value", False)
+ kwargs.setdefault("is_eager", True)
+ kwargs.setdefault("help", _("Show this message and exit."))
+ kwargs["callback"] = callback
+ return option(*param_decls, **kwargs)
diff --git a/venv/Lib/site-packages/click/exceptions.py b/venv/Lib/site-packages/click/exceptions.py
new file mode 100644
index 0000000..fe68a36
--- /dev/null
+++ b/venv/Lib/site-packages/click/exceptions.py
@@ -0,0 +1,288 @@
+import typing as t
+from gettext import gettext as _
+from gettext import ngettext
+
+from ._compat import get_text_stderr
+from .utils import echo
+from .utils import format_filename
+
+if t.TYPE_CHECKING:
+ from .core import Command
+ from .core import Context
+ from .core import Parameter
+
+
+def _join_param_hints(
+ param_hint: t.Optional[t.Union[t.Sequence[str], str]]
+) -> t.Optional[str]:
+ if param_hint is not None and not isinstance(param_hint, str):
+ return " / ".join(repr(x) for x in param_hint)
+
+ return param_hint
+
+
+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: str) -> None:
+ super().__init__(message)
+ self.message = message
+
+ def format_message(self) -> str:
+ return self.message
+
+ def __str__(self) -> str:
+ return self.message
+
+ def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
+ if file is None:
+ file = get_text_stderr()
+
+ echo(_("Error: {message}").format(message=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: str, ctx: t.Optional["Context"] = None) -> None:
+ super().__init__(message)
+ self.ctx = ctx
+ self.cmd: t.Optional["Command"] = self.ctx.command if self.ctx else None
+
+ def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None:
+ if file is None:
+ file = get_text_stderr()
+ color = None
+ hint = ""
+ if (
+ self.ctx is not None
+ and self.ctx.command.get_help_option(self.ctx) is not None
+ ):
+ hint = _("Try '{command} {option}' for help.").format(
+ command=self.ctx.command_path, option=self.ctx.help_option_names[0]
+ )
+ hint = f"{hint}\n"
+ if self.ctx is not None:
+ color = self.ctx.color
+ echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
+ echo(
+ _("Error: {message}").format(message=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: str,
+ ctx: t.Optional["Context"] = None,
+ param: t.Optional["Parameter"] = None,
+ param_hint: t.Optional[str] = None,
+ ) -> None:
+ super().__init__(message, ctx)
+ self.param = param
+ self.param_hint = param_hint
+
+ def format_message(self) -> str:
+ if self.param_hint is not None:
+ param_hint = self.param_hint
+ elif self.param is not None:
+ param_hint = self.param.get_error_hint(self.ctx) # type: ignore
+ else:
+ return _("Invalid value: {message}").format(message=self.message)
+
+ return _("Invalid value for {param_hint}: {message}").format(
+ param_hint=_join_param_hints(param_hint), message=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: t.Optional[str] = None,
+ ctx: t.Optional["Context"] = None,
+ param: t.Optional["Parameter"] = None,
+ param_hint: t.Optional[str] = None,
+ param_type: t.Optional[str] = None,
+ ) -> None:
+ super().__init__(message or "", ctx, param, param_hint)
+ self.param_type = param_type
+
+ def format_message(self) -> str:
+ if self.param_hint is not None:
+ param_hint: t.Optional[str] = self.param_hint
+ elif self.param is not None:
+ param_hint = self.param.get_error_hint(self.ctx) # type: ignore
+ else:
+ param_hint = None
+
+ param_hint = _join_param_hints(param_hint)
+ param_hint = f" {param_hint}" if param_hint else ""
+
+ 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 += f". {msg_extra}"
+ else:
+ msg = msg_extra
+
+ msg = f" {msg}" if msg else ""
+
+ # Translate param_type for known types.
+ if param_type == "argument":
+ missing = _("Missing argument")
+ elif param_type == "option":
+ missing = _("Missing option")
+ elif param_type == "parameter":
+ missing = _("Missing parameter")
+ else:
+ missing = _("Missing {param_type}").format(param_type=param_type)
+
+ return f"{missing}{param_hint}.{msg}"
+
+ def __str__(self) -> str:
+ if not self.message:
+ param_name = self.param.name if self.param else None
+ return _("Missing parameter: {param_name}").format(param_name=param_name)
+ else:
+ return self.message
+
+
+class NoSuchOption(UsageError):
+ """Raised if click attempted to handle an option that does not
+ exist.
+
+ .. versionadded:: 4.0
+ """
+
+ def __init__(
+ self,
+ option_name: str,
+ message: t.Optional[str] = None,
+ possibilities: t.Optional[t.Sequence[str]] = None,
+ ctx: t.Optional["Context"] = None,
+ ) -> None:
+ if message is None:
+ message = _("No such option: {name}").format(name=option_name)
+
+ super().__init__(message, ctx)
+ self.option_name = option_name
+ self.possibilities = possibilities
+
+ def format_message(self) -> str:
+ if not self.possibilities:
+ return self.message
+
+ possibility_str = ", ".join(sorted(self.possibilities))
+ suggest = ngettext(
+ "Did you mean {possibility}?",
+ "(Possible options: {possibilities})",
+ len(self.possibilities),
+ ).format(possibility=possibility_str, possibilities=possibility_str)
+ return f"{self.message} {suggest}"
+
+
+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
+
+ :param option_name: the name of the option being used incorrectly.
+ """
+
+ def __init__(
+ self, option_name: str, message: str, ctx: t.Optional["Context"] = None
+ ) -> None:
+ super().__init__(message, ctx)
+ self.option_name = option_name
+
+
+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
+ """
+
+
+class FileError(ClickException):
+ """Raised if a file cannot be opened."""
+
+ def __init__(self, filename: str, hint: t.Optional[str] = None) -> None:
+ if hint is None:
+ hint = _("unknown error")
+
+ super().__init__(hint)
+ self.ui_filename: str = format_filename(filename)
+ self.filename = filename
+
+ def format_message(self) -> str:
+ return _("Could not open file {filename!r}: {message}").format(
+ filename=self.ui_filename, message=self.message
+ )
+
+
+class Abort(RuntimeError):
+ """An internal signalling exception that signals Click to abort."""
+
+
+class Exit(RuntimeError):
+ """An exception that indicates that the application should exit with some
+ status code.
+
+ :param code: the status code to exit with.
+ """
+
+ __slots__ = ("exit_code",)
+
+ def __init__(self, code: int = 0) -> None:
+ self.exit_code: int = code
diff --git a/venv/Lib/site-packages/click/formatting.py b/venv/Lib/site-packages/click/formatting.py
new file mode 100644
index 0000000..ddd2a2f
--- /dev/null
+++ b/venv/Lib/site-packages/click/formatting.py
@@ -0,0 +1,301 @@
+import typing as t
+from contextlib import contextmanager
+from gettext import gettext as _
+
+from ._compat import term_len
+from .parser import split_opt
+
+# Can force a width. This is used by the test system
+FORCED_WIDTH: t.Optional[int] = None
+
+
+def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]:
+ widths: t.Dict[int, int] = {}
+
+ 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: t.Iterable[t.Tuple[str, str]], col_count: int
+) -> t.Iterator[t.Tuple[str, ...]]:
+ for row in rows:
+ yield row + ("",) * (col_count - len(row))
+
+
+def wrap_text(
+ text: str,
+ width: int = 78,
+ initial_indent: str = "",
+ subsequent_indent: str = "",
+ preserve_paragraphs: bool = False,
+) -> str:
+ """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: t.List[t.Tuple[int, bool, str]] = []
+ buf: t.List[str] = []
+ indent = None
+
+ def _flush_par() -> None:
+ 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:
+ """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: int = 2,
+ width: t.Optional[int] = None,
+ max_width: t.Optional[int] = None,
+ ) -> None:
+ import shutil
+
+ 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(shutil.get_terminal_size().columns, max_width) - 2, 50)
+ self.width = width
+ self.current_indent = 0
+ self.buffer: t.List[str] = []
+
+ def write(self, string: str) -> None:
+ """Writes a unicode string into the internal buffer."""
+ self.buffer.append(string)
+
+ def indent(self) -> None:
+ """Increases the indentation."""
+ self.current_indent += self.indent_increment
+
+ def dedent(self) -> None:
+ """Decreases the indentation."""
+ self.current_indent -= self.indent_increment
+
+ def write_usage(
+ self, prog: str, args: str = "", prefix: t.Optional[str] = None
+ ) -> None:
+ """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. Defaults to
+ ``"Usage: "``.
+ """
+ if prefix is None:
+ prefix = f"{_('Usage:')} "
+
+ usage_prefix = f"{prefix:>{self.current_indent}}{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: str) -> None:
+ """Writes a heading into the buffer."""
+ self.write(f"{'':>{self.current_indent}}{heading}:\n")
+
+ def write_paragraph(self) -> None:
+ """Writes a paragraph into the buffer."""
+ if self.buffer:
+ self.write("\n")
+
+ def write_text(self, text: str) -> None:
+ """Writes re-indented text into the buffer. This rewraps and
+ preserves paragraphs.
+ """
+ indent = " " * self.current_indent
+ self.write(
+ wrap_text(
+ text,
+ self.width,
+ initial_indent=indent,
+ subsequent_indent=indent,
+ preserve_paragraphs=True,
+ )
+ )
+ self.write("\n")
+
+ def write_dl(
+ self,
+ rows: t.Sequence[t.Tuple[str, str]],
+ col_max: int = 30,
+ col_spacing: int = 2,
+ ) -> None:
+ """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(f"{'':>{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)
+ wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
+ lines = wrapped_text.splitlines()
+
+ if lines:
+ self.write(f"{lines[0]}\n")
+
+ for line in lines[1:]:
+ self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
+ else:
+ self.write("\n")
+
+ @contextmanager
+ def section(self, name: str) -> t.Iterator[None]:
+ """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) -> t.Iterator[None]:
+ """A context manager that increases the indentation."""
+ self.indent()
+ try:
+ yield
+ finally:
+ self.dedent()
+
+ def getvalue(self) -> str:
+ """Returns the buffer contents."""
+ return "".join(self.buffer)
+
+
+def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]:
+ """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])
+ return ", ".join(x[1] for x in rv), any_prefix_is_slash
diff --git a/venv/Lib/site-packages/click/globals.py b/venv/Lib/site-packages/click/globals.py
new file mode 100644
index 0000000..480058f
--- /dev/null
+++ b/venv/Lib/site-packages/click/globals.py
@@ -0,0 +1,68 @@
+import typing as t
+from threading import local
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+ from .core import Context
+
+_local = local()
+
+
+@t.overload
+def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
+ ...
+
+
+@t.overload
+def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
+ ...
+
+
+def get_current_context(silent: bool = False) -> t.Optional["Context"]:
+ """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 its behavior based on the current context.
+
+ To push the current context, :meth:`Context.scope` can be used.
+
+ .. versionadded:: 5.0
+
+ :param silent: if set to `True` the return value is `None` if no context
+ is available. The default behavior is to raise a
+ :exc:`RuntimeError`.
+ """
+ try:
+ return t.cast("Context", _local.stack[-1])
+ except (AttributeError, IndexError) as e:
+ if not silent:
+ raise RuntimeError("There is no active click context.") from e
+
+ return None
+
+
+def push_context(ctx: "Context") -> None:
+ """Pushes a new context to the current stack."""
+ _local.__dict__.setdefault("stack", []).append(ctx)
+
+
+def pop_context() -> None:
+ """Removes the top level from the stack."""
+ _local.stack.pop()
+
+
+def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]:
+ """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
+
+ return None
diff --git a/venv/Lib/site-packages/click/parser.py b/venv/Lib/site-packages/click/parser.py
new file mode 100644
index 0000000..5fa7adf
--- /dev/null
+++ b/venv/Lib/site-packages/click/parser.py
@@ -0,0 +1,529 @@
+"""
+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.
+
+Click uses parts of optparse written by Gregory P. Ward and maintained
+by the Python Software Foundation. This is limited to code in parser.py.
+
+Copyright 2001-2006 Gregory P. Ward. All rights reserved.
+Copyright 2002-2006 Python Software Foundation. All rights reserved.
+"""
+# This code uses parts of optparse written by Gregory P. Ward and
+# maintained by the Python Software Foundation.
+# Copyright 2001-2006 Gregory P. Ward
+# Copyright 2002-2006 Python Software Foundation
+import typing as t
+from collections import deque
+from gettext import gettext as _
+from gettext import ngettext
+
+from .exceptions import BadArgumentUsage
+from .exceptions import BadOptionUsage
+from .exceptions import NoSuchOption
+from .exceptions import UsageError
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+ from .core import Argument as CoreArgument
+ from .core import Context
+ from .core import Option as CoreOption
+ from .core import Parameter as CoreParameter
+
+V = t.TypeVar("V")
+
+# Sentinel value that indicates an option was passed as a flag without a
+# value but is not a flag option. Option.consume_value uses this to
+# prompt or use the flag_value.
+_flag_needs_value = object()
+
+
+def _unpack_args(
+ args: t.Sequence[str], nargs_spec: t.Sequence[int]
+) -> t.Tuple[t.Sequence[t.Union[str, t.Sequence[t.Optional[str]], None]], t.List[str]]:
+ """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: t.List[t.Union[str, t.Tuple[t.Optional[str], ...], None]] = []
+ spos: t.Optional[int] = None
+
+ def _fetch(c: "te.Deque[V]") -> t.Optional[V]:
+ 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 is None:
+ continue
+
+ 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 split_opt(opt: str) -> t.Tuple[str, str]:
+ 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: str, ctx: t.Optional["Context"]) -> str:
+ if ctx is None or ctx.token_normalize_func is None:
+ return opt
+ prefix, opt = split_opt(opt)
+ return f"{prefix}{ctx.token_normalize_func(opt)}"
+
+
+def split_arg_string(string: str) -> t.List[str]:
+ """Split an argument string as with :func:`shlex.split`, but don't
+ fail if the string is incomplete. Ignores a missing closing quote or
+ incomplete escape sequence and uses the partial token as-is.
+
+ .. code-block:: python
+
+ split_arg_string("example 'my file")
+ ["example", "my file"]
+
+ split_arg_string("example my\\")
+ ["example", "my"]
+
+ :param string: String to split.
+ """
+ import shlex
+
+ lex = shlex.shlex(string, posix=True)
+ lex.whitespace_split = True
+ lex.commenters = ""
+ out = []
+
+ try:
+ for token in lex:
+ out.append(token)
+ except ValueError:
+ # Raised when end-of-string is reached in an invalid state. Use
+ # the partial token as-is. The quote or escape character is in
+ # lex.state, not lex.token.
+ out.append(lex.token)
+
+ return out
+
+
+class Option:
+ def __init__(
+ self,
+ obj: "CoreOption",
+ opts: t.Sequence[str],
+ dest: t.Optional[str],
+ action: t.Optional[str] = None,
+ nargs: int = 1,
+ const: t.Optional[t.Any] = None,
+ ):
+ self._short_opts = []
+ self._long_opts = []
+ self.prefixes: t.Set[str] = set()
+
+ for opt in opts:
+ prefix, value = split_opt(opt)
+ if not prefix:
+ raise ValueError(f"Invalid start character for option ({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) -> bool:
+ return self.action in ("store", "append")
+
+ def process(self, value: t.Any, state: "ParsingState") -> None:
+ if self.action == "store":
+ state.opts[self.dest] = value # type: ignore
+ elif self.action == "store_const":
+ state.opts[self.dest] = self.const # type: ignore
+ elif self.action == "append":
+ state.opts.setdefault(self.dest, []).append(value) # type: ignore
+ elif self.action == "append_const":
+ state.opts.setdefault(self.dest, []).append(self.const) # type: ignore
+ elif self.action == "count":
+ state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore
+ else:
+ raise ValueError(f"unknown action '{self.action}'")
+ state.order.append(self.obj)
+
+
+class Argument:
+ def __init__(self, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1):
+ self.dest = dest
+ self.nargs = nargs
+ self.obj = obj
+
+ def process(
+ self,
+ value: t.Union[t.Optional[str], t.Sequence[t.Optional[str]]],
+ state: "ParsingState",
+ ) -> None:
+ if self.nargs > 1:
+ assert value is not None
+ holes = sum(1 for x in value if x is None)
+ if holes == len(value):
+ value = None
+ elif holes != 0:
+ raise BadArgumentUsage(
+ _("Argument {name!r} takes {nargs} values.").format(
+ name=self.dest, nargs=self.nargs
+ )
+ )
+
+ if self.nargs == -1 and self.obj.envvar is not None and value == ():
+ # Replace empty tuple with None so that a value from the
+ # environment may be tried.
+ value = None
+
+ state.opts[self.dest] = value # type: ignore
+ state.order.append(self.obj)
+
+
+class ParsingState:
+ def __init__(self, rargs: t.List[str]) -> None:
+ self.opts: t.Dict[str, t.Any] = {}
+ self.largs: t.List[str] = []
+ self.rargs = rargs
+ self.order: t.List["CoreParameter"] = []
+
+
+class OptionParser:
+ """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: t.Optional["Context"] = None) -> 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: bool = 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: bool = 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: t.Dict[str, Option] = {}
+ self._long_opt: t.Dict[str, Option] = {}
+ self._opt_prefixes = {"-", "--"}
+ self._args: t.List[Argument] = []
+
+ def add_option(
+ self,
+ obj: "CoreOption",
+ opts: t.Sequence[str],
+ dest: t.Optional[str],
+ action: t.Optional[str] = None,
+ nargs: int = 1,
+ const: t.Optional[t.Any] = None,
+ ) -> 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``, ``append_const`` or ``count``.
+
+ The `obj` can be used to identify the option in the order list
+ that is returned from the parser.
+ """
+ opts = [normalize_opt(opt, self.ctx) for opt in opts]
+ option = Option(obj, opts, dest, action=action, nargs=nargs, const=const)
+ 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, obj: "CoreArgument", dest: t.Optional[str], nargs: int = 1
+ ) -> 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.
+ """
+ self._args.append(Argument(obj, dest=dest, nargs=nargs))
+
+ def parse_args(
+ self, args: t.List[str]
+ ) -> t.Tuple[t.Dict[str, t.Any], t.List[str], t.List["CoreParameter"]]:
+ """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: ParsingState) -> None:
+ 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: ParsingState) -> None:
+ 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: str, explicit_value: t.Optional[str], state: ParsingState
+ ) -> None:
+ if opt not in self._long_opt:
+ from difflib import get_close_matches
+
+ possibilities = get_close_matches(opt, self._long_opt)
+ raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx)
+
+ 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)
+
+ value = self._get_value_from_state(opt, option, state)
+
+ elif explicit_value is not None:
+ raise BadOptionUsage(
+ opt, _("Option {name!r} does not take a value.").format(name=opt)
+ )
+
+ else:
+ value = None
+
+ option.process(value, state)
+
+ def _match_short_opt(self, arg: str, state: ParsingState) -> None:
+ stop = False
+ i = 1
+ prefix = arg[0]
+ unknown_options = []
+
+ for ch in arg[1:]:
+ opt = normalize_opt(f"{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, ctx=self.ctx)
+ 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
+
+ value = self._get_value_from_state(opt, option, state)
+
+ else:
+ value = None
+
+ option.process(value, state)
+
+ if stop:
+ break
+
+ # If we got any unknown options we recombine 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(f"{prefix}{''.join(unknown_options)}")
+
+ def _get_value_from_state(
+ self, option_name: str, option: Option, state: ParsingState
+ ) -> t.Any:
+ nargs = option.nargs
+
+ if len(state.rargs) < nargs:
+ if option.obj._flag_needs_value:
+ # Option allows omitting the value.
+ value = _flag_needs_value
+ else:
+ raise BadOptionUsage(
+ option_name,
+ ngettext(
+ "Option {name!r} requires an argument.",
+ "Option {name!r} requires {nargs} arguments.",
+ nargs,
+ ).format(name=option_name, nargs=nargs),
+ )
+ elif nargs == 1:
+ next_rarg = state.rargs[0]
+
+ if (
+ option.obj._flag_needs_value
+ and isinstance(next_rarg, str)
+ and next_rarg[:1] in self._opt_prefixes
+ and len(next_rarg) > 1
+ ):
+ # The next arg looks like the start of an option, don't
+ # use it as the value if omitting the value is allowed.
+ value = _flag_needs_value
+ else:
+ value = state.rargs.pop(0)
+ else:
+ value = tuple(state.rargs[:nargs])
+ del state.rargs[:nargs]
+
+ return value
+
+ def _process_opts(self, arg: str, state: ParsingState) -> None:
+ 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:
+ self._match_short_opt(arg, state)
+ return
+
+ if not self.ignore_unknown_options:
+ raise
+
+ state.largs.append(arg)
diff --git a/venv/Lib/site-packages/click/py.typed b/venv/Lib/site-packages/click/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/click/shell_completion.py b/venv/Lib/site-packages/click/shell_completion.py
new file mode 100644
index 0000000..dc9e00b
--- /dev/null
+++ b/venv/Lib/site-packages/click/shell_completion.py
@@ -0,0 +1,596 @@
+import os
+import re
+import typing as t
+from gettext import gettext as _
+
+from .core import Argument
+from .core import BaseCommand
+from .core import Context
+from .core import MultiCommand
+from .core import Option
+from .core import Parameter
+from .core import ParameterSource
+from .parser import split_arg_string
+from .utils import echo
+
+
+def shell_complete(
+ cli: BaseCommand,
+ ctx_args: t.MutableMapping[str, t.Any],
+ prog_name: str,
+ complete_var: str,
+ instruction: str,
+) -> int:
+ """Perform shell completion for the given CLI program.
+
+ :param cli: Command being called.
+ :param ctx_args: Extra arguments to pass to
+ ``cli.make_context``.
+ :param prog_name: Name of the executable in the shell.
+ :param complete_var: Name of the environment variable that holds
+ the completion instruction.
+ :param instruction: Value of ``complete_var`` with the completion
+ instruction and shell, in the form ``instruction_shell``.
+ :return: Status code to exit with.
+ """
+ shell, _, instruction = instruction.partition("_")
+ comp_cls = get_completion_class(shell)
+
+ if comp_cls is None:
+ return 1
+
+ comp = comp_cls(cli, ctx_args, prog_name, complete_var)
+
+ if instruction == "source":
+ echo(comp.source())
+ return 0
+
+ if instruction == "complete":
+ echo(comp.complete())
+ return 0
+
+ return 1
+
+
+class CompletionItem:
+ """Represents a completion value and metadata about the value. The
+ default metadata is ``type`` to indicate special shell handling,
+ and ``help`` if a shell supports showing a help string next to the
+ value.
+
+ Arbitrary parameters can be passed when creating the object, and
+ accessed using ``item.attr``. If an attribute wasn't passed,
+ accessing it returns ``None``.
+
+ :param value: The completion suggestion.
+ :param type: Tells the shell script to provide special completion
+ support for the type. Click uses ``"dir"`` and ``"file"``.
+ :param help: String shown next to the value if supported.
+ :param kwargs: Arbitrary metadata. The built-in implementations
+ don't use this, but custom type completions paired with custom
+ shell support could use it.
+ """
+
+ __slots__ = ("value", "type", "help", "_info")
+
+ def __init__(
+ self,
+ value: t.Any,
+ type: str = "plain",
+ help: t.Optional[str] = None,
+ **kwargs: t.Any,
+ ) -> None:
+ self.value: t.Any = value
+ self.type: str = type
+ self.help: t.Optional[str] = help
+ self._info = kwargs
+
+ def __getattr__(self, name: str) -> t.Any:
+ return self._info.get(name)
+
+
+# Only Bash >= 4.4 has the nosort option.
+_SOURCE_BASH = """\
+%(complete_func)s() {
+ local IFS=$'\\n'
+ local response
+
+ response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \
+%(complete_var)s=bash_complete $1)
+
+ for completion in $response; do
+ IFS=',' read type value <<< "$completion"
+
+ if [[ $type == 'dir' ]]; then
+ COMPREPLY=()
+ compopt -o dirnames
+ elif [[ $type == 'file' ]]; then
+ COMPREPLY=()
+ compopt -o default
+ elif [[ $type == 'plain' ]]; then
+ COMPREPLY+=($value)
+ fi
+ done
+
+ return 0
+}
+
+%(complete_func)s_setup() {
+ complete -o nosort -F %(complete_func)s %(prog_name)s
+}
+
+%(complete_func)s_setup;
+"""
+
+_SOURCE_ZSH = """\
+#compdef %(prog_name)s
+
+%(complete_func)s() {
+ local -a completions
+ local -a completions_with_descriptions
+ local -a response
+ (( ! $+commands[%(prog_name)s] )) && return 1
+
+ response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \
+%(complete_var)s=zsh_complete %(prog_name)s)}")
+
+ for type key descr in ${response}; do
+ if [[ "$type" == "plain" ]]; then
+ if [[ "$descr" == "_" ]]; then
+ completions+=("$key")
+ else
+ completions_with_descriptions+=("$key":"$descr")
+ fi
+ elif [[ "$type" == "dir" ]]; then
+ _path_files -/
+ elif [[ "$type" == "file" ]]; then
+ _path_files -f
+ fi
+ done
+
+ if [ -n "$completions_with_descriptions" ]; then
+ _describe -V unsorted completions_with_descriptions -U
+ fi
+
+ if [ -n "$completions" ]; then
+ compadd -U -V unsorted -a completions
+ fi
+}
+
+if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
+ # autoload from fpath, call function directly
+ %(complete_func)s "$@"
+else
+ # eval/source/. command, register function for later
+ compdef %(complete_func)s %(prog_name)s
+fi
+"""
+
+_SOURCE_FISH = """\
+function %(complete_func)s;
+ set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \
+COMP_CWORD=(commandline -t) %(prog_name)s);
+
+ for completion in $response;
+ set -l metadata (string split "," $completion);
+
+ if test $metadata[1] = "dir";
+ __fish_complete_directories $metadata[2];
+ else if test $metadata[1] = "file";
+ __fish_complete_path $metadata[2];
+ else if test $metadata[1] = "plain";
+ echo $metadata[2];
+ end;
+ end;
+end;
+
+complete --no-files --command %(prog_name)s --arguments \
+"(%(complete_func)s)";
+"""
+
+
+class ShellComplete:
+ """Base class for providing shell completion support. A subclass for
+ a given shell will override attributes and methods to implement the
+ completion instructions (``source`` and ``complete``).
+
+ :param cli: Command being called.
+ :param prog_name: Name of the executable in the shell.
+ :param complete_var: Name of the environment variable that holds
+ the completion instruction.
+
+ .. versionadded:: 8.0
+ """
+
+ name: t.ClassVar[str]
+ """Name to register the shell as with :func:`add_completion_class`.
+ This is used in completion instructions (``{name}_source`` and
+ ``{name}_complete``).
+ """
+
+ source_template: t.ClassVar[str]
+ """Completion script template formatted by :meth:`source`. This must
+ be provided by subclasses.
+ """
+
+ def __init__(
+ self,
+ cli: BaseCommand,
+ ctx_args: t.MutableMapping[str, t.Any],
+ prog_name: str,
+ complete_var: str,
+ ) -> None:
+ self.cli = cli
+ self.ctx_args = ctx_args
+ self.prog_name = prog_name
+ self.complete_var = complete_var
+
+ @property
+ def func_name(self) -> str:
+ """The name of the shell function defined by the completion
+ script.
+ """
+ safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII)
+ return f"_{safe_name}_completion"
+
+ def source_vars(self) -> t.Dict[str, t.Any]:
+ """Vars for formatting :attr:`source_template`.
+
+ By default this provides ``complete_func``, ``complete_var``,
+ and ``prog_name``.
+ """
+ return {
+ "complete_func": self.func_name,
+ "complete_var": self.complete_var,
+ "prog_name": self.prog_name,
+ }
+
+ def source(self) -> str:
+ """Produce the shell script that defines the completion
+ function. By default this ``%``-style formats
+ :attr:`source_template` with the dict returned by
+ :meth:`source_vars`.
+ """
+ return self.source_template % self.source_vars()
+
+ def get_completion_args(self) -> t.Tuple[t.List[str], str]:
+ """Use the env vars defined by the shell script to return a
+ tuple of ``args, incomplete``. This must be implemented by
+ subclasses.
+ """
+ raise NotImplementedError
+
+ def get_completions(
+ self, args: t.List[str], incomplete: str
+ ) -> t.List[CompletionItem]:
+ """Determine the context and last complete command or parameter
+ from the complete args. Call that object's ``shell_complete``
+ method to get the completions for the incomplete value.
+
+ :param args: List of complete args before the incomplete value.
+ :param incomplete: Value being completed. May be empty.
+ """
+ ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args)
+ obj, incomplete = _resolve_incomplete(ctx, args, incomplete)
+ return obj.shell_complete(ctx, incomplete)
+
+ def format_completion(self, item: CompletionItem) -> str:
+ """Format a completion item into the form recognized by the
+ shell script. This must be implemented by subclasses.
+
+ :param item: Completion item to format.
+ """
+ raise NotImplementedError
+
+ def complete(self) -> str:
+ """Produce the completion data to send back to the shell.
+
+ By default this calls :meth:`get_completion_args`, gets the
+ completions, then calls :meth:`format_completion` for each
+ completion.
+ """
+ args, incomplete = self.get_completion_args()
+ completions = self.get_completions(args, incomplete)
+ out = [self.format_completion(item) for item in completions]
+ return "\n".join(out)
+
+
+class BashComplete(ShellComplete):
+ """Shell completion for Bash."""
+
+ name = "bash"
+ source_template = _SOURCE_BASH
+
+ @staticmethod
+ def _check_version() -> None:
+ import subprocess
+
+ output = subprocess.run(
+ ["bash", "-c", 'echo "${BASH_VERSION}"'], stdout=subprocess.PIPE
+ )
+ match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode())
+
+ if match is not None:
+ major, minor = match.groups()
+
+ if major < "4" or major == "4" and minor < "4":
+ echo(
+ _(
+ "Shell completion is not supported for Bash"
+ " versions older than 4.4."
+ ),
+ err=True,
+ )
+ else:
+ echo(
+ _("Couldn't detect Bash version, shell completion is not supported."),
+ err=True,
+ )
+
+ def source(self) -> str:
+ self._check_version()
+ return super().source()
+
+ def get_completion_args(self) -> t.Tuple[t.List[str], str]:
+ 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 = ""
+
+ return args, incomplete
+
+ def format_completion(self, item: CompletionItem) -> str:
+ return f"{item.type},{item.value}"
+
+
+class ZshComplete(ShellComplete):
+ """Shell completion for Zsh."""
+
+ name = "zsh"
+ source_template = _SOURCE_ZSH
+
+ def get_completion_args(self) -> t.Tuple[t.List[str], str]:
+ 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 = ""
+
+ return args, incomplete
+
+ def format_completion(self, item: CompletionItem) -> str:
+ return f"{item.type}\n{item.value}\n{item.help if item.help else '_'}"
+
+
+class FishComplete(ShellComplete):
+ """Shell completion for Fish."""
+
+ name = "fish"
+ source_template = _SOURCE_FISH
+
+ def get_completion_args(self) -> t.Tuple[t.List[str], str]:
+ cwords = split_arg_string(os.environ["COMP_WORDS"])
+ incomplete = os.environ["COMP_CWORD"]
+ args = cwords[1:]
+
+ # Fish stores the partial word in both COMP_WORDS and
+ # COMP_CWORD, remove it from complete args.
+ if incomplete and args and args[-1] == incomplete:
+ args.pop()
+
+ return args, incomplete
+
+ def format_completion(self, item: CompletionItem) -> str:
+ if item.help:
+ return f"{item.type},{item.value}\t{item.help}"
+
+ return f"{item.type},{item.value}"
+
+
+ShellCompleteType = t.TypeVar("ShellCompleteType", bound=t.Type[ShellComplete])
+
+
+_available_shells: t.Dict[str, t.Type[ShellComplete]] = {
+ "bash": BashComplete,
+ "fish": FishComplete,
+ "zsh": ZshComplete,
+}
+
+
+def add_completion_class(
+ cls: ShellCompleteType, name: t.Optional[str] = None
+) -> ShellCompleteType:
+ """Register a :class:`ShellComplete` subclass under the given name.
+ The name will be provided by the completion instruction environment
+ variable during completion.
+
+ :param cls: The completion class that will handle completion for the
+ shell.
+ :param name: Name to register the class under. Defaults to the
+ class's ``name`` attribute.
+ """
+ if name is None:
+ name = cls.name
+
+ _available_shells[name] = cls
+
+ return cls
+
+
+def get_completion_class(shell: str) -> t.Optional[t.Type[ShellComplete]]:
+ """Look up a registered :class:`ShellComplete` subclass by the name
+ provided by the completion instruction environment variable. If the
+ name isn't registered, returns ``None``.
+
+ :param shell: Name the class is registered under.
+ """
+ return _available_shells.get(shell)
+
+
+def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool:
+ """Determine if the given parameter is an argument that can still
+ accept values.
+
+ :param ctx: Invocation context for the command represented by the
+ parsed complete args.
+ :param param: Argument object being checked.
+ """
+ if not isinstance(param, Argument):
+ return False
+
+ assert param.name is not None
+ # Will be None if expose_value is False.
+ value = ctx.params.get(param.name)
+ return (
+ param.nargs == -1
+ or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE
+ or (
+ param.nargs > 1
+ and isinstance(value, (tuple, list))
+ and len(value) < param.nargs
+ )
+ )
+
+
+def _start_of_option(ctx: Context, value: str) -> bool:
+ """Check if the value looks like the start of an option."""
+ if not value:
+ return False
+
+ c = value[0]
+ return c in ctx._opt_prefixes
+
+
+def _is_incomplete_option(ctx: Context, args: t.List[str], param: Parameter) -> bool:
+ """Determine if the given parameter is an option that needs a value.
+
+ :param args: List of complete args before the incomplete value.
+ :param param: Option object being checked.
+ """
+ if not isinstance(param, Option):
+ return False
+
+ if param.is_flag or param.count:
+ return False
+
+ last_option = None
+
+ for index, arg in enumerate(reversed(args)):
+ if index + 1 > param.nargs:
+ break
+
+ if _start_of_option(ctx, arg):
+ last_option = arg
+
+ return last_option is not None and last_option in param.opts
+
+
+def _resolve_context(
+ cli: BaseCommand,
+ ctx_args: t.MutableMapping[str, t.Any],
+ prog_name: str,
+ args: t.List[str],
+) -> Context:
+ """Produce the context hierarchy starting with the command and
+ traversing the complete arguments. This only follows the commands,
+ it doesn't trigger input prompts or callbacks.
+
+ :param cli: Command being called.
+ :param prog_name: Name of the executable in the shell.
+ :param args: List of complete args before the incomplete value.
+ """
+ ctx_args["resilient_parsing"] = True
+ ctx = cli.make_context(prog_name, args.copy(), **ctx_args)
+ args = ctx.protected_args + ctx.args
+
+ while args:
+ command = ctx.command
+
+ if isinstance(command, MultiCommand):
+ if not command.chain:
+ name, cmd, args = command.resolve_command(ctx, args)
+
+ if cmd is None:
+ return ctx
+
+ ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True)
+ args = ctx.protected_args + ctx.args
+ else:
+ sub_ctx = ctx
+
+ while args:
+ name, cmd, args = command.resolve_command(ctx, args)
+
+ if cmd is None:
+ return ctx
+
+ sub_ctx = cmd.make_context(
+ name,
+ args,
+ parent=ctx,
+ allow_extra_args=True,
+ allow_interspersed_args=False,
+ resilient_parsing=True,
+ )
+ args = sub_ctx.args
+
+ ctx = sub_ctx
+ args = [*sub_ctx.protected_args, *sub_ctx.args]
+ else:
+ break
+
+ return ctx
+
+
+def _resolve_incomplete(
+ ctx: Context, args: t.List[str], incomplete: str
+) -> t.Tuple[t.Union[BaseCommand, Parameter], str]:
+ """Find the Click object that will handle the completion of the
+ incomplete value. Return the object and the incomplete value.
+
+ :param ctx: Invocation context for the command represented by
+ the parsed complete args.
+ :param args: List of complete args before the incomplete value.
+ :param incomplete: Value being completed. May be empty.
+ """
+ # Different shells treat an "=" between a long option name and
+ # value differently. Might keep the value joined, return the "="
+ # as a separate item, or return the split name and value. Always
+ # split and discard the "=" to make completion easier.
+ if incomplete == "=":
+ incomplete = ""
+ elif "=" in incomplete and _start_of_option(ctx, incomplete):
+ name, _, incomplete = incomplete.partition("=")
+ args.append(name)
+
+ # The "--" marker tells Click to stop treating values as options
+ # even if they start with the option character. If it hasn't been
+ # given and the incomplete arg looks like an option, the current
+ # command will provide option name completions.
+ if "--" not in args and _start_of_option(ctx, incomplete):
+ return ctx.command, incomplete
+
+ params = ctx.command.get_params(ctx)
+
+ # If the last complete arg is an option name with an incomplete
+ # value, the option will provide value completions.
+ for param in params:
+ if _is_incomplete_option(ctx, args, param):
+ return param, incomplete
+
+ # It's not an option name or value. The first argument without a
+ # parsed value will provide value completions.
+ for param in params:
+ if _is_incomplete_argument(ctx, param):
+ return param, incomplete
+
+ # There were no unparsed arguments, the command may be a group that
+ # will provide command name completions.
+ return ctx.command, incomplete
diff --git a/venv/Lib/site-packages/click/termui.py b/venv/Lib/site-packages/click/termui.py
new file mode 100644
index 0000000..db7a4b2
--- /dev/null
+++ b/venv/Lib/site-packages/click/termui.py
@@ -0,0 +1,784 @@
+import inspect
+import io
+import itertools
+import sys
+import typing as t
+from gettext import gettext as _
+
+from ._compat import isatty
+from ._compat import strip_ansi
+from .exceptions import Abort
+from .exceptions import UsageError
+from .globals import resolve_color_default
+from .types import Choice
+from .types import convert_type
+from .types import ParamType
+from .utils import echo
+from .utils import LazyFile
+
+if t.TYPE_CHECKING:
+ from ._termui_impl import ProgressBar
+
+V = t.TypeVar("V")
+
+# The prompt functions to use. The doc tools currently override these
+# functions to customize how they work.
+visible_prompt_func: t.Callable[[str], str] = input
+
+_ansi_colors = {
+ "black": 30,
+ "red": 31,
+ "green": 32,
+ "yellow": 33,
+ "blue": 34,
+ "magenta": 35,
+ "cyan": 36,
+ "white": 37,
+ "reset": 39,
+ "bright_black": 90,
+ "bright_red": 91,
+ "bright_green": 92,
+ "bright_yellow": 93,
+ "bright_blue": 94,
+ "bright_magenta": 95,
+ "bright_cyan": 96,
+ "bright_white": 97,
+}
+_ansi_reset_all = "\033[0m"
+
+
+def hidden_prompt_func(prompt: str) -> str:
+ import getpass
+
+ return getpass.getpass(prompt)
+
+
+def _build_prompt(
+ text: str,
+ suffix: str,
+ show_default: bool = False,
+ default: t.Optional[t.Any] = None,
+ show_choices: bool = True,
+ type: t.Optional[ParamType] = None,
+) -> str:
+ prompt = text
+ if type is not None and show_choices and isinstance(type, Choice):
+ prompt += f" ({', '.join(map(str, type.choices))})"
+ if default is not None and show_default:
+ prompt = f"{prompt} [{_format_default(default)}]"
+ return f"{prompt}{suffix}"
+
+
+def _format_default(default: t.Any) -> t.Any:
+ if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"):
+ return default.name
+
+ return default
+
+
+def prompt(
+ text: str,
+ default: t.Optional[t.Any] = None,
+ hide_input: bool = False,
+ confirmation_prompt: t.Union[bool, str] = False,
+ type: t.Optional[t.Union[ParamType, t.Any]] = None,
+ value_proc: t.Optional[t.Callable[[str], t.Any]] = None,
+ prompt_suffix: str = ": ",
+ show_default: bool = True,
+ err: bool = False,
+ show_choices: bool = True,
+) -> t.Any:
+ """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 an interrupt signal, this
+ function will catch it and raise a :exc:`Abort` exception.
+
+ :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: Prompt a second time to confirm the
+ value. Can be set to a string instead of ``True`` to customize
+ the message.
+ :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.
+ :param show_choices: Show or hide choices if the passed type is a Choice.
+ For example if type is a Choice of either day or week,
+ show_choices is true and text is "Group by" then the
+ prompt will be "Group by (day, week): ".
+
+ .. versionadded:: 8.0
+ ``confirmation_prompt`` can be a custom string.
+
+ .. versionadded:: 7.0
+ Added the ``show_choices`` parameter.
+
+ .. versionadded:: 6.0
+ Added unicode support for cmd.exe on Windows.
+
+ .. versionadded:: 4.0
+ Added the `err` parameter.
+
+ """
+
+ def prompt_func(text: str) -> str:
+ f = hidden_prompt_func if hide_input else visible_prompt_func
+ try:
+ # Write the prompt separately so that we get nice
+ # coloring through colorama on Windows
+ echo(text.rstrip(" "), nl=False, err=err)
+ # Echo a space to stdout to work around an issue where
+ # readline causes backspace to clear the whole line.
+ 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() from None
+
+ if value_proc is None:
+ value_proc = convert_type(type, default)
+
+ prompt = _build_prompt(
+ text, prompt_suffix, show_default, default, show_choices, type
+ )
+
+ if confirmation_prompt:
+ if confirmation_prompt is True:
+ confirmation_prompt = _("Repeat for confirmation")
+
+ confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix)
+
+ while True:
+ while True:
+ value = prompt_func(prompt)
+ if value:
+ break
+ elif default is not None:
+ value = default
+ break
+ try:
+ result = value_proc(value)
+ except UsageError as e:
+ if hide_input:
+ echo(_("Error: The value you entered was invalid."), err=err)
+ else:
+ echo(_("Error: {e.message}").format(e=e), err=err) # noqa: B306
+ continue
+ if not confirmation_prompt:
+ return result
+ while True:
+ value2 = prompt_func(confirmation_prompt)
+ is_empty = not value and not value2
+ if value2 or is_empty:
+ break
+ if value == value2:
+ return result
+ echo(_("Error: The two entered values do not match."), err=err)
+
+
+def confirm(
+ text: str,
+ default: t.Optional[bool] = False,
+ abort: bool = False,
+ prompt_suffix: str = ": ",
+ show_default: bool = True,
+ err: bool = False,
+) -> bool:
+ """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.
+
+ :param text: the question to ask.
+ :param default: The default value to use when no input is given. If
+ ``None``, repeat until input is given.
+ :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.
+
+ .. versionchanged:: 8.0
+ Repeat until input is given if ``default`` is ``None``.
+
+ .. versionadded:: 4.0
+ Added the ``err`` parameter.
+ """
+ prompt = _build_prompt(
+ text,
+ prompt_suffix,
+ show_default,
+ "y/n" if default is None else ("Y/n" if default else "y/N"),
+ )
+
+ while True:
+ try:
+ # Write the prompt separately so that we get nice
+ # coloring through colorama on Windows
+ echo(prompt.rstrip(" "), nl=False, err=err)
+ # Echo a space to stdout to work around an issue where
+ # readline causes backspace to clear the whole line.
+ value = visible_prompt_func(" ").lower().strip()
+ except (KeyboardInterrupt, EOFError):
+ raise Abort() from None
+ if value in ("y", "yes"):
+ rv = True
+ elif value in ("n", "no"):
+ rv = False
+ elif default is not None and value == "":
+ rv = default
+ else:
+ echo(_("Error: invalid input"), err=err)
+ continue
+ break
+ if abort and not rv:
+ raise Abort()
+ return rv
+
+
+def echo_via_pager(
+ text_or_generator: t.Union[t.Iterable[str], t.Callable[[], t.Iterable[str]], str],
+ color: t.Optional[bool] = None,
+) -> 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_or_generator: the text to page, or alternatively, a
+ generator emitting 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 inspect.isgeneratorfunction(text_or_generator):
+ i = t.cast(t.Callable[[], t.Iterable[str]], text_or_generator)()
+ elif isinstance(text_or_generator, str):
+ i = [text_or_generator]
+ else:
+ i = iter(t.cast(t.Iterable[str], text_or_generator))
+
+ # convert every element of i to a text type if necessary
+ text_generator = (el if isinstance(el, str) else str(el) for el in i)
+
+ from ._termui_impl import pager
+
+ return pager(itertools.chain(text_generator, "\n"), color)
+
+
+def progressbar(
+ iterable: t.Optional[t.Iterable[V]] = None,
+ length: t.Optional[int] = None,
+ label: t.Optional[str] = None,
+ show_eta: bool = True,
+ show_percent: t.Optional[bool] = None,
+ show_pos: bool = False,
+ item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
+ fill_char: str = "#",
+ empty_char: str = "-",
+ bar_template: str = "%(label)s [%(bar)s] %(info)s",
+ info_sep: str = " ",
+ width: int = 36,
+ file: t.Optional[t.TextIO] = None,
+ color: t.Optional[bool] = None,
+ update_min_steps: int = 1,
+) -> "ProgressBar[V]":
+ """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 created. 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.
+
+ Note: The progress bar is currently designed for use cases where the
+ total progress can be expected to take at least several seconds.
+ Because of this, the ProgressBar class object won't display
+ progress that is considered too fast, and progress where the time
+ between steps is less than a second.
+
+ 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)
+
+ The ``update()`` method also takes an optional value specifying the
+ ``current_item`` at the new position. This is useful when used
+ together with ``item_show_func`` to customize the output for each
+ manual step::
+
+ with click.progressbar(
+ length=total_size,
+ label='Unzipping archive',
+ item_show_func=lambda a: a.filename
+ ) as bar:
+ for archive in zip_file:
+ archive.extract()
+ bar.update(archive.size, archive)
+
+ :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 next to the progress bar. If the
+ function returns ``None`` nothing is shown. The current item can
+ be ``None``, such as when entering and exiting the bar.
+ :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.
+ :param update_min_steps: Render only when this many updates have
+ completed. This allows tuning for very fast iterators.
+
+ .. versionchanged:: 8.0
+ Output is shown even if execution time is less than 0.5 seconds.
+
+ .. versionchanged:: 8.0
+ ``item_show_func`` shows the current item, not the previous one.
+
+ .. versionchanged:: 8.0
+ Labels are echoed if the output is not a TTY. Reverts a change
+ in 7.0 that removed all output.
+
+ .. versionadded:: 8.0
+ Added the ``update_min_steps`` parameter.
+
+ .. versionchanged:: 4.0
+ Added the ``color`` parameter. Added the ``update`` method to
+ the object.
+
+ .. versionadded:: 2.0
+ """
+ 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,
+ update_min_steps=update_min_steps,
+ )
+
+
+def clear() -> None:
+ """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
+
+ # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor
+ echo("\033[2J\033[1;1H", nl=False)
+
+
+def _interpret_color(
+ color: t.Union[int, t.Tuple[int, int, int], str], offset: int = 0
+) -> str:
+ if isinstance(color, int):
+ return f"{38 + offset};5;{color:d}"
+
+ if isinstance(color, (tuple, list)):
+ r, g, b = color
+ return f"{38 + offset};2;{r:d};{g:d};{b:d}"
+
+ return str(_ansi_colors[color] + offset)
+
+
+def style(
+ text: t.Any,
+ fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None,
+ bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None,
+ bold: t.Optional[bool] = None,
+ dim: t.Optional[bool] = None,
+ underline: t.Optional[bool] = None,
+ overline: t.Optional[bool] = None,
+ italic: t.Optional[bool] = None,
+ blink: t.Optional[bool] = None,
+ reverse: t.Optional[bool] = None,
+ strikethrough: t.Optional[bool] = None,
+ reset: bool = True,
+) -> str:
+ """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'))
+ click.echo(click.style('More colors', fg=(255, 12, 128), bg=117))
+
+ Supported color names:
+
+ * ``black`` (might be a gray)
+ * ``red``
+ * ``green``
+ * ``yellow`` (might be an orange)
+ * ``blue``
+ * ``magenta``
+ * ``cyan``
+ * ``white`` (might be light gray)
+ * ``bright_black``
+ * ``bright_red``
+ * ``bright_green``
+ * ``bright_yellow``
+ * ``bright_blue``
+ * ``bright_magenta``
+ * ``bright_cyan``
+ * ``bright_white``
+ * ``reset`` (reset the color code only)
+
+ If the terminal supports it, color may also be specified as:
+
+ - An integer in the interval [0, 255]. The terminal must support
+ 8-bit/256-color mode.
+ - An RGB tuple of three integers in [0, 255]. The terminal must
+ support 24-bit/true-color mode.
+
+ See https://en.wikipedia.org/wiki/ANSI_color and
+ https://gist.github.com/XVilka/8346728 for more information.
+
+ :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 overline: if provided this will enable or disable overline.
+ :param italic: if provided this will enable or disable italic.
+ :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 strikethrough: if provided this will enable or disable
+ striking through text.
+ :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.
+
+ .. versionchanged:: 8.0
+ A non-string ``message`` is converted to a string.
+
+ .. versionchanged:: 8.0
+ Added support for 256 and RGB color codes.
+
+ .. versionchanged:: 8.0
+ Added the ``strikethrough``, ``italic``, and ``overline``
+ parameters.
+
+ .. versionchanged:: 7.0
+ Added support for bright colors.
+
+ .. versionadded:: 2.0
+ """
+ if not isinstance(text, str):
+ text = str(text)
+
+ bits = []
+
+ if fg:
+ try:
+ bits.append(f"\033[{_interpret_color(fg)}m")
+ except KeyError:
+ raise TypeError(f"Unknown color {fg!r}") from None
+
+ if bg:
+ try:
+ bits.append(f"\033[{_interpret_color(bg, 10)}m")
+ except KeyError:
+ raise TypeError(f"Unknown color {bg!r}") from None
+
+ if bold is not None:
+ bits.append(f"\033[{1 if bold else 22}m")
+ if dim is not None:
+ bits.append(f"\033[{2 if dim else 22}m")
+ if underline is not None:
+ bits.append(f"\033[{4 if underline else 24}m")
+ if overline is not None:
+ bits.append(f"\033[{53 if overline else 55}m")
+ if italic is not None:
+ bits.append(f"\033[{3 if italic else 23}m")
+ if blink is not None:
+ bits.append(f"\033[{5 if blink else 25}m")
+ if reverse is not None:
+ bits.append(f"\033[{7 if reverse else 27}m")
+ if strikethrough is not None:
+ bits.append(f"\033[{9 if strikethrough else 29}m")
+ bits.append(text)
+ if reset:
+ bits.append(_ansi_reset_all)
+ return "".join(bits)
+
+
+def unstyle(text: str) -> str:
+ """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(
+ message: t.Optional[t.Any] = None,
+ file: t.Optional[t.IO[t.AnyStr]] = None,
+ nl: bool = True,
+ err: bool = False,
+ color: t.Optional[bool] = None,
+ **styles: t.Any,
+) -> None:
+ """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.
+
+ Non-string types will be converted to :class:`str`. However,
+ :class:`bytes` are passed directly to :meth:`echo` without applying
+ style. If you want to style bytes that represent text, call
+ :meth:`bytes.decode` first.
+
+ .. versionchanged:: 8.0
+ A non-string ``message`` is converted to a string. Bytes are
+ passed through without style applied.
+
+ .. versionadded:: 2.0
+ """
+ if message is not None and not isinstance(message, (bytes, bytearray)):
+ message = style(message, **styles)
+
+ return echo(message, file=file, nl=nl, err=err, color=color)
+
+
+def edit(
+ text: t.Optional[t.AnyStr] = None,
+ editor: t.Optional[str] = None,
+ env: t.Optional[t.Mapping[str, str]] = None,
+ require_save: bool = True,
+ extension: str = ".txt",
+ filename: t.Optional[str] = None,
+) -> t.Optional[t.AnyStr]:
+ 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
+
+ ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension)
+
+ if filename is None:
+ return ed.edit(text)
+
+ ed.edit_file(filename)
+ return None
+
+
+def launch(url: str, wait: bool = False, locate: bool = False) -> int:
+ """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('https://click.palletsprojects.com/')
+ click.launch('/my/downloaded/file', locate=True)
+
+ .. versionadded:: 2.0
+
+ :param url: URL or filename of the thing to launch.
+ :param wait: Wait for the program to exit before returning. This
+ only works if the launched program blocks. In particular,
+ ``xdg-open`` on Linux does not block.
+ :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: t.Optional[t.Callable[[bool], str]] = None
+
+
+def getchar(echo: bool = False) -> str:
+ """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.
+
+ Note for Windows: in rare cases when typing non-ASCII characters, this
+ function might wait for a second character and then return both at once.
+ This is because certain Unicode characters look like special-key markers.
+
+ .. 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.
+ """
+ global _getchar
+
+ if _getchar is None:
+ from ._termui_impl import getchar as f
+
+ _getchar = f
+
+ return _getchar(echo)
+
+
+def raw_terminal() -> t.ContextManager[int]:
+ from ._termui_impl import raw_terminal as f
+
+ return f()
+
+
+def pause(info: t.Optional[str] = None, err: bool = False) -> None:
+ """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 message to print before pausing. Defaults to
+ ``"Press any key to continue..."``.
+ :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
+
+ if info is None:
+ info = _("Press any key to continue...")
+
+ try:
+ if info:
+ echo(info, nl=False, err=err)
+ try:
+ getchar()
+ except (KeyboardInterrupt, EOFError):
+ pass
+ finally:
+ if info:
+ echo(err=err)
diff --git a/venv/Lib/site-packages/click/testing.py b/venv/Lib/site-packages/click/testing.py
new file mode 100644
index 0000000..e0df0d2
--- /dev/null
+++ b/venv/Lib/site-packages/click/testing.py
@@ -0,0 +1,479 @@
+import contextlib
+import io
+import os
+import shlex
+import shutil
+import sys
+import tempfile
+import typing as t
+from types import TracebackType
+
+from . import formatting
+from . import termui
+from . import utils
+from ._compat import _find_binary_reader
+
+if t.TYPE_CHECKING:
+ from .core import BaseCommand
+
+
+class EchoingStdin:
+ def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None:
+ self._input = input
+ self._output = output
+ self._paused = False
+
+ def __getattr__(self, x: str) -> t.Any:
+ return getattr(self._input, x)
+
+ def _echo(self, rv: bytes) -> bytes:
+ if not self._paused:
+ self._output.write(rv)
+
+ return rv
+
+ def read(self, n: int = -1) -> bytes:
+ return self._echo(self._input.read(n))
+
+ def read1(self, n: int = -1) -> bytes:
+ return self._echo(self._input.read1(n)) # type: ignore
+
+ def readline(self, n: int = -1) -> bytes:
+ return self._echo(self._input.readline(n))
+
+ def readlines(self) -> t.List[bytes]:
+ return [self._echo(x) for x in self._input.readlines()]
+
+ def __iter__(self) -> t.Iterator[bytes]:
+ return iter(self._echo(x) for x in self._input)
+
+ def __repr__(self) -> str:
+ return repr(self._input)
+
+
+@contextlib.contextmanager
+def _pause_echo(stream: t.Optional[EchoingStdin]) -> t.Iterator[None]:
+ if stream is None:
+ yield
+ else:
+ stream._paused = True
+ yield
+ stream._paused = False
+
+
+class _NamedTextIOWrapper(io.TextIOWrapper):
+ def __init__(
+ self, buffer: t.BinaryIO, name: str, mode: str, **kwargs: t.Any
+ ) -> None:
+ super().__init__(buffer, **kwargs)
+ self._name = name
+ self._mode = mode
+
+ @property
+ def name(self) -> str:
+ return self._name
+
+ @property
+ def mode(self) -> str:
+ return self._mode
+
+
+def make_input_stream(
+ input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]], charset: str
+) -> t.BinaryIO:
+ # Is already an input stream.
+ if hasattr(input, "read"):
+ rv = _find_binary_reader(t.cast(t.IO[t.Any], 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 isinstance(input, str):
+ input = input.encode(charset)
+
+ return io.BytesIO(input)
+
+
+class Result:
+ """Holds the captured result of an invoked CLI script."""
+
+ def __init__(
+ self,
+ runner: "CliRunner",
+ stdout_bytes: bytes,
+ stderr_bytes: t.Optional[bytes],
+ return_value: t.Any,
+ exit_code: int,
+ exception: t.Optional[BaseException],
+ exc_info: t.Optional[
+ t.Tuple[t.Type[BaseException], BaseException, TracebackType]
+ ] = None,
+ ):
+ #: The runner that created the result
+ self.runner = runner
+ #: The standard output as bytes.
+ self.stdout_bytes = stdout_bytes
+ #: The standard error as bytes, or None if not available
+ self.stderr_bytes = stderr_bytes
+ #: The value returned from the invoked command.
+ #:
+ #: .. versionadded:: 8.0
+ self.return_value = return_value
+ #: The exit code as integer.
+ self.exit_code = exit_code
+ #: The exception that happened if one did.
+ self.exception = exception
+ #: The traceback
+ self.exc_info = exc_info
+
+ @property
+ def output(self) -> str:
+ """The (standard) output as unicode string."""
+ return self.stdout
+
+ @property
+ def stdout(self) -> str:
+ """The standard output as unicode string."""
+ return self.stdout_bytes.decode(self.runner.charset, "replace").replace(
+ "\r\n", "\n"
+ )
+
+ @property
+ def stderr(self) -> str:
+ """The standard error as unicode string."""
+ if self.stderr_bytes is None:
+ raise ValueError("stderr not separately captured")
+ return self.stderr_bytes.decode(self.runner.charset, "replace").replace(
+ "\r\n", "\n"
+ )
+
+ def __repr__(self) -> str:
+ exc_str = repr(self.exception) if self.exception else "okay"
+ return f"<{type(self).__name__} {exc_str}>"
+
+
+class CliRunner:
+ """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.
+ :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.
+ :param mix_stderr: if this is set to `False`, then stdout and stderr are
+ preserved as independent streams. This is useful for
+ Unix-philosophy apps that have predictable stdout and
+ noisy stderr, such that each may be measured
+ independently
+ """
+
+ def __init__(
+ self,
+ charset: str = "utf-8",
+ env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
+ echo_stdin: bool = False,
+ mix_stderr: bool = True,
+ ) -> None:
+ self.charset = charset
+ self.env: t.Mapping[str, t.Optional[str]] = env or {}
+ self.echo_stdin = echo_stdin
+ self.mix_stderr = mix_stderr
+
+ def get_default_prog_name(self, cli: "BaseCommand") -> str:
+ """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: t.Optional[t.Mapping[str, t.Optional[str]]] = None
+ ) -> t.Mapping[str, t.Optional[str]]:
+ """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: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,
+ env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
+ color: bool = False,
+ ) -> t.Iterator[t.Tuple[io.BytesIO, t.Optional[io.BytesIO]]]:
+ """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.
+
+ :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.
+
+ .. versionchanged:: 8.0
+ ``stderr`` is opened with ``errors="backslashreplace"``
+ instead of the default ``"strict"``.
+
+ .. versionchanged:: 4.0
+ Added the ``color`` parameter.
+ """
+ bytes_input = make_input_stream(input, self.charset)
+ echo_input = None
+
+ old_stdin = sys.stdin
+ old_stdout = sys.stdout
+ old_stderr = sys.stderr
+ old_forced_width = formatting.FORCED_WIDTH
+ formatting.FORCED_WIDTH = 80
+
+ env = self.make_env(env)
+
+ bytes_output = io.BytesIO()
+
+ if self.echo_stdin:
+ bytes_input = echo_input = t.cast(
+ t.BinaryIO, EchoingStdin(bytes_input, bytes_output)
+ )
+
+ sys.stdin = text_input = _NamedTextIOWrapper(
+ bytes_input, encoding=self.charset, name="", mode="r"
+ )
+
+ if self.echo_stdin:
+ # Force unbuffered reads, otherwise TextIOWrapper reads a
+ # large chunk which is echoed early.
+ text_input._CHUNK_SIZE = 1 # type: ignore
+
+ sys.stdout = _NamedTextIOWrapper(
+ bytes_output, encoding=self.charset, name="", mode="w"
+ )
+
+ bytes_error = None
+ if self.mix_stderr:
+ sys.stderr = sys.stdout
+ else:
+ bytes_error = io.BytesIO()
+ sys.stderr = _NamedTextIOWrapper(
+ bytes_error,
+ encoding=self.charset,
+ name="",
+ mode="w",
+ errors="backslashreplace",
+ )
+
+ @_pause_echo(echo_input) # type: ignore
+ def visible_input(prompt: t.Optional[str] = None) -> str:
+ sys.stdout.write(prompt or "")
+ val = text_input.readline().rstrip("\r\n")
+ sys.stdout.write(f"{val}\n")
+ sys.stdout.flush()
+ return val
+
+ @_pause_echo(echo_input) # type: ignore
+ def hidden_input(prompt: t.Optional[str] = None) -> str:
+ sys.stdout.write(f"{prompt or ''}\n")
+ sys.stdout.flush()
+ return text_input.readline().rstrip("\r\n")
+
+ @_pause_echo(echo_input) # type: ignore
+ def _getchar(echo: bool) -> str:
+ char = sys.stdin.read(1)
+
+ if echo:
+ sys.stdout.write(char)
+
+ sys.stdout.flush()
+ return char
+
+ default_color = color
+
+ def should_strip_ansi(
+ stream: t.Optional[t.IO[t.Any]] = None, color: t.Optional[bool] = None
+ ) -> bool:
+ if color is None:
+ return not default_color
+ return not color
+
+ old_visible_prompt_func = termui.visible_prompt_func
+ old_hidden_prompt_func = termui.hidden_prompt_func
+ old__getchar_func = termui._getchar
+ old_should_strip_ansi = utils.should_strip_ansi # type: ignore
+ termui.visible_prompt_func = visible_input
+ termui.hidden_prompt_func = hidden_input
+ termui._getchar = _getchar
+ utils.should_strip_ansi = should_strip_ansi # type: ignore
+
+ old_env = {}
+ try:
+ for key, value in env.items():
+ 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, bytes_error)
+ finally:
+ for key, value in old_env.items():
+ 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
+ termui.visible_prompt_func = old_visible_prompt_func
+ termui.hidden_prompt_func = old_hidden_prompt_func
+ termui._getchar = old__getchar_func
+ utils.should_strip_ansi = old_should_strip_ansi # type: ignore
+ formatting.FORCED_WIDTH = old_forced_width
+
+ def invoke(
+ self,
+ cli: "BaseCommand",
+ args: t.Optional[t.Union[str, t.Sequence[str]]] = None,
+ input: t.Optional[t.Union[str, bytes, t.IO[t.Any]]] = None,
+ env: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
+ catch_exceptions: bool = True,
+ color: bool = False,
+ **extra: t.Any,
+ ) -> Result:
+ """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.
+
+ :param cli: the command to invoke
+ :param args: the arguments to invoke. It may be given as an iterable
+ or a string. When given as string it will be interpreted
+ as a Unix shell command. More details at
+ :func:`shlex.split`.
+ :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.
+
+ .. versionchanged:: 8.0
+ The result object has the ``return_value`` attribute with
+ the value returned from the invoked command.
+
+ .. versionchanged:: 4.0
+ Added the ``color`` parameter.
+
+ .. versionchanged:: 3.0
+ Added the ``catch_exceptions`` parameter.
+
+ .. versionchanged:: 3.0
+ The result object has the ``exc_info`` attribute with the
+ traceback if available.
+ """
+ exc_info = None
+ with self.isolation(input=input, env=env, color=color) as outstreams:
+ return_value = None
+ exception: t.Optional[BaseException] = None
+ exit_code = 0
+
+ if isinstance(args, str):
+ args = shlex.split(args)
+
+ try:
+ prog_name = extra.pop("prog_name")
+ except KeyError:
+ prog_name = self.get_default_prog_name(cli)
+
+ try:
+ return_value = cli.main(args=args or (), prog_name=prog_name, **extra)
+ except SystemExit as e:
+ exc_info = sys.exc_info()
+ e_code = t.cast(t.Optional[t.Union[int, t.Any]], e.code)
+
+ if e_code is None:
+ e_code = 0
+
+ if e_code != 0:
+ exception = e
+
+ if not isinstance(e_code, int):
+ sys.stdout.write(str(e_code))
+ sys.stdout.write("\n")
+ e_code = 1
+
+ exit_code = e_code
+
+ except Exception as e:
+ if not catch_exceptions:
+ raise
+ exception = e
+ exit_code = 1
+ exc_info = sys.exc_info()
+ finally:
+ sys.stdout.flush()
+ stdout = outstreams[0].getvalue()
+ if self.mix_stderr:
+ stderr = None
+ else:
+ stderr = outstreams[1].getvalue() # type: ignore
+
+ return Result(
+ runner=self,
+ stdout_bytes=stdout,
+ stderr_bytes=stderr,
+ return_value=return_value,
+ exit_code=exit_code,
+ exception=exception,
+ exc_info=exc_info, # type: ignore
+ )
+
+ @contextlib.contextmanager
+ def isolated_filesystem(
+ self, temp_dir: t.Optional[t.Union[str, "os.PathLike[str]"]] = None
+ ) -> t.Iterator[str]:
+ """A context manager that creates a temporary directory and
+ changes the current working directory to it. This isolates tests
+ that affect the contents of the CWD to prevent them from
+ interfering with each other.
+
+ :param temp_dir: Create the temporary directory under this
+ directory. If given, the created directory is not removed
+ when exiting.
+
+ .. versionchanged:: 8.0
+ Added the ``temp_dir`` parameter.
+ """
+ cwd = os.getcwd()
+ dt = tempfile.mkdtemp(dir=temp_dir)
+ os.chdir(dt)
+
+ try:
+ yield dt
+ finally:
+ os.chdir(cwd)
+
+ if temp_dir is None:
+ try:
+ shutil.rmtree(dt)
+ except OSError: # noqa: B014
+ pass
diff --git a/venv/Lib/site-packages/click/types.py b/venv/Lib/site-packages/click/types.py
new file mode 100644
index 0000000..2b1d179
--- /dev/null
+++ b/venv/Lib/site-packages/click/types.py
@@ -0,0 +1,1089 @@
+import os
+import stat
+import sys
+import typing as t
+from datetime import datetime
+from gettext import gettext as _
+from gettext import ngettext
+
+from ._compat import _get_argv_encoding
+from ._compat import open_stream
+from .exceptions import BadParameter
+from .utils import format_filename
+from .utils import LazyFile
+from .utils import safecall
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+ from .core import Context
+ from .core import Parameter
+ from .shell_completion import CompletionItem
+
+
+class ParamType:
+ """Represents the type of a parameter. Validates and converts values
+ from the command line or Python into the correct type.
+
+ To implement a custom type, subclass and implement at least the
+ following:
+
+ - The :attr:`name` class attribute must be set.
+ - Calling an instance of the type with ``None`` must return
+ ``None``. This is already implemented by default.
+ - :meth:`convert` must convert string values to the correct type.
+ - :meth:`convert` must accept values that are already the correct
+ type.
+ - It must be able to convert a value if the ``ctx`` and ``param``
+ arguments are ``None``. This can occur when converting prompt
+ input.
+ """
+
+ is_composite: t.ClassVar[bool] = False
+ arity: t.ClassVar[int] = 1
+
+ #: the descriptive name of this type
+ name: str
+
+ #: 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: t.ClassVar[t.Optional[str]] = None
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ """Gather information that could be useful for a tool generating
+ user-facing documentation.
+
+ Use :meth:`click.Context.to_info_dict` to traverse the entire
+ CLI structure.
+
+ .. versionadded:: 8.0
+ """
+ # The class name without the "ParamType" suffix.
+ param_type = type(self).__name__.partition("ParamType")[0]
+ param_type = param_type.partition("ParameterType")[0]
+
+ # Custom subclasses might not remember to set a name.
+ if hasattr(self, "name"):
+ name = self.name
+ else:
+ name = param_type
+
+ return {"param_type": param_type, "name": name}
+
+ def __call__(
+ self,
+ value: t.Any,
+ param: t.Optional["Parameter"] = None,
+ ctx: t.Optional["Context"] = None,
+ ) -> t.Any:
+ if value is not None:
+ return self.convert(value, param, ctx)
+
+ def get_metavar(self, param: "Parameter") -> t.Optional[str]:
+ """Returns the metavar default for this param if it provides one."""
+
+ def get_missing_message(self, param: "Parameter") -> t.Optional[str]:
+ """Optionally might return extra information about a missing
+ parameter.
+
+ .. versionadded:: 2.0
+ """
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ """Convert the value to the correct type. This is not called if
+ the value is ``None`` (the missing value).
+
+ This must accept string values from the command line, as well as
+ values that are already the correct type. It may also convert
+ other compatible types.
+
+ The ``param`` and ``ctx`` arguments may be ``None`` in certain
+ situations, such as when converting prompt input.
+
+ If the value cannot be converted, call :meth:`fail` with a
+ descriptive message.
+
+ :param value: The value to convert.
+ :param param: The parameter that is using this type to convert
+ its value. May be ``None``.
+ :param ctx: The current context that arrived at this value. May
+ be ``None``.
+ """
+ return value
+
+ def split_envvar_value(self, rv: str) -> t.Sequence[str]:
+ """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: str,
+ param: t.Optional["Parameter"] = None,
+ ctx: t.Optional["Context"] = None,
+ ) -> "t.NoReturn":
+ """Helper method to fail with an invalid value message."""
+ raise BadParameter(message, ctx=ctx, param=param)
+
+ def shell_complete(
+ self, ctx: "Context", param: "Parameter", incomplete: str
+ ) -> t.List["CompletionItem"]:
+ """Return a list of
+ :class:`~click.shell_completion.CompletionItem` objects for the
+ incomplete value. Most types do not provide completions, but
+ some do, and this allows custom types to provide custom
+ completions as well.
+
+ :param ctx: Invocation context for this command.
+ :param param: The parameter that is requesting completion.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ return []
+
+
+class CompositeParamType(ParamType):
+ is_composite = True
+
+ @property
+ def arity(self) -> int: # type: ignore
+ raise NotImplementedError()
+
+
+class FuncParamType(ParamType):
+ def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None:
+ self.name: str = func.__name__
+ self.func = func
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict["func"] = self.func
+ return info_dict
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ try:
+ return self.func(value)
+ except ValueError:
+ try:
+ value = str(value)
+ except UnicodeError:
+ value = value.decode("utf-8", "replace")
+
+ self.fail(value, param, ctx)
+
+
+class UnprocessedParamType(ParamType):
+ name = "text"
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ return value
+
+ def __repr__(self) -> str:
+ return "UNPROCESSED"
+
+
+class StringParamType(ParamType):
+ name = "text"
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ if isinstance(value, bytes):
+ enc = _get_argv_encoding()
+ try:
+ value = value.decode(enc)
+ except UnicodeError:
+ fs_enc = sys.getfilesystemencoding()
+ if fs_enc != enc:
+ try:
+ value = value.decode(fs_enc)
+ except UnicodeError:
+ value = value.decode("utf-8", "replace")
+ else:
+ value = value.decode("utf-8", "replace")
+ return value
+ return str(value)
+
+ def __repr__(self) -> str:
+ 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.
+
+ You should only pass a list or tuple of choices. Other iterables
+ (like generators) may lead to surprising results.
+
+ The resulting value will always be one of the originally passed choices
+ regardless of ``case_sensitive`` or any ``ctx.token_normalize_func``
+ being specified.
+
+ See :ref:`choice-opts` for an example.
+
+ :param case_sensitive: Set to false to make choices case
+ insensitive. Defaults to true.
+ """
+
+ name = "choice"
+
+ def __init__(self, choices: t.Sequence[str], case_sensitive: bool = True) -> None:
+ self.choices = choices
+ self.case_sensitive = case_sensitive
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict["choices"] = self.choices
+ info_dict["case_sensitive"] = self.case_sensitive
+ return info_dict
+
+ def get_metavar(self, param: "Parameter") -> str:
+ choices_str = "|".join(self.choices)
+
+ # Use curly braces to indicate a required argument.
+ if param.required and param.param_type_name == "argument":
+ return f"{{{choices_str}}}"
+
+ # Use square braces to indicate an option or optional argument.
+ return f"[{choices_str}]"
+
+ def get_missing_message(self, param: "Parameter") -> str:
+ return _("Choose from:\n\t{choices}").format(choices=",\n\t".join(self.choices))
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ # Match through normalization and case sensitivity
+ # first do token_normalize_func, then lowercase
+ # preserve original `value` to produce an accurate message in
+ # `self.fail`
+ normed_value = value
+ normed_choices = {choice: choice for choice in self.choices}
+
+ if ctx is not None and ctx.token_normalize_func is not None:
+ normed_value = ctx.token_normalize_func(value)
+ normed_choices = {
+ ctx.token_normalize_func(normed_choice): original
+ for normed_choice, original in normed_choices.items()
+ }
+
+ if not self.case_sensitive:
+ normed_value = normed_value.casefold()
+ normed_choices = {
+ normed_choice.casefold(): original
+ for normed_choice, original in normed_choices.items()
+ }
+
+ if normed_value in normed_choices:
+ return normed_choices[normed_value]
+
+ choices_str = ", ".join(map(repr, self.choices))
+ self.fail(
+ ngettext(
+ "{value!r} is not {choice}.",
+ "{value!r} is not one of {choices}.",
+ len(self.choices),
+ ).format(value=value, choice=choices_str, choices=choices_str),
+ param,
+ ctx,
+ )
+
+ def __repr__(self) -> str:
+ return f"Choice({list(self.choices)})"
+
+ def shell_complete(
+ self, ctx: "Context", param: "Parameter", incomplete: str
+ ) -> t.List["CompletionItem"]:
+ """Complete choices that start with the incomplete value.
+
+ :param ctx: Invocation context for this command.
+ :param param: The parameter that is requesting completion.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ from click.shell_completion import CompletionItem
+
+ str_choices = map(str, self.choices)
+
+ if self.case_sensitive:
+ matched = (c for c in str_choices if c.startswith(incomplete))
+ else:
+ incomplete = incomplete.lower()
+ matched = (c for c in str_choices if c.lower().startswith(incomplete))
+
+ return [CompletionItem(c) for c in matched]
+
+
+class DateTime(ParamType):
+ """The DateTime type converts date strings into `datetime` objects.
+
+ The format strings which are checked are configurable, but default to some
+ common (non-timezone aware) ISO 8601 formats.
+
+ When specifying *DateTime* formats, you should only pass a list or a tuple.
+ Other iterables, like generators, may lead to surprising results.
+
+ The format strings are processed using ``datetime.strptime``, and this
+ consequently defines the format strings which are allowed.
+
+ Parsing is tried using each format, in order, and the first format which
+ parses successfully is used.
+
+ :param formats: A list or tuple of date format strings, in the order in
+ which they should be tried. Defaults to
+ ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
+ ``'%Y-%m-%d %H:%M:%S'``.
+ """
+
+ name = "datetime"
+
+ def __init__(self, formats: t.Optional[t.Sequence[str]] = None):
+ self.formats: t.Sequence[str] = formats or [
+ "%Y-%m-%d",
+ "%Y-%m-%dT%H:%M:%S",
+ "%Y-%m-%d %H:%M:%S",
+ ]
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict["formats"] = self.formats
+ return info_dict
+
+ def get_metavar(self, param: "Parameter") -> str:
+ return f"[{'|'.join(self.formats)}]"
+
+ def _try_to_convert_date(self, value: t.Any, format: str) -> t.Optional[datetime]:
+ try:
+ return datetime.strptime(value, format)
+ except ValueError:
+ return None
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ if isinstance(value, datetime):
+ return value
+
+ for format in self.formats:
+ converted = self._try_to_convert_date(value, format)
+
+ if converted is not None:
+ return converted
+
+ formats_str = ", ".join(map(repr, self.formats))
+ self.fail(
+ ngettext(
+ "{value!r} does not match the format {format}.",
+ "{value!r} does not match the formats {formats}.",
+ len(self.formats),
+ ).format(value=value, format=formats_str, formats=formats_str),
+ param,
+ ctx,
+ )
+
+ def __repr__(self) -> str:
+ return "DateTime"
+
+
+class _NumberParamTypeBase(ParamType):
+ _number_class: t.ClassVar[t.Type[t.Any]]
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ try:
+ return self._number_class(value)
+ except ValueError:
+ self.fail(
+ _("{value!r} is not a valid {number_type}.").format(
+ value=value, number_type=self.name
+ ),
+ param,
+ ctx,
+ )
+
+
+class _NumberRangeBase(_NumberParamTypeBase):
+ def __init__(
+ self,
+ min: t.Optional[float] = None,
+ max: t.Optional[float] = None,
+ min_open: bool = False,
+ max_open: bool = False,
+ clamp: bool = False,
+ ) -> None:
+ self.min = min
+ self.max = max
+ self.min_open = min_open
+ self.max_open = max_open
+ self.clamp = clamp
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict.update(
+ min=self.min,
+ max=self.max,
+ min_open=self.min_open,
+ max_open=self.max_open,
+ clamp=self.clamp,
+ )
+ return info_dict
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ import operator
+
+ rv = super().convert(value, param, ctx)
+ lt_min: bool = self.min is not None and (
+ operator.le if self.min_open else operator.lt
+ )(rv, self.min)
+ gt_max: bool = self.max is not None and (
+ operator.ge if self.max_open else operator.gt
+ )(rv, self.max)
+
+ if self.clamp:
+ if lt_min:
+ return self._clamp(self.min, 1, self.min_open) # type: ignore
+
+ if gt_max:
+ return self._clamp(self.max, -1, self.max_open) # type: ignore
+
+ if lt_min or gt_max:
+ self.fail(
+ _("{value} is not in the range {range}.").format(
+ value=rv, range=self._describe_range()
+ ),
+ param,
+ ctx,
+ )
+
+ return rv
+
+ def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
+ """Find the valid value to clamp to bound in the given
+ direction.
+
+ :param bound: The boundary value.
+ :param dir: 1 or -1 indicating the direction to move.
+ :param open: If true, the range does not include the bound.
+ """
+ raise NotImplementedError
+
+ def _describe_range(self) -> str:
+ """Describe the range for use in help text."""
+ if self.min is None:
+ op = "<" if self.max_open else "<="
+ return f"x{op}{self.max}"
+
+ if self.max is None:
+ op = ">" if self.min_open else ">="
+ return f"x{op}{self.min}"
+
+ lop = "<" if self.min_open else "<="
+ rop = "<" if self.max_open else "<="
+ return f"{self.min}{lop}x{rop}{self.max}"
+
+ def __repr__(self) -> str:
+ clamp = " clamped" if self.clamp else ""
+ return f"<{type(self).__name__} {self._describe_range()}{clamp}>"
+
+
+class IntParamType(_NumberParamTypeBase):
+ name = "integer"
+ _number_class = int
+
+ def __repr__(self) -> str:
+ return "INT"
+
+
+class IntRange(_NumberRangeBase, IntParamType):
+ """Restrict an :data:`click.INT` value to a range of accepted
+ values. See :ref:`ranges`.
+
+ If ``min`` or ``max`` are not passed, any value is accepted in that
+ direction. If ``min_open`` or ``max_open`` are enabled, the
+ corresponding boundary is not included in the range.
+
+ If ``clamp`` is enabled, a value outside the range is clamped to the
+ boundary instead of failing.
+
+ .. versionchanged:: 8.0
+ Added the ``min_open`` and ``max_open`` parameters.
+ """
+
+ name = "integer range"
+
+ def _clamp( # type: ignore
+ self, bound: int, dir: "te.Literal[1, -1]", open: bool
+ ) -> int:
+ if not open:
+ return bound
+
+ return bound + dir
+
+
+class FloatParamType(_NumberParamTypeBase):
+ name = "float"
+ _number_class = float
+
+ def __repr__(self) -> str:
+ return "FLOAT"
+
+
+class FloatRange(_NumberRangeBase, FloatParamType):
+ """Restrict a :data:`click.FLOAT` value to a range of accepted
+ values. See :ref:`ranges`.
+
+ If ``min`` or ``max`` are not passed, any value is accepted in that
+ direction. If ``min_open`` or ``max_open`` are enabled, the
+ corresponding boundary is not included in the range.
+
+ If ``clamp`` is enabled, a value outside the range is clamped to the
+ boundary instead of failing. This is not supported if either
+ boundary is marked ``open``.
+
+ .. versionchanged:: 8.0
+ Added the ``min_open`` and ``max_open`` parameters.
+ """
+
+ name = "float range"
+
+ def __init__(
+ self,
+ min: t.Optional[float] = None,
+ max: t.Optional[float] = None,
+ min_open: bool = False,
+ max_open: bool = False,
+ clamp: bool = False,
+ ) -> None:
+ super().__init__(
+ min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp
+ )
+
+ if (min_open or max_open) and clamp:
+ raise TypeError("Clamping is not supported for open bounds.")
+
+ def _clamp(self, bound: float, dir: "te.Literal[1, -1]", open: bool) -> float:
+ if not open:
+ return bound
+
+ # Could use Python 3.9's math.nextafter here, but clamping an
+ # open float range doesn't seem to be particularly useful. It's
+ # left up to the user to write a callback to do it if needed.
+ raise RuntimeError("Clamping is not supported for open bounds.")
+
+
+class BoolParamType(ParamType):
+ name = "boolean"
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ if value in {False, True}:
+ return bool(value)
+
+ norm = value.strip().lower()
+
+ if norm in {"1", "true", "t", "yes", "y", "on"}:
+ return True
+
+ if norm in {"0", "false", "f", "no", "n", "off"}:
+ return False
+
+ self.fail(
+ _("{value!r} is not a valid boolean.").format(value=value), param, ctx
+ )
+
+ def __repr__(self) -> str:
+ return "BOOL"
+
+
+class UUIDParameterType(ParamType):
+ name = "uuid"
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ import uuid
+
+ if isinstance(value, uuid.UUID):
+ return value
+
+ value = value.strip()
+
+ try:
+ return uuid.UUID(value)
+ except ValueError:
+ self.fail(
+ _("{value!r} is not a valid UUID.").format(value=value), param, ctx
+ )
+
+ def __repr__(self) -> str:
+ 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. When opening a
+ file lazily for reading, it is still opened temporarily for validation, but
+ will not be held open until first IO. lazy is mainly useful when opening
+ for writing to avoid creating the file until it is needed.
+
+ 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: t.ClassVar[str] = os.path.pathsep
+
+ def __init__(
+ self,
+ mode: str = "r",
+ encoding: t.Optional[str] = None,
+ errors: t.Optional[str] = "strict",
+ lazy: t.Optional[bool] = None,
+ atomic: bool = False,
+ ) -> None:
+ self.mode = mode
+ self.encoding = encoding
+ self.errors = errors
+ self.lazy = lazy
+ self.atomic = atomic
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict.update(mode=self.mode, encoding=self.encoding)
+ return info_dict
+
+ def resolve_lazy_flag(self, value: "t.Union[str, os.PathLike[str]]") -> bool:
+ if self.lazy is not None:
+ return self.lazy
+ if os.fspath(value) == "-":
+ return False
+ elif "w" in self.mode:
+ return True
+ return False
+
+ def convert(
+ self,
+ value: t.Union[str, "os.PathLike[str]", t.IO[t.Any]],
+ param: t.Optional["Parameter"],
+ ctx: t.Optional["Context"],
+ ) -> t.IO[t.Any]:
+ if _is_file_like(value):
+ return value
+
+ value = t.cast("t.Union[str, os.PathLike[str]]", value)
+
+ try:
+ lazy = self.resolve_lazy_flag(value)
+
+ if lazy:
+ lf = LazyFile(
+ value, self.mode, self.encoding, self.errors, atomic=self.atomic
+ )
+
+ if ctx is not None:
+ ctx.call_on_close(lf.close_intelligently)
+
+ return t.cast(t.IO[t.Any], lf)
+
+ 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 OSError as e: # noqa: B014
+ self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx)
+
+ def shell_complete(
+ self, ctx: "Context", param: "Parameter", incomplete: str
+ ) -> t.List["CompletionItem"]:
+ """Return a special completion marker that tells the completion
+ system to use the shell to provide file path completions.
+
+ :param ctx: Invocation context for this command.
+ :param param: The parameter that is requesting completion.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ from click.shell_completion import CompletionItem
+
+ return [CompletionItem(incomplete, type="file")]
+
+
+def _is_file_like(value: t.Any) -> "te.TypeGuard[t.IO[t.Any]]":
+ return hasattr(value, "read") or hasattr(value, "write")
+
+
+class Path(ParamType):
+ """The ``Path`` type is similar to the :class:`File` type, but
+ returns the filename instead of an open file. Various checks can be
+ enabled to validate the type of file and permissions.
+
+ :param exists: The file or directory needs to exist for the value to
+ be valid. If this is not set to ``True``, and the file does not
+ exist, then all further checks are silently skipped.
+ :param file_okay: Allow a file as a value.
+ :param dir_okay: Allow a directory as a value.
+ :param readable: if true, a readable check is performed.
+ :param writable: if true, a writable check is performed.
+ :param executable: if true, an executable check is performed.
+ :param resolve_path: Make the value absolute and resolve any
+ symlinks. A ``~`` is not expanded, as this is supposed to be
+ done by the shell only.
+ :param allow_dash: Allow a single dash as a value, which indicates
+ a standard stream (but does not open it). Use
+ :func:`~click.open_file` to handle opening this value.
+ :param path_type: Convert the incoming path value to this type. If
+ ``None``, keep Python's default, which is ``str``. Useful to
+ convert to :class:`pathlib.Path`.
+
+ .. versionchanged:: 8.1
+ Added the ``executable`` parameter.
+
+ .. versionchanged:: 8.0
+ Allow passing ``path_type=pathlib.Path``.
+
+ .. versionchanged:: 6.0
+ Added the ``allow_dash`` parameter.
+ """
+
+ envvar_list_splitter: t.ClassVar[str] = os.path.pathsep
+
+ def __init__(
+ self,
+ exists: bool = False,
+ file_okay: bool = True,
+ dir_okay: bool = True,
+ writable: bool = False,
+ readable: bool = True,
+ resolve_path: bool = False,
+ allow_dash: bool = False,
+ path_type: t.Optional[t.Type[t.Any]] = None,
+ executable: bool = False,
+ ):
+ self.exists = exists
+ self.file_okay = file_okay
+ self.dir_okay = dir_okay
+ self.readable = readable
+ self.writable = writable
+ self.executable = executable
+ 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: str = _("file")
+ elif self.dir_okay and not self.file_okay:
+ self.name = _("directory")
+ else:
+ self.name = _("path")
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict.update(
+ exists=self.exists,
+ file_okay=self.file_okay,
+ dir_okay=self.dir_okay,
+ writable=self.writable,
+ readable=self.readable,
+ allow_dash=self.allow_dash,
+ )
+ return info_dict
+
+ def coerce_path_result(
+ self, value: "t.Union[str, os.PathLike[str]]"
+ ) -> "t.Union[str, bytes, os.PathLike[str]]":
+ if self.type is not None and not isinstance(value, self.type):
+ if self.type is str:
+ return os.fsdecode(value)
+ elif self.type is bytes:
+ return os.fsencode(value)
+ else:
+ return t.cast("os.PathLike[str]", self.type(value))
+
+ return value
+
+ def convert(
+ self,
+ value: "t.Union[str, os.PathLike[str]]",
+ param: t.Optional["Parameter"],
+ ctx: t.Optional["Context"],
+ ) -> "t.Union[str, bytes, os.PathLike[str]]":
+ rv = value
+
+ is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-")
+
+ if not is_dash:
+ if self.resolve_path:
+ # os.path.realpath doesn't resolve symlinks on Windows
+ # until Python 3.8. Use pathlib for now.
+ import pathlib
+
+ rv = os.fsdecode(pathlib.Path(rv).resolve())
+
+ try:
+ st = os.stat(rv)
+ except OSError:
+ if not self.exists:
+ return self.coerce_path_result(rv)
+ self.fail(
+ _("{name} {filename!r} does not exist.").format(
+ name=self.name.title(), filename=format_filename(value)
+ ),
+ param,
+ ctx,
+ )
+
+ if not self.file_okay and stat.S_ISREG(st.st_mode):
+ self.fail(
+ _("{name} {filename!r} is a file.").format(
+ name=self.name.title(), filename=format_filename(value)
+ ),
+ param,
+ ctx,
+ )
+ if not self.dir_okay and stat.S_ISDIR(st.st_mode):
+ self.fail(
+ _("{name} '{filename}' is a directory.").format(
+ name=self.name.title(), filename=format_filename(value)
+ ),
+ param,
+ ctx,
+ )
+
+ if self.readable and not os.access(rv, os.R_OK):
+ self.fail(
+ _("{name} {filename!r} is not readable.").format(
+ name=self.name.title(), filename=format_filename(value)
+ ),
+ param,
+ ctx,
+ )
+
+ if self.writable and not os.access(rv, os.W_OK):
+ self.fail(
+ _("{name} {filename!r} is not writable.").format(
+ name=self.name.title(), filename=format_filename(value)
+ ),
+ param,
+ ctx,
+ )
+
+ if self.executable and not os.access(value, os.X_OK):
+ self.fail(
+ _("{name} {filename!r} is not executable.").format(
+ name=self.name.title(), filename=format_filename(value)
+ ),
+ param,
+ ctx,
+ )
+
+ return self.coerce_path_result(rv)
+
+ def shell_complete(
+ self, ctx: "Context", param: "Parameter", incomplete: str
+ ) -> t.List["CompletionItem"]:
+ """Return a special completion marker that tells the completion
+ system to use the shell to provide path completions for only
+ directories or any paths.
+
+ :param ctx: Invocation context for this command.
+ :param param: The parameter that is requesting completion.
+ :param incomplete: Value being completed. May be empty.
+
+ .. versionadded:: 8.0
+ """
+ from click.shell_completion import CompletionItem
+
+ type = "dir" if self.dir_okay and not self.file_okay else "file"
+ return [CompletionItem(incomplete, type=type)]
+
+
+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: t.Sequence[t.Union[t.Type[t.Any], ParamType]]) -> None:
+ self.types: t.Sequence[ParamType] = [convert_type(ty) for ty in types]
+
+ def to_info_dict(self) -> t.Dict[str, t.Any]:
+ info_dict = super().to_info_dict()
+ info_dict["types"] = [t.to_info_dict() for t in self.types]
+ return info_dict
+
+ @property
+ def name(self) -> str: # type: ignore
+ return f"<{' '.join(ty.name for ty in self.types)}>"
+
+ @property
+ def arity(self) -> int: # type: ignore
+ return len(self.types)
+
+ def convert(
+ self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+ ) -> t.Any:
+ len_type = len(self.types)
+ len_value = len(value)
+
+ if len_value != len_type:
+ self.fail(
+ ngettext(
+ "{len_type} values are required, but {len_value} was given.",
+ "{len_type} values are required, but {len_value} were given.",
+ len_value,
+ ).format(len_type=len_type, len_value=len_value),
+ param=param,
+ ctx=ctx,
+ )
+
+ return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
+
+
+def convert_type(ty: t.Optional[t.Any], default: t.Optional[t.Any] = None) -> ParamType:
+ """Find the most appropriate :class:`ParamType` for the given Python
+ type. If the type isn't provided, it can be inferred from a default
+ value.
+ """
+ guessed_type = False
+
+ if ty is None and default is not None:
+ if isinstance(default, (tuple, list)):
+ # If the default is empty, ty will remain None and will
+ # return STRING.
+ if default:
+ item = default[0]
+
+ # A tuple of tuples needs to detect the inner types.
+ # Can't call convert recursively because that would
+ # incorrectly unwind the tuple to a single type.
+ if isinstance(item, (tuple, list)):
+ ty = tuple(map(type, item))
+ else:
+ ty = type(item)
+ else:
+ ty = type(default)
+
+ guessed_type = True
+
+ if isinstance(ty, tuple):
+ return Tuple(ty)
+
+ if isinstance(ty, ParamType):
+ return ty
+
+ if ty is str or ty is None:
+ return STRING
+
+ if ty is int:
+ return INT
+
+ if ty is float:
+ return FLOAT
+
+ if ty is bool:
+ return BOOL
+
+ if guessed_type:
+ return STRING
+
+ if __debug__:
+ try:
+ if issubclass(ty, ParamType):
+ raise AssertionError(
+ f"Attempted to use an uninstantiated parameter type ({ty})."
+ )
+ except TypeError:
+ # ty is an instance (correct), so issubclass fails.
+ 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 if the input was bytes.
+#: 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()
diff --git a/venv/Lib/site-packages/click/utils.py b/venv/Lib/site-packages/click/utils.py
new file mode 100644
index 0000000..d536434
--- /dev/null
+++ b/venv/Lib/site-packages/click/utils.py
@@ -0,0 +1,624 @@
+import os
+import re
+import sys
+import typing as t
+from functools import update_wrapper
+from types import ModuleType
+from types import TracebackType
+
+from ._compat import _default_text_stderr
+from ._compat import _default_text_stdout
+from ._compat import _find_binary_writer
+from ._compat import auto_wrap_for_ansi
+from ._compat import binary_streams
+from ._compat import open_stream
+from ._compat import should_strip_ansi
+from ._compat import strip_ansi
+from ._compat import text_streams
+from ._compat import WIN
+from .globals import resolve_color_default
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+
+ P = te.ParamSpec("P")
+
+R = t.TypeVar("R")
+
+
+def _posixify(name: str) -> str:
+ return "-".join(name.split()).lower()
+
+
+def safecall(func: "t.Callable[P, R]") -> "t.Callable[P, t.Optional[R]]":
+ """Wraps a function so that it swallows exceptions."""
+
+ def wrapper(*args: "P.args", **kwargs: "P.kwargs") -> t.Optional[R]:
+ try:
+ return func(*args, **kwargs)
+ except Exception:
+ pass
+ return None
+
+ return update_wrapper(wrapper, func)
+
+
+def make_str(value: t.Any) -> str:
+ """Converts a value into a valid string."""
+ if isinstance(value, bytes):
+ try:
+ return value.decode(sys.getfilesystemencoding())
+ except UnicodeError:
+ return value.decode("utf-8", "replace")
+ return str(value)
+
+
+def make_default_short_help(help: str, max_length: int = 45) -> str:
+ """Returns a condensed version of help string."""
+ # Consider only the first paragraph.
+ paragraph_end = help.find("\n\n")
+
+ if paragraph_end != -1:
+ help = help[:paragraph_end]
+
+ # Collapse newlines, tabs, and spaces.
+ words = help.split()
+
+ if not words:
+ return ""
+
+ # The first paragraph started with a "no rewrap" marker, ignore it.
+ if words[0] == "\b":
+ words = words[1:]
+
+ total_length = 0
+ last_index = len(words) - 1
+
+ for i, word in enumerate(words):
+ total_length += len(word) + (i > 0)
+
+ if total_length > max_length: # too long, truncate
+ break
+
+ if word[-1] == ".": # sentence end, truncate without "..."
+ return " ".join(words[: i + 1])
+
+ if total_length == max_length and i != last_index:
+ break # not at sentence end, truncate with "..."
+ else:
+ return " ".join(words) # no truncation needed
+
+ # Account for the length of the suffix.
+ total_length += len("...")
+
+ # remove words until the length is short enough
+ while i > 0:
+ total_length -= len(words[i]) + (i > 0)
+
+ if total_length <= max_length:
+ break
+
+ i -= 1
+
+ return " ".join(words[:i]) + "..."
+
+
+class LazyFile:
+ """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: t.Union[str, "os.PathLike[str]"],
+ mode: str = "r",
+ encoding: t.Optional[str] = None,
+ errors: t.Optional[str] = "strict",
+ atomic: bool = False,
+ ):
+ self.name: str = os.fspath(filename)
+ self.mode = mode
+ self.encoding = encoding
+ self.errors = errors
+ self.atomic = atomic
+ self._f: t.Optional[t.IO[t.Any]]
+ self.should_close: bool
+
+ if self.name == "-":
+ 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: str) -> t.Any:
+ return getattr(self.open(), name)
+
+ def __repr__(self) -> str:
+ if self._f is not None:
+ return repr(self._f)
+ return f""
+
+ def open(self) -> t.IO[t.Any]:
+ """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 OSError as e: # noqa: E402
+ from .exceptions import FileError
+
+ raise FileError(self.name, hint=e.strerror) from e
+ self._f = rv
+ return rv
+
+ def close(self) -> None:
+ """Closes the underlying file, no matter what."""
+ if self._f is not None:
+ self._f.close()
+
+ def close_intelligently(self) -> None:
+ """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) -> "LazyFile":
+ return self
+
+ def __exit__(
+ self,
+ exc_type: t.Optional[t.Type[BaseException]],
+ exc_value: t.Optional[BaseException],
+ tb: t.Optional[TracebackType],
+ ) -> None:
+ self.close_intelligently()
+
+ def __iter__(self) -> t.Iterator[t.AnyStr]:
+ self.open()
+ return iter(self._f) # type: ignore
+
+
+class KeepOpenFile:
+ def __init__(self, file: t.IO[t.Any]) -> None:
+ self._file: t.IO[t.Any] = file
+
+ def __getattr__(self, name: str) -> t.Any:
+ return getattr(self._file, name)
+
+ def __enter__(self) -> "KeepOpenFile":
+ return self
+
+ def __exit__(
+ self,
+ exc_type: t.Optional[t.Type[BaseException]],
+ exc_value: t.Optional[BaseException],
+ tb: t.Optional[TracebackType],
+ ) -> None:
+ pass
+
+ def __repr__(self) -> str:
+ return repr(self._file)
+
+ def __iter__(self) -> t.Iterator[t.AnyStr]:
+ return iter(self._file)
+
+
+def echo(
+ message: t.Optional[t.Any] = None,
+ file: t.Optional[t.IO[t.Any]] = None,
+ nl: bool = True,
+ err: bool = False,
+ color: t.Optional[bool] = None,
+) -> None:
+ """Print a message and newline to stdout or a file. This should be
+ used instead of :func:`print` because it provides better support
+ for different data, files, and environments.
+
+ Compared to :func:`print`, this does the following:
+
+ - Ensures that the output encoding is not misconfigured on Linux.
+ - Supports Unicode in the Windows console.
+ - Supports writing to binary outputs, and supports writing bytes
+ to text outputs.
+ - Supports colors and styles on Windows.
+ - Removes ANSI color and style codes if the output does not look
+ like an interactive terminal.
+ - Always flushes the output.
+
+ :param message: The string or bytes to output. Other objects are
+ converted to strings.
+ :param file: The file to write to. Defaults to ``stdout``.
+ :param err: Write to ``stderr`` instead of ``stdout``.
+ :param nl: Print a newline after the message. Enabled by default.
+ :param color: Force showing or hiding colors and other styles. By
+ default Click will remove color if the output does not look like
+ an interactive terminal.
+
+ .. versionchanged:: 6.0
+ Support Unicode output on the Windows console. Click does not
+ modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()``
+ will still not support Unicode.
+
+ .. versionchanged:: 4.0
+ Added the ``color`` parameter.
+
+ .. versionadded:: 3.0
+ Added the ``err`` parameter.
+
+ .. versionchanged:: 2.0
+ Support colors on Windows if colorama is installed.
+ """
+ if file is None:
+ if err:
+ file = _default_text_stderr()
+ else:
+ file = _default_text_stdout()
+
+ # There are no standard streams attached to write to. For example,
+ # pythonw on Windows.
+ if file is None:
+ return
+
+ # Convert non bytes/text into the native string type.
+ if message is not None and not isinstance(message, (str, bytes, bytearray)):
+ out: t.Optional[t.Union[str, bytes]] = str(message)
+ else:
+ out = message
+
+ if nl:
+ out = out or ""
+ if isinstance(out, str):
+ out += "\n"
+ else:
+ out += b"\n"
+
+ if not out:
+ file.flush()
+ return
+
+ # If there is a message 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 isinstance(out, (bytes, bytearray)):
+ binary_file = _find_binary_writer(file)
+
+ if binary_file is not None:
+ file.flush()
+ binary_file.write(out)
+ binary_file.flush()
+ return
+
+ # ANSI style code support. For no message or bytes, nothing happens.
+ # When outputting to a file instead of a terminal, strip codes.
+ else:
+ color = resolve_color_default(color)
+
+ if should_strip_ansi(file, color):
+ out = strip_ansi(out)
+ elif WIN:
+ if auto_wrap_for_ansi is not None:
+ file = auto_wrap_for_ansi(file) # type: ignore
+ elif not color:
+ out = strip_ansi(out)
+
+ file.write(out) # type: ignore
+ file.flush()
+
+
+def get_binary_stream(name: "te.Literal['stdin', 'stdout', 'stderr']") -> t.BinaryIO:
+ """Returns a system stream for byte processing.
+
+ :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(f"Unknown standard stream '{name}'")
+ return opener()
+
+
+def get_text_stream(
+ name: "te.Literal['stdin', 'stdout', 'stderr']",
+ encoding: t.Optional[str] = None,
+ errors: t.Optional[str] = "strict",
+) -> t.TextIO:
+ """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 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(f"Unknown standard stream '{name}'")
+ return opener(encoding, errors)
+
+
+def open_file(
+ filename: str,
+ mode: str = "r",
+ encoding: t.Optional[str] = None,
+ errors: t.Optional[str] = "strict",
+ lazy: bool = False,
+ atomic: bool = False,
+) -> t.IO[t.Any]:
+ """Open a file, with extra behavior to handle ``'-'`` to indicate
+ a standard stream, lazy open on write, and atomic write. Similar to
+ the behavior of the :class:`~click.File` param type.
+
+ If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is
+ wrapped so that using it in a context manager will not close it.
+ This makes it possible to use the function without accidentally
+ closing a standard stream:
+
+ .. code-block:: python
+
+ with open_file(filename) as f:
+ ...
+
+ :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 decode or encode a file opened in
+ text mode.
+ :param errors: The error handling mode.
+ :param lazy: Wait to open the file until it is accessed. For read
+ mode, the file is temporarily opened to raise access errors
+ early, then closed until it is read again.
+ :param atomic: Write to a temporary file and replace the given file
+ on close.
+
+ .. versionadded:: 3.0
+ """
+ if lazy:
+ return t.cast(
+ t.IO[t.Any], LazyFile(filename, mode, encoding, errors, atomic=atomic)
+ )
+
+ f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic)
+
+ if not should_close:
+ f = t.cast(t.IO[t.Any], KeepOpenFile(f))
+
+ return f
+
+
+def format_filename(
+ filename: "t.Union[str, bytes, os.PathLike[str], os.PathLike[bytes]]",
+ shorten: bool = False,
+) -> str:
+ """Format a filename as a string for display. Ensures the filename can be
+ displayed by replacing any invalid bytes or surrogate escapes in the name
+ with the replacement character ``�``.
+
+ Invalid bytes or surrogate escapes will raise an error when written to a
+ stream with ``errors="strict". This will typically happen with ``stdout``
+ when the locale is something like ``en_GB.UTF-8``.
+
+ Many scenarios *are* safe to write surrogates though, due to PEP 538 and
+ PEP 540, including:
+
+ - Writing to ``stderr``, which uses ``errors="backslashreplace"``.
+ - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens
+ stdout and stderr with ``errors="surrogateescape"``.
+ - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``.
+ - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``.
+ Python opens stdout and stderr with ``errors="surrogateescape"``.
+
+ :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)
+ else:
+ filename = os.fspath(filename)
+
+ if isinstance(filename, bytes):
+ filename = filename.decode(sys.getfilesystemencoding(), "replace")
+ else:
+ filename = filename.encode("utf-8", "surrogateescape").decode(
+ "utf-8", "replace"
+ )
+
+ return filename
+
+
+def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str:
+ 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``
+ Windows (roaming):
+ ``C:\Users\\AppData\Roaming\Foo Bar``
+ Windows (not roaming):
+ ``C:\Users\\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 effect 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 = "APPDATA" if roaming else "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(f"~/.{_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),
+ )
+
+
+class PacifyFlushWrapper:
+ """This wrapper is used to catch and suppress BrokenPipeErrors resulting
+ from ``.flush()`` being called on broken pipe during the shutdown/final-GC
+ of the Python interpreter. Notably ``.flush()`` is always called on
+ ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any
+ other cleanup code, and the case where the underlying file is not a broken
+ pipe, all calls and attributes are proxied.
+ """
+
+ def __init__(self, wrapped: t.IO[t.Any]) -> None:
+ self.wrapped = wrapped
+
+ def flush(self) -> None:
+ try:
+ self.wrapped.flush()
+ except OSError as e:
+ import errno
+
+ if e.errno != errno.EPIPE:
+ raise
+
+ def __getattr__(self, attr: str) -> t.Any:
+ return getattr(self.wrapped, attr)
+
+
+def _detect_program_name(
+ path: t.Optional[str] = None, _main: t.Optional[ModuleType] = None
+) -> str:
+ """Determine the command used to run the program, for use in help
+ text. If a file or entry point was executed, the file name is
+ returned. If ``python -m`` was used to execute a module or package,
+ ``python -m name`` is returned.
+
+ This doesn't try to be too precise, the goal is to give a concise
+ name for help text. Files are only shown as their name without the
+ path. ``python`` is only shown for modules, and the full path to
+ ``sys.executable`` is not shown.
+
+ :param path: The Python file being executed. Python puts this in
+ ``sys.argv[0]``, which is used by default.
+ :param _main: The ``__main__`` module. This should only be passed
+ during internal testing.
+
+ .. versionadded:: 8.0
+ Based on command args detection in the Werkzeug reloader.
+
+ :meta private:
+ """
+ if _main is None:
+ _main = sys.modules["__main__"]
+
+ if not path:
+ path = sys.argv[0]
+
+ # The value of __package__ indicates how Python was called. It may
+ # not exist if a setuptools script is installed as an egg. It may be
+ # set incorrectly for entry points created with pip on Windows.
+ # It is set to "" inside a Shiv or PEX zipapp.
+ if getattr(_main, "__package__", None) in {None, ""} or (
+ os.name == "nt"
+ and _main.__package__ == ""
+ and not os.path.exists(path)
+ and os.path.exists(f"{path}.exe")
+ ):
+ # Executed a file, like "python app.py".
+ return os.path.basename(path)
+
+ # Executed a module, like "python -m example".
+ # Rewritten by Python from "-m script" to "/path/to/script.py".
+ # Need to look at main module to determine how it was executed.
+ py_module = t.cast(str, _main.__package__)
+ name = os.path.splitext(os.path.basename(path))[0]
+
+ # A submodule like "example.cli".
+ if name != "__main__":
+ py_module = f"{py_module}.{name}"
+
+ return f"python -m {py_module.lstrip('.')}"
+
+
+def _expand_args(
+ args: t.Iterable[str],
+ *,
+ user: bool = True,
+ env: bool = True,
+ glob_recursive: bool = True,
+) -> t.List[str]:
+ """Simulate Unix shell expansion with Python functions.
+
+ See :func:`glob.glob`, :func:`os.path.expanduser`, and
+ :func:`os.path.expandvars`.
+
+ This is intended for use on Windows, where the shell does not do any
+ expansion. It may not exactly match what a Unix shell would do.
+
+ :param args: List of command line arguments to expand.
+ :param user: Expand user home directory.
+ :param env: Expand environment variables.
+ :param glob_recursive: ``**`` matches directories recursively.
+
+ .. versionchanged:: 8.1
+ Invalid glob patterns are treated as empty expansions rather
+ than raising an error.
+
+ .. versionadded:: 8.0
+
+ :meta private:
+ """
+ from glob import glob
+
+ out = []
+
+ for arg in args:
+ if user:
+ arg = os.path.expanduser(arg)
+
+ if env:
+ arg = os.path.expandvars(arg)
+
+ try:
+ matches = glob(arg, recursive=glob_recursive)
+ except re.error:
+ matches = []
+
+ if not matches:
+ out.append(arg)
+ else:
+ out.extend(matches)
+
+ return out
diff --git a/venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER b/venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA b/venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA
new file mode 100644
index 0000000..a1b5c57
--- /dev/null
+++ b/venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA
@@ -0,0 +1,441 @@
+Metadata-Version: 2.1
+Name: colorama
+Version: 0.4.6
+Summary: Cross-platform colored terminal text.
+Project-URL: Homepage, https://github.com/tartley/colorama
+Author-email: Jonathan Hartley
+License-File: LICENSE.txt
+Keywords: ansi,color,colour,crossplatform,terminal,text,windows,xplatform
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+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.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Terminals
+Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7
+Description-Content-Type: text/x-rst
+
+.. image:: https://img.shields.io/pypi/v/colorama.svg
+ :target: https://pypi.org/project/colorama/
+ :alt: Latest Version
+
+.. image:: https://img.shields.io/pypi/pyversions/colorama.svg
+ :target: https://pypi.org/project/colorama/
+ :alt: Supported Python versions
+
+.. image:: https://github.com/tartley/colorama/actions/workflows/test.yml/badge.svg
+ :target: https://github.com/tartley/colorama/actions/workflows/test.yml
+ :alt: Build Status
+
+Colorama
+========
+
+Makes ANSI escape character sequences (for producing colored terminal text and
+cursor positioning) work under MS Windows.
+
+.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif
+ :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama¤cy_code=USD
+ :alt: Donate with Paypal
+
+`PyPI for releases `_ |
+`Github for source `_ |
+`Colorama for enterprise on Tidelift `_
+
+If you find Colorama useful, please |donate| to the authors. Thank you!
+
+Installation
+------------
+
+Tested on CPython 2.7, 3.7, 3.8, 3.9 and 3.10 and Pypy 2.7 and 3.8.
+
+No requirements other than the standard library.
+
+.. code-block:: bash
+
+ pip install colorama
+ # or
+ conda install -c anaconda colorama
+
+Description
+-----------
+
+ANSI escape character sequences have long been used to produce colored terminal
+text and cursor positioning on Unix and Macs. Colorama makes this work on
+Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which
+would appear as gobbledygook in the output), and converting them into the
+appropriate win32 calls to modify the state of the terminal. On other platforms,
+Colorama does nothing.
+
+This has the upshot of providing a simple cross-platform API for printing
+colored terminal text from Python, and has the happy side-effect that existing
+applications or libraries which use ANSI sequences to produce colored output on
+Linux or Macs can now also work on Windows, simply by calling
+``colorama.just_fix_windows_console()`` (since v0.4.6) or ``colorama.init()``
+(all versions, but may have other side-effects – see below).
+
+An alternative approach is to install ``ansi.sys`` on Windows machines, which
+provides the same behaviour for all applications running in terminals. Colorama
+is intended for situations where that isn't easy (e.g., maybe your app doesn't
+have an installer.)
+
+Demo scripts in the source code repository print some colored text using
+ANSI sequences. Compare their output under Gnome-terminal's built in ANSI
+handling, versus on Windows Command-Prompt using Colorama:
+
+.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png
+ :width: 661
+ :height: 357
+ :alt: ANSI sequences on Ubuntu under gnome-terminal.
+
+.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png
+ :width: 668
+ :height: 325
+ :alt: Same ANSI sequences on Windows, using Colorama.
+
+These screenshots show that, on Windows, Colorama does not support ANSI 'dim
+text'; it looks the same as 'normal text'.
+
+Usage
+-----
+
+Initialisation
+..............
+
+If the only thing you want from Colorama is to get ANSI escapes to work on
+Windows, then run:
+
+.. code-block:: python
+
+ from colorama import just_fix_windows_console
+ just_fix_windows_console()
+
+If you're on a recent version of Windows 10 or better, and your stdout/stderr
+are pointing to a Windows console, then this will flip the magic configuration
+switch to enable Windows' built-in ANSI support.
+
+If you're on an older version of Windows, and your stdout/stderr are pointing to
+a Windows console, then this will wrap ``sys.stdout`` and/or ``sys.stderr`` in a
+magic file object that intercepts ANSI escape sequences and issues the
+appropriate Win32 calls to emulate them.
+
+In all other circumstances, it does nothing whatsoever. Basically the idea is
+that this makes Windows act like Unix with respect to ANSI escape handling.
+
+It's safe to call this function multiple times. It's safe to call this function
+on non-Windows platforms, but it won't do anything. It's safe to call this
+function when one or both of your stdout/stderr are redirected to a file – it
+won't do anything to those streams.
+
+Alternatively, you can use the older interface with more features (but also more
+potential footguns):
+
+.. code-block:: python
+
+ from colorama import init
+ init()
+
+This does the same thing as ``just_fix_windows_console``, except for the
+following differences:
+
+- It's not safe to call ``init`` multiple times; you can end up with multiple
+ layers of wrapping and broken ANSI support.
+
+- Colorama will apply a heuristic to guess whether stdout/stderr support ANSI,
+ and if it thinks they don't, then it will wrap ``sys.stdout`` and
+ ``sys.stderr`` in a magic file object that strips out ANSI escape sequences
+ before printing them. This happens on all platforms, and can be convenient if
+ you want to write your code to emit ANSI escape sequences unconditionally, and
+ let Colorama decide whether they should actually be output. But note that
+ Colorama's heuristic is not particularly clever.
+
+- ``init`` also accepts explicit keyword args to enable/disable various
+ functionality – see below.
+
+To stop using Colorama before your program exits, simply call ``deinit()``.
+This will restore ``stdout`` and ``stderr`` to their original values, so that
+Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is
+cheaper than calling ``init()`` again (but does the same thing).
+
+Most users should depend on ``colorama >= 0.4.6``, and use
+``just_fix_windows_console``. The old ``init`` interface will be supported
+indefinitely for backwards compatibility, but we don't plan to fix any issues
+with it, also for backwards compatibility.
+
+Colored Output
+..............
+
+Cross-platform printing of colored text can then be done using Colorama's
+constant shorthand for ANSI escape sequences. These are deliberately
+rudimentary, see below.
+
+.. code-block:: python
+
+ from colorama import Fore, Back, Style
+ print(Fore.RED + 'some red text')
+ print(Back.GREEN + 'and with a green background')
+ print(Style.DIM + 'and in dim text')
+ print(Style.RESET_ALL)
+ print('back to normal now')
+
+...or simply by manually printing ANSI sequences from your own code:
+
+.. code-block:: python
+
+ print('\033[31m' + 'some red text')
+ print('\033[39m') # and reset to default color
+
+...or, Colorama can be used in conjunction with existing ANSI libraries
+such as the venerable `Termcolor `_
+the fabulous `Blessings `_,
+or the incredible `_Rich `_.
+
+If you wish Colorama's Fore, Back and Style constants were more capable,
+then consider using one of the above highly capable libraries to generate
+colors, etc, and use Colorama just for its primary purpose: to convert
+those ANSI sequences to also work on Windows:
+
+SIMILARLY, do not send PRs adding the generation of new ANSI types to Colorama.
+We are only interested in converting ANSI codes to win32 API calls, not
+shortcuts like the above to generate ANSI characters.
+
+.. code-block:: python
+
+ from colorama import just_fix_windows_console
+ from termcolor import colored
+
+ # use Colorama to make Termcolor work on Windows too
+ just_fix_windows_console()
+
+ # then use Termcolor for all colored text output
+ print(colored('Hello, World!', 'green', 'on_red'))
+
+Available formatting constants are::
+
+ Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
+ Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
+ Style: DIM, NORMAL, BRIGHT, RESET_ALL
+
+``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will
+perform this reset automatically on program exit.
+
+These are fairly well supported, but not part of the standard::
+
+ Fore: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX
+ Back: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX
+
+Cursor Positioning
+..................
+
+ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for
+an example of how to generate them.
+
+Init Keyword Args
+.................
+
+``init()`` accepts some ``**kwargs`` to override default behaviour.
+
+init(autoreset=False):
+ If you find yourself repeatedly sending reset sequences to turn off color
+ changes at the end of every print, then ``init(autoreset=True)`` will
+ automate that:
+
+ .. code-block:: python
+
+ from colorama import init
+ init(autoreset=True)
+ print(Fore.RED + 'some red text')
+ print('automatically back to default color again')
+
+init(strip=None):
+ Pass ``True`` or ``False`` to override whether ANSI codes should be
+ stripped from the output. The default behaviour is to strip if on Windows
+ or if output is redirected (not a tty).
+
+init(convert=None):
+ Pass ``True`` or ``False`` to override whether to convert ANSI codes in the
+ output into win32 calls. The default behaviour is to convert if on Windows
+ and output is to a tty (terminal).
+
+init(wrap=True):
+ On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr``
+ with proxy objects, which override the ``.write()`` method to do their work.
+ If this wrapping causes you problems, then this can be disabled by passing
+ ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or
+ ``strip`` or ``convert`` are True.
+
+ When wrapping is disabled, colored printing on non-Windows platforms will
+ continue to work as normal. To do cross-platform colored output, you can
+ use Colorama's ``AnsiToWin32`` proxy directly:
+
+ .. code-block:: python
+
+ import sys
+ from colorama import init, AnsiToWin32
+ init(wrap=False)
+ stream = AnsiToWin32(sys.stderr).stream
+
+ # Python 2
+ print >>stream, Fore.BLUE + 'blue text on stderr'
+
+ # Python 3
+ print(Fore.BLUE + 'blue text on stderr', file=stream)
+
+Recognised ANSI Sequences
+.........................
+
+ANSI sequences generally take the form::
+
+ ESC [ ; ...
+
+Where ```` is an integer, and ```` is a single letter. Zero or
+more params are passed to a ````. If no params are passed, it is
+generally synonymous with passing a single zero. No spaces exist in the
+sequence; they have been inserted here simply to read more easily.
+
+The only ANSI sequences that Colorama converts into win32 calls are::
+
+ ESC [ 0 m # reset all (colors and brightness)
+ ESC [ 1 m # bright
+ ESC [ 2 m # dim (looks same as normal brightness)
+ ESC [ 22 m # normal brightness
+
+ # FOREGROUND:
+ ESC [ 30 m # black
+ ESC [ 31 m # red
+ ESC [ 32 m # green
+ ESC [ 33 m # yellow
+ ESC [ 34 m # blue
+ ESC [ 35 m # magenta
+ ESC [ 36 m # cyan
+ ESC [ 37 m # white
+ ESC [ 39 m # reset
+
+ # BACKGROUND
+ ESC [ 40 m # black
+ ESC [ 41 m # red
+ ESC [ 42 m # green
+ ESC [ 43 m # yellow
+ ESC [ 44 m # blue
+ ESC [ 45 m # magenta
+ ESC [ 46 m # cyan
+ ESC [ 47 m # white
+ ESC [ 49 m # reset
+
+ # cursor positioning
+ ESC [ y;x H # position cursor at x across, y down
+ ESC [ y;x f # position cursor at x across, y down
+ ESC [ n A # move cursor n lines up
+ ESC [ n B # move cursor n lines down
+ ESC [ n C # move cursor n characters forward
+ ESC [ n D # move cursor n characters backward
+
+ # clear the screen
+ ESC [ mode J # clear the screen
+
+ # clear the line
+ ESC [ mode K # clear the line
+
+Multiple numeric params to the ``'m'`` command can be combined into a single
+sequence::
+
+ ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background
+
+All other ANSI sequences of the form ``ESC [ ; ... ``
+are silently stripped from the output on Windows.
+
+Any other form of ANSI sequence, such as single-character codes or alternative
+initial characters, are not recognised or stripped. It would be cool to add
+them though. Let me know if it would be useful for you, via the Issues on
+GitHub.
+
+Status & Known Problems
+-----------------------
+
+I've personally only tested it on Windows XP (CMD, Console2), Ubuntu
+(gnome-terminal, xterm), and OS X.
+
+Some valid ANSI sequences aren't recognised.
+
+If you're hacking on the code, see `README-hacking.md`_. ESPECIALLY, see the
+explanation there of why we do not want PRs that allow Colorama to generate new
+types of ANSI codes.
+
+See outstanding issues and wish-list:
+https://github.com/tartley/colorama/issues
+
+If anything doesn't work for you, or doesn't do what you expected or hoped for,
+I'd love to hear about it on that issues list, would be delighted by patches,
+and would be happy to grant commit access to anyone who submits a working patch
+or two.
+
+.. _README-hacking.md: README-hacking.md
+
+License
+-------
+
+Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see
+LICENSE file.
+
+Professional support
+--------------------
+
+.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png
+ :alt: Tidelift
+ :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme
+
+.. list-table::
+ :widths: 10 100
+
+ * - |tideliftlogo|
+ - Professional support for colorama is available as part of the
+ `Tidelift Subscription`_.
+ Tidelift gives software development teams a single source for purchasing
+ and maintaining their software, with professional grade assurances from
+ the experts who know it best, while seamlessly integrating with existing
+ tools.
+
+.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme
+
+Thanks
+------
+
+See the CHANGELOG for more thanks!
+
+* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5.
+* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``,
+ providing a solution to issue #7's setuptools/distutils debate,
+ and other fixes.
+* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``.
+* Matthew McCormick for politely pointing out a longstanding crash on non-Win.
+* Ben Hoyt, for a magnificent fix under 64-bit Windows.
+* Jesse at Empty Square for submitting a fix for examples in the README.
+* User 'jamessp', an observant documentation fix for cursor positioning.
+* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7
+ fix.
+* Julien Stuyck, for wisely suggesting Python3 compatible updates to README.
+* Daniel Griffith for multiple fabulous patches.
+* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty
+ output.
+* Roger Binns, for many suggestions, valuable feedback, & bug reports.
+* Tim Golden for thought and much appreciated feedback on the initial idea.
+* User 'Zearin' for updates to the README file.
+* John Szakmeister for adding support for light colors
+* Charles Merriam for adding documentation to demos
+* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes
+* Florian Bruhin for a fix when stdout or stderr are None
+* Thomas Weininger for fixing ValueError on Windows
+* Remi Rampin for better Github integration and fixes to the README file
+* Simeon Visser for closing a file handle using 'with' and updating classifiers
+ to include Python 3.3 and 3.4
+* Andy Neff for fixing RESET of LIGHT_EX colors.
+* Jonathan Hartley for the initial idea and implementation.
diff --git a/venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD b/venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD
new file mode 100644
index 0000000..cd6b130
--- /dev/null
+++ b/venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD
@@ -0,0 +1,31 @@
+colorama-0.4.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+colorama-0.4.6.dist-info/METADATA,sha256=e67SnrUMOym9sz_4TjF3vxvAV4T3aF7NyqRHHH3YEMw,17158
+colorama-0.4.6.dist-info/RECORD,,
+colorama-0.4.6.dist-info/WHEEL,sha256=cdcF4Fbd0FPtw2EMIOwH-3rSOTUdTCeOSXRMD1iLUb8,105
+colorama-0.4.6.dist-info/licenses/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491
+colorama/__init__.py,sha256=wePQA4U20tKgYARySLEC047ucNX-g8pRLpYBuiHlLb8,266
+colorama/__pycache__/__init__.cpython-312.pyc,,
+colorama/__pycache__/ansi.cpython-312.pyc,,
+colorama/__pycache__/ansitowin32.cpython-312.pyc,,
+colorama/__pycache__/initialise.cpython-312.pyc,,
+colorama/__pycache__/win32.cpython-312.pyc,,
+colorama/__pycache__/winterm.cpython-312.pyc,,
+colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522
+colorama/ansitowin32.py,sha256=vPNYa3OZbxjbuFyaVo0Tmhmy1FZ1lKMWCnT7odXpItk,11128
+colorama/initialise.py,sha256=-hIny86ClXo39ixh5iSCfUIa2f_h_bgKRDW7gqs-KLU,3325
+colorama/tests/__init__.py,sha256=MkgPAEzGQd-Rq0w0PZXSX2LadRWhUECcisJY8lSrm4Q,75
+colorama/tests/__pycache__/__init__.cpython-312.pyc,,
+colorama/tests/__pycache__/ansi_test.cpython-312.pyc,,
+colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc,,
+colorama/tests/__pycache__/initialise_test.cpython-312.pyc,,
+colorama/tests/__pycache__/isatty_test.cpython-312.pyc,,
+colorama/tests/__pycache__/utils.cpython-312.pyc,,
+colorama/tests/__pycache__/winterm_test.cpython-312.pyc,,
+colorama/tests/ansi_test.py,sha256=FeViDrUINIZcr505PAxvU4AjXz1asEiALs9GXMhwRaE,2839
+colorama/tests/ansitowin32_test.py,sha256=RN7AIhMJ5EqDsYaCjVo-o4u8JzDD4ukJbmevWKS70rY,10678
+colorama/tests/initialise_test.py,sha256=BbPy-XfyHwJ6zKozuQOvNvQZzsx9vdb_0bYXn7hsBTc,6741
+colorama/tests/isatty_test.py,sha256=Pg26LRpv0yQDB5Ac-sxgVXG7hsA1NYvapFgApZfYzZg,1866
+colorama/tests/utils.py,sha256=1IIRylG39z5-dzq09R_ngufxyPZxgldNbrxKxUGwGKE,1079
+colorama/tests/winterm_test.py,sha256=qoWFPEjym5gm2RuMwpf3pOis3a5r_PJZFCzK254JL8A,3709
+colorama/win32.py,sha256=YQOKwMTwtGBbsY4dL5HYTvwTeP9wIQra5MvPNddpxZs,6181
+colorama/winterm.py,sha256=XCQFDHjPi6AHYNdZwy0tA02H-Jh48Jp-HvCjeLeLp3U,7134
diff --git a/venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL b/venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL
new file mode 100644
index 0000000..d79189f
--- /dev/null
+++ b/venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: hatchling 1.11.1
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
diff --git a/venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt b/venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt
new file mode 100644
index 0000000..3105888
--- /dev/null
+++ b/venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2010 Jonathan Hartley
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holders, nor those of its contributors
+ may be used to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/colorama/__init__.py b/venv/Lib/site-packages/colorama/__init__.py
new file mode 100644
index 0000000..383101c
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/__init__.py
@@ -0,0 +1,7 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console
+from .ansi import Fore, Back, Style, Cursor
+from .ansitowin32 import AnsiToWin32
+
+__version__ = '0.4.6'
+
diff --git a/venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..45ad36d
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc b/venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc
new file mode 100644
index 0000000..27a935d
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/ansi.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-312.pyc b/venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-312.pyc
new file mode 100644
index 0000000..e81d584
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/ansitowin32.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc b/venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc
new file mode 100644
index 0000000..ccc3a8c
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/initialise.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc b/venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc
new file mode 100644
index 0000000..055f2ce
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/win32.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc b/venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc
new file mode 100644
index 0000000..c61ad9a
Binary files /dev/null and b/venv/Lib/site-packages/colorama/__pycache__/winterm.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/ansi.py b/venv/Lib/site-packages/colorama/ansi.py
new file mode 100644
index 0000000..11ec695
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/ansi.py
@@ -0,0 +1,102 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+'''
+This module generates ANSI character codes to printing colors to terminals.
+See: http://en.wikipedia.org/wiki/ANSI_escape_code
+'''
+
+CSI = '\033['
+OSC = '\033]'
+BEL = '\a'
+
+
+def code_to_chars(code):
+ return CSI + str(code) + 'm'
+
+def set_title(title):
+ return OSC + '2;' + title + BEL
+
+def clear_screen(mode=2):
+ return CSI + str(mode) + 'J'
+
+def clear_line(mode=2):
+ return CSI + str(mode) + 'K'
+
+
+class AnsiCodes(object):
+ def __init__(self):
+ # the subclasses declare class attributes which are numbers.
+ # Upon instantiation we define instance attributes, which are the same
+ # as the class attributes but wrapped with the ANSI escape sequence
+ for name in dir(self):
+ if not name.startswith('_'):
+ value = getattr(self, name)
+ setattr(self, name, code_to_chars(value))
+
+
+class AnsiCursor(object):
+ def UP(self, n=1):
+ return CSI + str(n) + 'A'
+ def DOWN(self, n=1):
+ return CSI + str(n) + 'B'
+ def FORWARD(self, n=1):
+ return CSI + str(n) + 'C'
+ def BACK(self, n=1):
+ return CSI + str(n) + 'D'
+ def POS(self, x=1, y=1):
+ return CSI + str(y) + ';' + str(x) + 'H'
+
+
+class AnsiFore(AnsiCodes):
+ BLACK = 30
+ RED = 31
+ GREEN = 32
+ YELLOW = 33
+ BLUE = 34
+ MAGENTA = 35
+ CYAN = 36
+ WHITE = 37
+ RESET = 39
+
+ # These are fairly well supported, but not part of the standard.
+ LIGHTBLACK_EX = 90
+ LIGHTRED_EX = 91
+ LIGHTGREEN_EX = 92
+ LIGHTYELLOW_EX = 93
+ LIGHTBLUE_EX = 94
+ LIGHTMAGENTA_EX = 95
+ LIGHTCYAN_EX = 96
+ LIGHTWHITE_EX = 97
+
+
+class AnsiBack(AnsiCodes):
+ BLACK = 40
+ RED = 41
+ GREEN = 42
+ YELLOW = 43
+ BLUE = 44
+ MAGENTA = 45
+ CYAN = 46
+ WHITE = 47
+ RESET = 49
+
+ # These are fairly well supported, but not part of the standard.
+ LIGHTBLACK_EX = 100
+ LIGHTRED_EX = 101
+ LIGHTGREEN_EX = 102
+ LIGHTYELLOW_EX = 103
+ LIGHTBLUE_EX = 104
+ LIGHTMAGENTA_EX = 105
+ LIGHTCYAN_EX = 106
+ LIGHTWHITE_EX = 107
+
+
+class AnsiStyle(AnsiCodes):
+ BRIGHT = 1
+ DIM = 2
+ NORMAL = 22
+ RESET_ALL = 0
+
+Fore = AnsiFore()
+Back = AnsiBack()
+Style = AnsiStyle()
+Cursor = AnsiCursor()
diff --git a/venv/Lib/site-packages/colorama/ansitowin32.py b/venv/Lib/site-packages/colorama/ansitowin32.py
new file mode 100644
index 0000000..abf209e
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/ansitowin32.py
@@ -0,0 +1,277 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import re
+import sys
+import os
+
+from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL
+from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle
+from .win32 import windll, winapi_test
+
+
+winterm = None
+if windll is not None:
+ winterm = WinTerm()
+
+
+class StreamWrapper(object):
+ '''
+ Wraps a stream (such as stdout), acting as a transparent proxy for all
+ attribute access apart from method 'write()', which is delegated to our
+ Converter instance.
+ '''
+ def __init__(self, wrapped, converter):
+ # double-underscore everything to prevent clashes with names of
+ # attributes on the wrapped stream object.
+ self.__wrapped = wrapped
+ self.__convertor = converter
+
+ def __getattr__(self, name):
+ return getattr(self.__wrapped, name)
+
+ def __enter__(self, *args, **kwargs):
+ # special method lookup bypasses __getattr__/__getattribute__, see
+ # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit
+ # thus, contextlib magic methods are not proxied via __getattr__
+ return self.__wrapped.__enter__(*args, **kwargs)
+
+ def __exit__(self, *args, **kwargs):
+ return self.__wrapped.__exit__(*args, **kwargs)
+
+ def __setstate__(self, state):
+ self.__dict__ = state
+
+ def __getstate__(self):
+ return self.__dict__
+
+ def write(self, text):
+ self.__convertor.write(text)
+
+ def isatty(self):
+ stream = self.__wrapped
+ if 'PYCHARM_HOSTED' in os.environ:
+ if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__):
+ return True
+ try:
+ stream_isatty = stream.isatty
+ except AttributeError:
+ return False
+ else:
+ return stream_isatty()
+
+ @property
+ def closed(self):
+ stream = self.__wrapped
+ try:
+ return stream.closed
+ # AttributeError in the case that the stream doesn't support being closed
+ # ValueError for the case that the stream has already been detached when atexit runs
+ except (AttributeError, ValueError):
+ return True
+
+
+class AnsiToWin32(object):
+ '''
+ Implements a 'write()' method which, on Windows, will strip ANSI character
+ sequences from the text, and if outputting to a tty, will convert them into
+ win32 function calls.
+ '''
+ ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer
+ ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command
+
+ def __init__(self, wrapped, convert=None, strip=None, autoreset=False):
+ # The wrapped stream (normally sys.stdout or sys.stderr)
+ self.wrapped = wrapped
+
+ # should we reset colors to defaults after every .write()
+ self.autoreset = autoreset
+
+ # create the proxy wrapping our output stream
+ self.stream = StreamWrapper(wrapped, self)
+
+ on_windows = os.name == 'nt'
+ # We test if the WinAPI works, because even if we are on Windows
+ # we may be using a terminal that doesn't support the WinAPI
+ # (e.g. Cygwin Terminal). In this case it's up to the terminal
+ # to support the ANSI codes.
+ conversion_supported = on_windows and winapi_test()
+ try:
+ fd = wrapped.fileno()
+ except Exception:
+ fd = -1
+ system_has_native_ansi = not on_windows or enable_vt_processing(fd)
+ have_tty = not self.stream.closed and self.stream.isatty()
+ need_conversion = conversion_supported and not system_has_native_ansi
+
+ # should we strip ANSI sequences from our output?
+ if strip is None:
+ strip = need_conversion or not have_tty
+ self.strip = strip
+
+ # should we should convert ANSI sequences into win32 calls?
+ if convert is None:
+ convert = need_conversion and have_tty
+ self.convert = convert
+
+ # dict of ansi codes to win32 functions and parameters
+ self.win32_calls = self.get_win32_calls()
+
+ # are we wrapping stderr?
+ self.on_stderr = self.wrapped is sys.stderr
+
+ def should_wrap(self):
+ '''
+ True if this class is actually needed. If false, then the output
+ stream will not be affected, nor will win32 calls be issued, so
+ wrapping stdout is not actually required. This will generally be
+ False on non-Windows platforms, unless optional functionality like
+ autoreset has been requested using kwargs to init()
+ '''
+ return self.convert or self.strip or self.autoreset
+
+ def get_win32_calls(self):
+ if self.convert and winterm:
+ return {
+ AnsiStyle.RESET_ALL: (winterm.reset_all, ),
+ AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT),
+ AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL),
+ AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL),
+ AnsiFore.BLACK: (winterm.fore, WinColor.BLACK),
+ AnsiFore.RED: (winterm.fore, WinColor.RED),
+ AnsiFore.GREEN: (winterm.fore, WinColor.GREEN),
+ AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW),
+ AnsiFore.BLUE: (winterm.fore, WinColor.BLUE),
+ AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA),
+ AnsiFore.CYAN: (winterm.fore, WinColor.CYAN),
+ AnsiFore.WHITE: (winterm.fore, WinColor.GREY),
+ AnsiFore.RESET: (winterm.fore, ),
+ AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True),
+ AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True),
+ AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True),
+ AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True),
+ AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True),
+ AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True),
+ AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True),
+ AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True),
+ AnsiBack.BLACK: (winterm.back, WinColor.BLACK),
+ AnsiBack.RED: (winterm.back, WinColor.RED),
+ AnsiBack.GREEN: (winterm.back, WinColor.GREEN),
+ AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW),
+ AnsiBack.BLUE: (winterm.back, WinColor.BLUE),
+ AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA),
+ AnsiBack.CYAN: (winterm.back, WinColor.CYAN),
+ AnsiBack.WHITE: (winterm.back, WinColor.GREY),
+ AnsiBack.RESET: (winterm.back, ),
+ AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True),
+ AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True),
+ AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True),
+ AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True),
+ AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True),
+ AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True),
+ AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True),
+ AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True),
+ }
+ return dict()
+
+ def write(self, text):
+ if self.strip or self.convert:
+ self.write_and_convert(text)
+ else:
+ self.wrapped.write(text)
+ self.wrapped.flush()
+ if self.autoreset:
+ self.reset_all()
+
+
+ def reset_all(self):
+ if self.convert:
+ self.call_win32('m', (0,))
+ elif not self.strip and not self.stream.closed:
+ self.wrapped.write(Style.RESET_ALL)
+
+
+ def write_and_convert(self, text):
+ '''
+ Write the given text to our wrapped stream, stripping any ANSI
+ sequences from the text, and optionally converting them into win32
+ calls.
+ '''
+ cursor = 0
+ text = self.convert_osc(text)
+ for match in self.ANSI_CSI_RE.finditer(text):
+ start, end = match.span()
+ self.write_plain_text(text, cursor, start)
+ self.convert_ansi(*match.groups())
+ cursor = end
+ self.write_plain_text(text, cursor, len(text))
+
+
+ def write_plain_text(self, text, start, end):
+ if start < end:
+ self.wrapped.write(text[start:end])
+ self.wrapped.flush()
+
+
+ def convert_ansi(self, paramstring, command):
+ if self.convert:
+ params = self.extract_params(command, paramstring)
+ self.call_win32(command, params)
+
+
+ def extract_params(self, command, paramstring):
+ if command in 'Hf':
+ params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';'))
+ while len(params) < 2:
+ # defaults:
+ params = params + (1,)
+ else:
+ params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0)
+ if len(params) == 0:
+ # defaults:
+ if command in 'JKm':
+ params = (0,)
+ elif command in 'ABCD':
+ params = (1,)
+
+ return params
+
+
+ def call_win32(self, command, params):
+ if command == 'm':
+ for param in params:
+ if param in self.win32_calls:
+ func_args = self.win32_calls[param]
+ func = func_args[0]
+ args = func_args[1:]
+ kwargs = dict(on_stderr=self.on_stderr)
+ func(*args, **kwargs)
+ elif command in 'J':
+ winterm.erase_screen(params[0], on_stderr=self.on_stderr)
+ elif command in 'K':
+ winterm.erase_line(params[0], on_stderr=self.on_stderr)
+ elif command in 'Hf': # cursor position - absolute
+ winterm.set_cursor_position(params, on_stderr=self.on_stderr)
+ elif command in 'ABCD': # cursor position - relative
+ n = params[0]
+ # A - up, B - down, C - forward, D - back
+ x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command]
+ winterm.cursor_adjust(x, y, on_stderr=self.on_stderr)
+
+
+ def convert_osc(self, text):
+ for match in self.ANSI_OSC_RE.finditer(text):
+ start, end = match.span()
+ text = text[:start] + text[end:]
+ paramstring, command = match.groups()
+ if command == BEL:
+ if paramstring.count(";") == 1:
+ params = paramstring.split(";")
+ # 0 - change title and icon (we will only change title)
+ # 1 - change icon (we don't support this)
+ # 2 - change title
+ if params[0] in '02':
+ winterm.set_title(params[1])
+ return text
+
+
+ def flush(self):
+ self.wrapped.flush()
diff --git a/venv/Lib/site-packages/colorama/initialise.py b/venv/Lib/site-packages/colorama/initialise.py
new file mode 100644
index 0000000..d5fd4b7
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/initialise.py
@@ -0,0 +1,121 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import atexit
+import contextlib
+import sys
+
+from .ansitowin32 import AnsiToWin32
+
+
+def _wipe_internal_state_for_tests():
+ global orig_stdout, orig_stderr
+ orig_stdout = None
+ orig_stderr = None
+
+ global wrapped_stdout, wrapped_stderr
+ wrapped_stdout = None
+ wrapped_stderr = None
+
+ global atexit_done
+ atexit_done = False
+
+ global fixed_windows_console
+ fixed_windows_console = False
+
+ try:
+ # no-op if it wasn't registered
+ atexit.unregister(reset_all)
+ except AttributeError:
+ # python 2: no atexit.unregister. Oh well, we did our best.
+ pass
+
+
+def reset_all():
+ if AnsiToWin32 is not None: # Issue #74: objects might become None at exit
+ AnsiToWin32(orig_stdout).reset_all()
+
+
+def init(autoreset=False, convert=None, strip=None, wrap=True):
+
+ if not wrap and any([autoreset, convert, strip]):
+ raise ValueError('wrap=False conflicts with any other arg=True')
+
+ global wrapped_stdout, wrapped_stderr
+ global orig_stdout, orig_stderr
+
+ orig_stdout = sys.stdout
+ orig_stderr = sys.stderr
+
+ if sys.stdout is None:
+ wrapped_stdout = None
+ else:
+ sys.stdout = wrapped_stdout = \
+ wrap_stream(orig_stdout, convert, strip, autoreset, wrap)
+ if sys.stderr is None:
+ wrapped_stderr = None
+ else:
+ sys.stderr = wrapped_stderr = \
+ wrap_stream(orig_stderr, convert, strip, autoreset, wrap)
+
+ global atexit_done
+ if not atexit_done:
+ atexit.register(reset_all)
+ atexit_done = True
+
+
+def deinit():
+ if orig_stdout is not None:
+ sys.stdout = orig_stdout
+ if orig_stderr is not None:
+ sys.stderr = orig_stderr
+
+
+def just_fix_windows_console():
+ global fixed_windows_console
+
+ if sys.platform != "win32":
+ return
+ if fixed_windows_console:
+ return
+ if wrapped_stdout is not None or wrapped_stderr is not None:
+ # Someone already ran init() and it did stuff, so we won't second-guess them
+ return
+
+ # On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the
+ # native ANSI support in the console as a side-effect. We only need to actually
+ # replace sys.stdout/stderr if we're in the old-style conversion mode.
+ new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False)
+ if new_stdout.convert:
+ sys.stdout = new_stdout
+ new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False)
+ if new_stderr.convert:
+ sys.stderr = new_stderr
+
+ fixed_windows_console = True
+
+@contextlib.contextmanager
+def colorama_text(*args, **kwargs):
+ init(*args, **kwargs)
+ try:
+ yield
+ finally:
+ deinit()
+
+
+def reinit():
+ if wrapped_stdout is not None:
+ sys.stdout = wrapped_stdout
+ if wrapped_stderr is not None:
+ sys.stderr = wrapped_stderr
+
+
+def wrap_stream(stream, convert, strip, autoreset, wrap):
+ if wrap:
+ wrapper = AnsiToWin32(stream,
+ convert=convert, strip=strip, autoreset=autoreset)
+ if wrapper.should_wrap():
+ stream = wrapper.stream
+ return stream
+
+
+# Use this for initial setup as well, to reduce code duplication
+_wipe_internal_state_for_tests()
diff --git a/venv/Lib/site-packages/colorama/tests/__init__.py b/venv/Lib/site-packages/colorama/tests/__init__.py
new file mode 100644
index 0000000..8c5661e
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/__init__.py
@@ -0,0 +1 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..bee339a
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/ansi_test.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/ansi_test.cpython-312.pyc
new file mode 100644
index 0000000..1a9f247
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/ansi_test.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc
new file mode 100644
index 0000000..925a188
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/ansitowin32_test.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/initialise_test.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/initialise_test.cpython-312.pyc
new file mode 100644
index 0000000..11575de
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/initialise_test.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc
new file mode 100644
index 0000000..0ec80d7
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/isatty_test.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000..1043aa8
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/utils.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc b/venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc
new file mode 100644
index 0000000..395bc1f
Binary files /dev/null and b/venv/Lib/site-packages/colorama/tests/__pycache__/winterm_test.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/colorama/tests/ansi_test.py b/venv/Lib/site-packages/colorama/tests/ansi_test.py
new file mode 100644
index 0000000..0a20c80
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/ansi_test.py
@@ -0,0 +1,76 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main
+
+from ..ansi import Back, Fore, Style
+from ..ansitowin32 import AnsiToWin32
+
+stdout_orig = sys.stdout
+stderr_orig = sys.stderr
+
+
+class AnsiTest(TestCase):
+
+ def setUp(self):
+ # sanity check: stdout should be a file or StringIO object.
+ # It will only be AnsiToWin32 if init() has previously wrapped it
+ self.assertNotEqual(type(sys.stdout), AnsiToWin32)
+ self.assertNotEqual(type(sys.stderr), AnsiToWin32)
+
+ def tearDown(self):
+ sys.stdout = stdout_orig
+ sys.stderr = stderr_orig
+
+
+ def testForeAttributes(self):
+ self.assertEqual(Fore.BLACK, '\033[30m')
+ self.assertEqual(Fore.RED, '\033[31m')
+ self.assertEqual(Fore.GREEN, '\033[32m')
+ self.assertEqual(Fore.YELLOW, '\033[33m')
+ self.assertEqual(Fore.BLUE, '\033[34m')
+ self.assertEqual(Fore.MAGENTA, '\033[35m')
+ self.assertEqual(Fore.CYAN, '\033[36m')
+ self.assertEqual(Fore.WHITE, '\033[37m')
+ self.assertEqual(Fore.RESET, '\033[39m')
+
+ # Check the light, extended versions.
+ self.assertEqual(Fore.LIGHTBLACK_EX, '\033[90m')
+ self.assertEqual(Fore.LIGHTRED_EX, '\033[91m')
+ self.assertEqual(Fore.LIGHTGREEN_EX, '\033[92m')
+ self.assertEqual(Fore.LIGHTYELLOW_EX, '\033[93m')
+ self.assertEqual(Fore.LIGHTBLUE_EX, '\033[94m')
+ self.assertEqual(Fore.LIGHTMAGENTA_EX, '\033[95m')
+ self.assertEqual(Fore.LIGHTCYAN_EX, '\033[96m')
+ self.assertEqual(Fore.LIGHTWHITE_EX, '\033[97m')
+
+
+ def testBackAttributes(self):
+ self.assertEqual(Back.BLACK, '\033[40m')
+ self.assertEqual(Back.RED, '\033[41m')
+ self.assertEqual(Back.GREEN, '\033[42m')
+ self.assertEqual(Back.YELLOW, '\033[43m')
+ self.assertEqual(Back.BLUE, '\033[44m')
+ self.assertEqual(Back.MAGENTA, '\033[45m')
+ self.assertEqual(Back.CYAN, '\033[46m')
+ self.assertEqual(Back.WHITE, '\033[47m')
+ self.assertEqual(Back.RESET, '\033[49m')
+
+ # Check the light, extended versions.
+ self.assertEqual(Back.LIGHTBLACK_EX, '\033[100m')
+ self.assertEqual(Back.LIGHTRED_EX, '\033[101m')
+ self.assertEqual(Back.LIGHTGREEN_EX, '\033[102m')
+ self.assertEqual(Back.LIGHTYELLOW_EX, '\033[103m')
+ self.assertEqual(Back.LIGHTBLUE_EX, '\033[104m')
+ self.assertEqual(Back.LIGHTMAGENTA_EX, '\033[105m')
+ self.assertEqual(Back.LIGHTCYAN_EX, '\033[106m')
+ self.assertEqual(Back.LIGHTWHITE_EX, '\033[107m')
+
+
+ def testStyleAttributes(self):
+ self.assertEqual(Style.DIM, '\033[2m')
+ self.assertEqual(Style.NORMAL, '\033[22m')
+ self.assertEqual(Style.BRIGHT, '\033[1m')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/venv/Lib/site-packages/colorama/tests/ansitowin32_test.py b/venv/Lib/site-packages/colorama/tests/ansitowin32_test.py
new file mode 100644
index 0000000..91ca551
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/ansitowin32_test.py
@@ -0,0 +1,294 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from io import StringIO, TextIOWrapper
+from unittest import TestCase, main
+try:
+ from contextlib import ExitStack
+except ImportError:
+ # python 2
+ from contextlib2 import ExitStack
+
+try:
+ from unittest.mock import MagicMock, Mock, patch
+except ImportError:
+ from mock import MagicMock, Mock, patch
+
+from ..ansitowin32 import AnsiToWin32, StreamWrapper
+from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING
+from .utils import osname
+
+
+class StreamWrapperTest(TestCase):
+
+ def testIsAProxy(self):
+ mockStream = Mock()
+ wrapper = StreamWrapper(mockStream, None)
+ self.assertTrue( wrapper.random_attr is mockStream.random_attr )
+
+ def testDelegatesWrite(self):
+ mockStream = Mock()
+ mockConverter = Mock()
+ wrapper = StreamWrapper(mockStream, mockConverter)
+ wrapper.write('hello')
+ self.assertTrue(mockConverter.write.call_args, (('hello',), {}))
+
+ def testDelegatesContext(self):
+ mockConverter = Mock()
+ s = StringIO()
+ with StreamWrapper(s, mockConverter) as fp:
+ fp.write(u'hello')
+ self.assertTrue(s.closed)
+
+ def testProxyNoContextManager(self):
+ mockStream = MagicMock()
+ mockStream.__enter__.side_effect = AttributeError()
+ mockConverter = Mock()
+ with self.assertRaises(AttributeError) as excinfo:
+ with StreamWrapper(mockStream, mockConverter) as wrapper:
+ wrapper.write('hello')
+
+ def test_closed_shouldnt_raise_on_closed_stream(self):
+ stream = StringIO()
+ stream.close()
+ wrapper = StreamWrapper(stream, None)
+ self.assertEqual(wrapper.closed, True)
+
+ def test_closed_shouldnt_raise_on_detached_stream(self):
+ stream = TextIOWrapper(StringIO())
+ stream.detach()
+ wrapper = StreamWrapper(stream, None)
+ self.assertEqual(wrapper.closed, True)
+
+class AnsiToWin32Test(TestCase):
+
+ def testInit(self):
+ mockStdout = Mock()
+ auto = Mock()
+ stream = AnsiToWin32(mockStdout, autoreset=auto)
+ self.assertEqual(stream.wrapped, mockStdout)
+ self.assertEqual(stream.autoreset, auto)
+
+ @patch('colorama.ansitowin32.winterm', None)
+ @patch('colorama.ansitowin32.winapi_test', lambda *_: True)
+ def testStripIsTrueOnWindows(self):
+ with osname('nt'):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout)
+ self.assertTrue(stream.strip)
+
+ def testStripIsFalseOffWindows(self):
+ with osname('posix'):
+ mockStdout = Mock(closed=False)
+ stream = AnsiToWin32(mockStdout)
+ self.assertFalse(stream.strip)
+
+ def testWriteStripsAnsi(self):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout)
+ stream.wrapped = Mock()
+ stream.write_and_convert = Mock()
+ stream.strip = True
+
+ stream.write('abc')
+
+ self.assertFalse(stream.wrapped.write.called)
+ self.assertEqual(stream.write_and_convert.call_args, (('abc',), {}))
+
+ def testWriteDoesNotStripAnsi(self):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout)
+ stream.wrapped = Mock()
+ stream.write_and_convert = Mock()
+ stream.strip = False
+ stream.convert = False
+
+ stream.write('abc')
+
+ self.assertFalse(stream.write_and_convert.called)
+ self.assertEqual(stream.wrapped.write.call_args, (('abc',), {}))
+
+ def assert_autoresets(self, convert, autoreset=True):
+ stream = AnsiToWin32(Mock())
+ stream.convert = convert
+ stream.reset_all = Mock()
+ stream.autoreset = autoreset
+ stream.winterm = Mock()
+
+ stream.write('abc')
+
+ self.assertEqual(stream.reset_all.called, autoreset)
+
+ def testWriteAutoresets(self):
+ self.assert_autoresets(convert=True)
+ self.assert_autoresets(convert=False)
+ self.assert_autoresets(convert=True, autoreset=False)
+ self.assert_autoresets(convert=False, autoreset=False)
+
+ def testWriteAndConvertWritesPlainText(self):
+ stream = AnsiToWin32(Mock())
+ stream.write_and_convert( 'abc' )
+ self.assertEqual( stream.wrapped.write.call_args, (('abc',), {}) )
+
+ def testWriteAndConvertStripsAllValidAnsi(self):
+ stream = AnsiToWin32(Mock())
+ stream.call_win32 = Mock()
+ data = [
+ 'abc\033[mdef',
+ 'abc\033[0mdef',
+ 'abc\033[2mdef',
+ 'abc\033[02mdef',
+ 'abc\033[002mdef',
+ 'abc\033[40mdef',
+ 'abc\033[040mdef',
+ 'abc\033[0;1mdef',
+ 'abc\033[40;50mdef',
+ 'abc\033[50;30;40mdef',
+ 'abc\033[Adef',
+ 'abc\033[0Gdef',
+ 'abc\033[1;20;128Hdef',
+ ]
+ for datum in data:
+ stream.wrapped.write.reset_mock()
+ stream.write_and_convert( datum )
+ self.assertEqual(
+ [args[0] for args in stream.wrapped.write.call_args_list],
+ [ ('abc',), ('def',) ]
+ )
+
+ def testWriteAndConvertSkipsEmptySnippets(self):
+ stream = AnsiToWin32(Mock())
+ stream.call_win32 = Mock()
+ stream.write_and_convert( '\033[40m\033[41m' )
+ self.assertFalse( stream.wrapped.write.called )
+
+ def testWriteAndConvertCallsWin32WithParamsAndCommand(self):
+ stream = AnsiToWin32(Mock())
+ stream.convert = True
+ stream.call_win32 = Mock()
+ stream.extract_params = Mock(return_value='params')
+ data = {
+ 'abc\033[adef': ('a', 'params'),
+ 'abc\033[;;bdef': ('b', 'params'),
+ 'abc\033[0cdef': ('c', 'params'),
+ 'abc\033[;;0;;Gdef': ('G', 'params'),
+ 'abc\033[1;20;128Hdef': ('H', 'params'),
+ }
+ for datum, expected in data.items():
+ stream.call_win32.reset_mock()
+ stream.write_and_convert( datum )
+ self.assertEqual( stream.call_win32.call_args[0], expected )
+
+ def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self):
+ stream = StringIO()
+ converter = AnsiToWin32(stream)
+ stream.close()
+
+ converter.reset_all()
+
+ def test_wrap_shouldnt_raise_on_closed_orig_stdout(self):
+ stream = StringIO()
+ stream.close()
+ with \
+ patch("colorama.ansitowin32.os.name", "nt"), \
+ patch("colorama.ansitowin32.winapi_test", lambda: True):
+ converter = AnsiToWin32(stream)
+ self.assertTrue(converter.strip)
+ self.assertFalse(converter.convert)
+
+ def test_wrap_shouldnt_raise_on_missing_closed_attr(self):
+ with \
+ patch("colorama.ansitowin32.os.name", "nt"), \
+ patch("colorama.ansitowin32.winapi_test", lambda: True):
+ converter = AnsiToWin32(object())
+ self.assertTrue(converter.strip)
+ self.assertFalse(converter.convert)
+
+ def testExtractParams(self):
+ stream = AnsiToWin32(Mock())
+ data = {
+ '': (0,),
+ ';;': (0,),
+ '2': (2,),
+ ';;002;;': (2,),
+ '0;1': (0, 1),
+ ';;003;;456;;': (3, 456),
+ '11;22;33;44;55': (11, 22, 33, 44, 55),
+ }
+ for datum, expected in data.items():
+ self.assertEqual(stream.extract_params('m', datum), expected)
+
+ def testCallWin32UsesLookup(self):
+ listener = Mock()
+ stream = AnsiToWin32(listener)
+ stream.win32_calls = {
+ 1: (lambda *_, **__: listener(11),),
+ 2: (lambda *_, **__: listener(22),),
+ 3: (lambda *_, **__: listener(33),),
+ }
+ stream.call_win32('m', (3, 1, 99, 2))
+ self.assertEqual(
+ [a[0][0] for a in listener.call_args_list],
+ [33, 11, 22] )
+
+ def test_osc_codes(self):
+ mockStdout = Mock()
+ stream = AnsiToWin32(mockStdout, convert=True)
+ with patch('colorama.ansitowin32.winterm') as winterm:
+ data = [
+ '\033]0\x07', # missing arguments
+ '\033]0;foo\x08', # wrong OSC command
+ '\033]0;colorama_test_title\x07', # should work
+ '\033]1;colorama_test_title\x07', # wrong set command
+ '\033]2;colorama_test_title\x07', # should work
+ '\033]' + ';' * 64 + '\x08', # see issue #247
+ ]
+ for code in data:
+ stream.write(code)
+ self.assertEqual(winterm.set_title.call_count, 2)
+
+ def test_native_windows_ansi(self):
+ with ExitStack() as stack:
+ def p(a, b):
+ stack.enter_context(patch(a, b, create=True))
+ # Pretend to be on Windows
+ p("colorama.ansitowin32.os.name", "nt")
+ p("colorama.ansitowin32.winapi_test", lambda: True)
+ p("colorama.win32.winapi_test", lambda: True)
+ p("colorama.winterm.win32.windll", "non-None")
+ p("colorama.winterm.get_osfhandle", lambda _: 1234)
+
+ # Pretend that our mock stream has native ANSI support
+ p(
+ "colorama.winterm.win32.GetConsoleMode",
+ lambda _: ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+ )
+ SetConsoleMode = Mock()
+ p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode)
+
+ stdout = Mock()
+ stdout.closed = False
+ stdout.isatty.return_value = True
+ stdout.fileno.return_value = 1
+
+ # Our fake console says it has native vt support, so AnsiToWin32 should
+ # enable that support and do nothing else.
+ stream = AnsiToWin32(stdout)
+ SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ self.assertFalse(stream.strip)
+ self.assertFalse(stream.convert)
+ self.assertFalse(stream.should_wrap())
+
+ # Now let's pretend we're on an old Windows console, that doesn't have
+ # native ANSI support.
+ p("colorama.winterm.win32.GetConsoleMode", lambda _: 0)
+ SetConsoleMode = Mock()
+ p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode)
+
+ stream = AnsiToWin32(stdout)
+ SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING)
+ self.assertTrue(stream.strip)
+ self.assertTrue(stream.convert)
+ self.assertTrue(stream.should_wrap())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/venv/Lib/site-packages/colorama/tests/initialise_test.py b/venv/Lib/site-packages/colorama/tests/initialise_test.py
new file mode 100644
index 0000000..89f9b07
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/initialise_test.py
@@ -0,0 +1,189 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main, skipUnless
+
+try:
+ from unittest.mock import patch, Mock
+except ImportError:
+ from mock import patch, Mock
+
+from ..ansitowin32 import StreamWrapper
+from ..initialise import init, just_fix_windows_console, _wipe_internal_state_for_tests
+from .utils import osname, replace_by
+
+orig_stdout = sys.stdout
+orig_stderr = sys.stderr
+
+
+class InitTest(TestCase):
+
+ @skipUnless(sys.stdout.isatty(), "sys.stdout is not a tty")
+ def setUp(self):
+ # sanity check
+ self.assertNotWrapped()
+
+ def tearDown(self):
+ _wipe_internal_state_for_tests()
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+
+ def assertWrapped(self):
+ self.assertIsNot(sys.stdout, orig_stdout, 'stdout should be wrapped')
+ self.assertIsNot(sys.stderr, orig_stderr, 'stderr should be wrapped')
+ self.assertTrue(isinstance(sys.stdout, StreamWrapper),
+ 'bad stdout wrapper')
+ self.assertTrue(isinstance(sys.stderr, StreamWrapper),
+ 'bad stderr wrapper')
+
+ def assertNotWrapped(self):
+ self.assertIs(sys.stdout, orig_stdout, 'stdout should not be wrapped')
+ self.assertIs(sys.stderr, orig_stderr, 'stderr should not be wrapped')
+
+ @patch('colorama.initialise.reset_all')
+ @patch('colorama.ansitowin32.winapi_test', lambda *_: True)
+ @patch('colorama.ansitowin32.enable_vt_processing', lambda *_: False)
+ def testInitWrapsOnWindows(self, _):
+ with osname("nt"):
+ init()
+ self.assertWrapped()
+
+ @patch('colorama.initialise.reset_all')
+ @patch('colorama.ansitowin32.winapi_test', lambda *_: False)
+ def testInitDoesntWrapOnEmulatedWindows(self, _):
+ with osname("nt"):
+ init()
+ self.assertNotWrapped()
+
+ def testInitDoesntWrapOnNonWindows(self):
+ with osname("posix"):
+ init()
+ self.assertNotWrapped()
+
+ def testInitDoesntWrapIfNone(self):
+ with replace_by(None):
+ init()
+ # We can't use assertNotWrapped here because replace_by(None)
+ # changes stdout/stderr already.
+ self.assertIsNone(sys.stdout)
+ self.assertIsNone(sys.stderr)
+
+ def testInitAutoresetOnWrapsOnAllPlatforms(self):
+ with osname("posix"):
+ init(autoreset=True)
+ self.assertWrapped()
+
+ def testInitWrapOffDoesntWrapOnWindows(self):
+ with osname("nt"):
+ init(wrap=False)
+ self.assertNotWrapped()
+
+ def testInitWrapOffIncompatibleWithAutoresetOn(self):
+ self.assertRaises(ValueError, lambda: init(autoreset=True, wrap=False))
+
+ @patch('colorama.win32.SetConsoleTextAttribute')
+ @patch('colorama.initialise.AnsiToWin32')
+ def testAutoResetPassedOn(self, mockATW32, _):
+ with osname("nt"):
+ init(autoreset=True)
+ self.assertEqual(len(mockATW32.call_args_list), 2)
+ self.assertEqual(mockATW32.call_args_list[1][1]['autoreset'], True)
+ self.assertEqual(mockATW32.call_args_list[0][1]['autoreset'], True)
+
+ @patch('colorama.initialise.AnsiToWin32')
+ def testAutoResetChangeable(self, mockATW32):
+ with osname("nt"):
+ init()
+
+ init(autoreset=True)
+ self.assertEqual(len(mockATW32.call_args_list), 4)
+ self.assertEqual(mockATW32.call_args_list[2][1]['autoreset'], True)
+ self.assertEqual(mockATW32.call_args_list[3][1]['autoreset'], True)
+
+ init()
+ self.assertEqual(len(mockATW32.call_args_list), 6)
+ self.assertEqual(
+ mockATW32.call_args_list[4][1]['autoreset'], False)
+ self.assertEqual(
+ mockATW32.call_args_list[5][1]['autoreset'], False)
+
+
+ @patch('colorama.initialise.atexit.register')
+ def testAtexitRegisteredOnlyOnce(self, mockRegister):
+ init()
+ self.assertTrue(mockRegister.called)
+ mockRegister.reset_mock()
+ init()
+ self.assertFalse(mockRegister.called)
+
+
+class JustFixWindowsConsoleTest(TestCase):
+ def _reset(self):
+ _wipe_internal_state_for_tests()
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+
+ def tearDown(self):
+ self._reset()
+
+ @patch("colorama.ansitowin32.winapi_test", lambda: True)
+ def testJustFixWindowsConsole(self):
+ if sys.platform != "win32":
+ # just_fix_windows_console should be a no-op
+ just_fix_windows_console()
+ self.assertIs(sys.stdout, orig_stdout)
+ self.assertIs(sys.stderr, orig_stderr)
+ else:
+ def fake_std():
+ # Emulate stdout=not a tty, stderr=tty
+ # to check that we handle both cases correctly
+ stdout = Mock()
+ stdout.closed = False
+ stdout.isatty.return_value = False
+ stdout.fileno.return_value = 1
+ sys.stdout = stdout
+
+ stderr = Mock()
+ stderr.closed = False
+ stderr.isatty.return_value = True
+ stderr.fileno.return_value = 2
+ sys.stderr = stderr
+
+ for native_ansi in [False, True]:
+ with patch(
+ 'colorama.ansitowin32.enable_vt_processing',
+ lambda *_: native_ansi
+ ):
+ self._reset()
+ fake_std()
+
+ # Regular single-call test
+ prev_stdout = sys.stdout
+ prev_stderr = sys.stderr
+ just_fix_windows_console()
+ self.assertIs(sys.stdout, prev_stdout)
+ if native_ansi:
+ self.assertIs(sys.stderr, prev_stderr)
+ else:
+ self.assertIsNot(sys.stderr, prev_stderr)
+
+ # second call without resetting is always a no-op
+ prev_stdout = sys.stdout
+ prev_stderr = sys.stderr
+ just_fix_windows_console()
+ self.assertIs(sys.stdout, prev_stdout)
+ self.assertIs(sys.stderr, prev_stderr)
+
+ self._reset()
+ fake_std()
+
+ # If init() runs first, just_fix_windows_console should be a no-op
+ init()
+ prev_stdout = sys.stdout
+ prev_stderr = sys.stderr
+ just_fix_windows_console()
+ self.assertIs(prev_stdout, sys.stdout)
+ self.assertIs(prev_stderr, sys.stderr)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/venv/Lib/site-packages/colorama/tests/isatty_test.py b/venv/Lib/site-packages/colorama/tests/isatty_test.py
new file mode 100644
index 0000000..0f84e4b
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/isatty_test.py
@@ -0,0 +1,57 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main
+
+from ..ansitowin32 import StreamWrapper, AnsiToWin32
+from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY
+
+
+def is_a_tty(stream):
+ return StreamWrapper(stream, None).isatty()
+
+class IsattyTest(TestCase):
+
+ def test_TTY(self):
+ tty = StreamTTY()
+ self.assertTrue(is_a_tty(tty))
+ with pycharm():
+ self.assertTrue(is_a_tty(tty))
+
+ def test_nonTTY(self):
+ non_tty = StreamNonTTY()
+ self.assertFalse(is_a_tty(non_tty))
+ with pycharm():
+ self.assertFalse(is_a_tty(non_tty))
+
+ def test_withPycharm(self):
+ with pycharm():
+ self.assertTrue(is_a_tty(sys.stderr))
+ self.assertTrue(is_a_tty(sys.stdout))
+
+ def test_withPycharmTTYOverride(self):
+ tty = StreamTTY()
+ with pycharm(), replace_by(tty):
+ self.assertTrue(is_a_tty(tty))
+
+ def test_withPycharmNonTTYOverride(self):
+ non_tty = StreamNonTTY()
+ with pycharm(), replace_by(non_tty):
+ self.assertFalse(is_a_tty(non_tty))
+
+ def test_withPycharmNoneOverride(self):
+ with pycharm():
+ with replace_by(None), replace_original_by(None):
+ self.assertFalse(is_a_tty(None))
+ self.assertFalse(is_a_tty(StreamNonTTY()))
+ self.assertTrue(is_a_tty(StreamTTY()))
+
+ def test_withPycharmStreamWrapped(self):
+ with pycharm():
+ self.assertTrue(AnsiToWin32(StreamTTY()).stream.isatty())
+ self.assertFalse(AnsiToWin32(StreamNonTTY()).stream.isatty())
+ self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty())
+ self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/venv/Lib/site-packages/colorama/tests/utils.py b/venv/Lib/site-packages/colorama/tests/utils.py
new file mode 100644
index 0000000..472fafb
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/utils.py
@@ -0,0 +1,49 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+from contextlib import contextmanager
+from io import StringIO
+import sys
+import os
+
+
+class StreamTTY(StringIO):
+ def isatty(self):
+ return True
+
+class StreamNonTTY(StringIO):
+ def isatty(self):
+ return False
+
+@contextmanager
+def osname(name):
+ orig = os.name
+ os.name = name
+ yield
+ os.name = orig
+
+@contextmanager
+def replace_by(stream):
+ orig_stdout = sys.stdout
+ orig_stderr = sys.stderr
+ sys.stdout = stream
+ sys.stderr = stream
+ yield
+ sys.stdout = orig_stdout
+ sys.stderr = orig_stderr
+
+@contextmanager
+def replace_original_by(stream):
+ orig_stdout = sys.__stdout__
+ orig_stderr = sys.__stderr__
+ sys.__stdout__ = stream
+ sys.__stderr__ = stream
+ yield
+ sys.__stdout__ = orig_stdout
+ sys.__stderr__ = orig_stderr
+
+@contextmanager
+def pycharm():
+ os.environ["PYCHARM_HOSTED"] = "1"
+ non_tty = StreamNonTTY()
+ with replace_by(non_tty), replace_original_by(non_tty):
+ yield
+ del os.environ["PYCHARM_HOSTED"]
diff --git a/venv/Lib/site-packages/colorama/tests/winterm_test.py b/venv/Lib/site-packages/colorama/tests/winterm_test.py
new file mode 100644
index 0000000..d0955f9
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/tests/winterm_test.py
@@ -0,0 +1,131 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+import sys
+from unittest import TestCase, main, skipUnless
+
+try:
+ from unittest.mock import Mock, patch
+except ImportError:
+ from mock import Mock, patch
+
+from ..winterm import WinColor, WinStyle, WinTerm
+
+
+class WinTermTest(TestCase):
+
+ @patch('colorama.winterm.win32')
+ def testInit(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 7 + 6 * 16 + 8
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+ self.assertEqual(term._fore, 7)
+ self.assertEqual(term._back, 6)
+ self.assertEqual(term._style, 8)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testGetAttrs(self):
+ term = WinTerm()
+
+ term._fore = 0
+ term._back = 0
+ term._style = 0
+ self.assertEqual(term.get_attrs(), 0)
+
+ term._fore = WinColor.YELLOW
+ self.assertEqual(term.get_attrs(), WinColor.YELLOW)
+
+ term._back = WinColor.MAGENTA
+ self.assertEqual(
+ term.get_attrs(),
+ WinColor.YELLOW + WinColor.MAGENTA * 16)
+
+ term._style = WinStyle.BRIGHT
+ self.assertEqual(
+ term.get_attrs(),
+ WinColor.YELLOW + WinColor.MAGENTA * 16 + WinStyle.BRIGHT)
+
+ @patch('colorama.winterm.win32')
+ def testResetAll(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 1 + 2 * 16 + 8
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+
+ term.set_console = Mock()
+ term._fore = -1
+ term._back = -1
+ term._style = -1
+
+ term.reset_all()
+
+ self.assertEqual(term._fore, 1)
+ self.assertEqual(term._back, 2)
+ self.assertEqual(term._style, 8)
+ self.assertEqual(term.set_console.called, True)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testFore(self):
+ term = WinTerm()
+ term.set_console = Mock()
+ term._fore = 0
+
+ term.fore(5)
+
+ self.assertEqual(term._fore, 5)
+ self.assertEqual(term.set_console.called, True)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testBack(self):
+ term = WinTerm()
+ term.set_console = Mock()
+ term._back = 0
+
+ term.back(5)
+
+ self.assertEqual(term._back, 5)
+ self.assertEqual(term.set_console.called, True)
+
+ @skipUnless(sys.platform.startswith("win"), "requires Windows")
+ def testStyle(self):
+ term = WinTerm()
+ term.set_console = Mock()
+ term._style = 0
+
+ term.style(22)
+
+ self.assertEqual(term._style, 22)
+ self.assertEqual(term.set_console.called, True)
+
+ @patch('colorama.winterm.win32')
+ def testSetConsole(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 0
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+ term.windll = Mock()
+
+ term.set_console()
+
+ self.assertEqual(
+ mockWin32.SetConsoleTextAttribute.call_args,
+ ((mockWin32.STDOUT, term.get_attrs()), {})
+ )
+
+ @patch('colorama.winterm.win32')
+ def testSetConsoleOnStderr(self, mockWin32):
+ mockAttr = Mock()
+ mockAttr.wAttributes = 0
+ mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr
+ term = WinTerm()
+ term.windll = Mock()
+
+ term.set_console(on_stderr=True)
+
+ self.assertEqual(
+ mockWin32.SetConsoleTextAttribute.call_args,
+ ((mockWin32.STDERR, term.get_attrs()), {})
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/venv/Lib/site-packages/colorama/win32.py b/venv/Lib/site-packages/colorama/win32.py
new file mode 100644
index 0000000..841b0e2
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/win32.py
@@ -0,0 +1,180 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+
+# from winbase.h
+STDOUT = -11
+STDERR = -12
+
+ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+
+try:
+ import ctypes
+ from ctypes import LibraryLoader
+ windll = LibraryLoader(ctypes.WinDLL)
+ from ctypes import wintypes
+except (AttributeError, ImportError):
+ windll = None
+ SetConsoleTextAttribute = lambda *_: None
+ winapi_test = lambda *_: None
+else:
+ from ctypes import byref, Structure, c_char, POINTER
+
+ COORD = wintypes._COORD
+
+ class CONSOLE_SCREEN_BUFFER_INFO(Structure):
+ """struct in wincon.h."""
+ _fields_ = [
+ ("dwSize", COORD),
+ ("dwCursorPosition", COORD),
+ ("wAttributes", wintypes.WORD),
+ ("srWindow", wintypes.SMALL_RECT),
+ ("dwMaximumWindowSize", COORD),
+ ]
+ def __str__(self):
+ return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % (
+ self.dwSize.Y, self.dwSize.X
+ , self.dwCursorPosition.Y, self.dwCursorPosition.X
+ , self.wAttributes
+ , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right
+ , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X
+ )
+
+ _GetStdHandle = windll.kernel32.GetStdHandle
+ _GetStdHandle.argtypes = [
+ wintypes.DWORD,
+ ]
+ _GetStdHandle.restype = wintypes.HANDLE
+
+ _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo
+ _GetConsoleScreenBufferInfo.argtypes = [
+ wintypes.HANDLE,
+ POINTER(CONSOLE_SCREEN_BUFFER_INFO),
+ ]
+ _GetConsoleScreenBufferInfo.restype = wintypes.BOOL
+
+ _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute
+ _SetConsoleTextAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ ]
+ _SetConsoleTextAttribute.restype = wintypes.BOOL
+
+ _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition
+ _SetConsoleCursorPosition.argtypes = [
+ wintypes.HANDLE,
+ COORD,
+ ]
+ _SetConsoleCursorPosition.restype = wintypes.BOOL
+
+ _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA
+ _FillConsoleOutputCharacterA.argtypes = [
+ wintypes.HANDLE,
+ c_char,
+ wintypes.DWORD,
+ COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputCharacterA.restype = wintypes.BOOL
+
+ _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute
+ _FillConsoleOutputAttribute.argtypes = [
+ wintypes.HANDLE,
+ wintypes.WORD,
+ wintypes.DWORD,
+ COORD,
+ POINTER(wintypes.DWORD),
+ ]
+ _FillConsoleOutputAttribute.restype = wintypes.BOOL
+
+ _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW
+ _SetConsoleTitleW.argtypes = [
+ wintypes.LPCWSTR
+ ]
+ _SetConsoleTitleW.restype = wintypes.BOOL
+
+ _GetConsoleMode = windll.kernel32.GetConsoleMode
+ _GetConsoleMode.argtypes = [
+ wintypes.HANDLE,
+ POINTER(wintypes.DWORD)
+ ]
+ _GetConsoleMode.restype = wintypes.BOOL
+
+ _SetConsoleMode = windll.kernel32.SetConsoleMode
+ _SetConsoleMode.argtypes = [
+ wintypes.HANDLE,
+ wintypes.DWORD
+ ]
+ _SetConsoleMode.restype = wintypes.BOOL
+
+ def _winapi_test(handle):
+ csbi = CONSOLE_SCREEN_BUFFER_INFO()
+ success = _GetConsoleScreenBufferInfo(
+ handle, byref(csbi))
+ return bool(success)
+
+ def winapi_test():
+ return any(_winapi_test(h) for h in
+ (_GetStdHandle(STDOUT), _GetStdHandle(STDERR)))
+
+ def GetConsoleScreenBufferInfo(stream_id=STDOUT):
+ handle = _GetStdHandle(stream_id)
+ csbi = CONSOLE_SCREEN_BUFFER_INFO()
+ success = _GetConsoleScreenBufferInfo(
+ handle, byref(csbi))
+ return csbi
+
+ def SetConsoleTextAttribute(stream_id, attrs):
+ handle = _GetStdHandle(stream_id)
+ return _SetConsoleTextAttribute(handle, attrs)
+
+ def SetConsoleCursorPosition(stream_id, position, adjust=True):
+ position = COORD(*position)
+ # If the position is out of range, do nothing.
+ if position.Y <= 0 or position.X <= 0:
+ return
+ # Adjust for Windows' SetConsoleCursorPosition:
+ # 1. being 0-based, while ANSI is 1-based.
+ # 2. expecting (x,y), while ANSI uses (y,x).
+ adjusted_position = COORD(position.Y - 1, position.X - 1)
+ if adjust:
+ # Adjust for viewport's scroll position
+ sr = GetConsoleScreenBufferInfo(STDOUT).srWindow
+ adjusted_position.Y += sr.Top
+ adjusted_position.X += sr.Left
+ # Resume normal processing
+ handle = _GetStdHandle(stream_id)
+ return _SetConsoleCursorPosition(handle, adjusted_position)
+
+ def FillConsoleOutputCharacter(stream_id, char, length, start):
+ handle = _GetStdHandle(stream_id)
+ char = c_char(char.encode())
+ length = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ # Note that this is hard-coded for ANSI (vs wide) bytes.
+ success = _FillConsoleOutputCharacterA(
+ handle, char, length, start, byref(num_written))
+ return num_written.value
+
+ def FillConsoleOutputAttribute(stream_id, attr, length, start):
+ ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )'''
+ handle = _GetStdHandle(stream_id)
+ attribute = wintypes.WORD(attr)
+ length = wintypes.DWORD(length)
+ num_written = wintypes.DWORD(0)
+ # Note that this is hard-coded for ANSI (vs wide) bytes.
+ return _FillConsoleOutputAttribute(
+ handle, attribute, length, start, byref(num_written))
+
+ def SetConsoleTitle(title):
+ return _SetConsoleTitleW(title)
+
+ def GetConsoleMode(handle):
+ mode = wintypes.DWORD()
+ success = _GetConsoleMode(handle, byref(mode))
+ if not success:
+ raise ctypes.WinError()
+ return mode.value
+
+ def SetConsoleMode(handle, mode):
+ success = _SetConsoleMode(handle, mode)
+ if not success:
+ raise ctypes.WinError()
diff --git a/venv/Lib/site-packages/colorama/winterm.py b/venv/Lib/site-packages/colorama/winterm.py
new file mode 100644
index 0000000..aad867e
--- /dev/null
+++ b/venv/Lib/site-packages/colorama/winterm.py
@@ -0,0 +1,195 @@
+# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file.
+try:
+ from msvcrt import get_osfhandle
+except ImportError:
+ def get_osfhandle(_):
+ raise OSError("This isn't windows!")
+
+
+from . import win32
+
+# from wincon.h
+class WinColor(object):
+ BLACK = 0
+ BLUE = 1
+ GREEN = 2
+ CYAN = 3
+ RED = 4
+ MAGENTA = 5
+ YELLOW = 6
+ GREY = 7
+
+# from wincon.h
+class WinStyle(object):
+ NORMAL = 0x00 # dim text, dim background
+ BRIGHT = 0x08 # bright text, dim background
+ BRIGHT_BACKGROUND = 0x80 # dim text, bright background
+
+class WinTerm(object):
+
+ def __init__(self):
+ self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes
+ self.set_attrs(self._default)
+ self._default_fore = self._fore
+ self._default_back = self._back
+ self._default_style = self._style
+ # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style.
+ # So that LIGHT_EX colors and BRIGHT style do not clobber each other,
+ # we track them separately, since LIGHT_EX is overwritten by Fore/Back
+ # and BRIGHT is overwritten by Style codes.
+ self._light = 0
+
+ def get_attrs(self):
+ return self._fore + self._back * 16 + (self._style | self._light)
+
+ def set_attrs(self, value):
+ self._fore = value & 7
+ self._back = (value >> 4) & 7
+ self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND)
+
+ def reset_all(self, on_stderr=None):
+ self.set_attrs(self._default)
+ self.set_console(attrs=self._default)
+ self._light = 0
+
+ def fore(self, fore=None, light=False, on_stderr=False):
+ if fore is None:
+ fore = self._default_fore
+ self._fore = fore
+ # Emulate LIGHT_EX with BRIGHT Style
+ if light:
+ self._light |= WinStyle.BRIGHT
+ else:
+ self._light &= ~WinStyle.BRIGHT
+ self.set_console(on_stderr=on_stderr)
+
+ def back(self, back=None, light=False, on_stderr=False):
+ if back is None:
+ back = self._default_back
+ self._back = back
+ # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style
+ if light:
+ self._light |= WinStyle.BRIGHT_BACKGROUND
+ else:
+ self._light &= ~WinStyle.BRIGHT_BACKGROUND
+ self.set_console(on_stderr=on_stderr)
+
+ def style(self, style=None, on_stderr=False):
+ if style is None:
+ style = self._default_style
+ self._style = style
+ self.set_console(on_stderr=on_stderr)
+
+ def set_console(self, attrs=None, on_stderr=False):
+ if attrs is None:
+ attrs = self.get_attrs()
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ win32.SetConsoleTextAttribute(handle, attrs)
+
+ def get_position(self, handle):
+ position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition
+ # Because Windows coordinates are 0-based,
+ # and win32.SetConsoleCursorPosition expects 1-based.
+ position.X += 1
+ position.Y += 1
+ return position
+
+ def set_cursor_position(self, position=None, on_stderr=False):
+ if position is None:
+ # I'm not currently tracking the position, so there is no default.
+ # position = self.get_position()
+ return
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ win32.SetConsoleCursorPosition(handle, position)
+
+ def cursor_adjust(self, x, y, on_stderr=False):
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ position = self.get_position(handle)
+ adjusted_position = (position.Y + y, position.X + x)
+ win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False)
+
+ def erase_screen(self, mode=0, on_stderr=False):
+ # 0 should clear from the cursor to the end of the screen.
+ # 1 should clear from the cursor to the beginning of the screen.
+ # 2 should clear the entire screen, and move cursor to (1,1)
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ # get the number of character cells in the current buffer
+ cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y
+ # get number of character cells before current cursor position
+ cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X
+ if mode == 0:
+ from_coord = csbi.dwCursorPosition
+ cells_to_erase = cells_in_screen - cells_before_cursor
+ elif mode == 1:
+ from_coord = win32.COORD(0, 0)
+ cells_to_erase = cells_before_cursor
+ elif mode == 2:
+ from_coord = win32.COORD(0, 0)
+ cells_to_erase = cells_in_screen
+ else:
+ # invalid mode
+ return
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
+ if mode == 2:
+ # put the cursor where needed
+ win32.SetConsoleCursorPosition(handle, (1, 1))
+
+ def erase_line(self, mode=0, on_stderr=False):
+ # 0 should clear from the cursor to the end of the line.
+ # 1 should clear from the cursor to the beginning of the line.
+ # 2 should clear the entire line.
+ handle = win32.STDOUT
+ if on_stderr:
+ handle = win32.STDERR
+ csbi = win32.GetConsoleScreenBufferInfo(handle)
+ if mode == 0:
+ from_coord = csbi.dwCursorPosition
+ cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X
+ elif mode == 1:
+ from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
+ cells_to_erase = csbi.dwCursorPosition.X
+ elif mode == 2:
+ from_coord = win32.COORD(0, csbi.dwCursorPosition.Y)
+ cells_to_erase = csbi.dwSize.X
+ else:
+ # invalid mode
+ return
+ # fill the entire screen with blanks
+ win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord)
+ # now set the buffer's attributes accordingly
+ win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord)
+
+ def set_title(self, title):
+ win32.SetConsoleTitle(title)
+
+
+def enable_vt_processing(fd):
+ if win32.windll is None or not win32.winapi_test():
+ return False
+
+ try:
+ handle = get_osfhandle(fd)
+ mode = win32.GetConsoleMode(handle)
+ win32.SetConsoleMode(
+ handle,
+ mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING,
+ )
+
+ mode = win32.GetConsoleMode(handle)
+ if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING:
+ return True
+ # Can get TypeError in testsuite where 'fd' is a Mock()
+ except (OSError, TypeError):
+ return False
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/INSTALLER b/venv/Lib/site-packages/flask-3.0.2.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/flask-3.0.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/LICENSE.rst b/venv/Lib/site-packages/flask-3.0.2.dist-info/LICENSE.rst
new file mode 100644
index 0000000..9d227a0
--- /dev/null
+++ b/venv/Lib/site-packages/flask-3.0.2.dist-info/LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2010 Pallets
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/METADATA b/venv/Lib/site-packages/flask-3.0.2.dist-info/METADATA
new file mode 100644
index 0000000..71551b9
--- /dev/null
+++ b/venv/Lib/site-packages/flask-3.0.2.dist-info/METADATA
@@ -0,0 +1,116 @@
+Metadata-Version: 2.1
+Name: Flask
+Version: 3.0.2
+Summary: A simple framework for building complex web applications.
+Maintainer-email: Pallets
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+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: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
+Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
+Requires-Dist: Werkzeug>=3.0.0
+Requires-Dist: Jinja2>=3.1.2
+Requires-Dist: itsdangerous>=2.1.2
+Requires-Dist: click>=8.1.3
+Requires-Dist: blinker>=1.6.2
+Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10'
+Requires-Dist: asgiref>=3.2 ; extra == "async"
+Requires-Dist: python-dotenv ; extra == "dotenv"
+Project-URL: Changes, https://flask.palletsprojects.com/changes/
+Project-URL: Chat, https://discord.gg/pallets
+Project-URL: Documentation, https://flask.palletsprojects.com/
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
+Project-URL: Source Code, https://github.com/pallets/flask/
+Provides-Extra: async
+Provides-Extra: dotenv
+
+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.
+
+.. _WSGI: https://wsgi.readthedocs.io/
+.. _Werkzeug: https://werkzeug.palletsprojects.com/
+.. _Jinja: https://jinja.palletsprojects.com/
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+ $ pip install -U Flask
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+A Simple Example
+----------------
+
+.. code-block:: python
+
+ # save this as app.py
+ from flask import Flask
+
+ app = Flask(__name__)
+
+ @app.route("/")
+ def hello():
+ return "Hello, World!"
+
+.. code-block:: text
+
+ $ flask run
+ * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
+
+
+Contributing
+------------
+
+For guidance on setting up a development environment and how to make a
+contribution to Flask, see the `contributing guidelines`_.
+
+.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
+
+
+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://palletsprojects.com/donate
+
+
+Links
+-----
+
+- Documentation: https://flask.palletsprojects.com/
+- Changes: https://flask.palletsprojects.com/changes/
+- PyPI Releases: https://pypi.org/project/Flask/
+- Source Code: https://github.com/pallets/flask/
+- Issue Tracker: https://github.com/pallets/flask/issues/
+- Chat: https://discord.gg/pallets
+
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/RECORD b/venv/Lib/site-packages/flask-3.0.2.dist-info/RECORD
new file mode 100644
index 0000000..b5952a5
--- /dev/null
+++ b/venv/Lib/site-packages/flask-3.0.2.dist-info/RECORD
@@ -0,0 +1,58 @@
+../../Scripts/flask.exe,sha256=akFdx6Qkk12Cma5ZWQ4vbeVXTZbTu4MFEwAY3Hy8OH8,108413
+flask-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+flask-3.0.2.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
+flask-3.0.2.dist-info/METADATA,sha256=5SsBudAoun3E_3ZSRXJLB2V3NAdALovsQMKUvzqcJfM,3588
+flask-3.0.2.dist-info/RECORD,,
+flask-3.0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask-3.0.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+flask-3.0.2.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40
+flask/__init__.py,sha256=6xMqdVA0FIQ2U1KVaGX3lzNCdXPzoHPaa0hvQCNcfSk,2625
+flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
+flask/__pycache__/__init__.cpython-312.pyc,,
+flask/__pycache__/__main__.cpython-312.pyc,,
+flask/__pycache__/app.cpython-312.pyc,,
+flask/__pycache__/blueprints.cpython-312.pyc,,
+flask/__pycache__/cli.cpython-312.pyc,,
+flask/__pycache__/config.cpython-312.pyc,,
+flask/__pycache__/ctx.cpython-312.pyc,,
+flask/__pycache__/debughelpers.cpython-312.pyc,,
+flask/__pycache__/globals.cpython-312.pyc,,
+flask/__pycache__/helpers.cpython-312.pyc,,
+flask/__pycache__/logging.cpython-312.pyc,,
+flask/__pycache__/sessions.cpython-312.pyc,,
+flask/__pycache__/signals.cpython-312.pyc,,
+flask/__pycache__/templating.cpython-312.pyc,,
+flask/__pycache__/testing.cpython-312.pyc,,
+flask/__pycache__/typing.cpython-312.pyc,,
+flask/__pycache__/views.cpython-312.pyc,,
+flask/__pycache__/wrappers.cpython-312.pyc,,
+flask/app.py,sha256=TQfhvSlv1QpaPHeeRz_Ke7JmiSFMMHT-rJR4tqsEHdc,59706
+flask/blueprints.py,sha256=H7u4HzNn--riGMTt5GkxUHpYRzCav-WDGSKNnBSEMcU,3160
+flask/cli.py,sha256=eegT_64cSOqaKOwI_Am3XwaCSJPZ9UEJ6EmSL0qg8xg,35833
+flask/config.py,sha256=QiL9KkQT8RWc0HU2AE26Yw5mdOkNsKv8TEFEbXkqhJk,13328
+flask/ctx.py,sha256=4atDhJJ_cpV1VMq4qsfU4E_61M1oN93jlS2H9gjrl58,15120
+flask/debughelpers.py,sha256=PGIDhStW_efRjpaa3zHIpo-htStJOR41Ip3OJWPYBwo,6080
+flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713
+flask/helpers.py,sha256=tYrcQ_73GuSZVEgwFr-eMmV69UriFQDBmt8wZJIAqvg,23084
+flask/json/__init__.py,sha256=hLNR898paqoefdeAhraa5wyJy-bmRB2k2dV4EgVy2Z8,5602
+flask/json/__pycache__/__init__.cpython-312.pyc,,
+flask/json/__pycache__/provider.cpython-312.pyc,,
+flask/json/__pycache__/tag.cpython-312.pyc,,
+flask/json/provider.py,sha256=q6iB83lSiopy80DZPrU-9mGcWwrD0mvLjiv9fHrRZgc,7646
+flask/json/tag.py,sha256=aXslvQyO4QpxviWJqxhyOj0CCQKlYXq1r0H9DKqiEY8,9280
+flask/logging.py,sha256=8sM3WMTubi1cBb2c_lPkWpN0J8dMAqrgKRYLLi1dCVI,2377
+flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask/sansio/README.md,sha256=-0X1tECnilmz1cogx-YhNw5d7guK7GKrq_DEV2OzlU0,228
+flask/sansio/__pycache__/app.cpython-312.pyc,,
+flask/sansio/__pycache__/blueprints.cpython-312.pyc,,
+flask/sansio/__pycache__/scaffold.cpython-312.pyc,,
+flask/sansio/app.py,sha256=ZF0Yy610NKSpdJ1d6qtG4L2RkCmzngu0G9FFXZf4O_M,38209
+flask/sansio/blueprints.py,sha256=Tqe-7EkZ-tbWchm8iDoCfD848f0_3nLv6NNjeIPvHwM,24637
+flask/sansio/scaffold.py,sha256=9SSSC6A_zzXhcEVYf9wkrKx2r4uDqfIWsnRNYSvDclU,30879
+flask/sessions.py,sha256=bIpZRwiTfnYJn3ikVnCPcF2kNtyRz0dfpsuMADIpSJc,14518
+flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750
+flask/templating.py,sha256=2TcXLT85Asflm2W9WOSFxKCmYn5e49w_Jkg9-NaaJWo,7537
+flask/testing.py,sha256=3BFXb3bP7R5r-XLBuobhczbxDu8-1LWRzYuhbr-lwaE,10163
+flask/typing.py,sha256=ZavK-wV28Yv8CQB7u73qZp_jLalpbWdrXS37QR1ftN0,3190
+flask/views.py,sha256=B66bTvYBBcHMYk4dA1ScZD0oTRTBl0I5smp1lRm9riI,6939
+flask/wrappers.py,sha256=m1j5tIJxIu8_sPPgTAB_G4TTh52Q-HoDuw_qHV5J59g,5831
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/REQUESTED b/venv/Lib/site-packages/flask-3.0.2.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/WHEEL b/venv/Lib/site-packages/flask-3.0.2.dist-info/WHEEL
new file mode 100644
index 0000000..3b5e64b
--- /dev/null
+++ b/venv/Lib/site-packages/flask-3.0.2.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/venv/Lib/site-packages/flask-3.0.2.dist-info/entry_points.txt b/venv/Lib/site-packages/flask-3.0.2.dist-info/entry_points.txt
new file mode 100644
index 0000000..eec6733
--- /dev/null
+++ b/venv/Lib/site-packages/flask-3.0.2.dist-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+flask=flask.cli:main
+
diff --git a/venv/Lib/site-packages/flask/__init__.py b/venv/Lib/site-packages/flask/__init__.py
new file mode 100644
index 0000000..e86eb43
--- /dev/null
+++ b/venv/Lib/site-packages/flask/__init__.py
@@ -0,0 +1,60 @@
+from __future__ import annotations
+
+import typing as t
+
+from . import json as json
+from .app import Flask as Flask
+from .blueprints import Blueprint as Blueprint
+from .config import Config as Config
+from .ctx import after_this_request as after_this_request
+from .ctx import copy_current_request_context as copy_current_request_context
+from .ctx import has_app_context as has_app_context
+from .ctx import has_request_context as has_request_context
+from .globals import current_app as current_app
+from .globals import g as g
+from .globals import request as request
+from .globals import session as session
+from .helpers import abort as abort
+from .helpers import flash as flash
+from .helpers import get_flashed_messages as get_flashed_messages
+from .helpers import get_template_attribute as get_template_attribute
+from .helpers import make_response as make_response
+from .helpers import redirect as redirect
+from .helpers import send_file as send_file
+from .helpers import send_from_directory as send_from_directory
+from .helpers import stream_with_context as stream_with_context
+from .helpers import url_for as url_for
+from .json import jsonify as jsonify
+from .signals import appcontext_popped as appcontext_popped
+from .signals import appcontext_pushed as appcontext_pushed
+from .signals import appcontext_tearing_down as appcontext_tearing_down
+from .signals import before_render_template as before_render_template
+from .signals import got_request_exception as got_request_exception
+from .signals import message_flashed as message_flashed
+from .signals import request_finished as request_finished
+from .signals import request_started as request_started
+from .signals import request_tearing_down as request_tearing_down
+from .signals import template_rendered as template_rendered
+from .templating import render_template as render_template
+from .templating import render_template_string as render_template_string
+from .templating import stream_template as stream_template
+from .templating import stream_template_string as stream_template_string
+from .wrappers import Request as Request
+from .wrappers import Response as Response
+
+
+def __getattr__(name: str) -> t.Any:
+ if name == "__version__":
+ import importlib.metadata
+ import warnings
+
+ warnings.warn(
+ "The '__version__' attribute is deprecated and will be removed in"
+ " Flask 3.1. Use feature detection or"
+ " 'importlib.metadata.version(\"flask\")' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return importlib.metadata.version("flask")
+
+ raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask/__main__.py b/venv/Lib/site-packages/flask/__main__.py
new file mode 100644
index 0000000..4e28416
--- /dev/null
+++ b/venv/Lib/site-packages/flask/__main__.py
@@ -0,0 +1,3 @@
+from .cli import main
+
+main()
diff --git a/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..a3745ba
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-312.pyc
new file mode 100644
index 0000000..6745bab
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/__main__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/app.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/app.cpython-312.pyc
new file mode 100644
index 0000000..6aeb92e
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/app.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc
new file mode 100644
index 0000000..2faa4ac
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/blueprints.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc
new file mode 100644
index 0000000..4783fb7
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/cli.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc
new file mode 100644
index 0000000..6307f1a
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/config.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc
new file mode 100644
index 0000000..1298cd6
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/ctx.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc
new file mode 100644
index 0000000..bea4693
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/debughelpers.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc
new file mode 100644
index 0000000..3e08801
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/globals.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc
new file mode 100644
index 0000000..eb77fde
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/helpers.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc
new file mode 100644
index 0000000..2719101
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/logging.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-312.pyc
new file mode 100644
index 0000000..b22faf6
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/sessions.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc
new file mode 100644
index 0000000..767f71a
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/signals.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc
new file mode 100644
index 0000000..b62d617
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/templating.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc
new file mode 100644
index 0000000..88bfb9e
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/testing.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc
new file mode 100644
index 0000000..4183482
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/typing.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc
new file mode 100644
index 0000000..b0c6802
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/views.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc b/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc
new file mode 100644
index 0000000..0cb443a
Binary files /dev/null and b/venv/Lib/site-packages/flask/__pycache__/wrappers.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/app.py b/venv/Lib/site-packages/flask/app.py
new file mode 100644
index 0000000..12ac50d
--- /dev/null
+++ b/venv/Lib/site-packages/flask/app.py
@@ -0,0 +1,1488 @@
+from __future__ import annotations
+
+import collections.abc as cabc
+import os
+import sys
+import typing as t
+import weakref
+from datetime import timedelta
+from inspect import iscoroutinefunction
+from itertools import chain
+from types import TracebackType
+from urllib.parse import quote as _url_quote
+
+import click
+from werkzeug.datastructures import Headers
+from werkzeug.datastructures import ImmutableDict
+from werkzeug.exceptions import BadRequestKeyError
+from werkzeug.exceptions import HTTPException
+from werkzeug.exceptions import InternalServerError
+from werkzeug.routing import BuildError
+from werkzeug.routing import MapAdapter
+from werkzeug.routing import RequestRedirect
+from werkzeug.routing import RoutingException
+from werkzeug.routing import Rule
+from werkzeug.serving import is_running_from_reloader
+from werkzeug.wrappers import Response as BaseResponse
+
+from . import cli
+from . import typing as ft
+from .ctx import AppContext
+from .ctx import RequestContext
+from .globals import _cv_app
+from .globals import _cv_request
+from .globals import current_app
+from .globals import g
+from .globals import request
+from .globals import request_ctx
+from .globals import session
+from .helpers import get_debug_flag
+from .helpers import get_flashed_messages
+from .helpers import get_load_dotenv
+from .helpers import send_from_directory
+from .sansio.app import App
+from .sansio.scaffold import _sentinel
+from .sessions import SecureCookieSessionInterface
+from .sessions import SessionInterface
+from .signals import appcontext_tearing_down
+from .signals import got_request_exception
+from .signals import request_finished
+from .signals import request_started
+from .signals import request_tearing_down
+from .templating import Environment
+from .wrappers import Request
+from .wrappers import Response
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import StartResponse
+ from _typeshed.wsgi import WSGIEnvironment
+
+ from .testing import FlaskClient
+ from .testing import FlaskCliRunner
+
+T_shell_context_processor = t.TypeVar(
+ "T_shell_context_processor", bound=ft.ShellContextProcessorCallable
+)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+
+
+def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
+ if value is None or isinstance(value, timedelta):
+ return value
+
+ return timedelta(seconds=value)
+
+
+class Flask(App):
+ """The flask object implements a WSGI application and acts as the central
+ object. It is passed the name of the module or package of the
+ application. Once it is created it will act as a central registry for
+ the view functions, the URL rules, template configuration and much more.
+
+ The name of the package is used to resolve resources from inside the
+ package or the folder the module is contained in depending on if the
+ package parameter resolves to an actual python package (a folder with
+ an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
+
+ For more information about resource loading, see :func:`open_resource`.
+
+ Usually you create a :class:`Flask` instance in your main module or
+ in the :file:`__init__.py` file of your package like this::
+
+ from flask import Flask
+ app = Flask(__name__)
+
+ .. admonition:: About the First Parameter
+
+ The idea of the first parameter is to give Flask an idea of what
+ belongs to your application. This name is used to find resources
+ on the filesystem, can be used by extensions to improve debugging
+ information and a lot more.
+
+ So it's important what you provide there. If you are using a single
+ module, `__name__` is always the correct value. If you however are
+ using a package, it's usually recommended to hardcode the name of
+ your package there.
+
+ For example if your application is defined in :file:`yourapplication/app.py`
+ you should create it with one of the two versions below::
+
+ app = Flask('yourapplication')
+ app = Flask(__name__.split('.')[0])
+
+ Why is that? The application will work even with `__name__`, thanks
+ to how resources are looked up. However it will make debugging more
+ painful. Certain extensions can make assumptions based on the
+ import name of your application. For example the Flask-SQLAlchemy
+ extension will look for the code in your application that triggered
+ an SQL query in debug mode. If the import name is not properly set
+ up, that debugging information is lost. (For example it would only
+ pick up SQL queries in `yourapplication.app` and not
+ `yourapplication.views.frontend`)
+
+ .. versionadded:: 0.7
+ The `static_url_path`, `static_folder`, and `template_folder`
+ parameters were added.
+
+ .. versionadded:: 0.8
+ The `instance_path` and `instance_relative_config` parameters were
+ added.
+
+ .. versionadded:: 0.11
+ The `root_path` parameter was added.
+
+ .. versionadded:: 1.0
+ The ``host_matching`` and ``static_host`` parameters were added.
+
+ .. versionadded:: 1.0
+ The ``subdomain_matching`` parameter was added. Subdomain
+ matching needs to be enabled manually now. Setting
+ :data:`SERVER_NAME` does not implicitly enable it.
+
+ :param import_name: the name of the application package
+ :param static_url_path: can be used to specify a different path for the
+ static files on the web. Defaults to the name
+ of the `static_folder` folder.
+ :param static_folder: The folder with static files that is served at
+ ``static_url_path``. Relative to the application ``root_path``
+ or an absolute path. Defaults to ``'static'``.
+ :param static_host: the host to use when adding the static route.
+ Defaults to None. Required when using ``host_matching=True``
+ with a ``static_folder`` configured.
+ :param host_matching: set ``url_map.host_matching`` attribute.
+ Defaults to False.
+ :param subdomain_matching: consider the subdomain relative to
+ :data:`SERVER_NAME` when matching routes. Defaults to False.
+ :param template_folder: the folder that contains the templates that should
+ be used by the application. Defaults to
+ ``'templates'`` folder in the root path of the
+ application.
+ :param instance_path: An alternative instance path for the application.
+ By default the folder ``'instance'`` next to the
+ package or module is assumed to be the instance
+ path.
+ :param instance_relative_config: if set to ``True`` relative filenames
+ for loading the config are assumed to
+ be relative to the instance path instead
+ of the application root.
+ :param root_path: The path to the root of the application files.
+ This should only be set manually when it can't be detected
+ automatically, such as for namespace packages.
+ """
+
+ default_config = ImmutableDict(
+ {
+ "DEBUG": None,
+ "TESTING": False,
+ "PROPAGATE_EXCEPTIONS": None,
+ "SECRET_KEY": None,
+ "PERMANENT_SESSION_LIFETIME": timedelta(days=31),
+ "USE_X_SENDFILE": False,
+ "SERVER_NAME": None,
+ "APPLICATION_ROOT": "/",
+ "SESSION_COOKIE_NAME": "session",
+ "SESSION_COOKIE_DOMAIN": None,
+ "SESSION_COOKIE_PATH": None,
+ "SESSION_COOKIE_HTTPONLY": True,
+ "SESSION_COOKIE_SECURE": False,
+ "SESSION_COOKIE_SAMESITE": None,
+ "SESSION_REFRESH_EACH_REQUEST": True,
+ "MAX_CONTENT_LENGTH": None,
+ "SEND_FILE_MAX_AGE_DEFAULT": None,
+ "TRAP_BAD_REQUEST_ERRORS": None,
+ "TRAP_HTTP_EXCEPTIONS": False,
+ "EXPLAIN_TEMPLATE_LOADING": False,
+ "PREFERRED_URL_SCHEME": "http",
+ "TEMPLATES_AUTO_RELOAD": None,
+ "MAX_COOKIE_SIZE": 4093,
+ }
+ )
+
+ #: The class that is used for request objects. See :class:`~flask.Request`
+ #: for more information.
+ request_class: type[Request] = Request
+
+ #: The class that is used for response objects. See
+ #: :class:`~flask.Response` for more information.
+ response_class: type[Response] = Response
+
+ #: the session interface to use. By default an instance of
+ #: :class:`~flask.sessions.SecureCookieSessionInterface` is used here.
+ #:
+ #: .. versionadded:: 0.8
+ session_interface: SessionInterface = SecureCookieSessionInterface()
+
+ def __init__(
+ self,
+ import_name: str,
+ static_url_path: str | None = None,
+ static_folder: str | os.PathLike[str] | None = "static",
+ static_host: str | None = None,
+ host_matching: bool = False,
+ subdomain_matching: bool = False,
+ template_folder: str | os.PathLike[str] | None = "templates",
+ instance_path: str | None = None,
+ instance_relative_config: bool = False,
+ root_path: str | None = None,
+ ):
+ super().__init__(
+ import_name=import_name,
+ static_url_path=static_url_path,
+ static_folder=static_folder,
+ static_host=static_host,
+ host_matching=host_matching,
+ subdomain_matching=subdomain_matching,
+ template_folder=template_folder,
+ instance_path=instance_path,
+ instance_relative_config=instance_relative_config,
+ root_path=root_path,
+ )
+
+ # Add a static route using the provided static_url_path, static_host,
+ # and static_folder if there is a configured static_folder.
+ # Note we do this without checking if static_folder exists.
+ # For one, it might be created while the server is running (e.g. during
+ # development). Also, Google App Engine stores static files somewhere
+ if self.has_static_folder:
+ assert (
+ bool(static_host) == host_matching
+ ), "Invalid static_host/host_matching combination"
+ # Use a weakref to avoid creating a reference cycle between the app
+ # and the view function (see #3761).
+ self_ref = weakref.ref(self)
+ self.add_url_rule(
+ f"{self.static_url_path}/",
+ endpoint="static",
+ host=static_host,
+ view_func=lambda **kw: self_ref().send_static_file(**kw), # type: ignore # noqa: B950
+ )
+
+ def get_send_file_max_age(self, filename: str | None) -> int | None:
+ """Used by :func:`send_file` to determine the ``max_age`` cache
+ value for a given file path if it wasn't passed.
+
+ By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
+ the configuration of :data:`~flask.current_app`. This defaults
+ to ``None``, which tells the browser to use conditional requests
+ instead of a timed cache, which is usually preferable.
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ .. versionchanged:: 2.0
+ The default configuration is ``None`` instead of 12 hours.
+
+ .. versionadded:: 0.9
+ """
+ value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
+
+ if value is None:
+ return None
+
+ if isinstance(value, timedelta):
+ return int(value.total_seconds())
+
+ return value # type: ignore[no-any-return]
+
+ def send_static_file(self, filename: str) -> Response:
+ """The view function used to serve files from
+ :attr:`static_folder`. A route is automatically registered for
+ this view at :attr:`static_url_path` if :attr:`static_folder` is
+ set.
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ .. versionadded:: 0.5
+
+ """
+ if not self.has_static_folder:
+ raise RuntimeError("'static_folder' must be set to serve static_files.")
+
+ # send_file only knows to call get_send_file_max_age on the app,
+ # call it here so it works for blueprints too.
+ max_age = self.get_send_file_max_age(filename)
+ return send_from_directory(
+ t.cast(str, self.static_folder), filename, max_age=max_age
+ )
+
+ def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Open a resource file relative to :attr:`root_path` for
+ reading.
+
+ For example, if the file ``schema.sql`` is next to the file
+ ``app.py`` where the ``Flask`` app is defined, it can be opened
+ with:
+
+ .. code-block:: python
+
+ with app.open_resource("schema.sql") as f:
+ conn.executescript(f.read())
+
+ :param resource: Path to the resource relative to
+ :attr:`root_path`.
+ :param mode: Open the file in this mode. Only reading is
+ supported, valid values are "r" (or "rt") and "rb".
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ """
+ if mode not in {"r", "rt", "rb"}:
+ raise ValueError("Resources can only be opened for reading.")
+
+ return open(os.path.join(self.root_path, resource), mode)
+
+ def open_instance_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Opens a resource from the application's instance folder
+ (:attr:`instance_path`). Otherwise works like
+ :meth:`open_resource`. Instance resources can also be opened for
+ writing.
+
+ :param resource: the name of the resource. To access resources within
+ subfolders use forward slashes as separator.
+ :param mode: resource file opening mode, default is 'rb'.
+ """
+ return open(os.path.join(self.instance_path, resource), mode)
+
+ def create_jinja_environment(self) -> Environment:
+ """Create the Jinja environment based on :attr:`jinja_options`
+ and the various Jinja-related methods of the app. Changing
+ :attr:`jinja_options` after this will have no effect. Also adds
+ Flask-related globals and filters to the environment.
+
+ .. versionchanged:: 0.11
+ ``Environment.auto_reload`` set in accordance with
+ ``TEMPLATES_AUTO_RELOAD`` configuration option.
+
+ .. versionadded:: 0.5
+ """
+ options = dict(self.jinja_options)
+
+ if "autoescape" not in options:
+ options["autoescape"] = self.select_jinja_autoescape
+
+ if "auto_reload" not in options:
+ auto_reload = self.config["TEMPLATES_AUTO_RELOAD"]
+
+ if auto_reload is None:
+ auto_reload = self.debug
+
+ options["auto_reload"] = auto_reload
+
+ rv = self.jinja_environment(self, **options)
+ rv.globals.update(
+ url_for=self.url_for,
+ get_flashed_messages=get_flashed_messages,
+ config=self.config,
+ # request, session and g are normally added with the
+ # context processor for efficiency reasons but for imported
+ # templates we also want the proxies in there.
+ request=request,
+ session=session,
+ g=g,
+ )
+ rv.policies["json.dumps_function"] = self.json.dumps
+ return rv
+
+ def create_url_adapter(self, request: Request | None) -> MapAdapter | None:
+ """Creates a URL adapter for the given request. The URL adapter
+ is created at a point where the request context is not yet set
+ up so the request is passed explicitly.
+
+ .. versionadded:: 0.6
+
+ .. versionchanged:: 0.9
+ This can now also be called without a request object when the
+ URL adapter is created for the application context.
+
+ .. versionchanged:: 1.0
+ :data:`SERVER_NAME` no longer implicitly enables subdomain
+ matching. Use :attr:`subdomain_matching` instead.
+ """
+ if request is not None:
+ # If subdomain matching is disabled (the default), use the
+ # default subdomain in all cases. This should be the default
+ # in Werkzeug but it currently does not have that feature.
+ if not self.subdomain_matching:
+ subdomain = self.url_map.default_subdomain or None
+ else:
+ subdomain = None
+
+ return self.url_map.bind_to_environ(
+ request.environ,
+ server_name=self.config["SERVER_NAME"],
+ subdomain=subdomain,
+ )
+ # We need at the very least the server name to be set for this
+ # to work.
+ if self.config["SERVER_NAME"] is not None:
+ return self.url_map.bind(
+ self.config["SERVER_NAME"],
+ script_name=self.config["APPLICATION_ROOT"],
+ url_scheme=self.config["PREFERRED_URL_SCHEME"],
+ )
+
+ return None
+
+ def raise_routing_exception(self, request: Request) -> t.NoReturn:
+ """Intercept routing exceptions and possibly do something else.
+
+ In debug mode, intercept a routing redirect and replace it with
+ an error if the body will be discarded.
+
+ With modern Werkzeug this shouldn't occur, since it now uses a
+ 308 status which tells the browser to resend the method and
+ body.
+
+ .. versionchanged:: 2.1
+ Don't intercept 307 and 308 redirects.
+
+ :meta private:
+ :internal:
+ """
+ if (
+ not self.debug
+ or not isinstance(request.routing_exception, RequestRedirect)
+ or request.routing_exception.code in {307, 308}
+ or request.method in {"GET", "HEAD", "OPTIONS"}
+ ):
+ raise request.routing_exception # type: ignore[misc]
+
+ from .debughelpers import FormDataRoutingRedirect
+
+ raise FormDataRoutingRedirect(request)
+
+ def update_template_context(self, context: dict[str, t.Any]) -> None:
+ """Update the template context with some commonly used variables.
+ This injects request, session, config and g into the template
+ context as well as everything template context processors want
+ to inject. Note that the as of Flask 0.6, the original values
+ in the context will not be overridden if a context processor
+ decides to return a value with the same key.
+
+ :param context: the context as a dictionary that is updated in place
+ to add extra variables.
+ """
+ names: t.Iterable[str | None] = (None,)
+
+ # A template may be rendered outside a request context.
+ if request:
+ names = chain(names, reversed(request.blueprints))
+
+ # The values passed to render_template take precedence. Keep a
+ # copy to re-apply after all context functions.
+ orig_ctx = context.copy()
+
+ for name in names:
+ if name in self.template_context_processors:
+ for func in self.template_context_processors[name]:
+ context.update(self.ensure_sync(func)())
+
+ context.update(orig_ctx)
+
+ def make_shell_context(self) -> dict[str, t.Any]:
+ """Returns the shell context for an interactive shell for this
+ application. This runs all the registered shell context
+ processors.
+
+ .. versionadded:: 0.11
+ """
+ rv = {"app": self, "g": g}
+ for processor in self.shell_context_processors:
+ rv.update(processor())
+ return rv
+
+ def run(
+ self,
+ host: str | None = None,
+ port: int | None = None,
+ debug: bool | None = None,
+ load_dotenv: bool = True,
+ **options: t.Any,
+ ) -> None:
+ """Runs the application on a local development server.
+
+ Do not use ``run()`` in a production setting. It is not intended to
+ meet security and performance requirements for a production server.
+ Instead, see :doc:`/deploying/index` for WSGI server recommendations.
+
+ If the :attr:`debug` flag is set the server will automatically reload
+ for code changes and show a debugger in case an exception happened.
+
+ If you want to run the application in debug mode, but disable the
+ code execution on the interactive debugger, you can pass
+ ``use_evalex=False`` as parameter. This will keep the debugger's
+ traceback screen active, but disable code execution.
+
+ It is not recommended to use this function for development with
+ automatic reloading as this is badly supported. Instead you should
+ be using the :command:`flask` command line script's ``run`` support.
+
+ .. admonition:: Keep in Mind
+
+ Flask will suppress any server error with a generic error page
+ unless it is in debug mode. As such to enable just the
+ interactive debugger without the code reloading, you have to
+ invoke :meth:`run` with ``debug=True`` and ``use_reloader=False``.
+ Setting ``use_debugger`` to ``True`` without being in debug mode
+ won't catch any exceptions because there won't be any to
+ catch.
+
+ :param host: the hostname to listen on. Set this to ``'0.0.0.0'`` to
+ have the server available externally as well. Defaults to
+ ``'127.0.0.1'`` or the host in the ``SERVER_NAME`` config variable
+ if present.
+ :param port: the port of the webserver. Defaults to ``5000`` or the
+ port defined in the ``SERVER_NAME`` config variable if present.
+ :param debug: if given, enable or disable debug mode. See
+ :attr:`debug`.
+ :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.
+ :param options: the options to be forwarded to the underlying Werkzeug
+ server. See :func:`werkzeug.serving.run_simple` for more
+ information.
+
+ .. versionchanged:: 1.0
+ If installed, python-dotenv will be used to load environment
+ variables from :file:`.env` and :file:`.flaskenv` files.
+
+ The :envvar:`FLASK_DEBUG` environment variable will override :attr:`debug`.
+
+ Threaded mode is enabled by default.
+
+ .. versionchanged:: 0.10
+ The default port is now picked from the ``SERVER_NAME``
+ variable.
+ """
+ # Ignore this call so that it doesn't start another server if
+ # the 'flask run' command is used.
+ if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
+ if not is_running_from_reloader():
+ click.secho(
+ " * Ignoring a call to 'app.run()' that would block"
+ " the current 'flask' CLI command.\n"
+ " Only call 'app.run()' in an 'if __name__ =="
+ ' "__main__"\' guard.',
+ fg="red",
+ )
+
+ return
+
+ if get_load_dotenv(load_dotenv):
+ cli.load_dotenv()
+
+ # if set, env var overrides existing value
+ if "FLASK_DEBUG" in os.environ:
+ self.debug = get_debug_flag()
+
+ # debug passed to method overrides all other sources
+ if debug is not None:
+ self.debug = bool(debug)
+
+ server_name = self.config.get("SERVER_NAME")
+ sn_host = sn_port = None
+
+ if server_name:
+ sn_host, _, sn_port = server_name.partition(":")
+
+ if not host:
+ if sn_host:
+ host = sn_host
+ else:
+ host = "127.0.0.1"
+
+ if port or port == 0:
+ port = int(port)
+ elif sn_port:
+ port = int(sn_port)
+ else:
+ port = 5000
+
+ options.setdefault("use_reloader", self.debug)
+ options.setdefault("use_debugger", self.debug)
+ options.setdefault("threaded", True)
+
+ cli.show_server_banner(self.debug, self.name)
+
+ from werkzeug.serving import run_simple
+
+ try:
+ run_simple(t.cast(str, host), port, self, **options)
+ finally:
+ # reset the first request information if the development server
+ # reset normally. This makes it possible to restart the server
+ # without reloader and that stuff from an interactive shell.
+ self._got_first_request = False
+
+ def test_client(self, use_cookies: bool = True, **kwargs: t.Any) -> FlaskClient:
+ """Creates a test client for this application. For information
+ about unit testing head over to :doc:`/testing`.
+
+ Note that if you are testing for assertions or exceptions in your
+ application code, you must set ``app.testing = True`` in order for the
+ exceptions to propagate to the test client. Otherwise, the exception
+ will be handled by the application (not visible to the test client) and
+ the only indication of an AssertionError or other exception will be a
+ 500 status code response to the test client. See the :attr:`testing`
+ attribute. For example::
+
+ app.testing = True
+ client = app.test_client()
+
+ The test client can be used in a ``with`` block to defer the closing down
+ of the context until the end of the ``with`` block. This is useful if
+ you want to access the context locals for testing::
+
+ with app.test_client() as c:
+ rv = c.get('/?vodka=42')
+ assert request.args['vodka'] == '42'
+
+ Additionally, you may pass optional keyword arguments that will then
+ be passed to the application's :attr:`test_client_class` constructor.
+ For example::
+
+ from flask.testing import FlaskClient
+
+ class CustomClient(FlaskClient):
+ def __init__(self, *args, **kwargs):
+ self._authentication = kwargs.pop("authentication")
+ super(CustomClient,self).__init__( *args, **kwargs)
+
+ app.test_client_class = CustomClient
+ client = app.test_client(authentication='Basic ....')
+
+ See :class:`~flask.testing.FlaskClient` for more information.
+
+ .. versionchanged:: 0.4
+ added support for ``with`` block usage for the client.
+
+ .. versionadded:: 0.7
+ The `use_cookies` parameter was added as well as the ability
+ to override the client to be used by setting the
+ :attr:`test_client_class` attribute.
+
+ .. versionchanged:: 0.11
+ Added `**kwargs` to support passing additional keyword arguments to
+ the constructor of :attr:`test_client_class`.
+ """
+ cls = self.test_client_class
+ if cls is None:
+ from .testing import FlaskClient as cls
+ return cls( # type: ignore
+ self, self.response_class, use_cookies=use_cookies, **kwargs
+ )
+
+ def test_cli_runner(self, **kwargs: t.Any) -> FlaskCliRunner:
+ """Create a CLI runner for testing CLI commands.
+ See :ref:`testing-cli`.
+
+ Returns an instance of :attr:`test_cli_runner_class`, by default
+ :class:`~flask.testing.FlaskCliRunner`. The Flask app object is
+ passed as the first argument.
+
+ .. versionadded:: 1.0
+ """
+ cls = self.test_cli_runner_class
+
+ if cls is None:
+ from .testing import FlaskCliRunner as cls
+
+ return cls(self, **kwargs) # type: ignore
+
+ def handle_http_exception(
+ self, e: HTTPException
+ ) -> HTTPException | ft.ResponseReturnValue:
+ """Handles an HTTP exception. By default this will invoke the
+ registered error handlers and fall back to returning the
+ exception as response.
+
+ .. versionchanged:: 1.0.3
+ ``RoutingException``, used internally for actions such as
+ slash redirects during routing, is not passed to error
+ handlers.
+
+ .. versionchanged:: 1.0
+ Exceptions are looked up by code *and* by MRO, so
+ ``HTTPException`` subclasses can be handled with a catch-all
+ handler for the base ``HTTPException``.
+
+ .. versionadded:: 0.3
+ """
+ # Proxy exceptions don't have error codes. We want to always return
+ # those unchanged as errors
+ if e.code is None:
+ return e
+
+ # RoutingExceptions are used internally to trigger routing
+ # actions, such as slash redirects raising RequestRedirect. They
+ # are not raised or handled in user code.
+ if isinstance(e, RoutingException):
+ return e
+
+ handler = self._find_error_handler(e, request.blueprints)
+ if handler is None:
+ return e
+ return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
+
+ def handle_user_exception(
+ self, e: Exception
+ ) -> HTTPException | ft.ResponseReturnValue:
+ """This method is called whenever an exception occurs that
+ should be handled. A special case is :class:`~werkzeug
+ .exceptions.HTTPException` which is forwarded to the
+ :meth:`handle_http_exception` method. This function will either
+ return a response value or reraise the exception with the same
+ traceback.
+
+ .. versionchanged:: 1.0
+ Key errors raised from request data like ``form`` show the
+ bad key in debug mode rather than a generic bad request
+ message.
+
+ .. versionadded:: 0.7
+ """
+ if isinstance(e, BadRequestKeyError) and (
+ self.debug or self.config["TRAP_BAD_REQUEST_ERRORS"]
+ ):
+ e.show_exception = True
+
+ if isinstance(e, HTTPException) and not self.trap_http_exception(e):
+ return self.handle_http_exception(e)
+
+ handler = self._find_error_handler(e, request.blueprints)
+
+ if handler is None:
+ raise
+
+ return self.ensure_sync(handler)(e) # type: ignore[no-any-return]
+
+ def handle_exception(self, e: Exception) -> Response:
+ """Handle an exception that did not have an error handler
+ associated with it, or that was raised from an error handler.
+ This always causes a 500 ``InternalServerError``.
+
+ Always sends the :data:`got_request_exception` signal.
+
+ If :data:`PROPAGATE_EXCEPTIONS` is ``True``, such as in debug
+ mode, the error will be re-raised so that the debugger can
+ display it. Otherwise, the original exception is logged, and
+ an :exc:`~werkzeug.exceptions.InternalServerError` is returned.
+
+ If an error handler is registered for ``InternalServerError`` or
+ ``500``, it will be used. For consistency, the handler will
+ always receive the ``InternalServerError``. The original
+ unhandled exception is available as ``e.original_exception``.
+
+ .. versionchanged:: 1.1.0
+ Always passes the ``InternalServerError`` instance to the
+ handler, setting ``original_exception`` to the unhandled
+ error.
+
+ .. versionchanged:: 1.1.0
+ ``after_request`` functions and other finalization is done
+ even for the default 500 response when there is no handler.
+
+ .. versionadded:: 0.3
+ """
+ exc_info = sys.exc_info()
+ got_request_exception.send(self, _async_wrapper=self.ensure_sync, exception=e)
+ propagate = self.config["PROPAGATE_EXCEPTIONS"]
+
+ if propagate is None:
+ propagate = self.testing or self.debug
+
+ if propagate:
+ # Re-raise if called with an active exception, otherwise
+ # raise the passed in exception.
+ if exc_info[1] is e:
+ raise
+
+ raise e
+
+ self.log_exception(exc_info)
+ server_error: InternalServerError | ft.ResponseReturnValue
+ server_error = InternalServerError(original_exception=e)
+ handler = self._find_error_handler(server_error, request.blueprints)
+
+ if handler is not None:
+ server_error = self.ensure_sync(handler)(server_error)
+
+ return self.finalize_request(server_error, from_error_handler=True)
+
+ def log_exception(
+ self,
+ exc_info: (tuple[type, BaseException, TracebackType] | tuple[None, None, None]),
+ ) -> None:
+ """Logs an exception. This is called by :meth:`handle_exception`
+ if debugging is disabled and right before the handler is called.
+ The default implementation logs the exception as error on the
+ :attr:`logger`.
+
+ .. versionadded:: 0.8
+ """
+ self.logger.error(
+ f"Exception on {request.path} [{request.method}]", exc_info=exc_info
+ )
+
+ def dispatch_request(self) -> ft.ResponseReturnValue:
+ """Does the request dispatching. Matches the URL and returns the
+ return value of the view or error handler. This does not have to
+ be a response object. In order to convert the return value to a
+ proper response object, call :func:`make_response`.
+
+ .. versionchanged:: 0.7
+ This no longer does the exception handling, this code was
+ moved to the new :meth:`full_dispatch_request`.
+ """
+ req = request_ctx.request
+ if req.routing_exception is not None:
+ self.raise_routing_exception(req)
+ rule: Rule = req.url_rule # type: ignore[assignment]
+ # if we provide automatic options for this URL and the
+ # request came with the OPTIONS method, reply automatically
+ if (
+ getattr(rule, "provide_automatic_options", False)
+ and req.method == "OPTIONS"
+ ):
+ return self.make_default_options_response()
+ # otherwise dispatch to the handler for that endpoint
+ view_args: dict[str, t.Any] = req.view_args # type: ignore[assignment]
+ return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
+
+ def full_dispatch_request(self) -> Response:
+ """Dispatches the request and on top of that performs request
+ pre and postprocessing as well as HTTP exception catching and
+ error handling.
+
+ .. versionadded:: 0.7
+ """
+ self._got_first_request = True
+
+ try:
+ request_started.send(self, _async_wrapper=self.ensure_sync)
+ rv = self.preprocess_request()
+ if rv is None:
+ rv = self.dispatch_request()
+ except Exception as e:
+ rv = self.handle_user_exception(e)
+ return self.finalize_request(rv)
+
+ def finalize_request(
+ self,
+ rv: ft.ResponseReturnValue | HTTPException,
+ from_error_handler: bool = False,
+ ) -> Response:
+ """Given the return value from a view function this finalizes
+ the request by converting it into a response and invoking the
+ postprocessing functions. This is invoked for both normal
+ request dispatching as well as error handlers.
+
+ Because this means that it might be called as a result of a
+ failure a special safe mode is available which can be enabled
+ with the `from_error_handler` flag. If enabled, failures in
+ response processing will be logged and otherwise ignored.
+
+ :internal:
+ """
+ response = self.make_response(rv)
+ try:
+ response = self.process_response(response)
+ request_finished.send(
+ self, _async_wrapper=self.ensure_sync, response=response
+ )
+ except Exception:
+ if not from_error_handler:
+ raise
+ self.logger.exception(
+ "Request finalizing failed with an error while handling an error"
+ )
+ return response
+
+ def make_default_options_response(self) -> Response:
+ """This method is called to create the default ``OPTIONS`` response.
+ This can be changed through subclassing to change the default
+ behavior of ``OPTIONS`` responses.
+
+ .. versionadded:: 0.7
+ """
+ adapter = request_ctx.url_adapter
+ methods = adapter.allowed_methods() # type: ignore[union-attr]
+ rv = self.response_class()
+ rv.allow.update(methods)
+ return rv
+
+ def ensure_sync(self, func: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]:
+ """Ensure that the function is synchronous for WSGI workers.
+ Plain ``def`` functions are returned as-is. ``async def``
+ functions are wrapped to run and wait for the response.
+
+ Override this method to change how the app runs async views.
+
+ .. versionadded:: 2.0
+ """
+ if iscoroutinefunction(func):
+ return self.async_to_sync(func)
+
+ return func
+
+ def async_to_sync(
+ self, func: t.Callable[..., t.Coroutine[t.Any, t.Any, t.Any]]
+ ) -> t.Callable[..., t.Any]:
+ """Return a sync function that will run the coroutine function.
+
+ .. code-block:: python
+
+ result = app.async_to_sync(func)(*args, **kwargs)
+
+ Override this method to change how the app converts async code
+ to be synchronously callable.
+
+ .. versionadded:: 2.0
+ """
+ try:
+ from asgiref.sync import async_to_sync as asgiref_async_to_sync
+ except ImportError:
+ raise RuntimeError(
+ "Install Flask with the 'async' extra in order to use async views."
+ ) from None
+
+ return asgiref_async_to_sync(func)
+
+ def url_for(
+ self,
+ /,
+ endpoint: str,
+ *,
+ _anchor: str | None = None,
+ _method: str | None = None,
+ _scheme: str | None = None,
+ _external: bool | None = None,
+ **values: t.Any,
+ ) -> str:
+ """Generate a URL to the given endpoint with the given values.
+
+ This is called by :func:`flask.url_for`, and can be called
+ directly as well.
+
+ An *endpoint* is the name of a URL rule, usually added with
+ :meth:`@app.route() `, and usually the same name as the
+ view function. A route defined in a :class:`~flask.Blueprint`
+ will prepend the blueprint's name separated by a ``.`` to the
+ endpoint.
+
+ In some cases, such as email messages, you want URLs to include
+ the scheme and domain, like ``https://example.com/hello``. When
+ not in an active request, URLs will be external by default, but
+ this requires setting :data:`SERVER_NAME` so Flask knows what
+ domain to use. :data:`APPLICATION_ROOT` and
+ :data:`PREFERRED_URL_SCHEME` should also be configured as
+ needed. This config is only used when not in an active request.
+
+ Functions can be decorated with :meth:`url_defaults` to modify
+ keyword arguments before the URL is built.
+
+ If building fails for some reason, such as an unknown endpoint
+ or incorrect values, the app's :meth:`handle_url_build_error`
+ method is called. If that returns a string, that is returned,
+ otherwise a :exc:`~werkzeug.routing.BuildError` is raised.
+
+ :param endpoint: The endpoint name associated with the URL to
+ generate. If this starts with a ``.``, the current blueprint
+ name (if any) will be used.
+ :param _anchor: If given, append this as ``#anchor`` to the URL.
+ :param _method: If given, generate the URL associated with this
+ method for the endpoint.
+ :param _scheme: If given, the URL will have this scheme if it
+ is external.
+ :param _external: If given, prefer the URL to be internal
+ (False) or require it to be external (True). External URLs
+ include the scheme and domain. When not in an active
+ request, URLs are external by default.
+ :param values: Values to use for the variable parts of the URL
+ rule. Unknown keys are appended as query string arguments,
+ like ``?a=b&c=d``.
+
+ .. versionadded:: 2.2
+ Moved from ``flask.url_for``, which calls this method.
+ """
+ req_ctx = _cv_request.get(None)
+
+ if req_ctx is not None:
+ url_adapter = req_ctx.url_adapter
+ blueprint_name = req_ctx.request.blueprint
+
+ # If the endpoint starts with "." and the request matches a
+ # blueprint, the endpoint is relative to the blueprint.
+ if endpoint[:1] == ".":
+ if blueprint_name is not None:
+ endpoint = f"{blueprint_name}{endpoint}"
+ else:
+ endpoint = endpoint[1:]
+
+ # When in a request, generate a URL without scheme and
+ # domain by default, unless a scheme is given.
+ if _external is None:
+ _external = _scheme is not None
+ else:
+ app_ctx = _cv_app.get(None)
+
+ # If called by helpers.url_for, an app context is active,
+ # use its url_adapter. Otherwise, app.url_for was called
+ # directly, build an adapter.
+ if app_ctx is not None:
+ url_adapter = app_ctx.url_adapter
+ else:
+ url_adapter = self.create_url_adapter(None)
+
+ if url_adapter is None:
+ raise RuntimeError(
+ "Unable to build URLs outside an active request"
+ " without 'SERVER_NAME' configured. Also configure"
+ " 'APPLICATION_ROOT' and 'PREFERRED_URL_SCHEME' as"
+ " needed."
+ )
+
+ # When outside a request, generate a URL with scheme and
+ # domain by default.
+ if _external is None:
+ _external = True
+
+ # It is an error to set _scheme when _external=False, in order
+ # to avoid accidental insecure URLs.
+ if _scheme is not None and not _external:
+ raise ValueError("When specifying '_scheme', '_external' must be True.")
+
+ self.inject_url_defaults(endpoint, values)
+
+ try:
+ rv = url_adapter.build( # type: ignore[union-attr]
+ endpoint,
+ values,
+ method=_method,
+ url_scheme=_scheme,
+ force_external=_external,
+ )
+ except BuildError as error:
+ values.update(
+ _anchor=_anchor, _method=_method, _scheme=_scheme, _external=_external
+ )
+ return self.handle_url_build_error(error, endpoint, values)
+
+ if _anchor is not None:
+ _anchor = _url_quote(_anchor, safe="%!#$&'()*+,/:;=?@")
+ rv = f"{rv}#{_anchor}"
+
+ return rv
+
+ def make_response(self, rv: ft.ResponseReturnValue) -> Response:
+ """Convert the return value from a view function to an instance of
+ :attr:`response_class`.
+
+ :param rv: the return value from the view function. The view function
+ must return a response. Returning ``None``, or the view ending
+ without returning, is not allowed. The following types are allowed
+ for ``view_rv``:
+
+ ``str``
+ A response object is created with the string encoded to UTF-8
+ as the body.
+
+ ``bytes``
+ A response object is created with the bytes as the body.
+
+ ``dict``
+ A dictionary that will be jsonify'd before being returned.
+
+ ``list``
+ A list that will be jsonify'd before being returned.
+
+ ``generator`` or ``iterator``
+ A generator that returns ``str`` or ``bytes`` to be
+ streamed as the response.
+
+ ``tuple``
+ Either ``(body, status, headers)``, ``(body, status)``, or
+ ``(body, headers)``, where ``body`` is any of the other types
+ allowed here, ``status`` is a string or an integer, and
+ ``headers`` is a dictionary or a list of ``(key, value)``
+ tuples. If ``body`` is a :attr:`response_class` instance,
+ ``status`` overwrites the exiting value and ``headers`` are
+ extended.
+
+ :attr:`response_class`
+ The object is returned unchanged.
+
+ other :class:`~werkzeug.wrappers.Response` class
+ The object is coerced to :attr:`response_class`.
+
+ :func:`callable`
+ The function is called as a WSGI application. The result is
+ used to create a response object.
+
+ .. versionchanged:: 2.2
+ A generator will be converted to a streaming response.
+ A list will be converted to a JSON response.
+
+ .. versionchanged:: 1.1
+ A dict will be converted to a JSON response.
+
+ .. versionchanged:: 0.9
+ Previously a tuple was interpreted as the arguments for the
+ response object.
+ """
+
+ status = headers = None
+
+ # unpack tuple returns
+ if isinstance(rv, tuple):
+ len_rv = len(rv)
+
+ # a 3-tuple is unpacked directly
+ if len_rv == 3:
+ rv, status, headers = rv # type: ignore[misc]
+ # decide if a 2-tuple has status or headers
+ elif len_rv == 2:
+ if isinstance(rv[1], (Headers, dict, tuple, list)):
+ rv, headers = rv
+ else:
+ rv, status = rv # type: ignore[assignment,misc]
+ # other sized tuples are not allowed
+ else:
+ raise TypeError(
+ "The view function did not return a valid response tuple."
+ " The tuple must have the form (body, status, headers),"
+ " (body, status), or (body, headers)."
+ )
+
+ # the body must not be None
+ if rv is None:
+ raise TypeError(
+ f"The view function for {request.endpoint!r} did not"
+ " return a valid response. The function either returned"
+ " None or ended without a return statement."
+ )
+
+ # make sure the body is an instance of the response class
+ if not isinstance(rv, self.response_class):
+ if isinstance(rv, (str, bytes, bytearray)) or isinstance(rv, cabc.Iterator):
+ # let the response class set the status and headers instead of
+ # waiting to do it manually, so that the class can handle any
+ # special logic
+ rv = self.response_class(
+ rv,
+ status=status,
+ headers=headers, # type: ignore[arg-type]
+ )
+ status = headers = None
+ elif isinstance(rv, (dict, list)):
+ rv = self.json.response(rv)
+ elif isinstance(rv, BaseResponse) or callable(rv):
+ # evaluate a WSGI callable, or coerce a different response
+ # class to the correct type
+ try:
+ rv = self.response_class.force_type(
+ rv, # type: ignore[arg-type]
+ request.environ,
+ )
+ except TypeError as e:
+ raise TypeError(
+ f"{e}\nThe view function did not return a valid"
+ " response. The return type must be a string,"
+ " dict, list, tuple with headers or status,"
+ " Response instance, or WSGI callable, but it"
+ f" was a {type(rv).__name__}."
+ ).with_traceback(sys.exc_info()[2]) from None
+ else:
+ raise TypeError(
+ "The view function did not return a valid"
+ " response. The return type must be a string,"
+ " dict, list, tuple with headers or status,"
+ " Response instance, or WSGI callable, but it was a"
+ f" {type(rv).__name__}."
+ )
+
+ rv = t.cast(Response, rv)
+ # prefer the status if it was provided
+ if status is not None:
+ if isinstance(status, (str, bytes, bytearray)):
+ rv.status = status
+ else:
+ rv.status_code = status
+
+ # extend existing headers with provided headers
+ if headers:
+ rv.headers.update(headers) # type: ignore[arg-type]
+
+ return rv
+
+ def preprocess_request(self) -> ft.ResponseReturnValue | None:
+ """Called before the request is dispatched. Calls
+ :attr:`url_value_preprocessors` registered with the app and the
+ current blueprint (if any). Then calls :attr:`before_request_funcs`
+ registered with the app and the blueprint.
+
+ If any :meth:`before_request` handler returns a non-None value, the
+ value is handled as if it was the return value from the view, and
+ further request handling is stopped.
+ """
+ names = (None, *reversed(request.blueprints))
+
+ for name in names:
+ if name in self.url_value_preprocessors:
+ for url_func in self.url_value_preprocessors[name]:
+ url_func(request.endpoint, request.view_args)
+
+ for name in names:
+ if name in self.before_request_funcs:
+ for before_func in self.before_request_funcs[name]:
+ rv = self.ensure_sync(before_func)()
+
+ if rv is not None:
+ return rv # type: ignore[no-any-return]
+
+ return None
+
+ def process_response(self, response: Response) -> Response:
+ """Can be overridden in order to modify the response object
+ before it's sent to the WSGI server. By default this will
+ call all the :meth:`after_request` decorated functions.
+
+ .. versionchanged:: 0.5
+ As of Flask 0.5 the functions registered for after request
+ execution are called in reverse order of registration.
+
+ :param response: a :attr:`response_class` object.
+ :return: a new response object or the same, has to be an
+ instance of :attr:`response_class`.
+ """
+ ctx = request_ctx._get_current_object() # type: ignore[attr-defined]
+
+ for func in ctx._after_request_functions:
+ response = self.ensure_sync(func)(response)
+
+ for name in chain(request.blueprints, (None,)):
+ if name in self.after_request_funcs:
+ for func in reversed(self.after_request_funcs[name]):
+ response = self.ensure_sync(func)(response)
+
+ if not self.session_interface.is_null_session(ctx.session):
+ self.session_interface.save_session(self, ctx.session, response)
+
+ return response
+
+ def do_teardown_request(
+ self,
+ exc: BaseException | None = _sentinel, # type: ignore[assignment]
+ ) -> None:
+ """Called after the request is dispatched and the response is
+ returned, right before the request context is popped.
+
+ This calls all functions decorated with
+ :meth:`teardown_request`, and :meth:`Blueprint.teardown_request`
+ if a blueprint handled the request. Finally, the
+ :data:`request_tearing_down` signal is sent.
+
+ This is called by
+ :meth:`RequestContext.pop() `,
+ which may be delayed during testing to maintain access to
+ resources.
+
+ :param exc: An unhandled exception raised while dispatching the
+ request. Detected from the current exception information if
+ not passed. Passed to each teardown function.
+
+ .. versionchanged:: 0.9
+ Added the ``exc`` argument.
+ """
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+
+ for name in chain(request.blueprints, (None,)):
+ if name in self.teardown_request_funcs:
+ for func in reversed(self.teardown_request_funcs[name]):
+ self.ensure_sync(func)(exc)
+
+ request_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
+
+ def do_teardown_appcontext(
+ self,
+ exc: BaseException | None = _sentinel, # type: ignore[assignment]
+ ) -> None:
+ """Called right before the application context is popped.
+
+ When handling a request, the application context is popped
+ after the request context. See :meth:`do_teardown_request`.
+
+ This calls all functions decorated with
+ :meth:`teardown_appcontext`. Then the
+ :data:`appcontext_tearing_down` signal is sent.
+
+ This is called by
+ :meth:`AppContext.pop() `.
+
+ .. versionadded:: 0.9
+ """
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+
+ for func in reversed(self.teardown_appcontext_funcs):
+ self.ensure_sync(func)(exc)
+
+ appcontext_tearing_down.send(self, _async_wrapper=self.ensure_sync, exc=exc)
+
+ def app_context(self) -> AppContext:
+ """Create an :class:`~flask.ctx.AppContext`. Use as a ``with``
+ block to push the context, which will make :data:`current_app`
+ point at this application.
+
+ An application context is automatically pushed by
+ :meth:`RequestContext.push() `
+ when handling a request, and when running a CLI command. Use
+ this to manually create a context outside of these situations.
+
+ ::
+
+ with app.app_context():
+ init_db()
+
+ See :doc:`/appcontext`.
+
+ .. versionadded:: 0.9
+ """
+ return AppContext(self)
+
+ def request_context(self, environ: WSGIEnvironment) -> RequestContext:
+ """Create a :class:`~flask.ctx.RequestContext` representing a
+ WSGI environment. Use a ``with`` block to push the context,
+ which will make :data:`request` point at this request.
+
+ See :doc:`/reqcontext`.
+
+ Typically you should not call this from your own code. A request
+ context is automatically pushed by the :meth:`wsgi_app` when
+ handling a request. Use :meth:`test_request_context` to create
+ an environment and context instead of this method.
+
+ :param environ: a WSGI environment
+ """
+ return RequestContext(self, environ)
+
+ def test_request_context(self, *args: t.Any, **kwargs: t.Any) -> RequestContext:
+ """Create a :class:`~flask.ctx.RequestContext` for a WSGI
+ environment created from the given values. This is mostly useful
+ during testing, where you may want to run a function that uses
+ request data without dispatching a full request.
+
+ See :doc:`/reqcontext`.
+
+ Use a ``with`` block to push the context, which will make
+ :data:`request` point at the request for the created
+ environment. ::
+
+ with app.test_request_context(...):
+ generate_report()
+
+ When using the shell, it may be easier to push and pop the
+ context manually to avoid indentation. ::
+
+ ctx = app.test_request_context(...)
+ ctx.push()
+ ...
+ ctx.pop()
+
+ Takes the same arguments as Werkzeug's
+ :class:`~werkzeug.test.EnvironBuilder`, with some defaults from
+ the application. See the linked Werkzeug docs for most of the
+ available arguments. Flask-specific behavior is listed here.
+
+ :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 data: The request body, either as a string or a dict of
+ form keys and values.
+ :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`.
+ """
+ from .testing import EnvironBuilder
+
+ builder = EnvironBuilder(self, *args, **kwargs)
+
+ try:
+ return self.request_context(builder.get_environ())
+ finally:
+ builder.close()
+
+ def wsgi_app(
+ self, environ: WSGIEnvironment, start_response: StartResponse
+ ) -> cabc.Iterable[bytes]:
+ """The actual WSGI application. This is not implemented in
+ :meth:`__call__` so that middlewares can be applied without
+ losing a reference to the app object. Instead of doing this::
+
+ app = MyMiddleware(app)
+
+ It's a better idea to do this instead::
+
+ app.wsgi_app = MyMiddleware(app.wsgi_app)
+
+ Then you still have the original application object around and
+ can continue to call methods on it.
+
+ .. versionchanged:: 0.7
+ Teardown events for the request and app contexts are called
+ even if an unhandled error occurs. Other events may not be
+ called depending on when an error occurs during dispatch.
+ See :ref:`callbacks-and-errors`.
+
+ :param environ: A WSGI environment.
+ :param start_response: A callable accepting a status code,
+ a list of headers, and an optional exception context to
+ start the response.
+ """
+ ctx = self.request_context(environ)
+ error: BaseException | None = None
+ try:
+ try:
+ ctx.push()
+ response = self.full_dispatch_request()
+ except Exception as e:
+ error = e
+ response = self.handle_exception(e)
+ except: # noqa: B001
+ error = sys.exc_info()[1]
+ raise
+ return response(environ, start_response)
+ finally:
+ if "werkzeug.debug.preserve_context" in environ:
+ environ["werkzeug.debug.preserve_context"](_cv_app.get())
+ environ["werkzeug.debug.preserve_context"](_cv_request.get())
+
+ if error is not None and self.should_ignore_error(error):
+ error = None
+
+ ctx.pop(error)
+
+ def __call__(
+ self, environ: WSGIEnvironment, start_response: StartResponse
+ ) -> cabc.Iterable[bytes]:
+ """The WSGI server calls the Flask application object as the
+ WSGI application. This calls :meth:`wsgi_app`, which can be
+ wrapped to apply middleware.
+ """
+ return self.wsgi_app(environ, start_response)
diff --git a/venv/Lib/site-packages/flask/blueprints.py b/venv/Lib/site-packages/flask/blueprints.py
new file mode 100644
index 0000000..52859b8
--- /dev/null
+++ b/venv/Lib/site-packages/flask/blueprints.py
@@ -0,0 +1,91 @@
+from __future__ import annotations
+
+import os
+import typing as t
+from datetime import timedelta
+
+from .globals import current_app
+from .helpers import send_from_directory
+from .sansio.blueprints import Blueprint as SansioBlueprint
+from .sansio.blueprints import BlueprintSetupState as BlueprintSetupState # noqa
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .wrappers import Response
+
+
+class Blueprint(SansioBlueprint):
+ def get_send_file_max_age(self, filename: str | None) -> int | None:
+ """Used by :func:`send_file` to determine the ``max_age`` cache
+ value for a given file path if it wasn't passed.
+
+ By default, this returns :data:`SEND_FILE_MAX_AGE_DEFAULT` from
+ the configuration of :data:`~flask.current_app`. This defaults
+ to ``None``, which tells the browser to use conditional requests
+ instead of a timed cache, which is usually preferable.
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ .. versionchanged:: 2.0
+ The default configuration is ``None`` instead of 12 hours.
+
+ .. versionadded:: 0.9
+ """
+ value = current_app.config["SEND_FILE_MAX_AGE_DEFAULT"]
+
+ if value is None:
+ return None
+
+ if isinstance(value, timedelta):
+ return int(value.total_seconds())
+
+ return value # type: ignore[no-any-return]
+
+ def send_static_file(self, filename: str) -> Response:
+ """The view function used to serve files from
+ :attr:`static_folder`. A route is automatically registered for
+ this view at :attr:`static_url_path` if :attr:`static_folder` is
+ set.
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ .. versionadded:: 0.5
+
+ """
+ if not self.has_static_folder:
+ raise RuntimeError("'static_folder' must be set to serve static_files.")
+
+ # send_file only knows to call get_send_file_max_age on the app,
+ # call it here so it works for blueprints too.
+ max_age = self.get_send_file_max_age(filename)
+ return send_from_directory(
+ t.cast(str, self.static_folder), filename, max_age=max_age
+ )
+
+ def open_resource(self, resource: str, mode: str = "rb") -> t.IO[t.AnyStr]:
+ """Open a resource file relative to :attr:`root_path` for
+ reading.
+
+ For example, if the file ``schema.sql`` is next to the file
+ ``app.py`` where the ``Flask`` app is defined, it can be opened
+ with:
+
+ .. code-block:: python
+
+ with app.open_resource("schema.sql") as f:
+ conn.executescript(f.read())
+
+ :param resource: Path to the resource relative to
+ :attr:`root_path`.
+ :param mode: Open the file in this mode. Only reading is
+ supported, valid values are "r" (or "rt") and "rb".
+
+ Note this is a duplicate of the same method in the Flask
+ class.
+
+ """
+ if mode not in {"r", "rt", "rb"}:
+ raise ValueError("Resources can only be opened for reading.")
+
+ return open(os.path.join(self.root_path, resource), mode)
diff --git a/venv/Lib/site-packages/flask/cli.py b/venv/Lib/site-packages/flask/cli.py
new file mode 100644
index 0000000..d4df280
--- /dev/null
+++ b/venv/Lib/site-packages/flask/cli.py
@@ -0,0 +1,1111 @@
+from __future__ import annotations
+
+import ast
+import collections.abc as cabc
+import importlib.metadata
+import inspect
+import os
+import platform
+import re
+import sys
+import traceback
+import typing as t
+from functools import update_wrapper
+from operator import itemgetter
+from types import ModuleType
+
+import click
+from click.core import ParameterSource
+from werkzeug import run_simple
+from werkzeug.serving import is_running_from_reloader
+from werkzeug.utils import import_string
+
+from .globals import current_app
+from .helpers import get_debug_flag
+from .helpers import get_load_dotenv
+
+if t.TYPE_CHECKING:
+ import ssl
+
+ from _typeshed.wsgi import StartResponse
+ from _typeshed.wsgi import WSGIApplication
+ from _typeshed.wsgi import WSGIEnvironment
+
+ from .app import Flask
+
+
+class NoAppException(click.UsageError):
+ """Raised if an application cannot be found or loaded."""
+
+
+def find_best_app(module: ModuleType) -> Flask:
+ """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 v in module.__dict__.values() if isinstance(v, Flask)]
+
+ if len(matches) == 1:
+ return matches[0]
+ elif len(matches) > 1:
+ raise NoAppException(
+ "Detected multiple Flask applications in module"
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
+ " to specify the correct one."
+ )
+
+ # 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 = app_factory()
+
+ if isinstance(app, Flask):
+ return app
+ except TypeError as e:
+ if not _called_with_wrong_args(app_factory):
+ raise
+
+ raise NoAppException(
+ f"Detected factory '{attr_name}' in module '{module.__name__}',"
+ " but could not call it without arguments. Use"
+ f" '{module.__name__}:{attr_name}(args)'"
+ " to specify arguments."
+ ) from e
+
+ raise NoAppException(
+ "Failed to find Flask application or factory in module"
+ f" '{module.__name__}'. Use '{module.__name__}:name'"
+ " to specify one."
+ )
+
+
+def _called_with_wrong_args(f: t.Callable[..., Flask]) -> bool:
+ """Check whether calling a function raised a ``TypeError`` because
+ the call failed or because something in the factory raised the
+ error.
+
+ :param f: The 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 f.__code__:
+ # In the function, it was called successfully.
+ return False
+
+ tb = tb.tb_next
+
+ # Didn't reach the function.
+ return True
+ finally:
+ # Delete tb to break a circular reference.
+ # https://docs.python.org/2/library/sys.html#sys.exc_info
+ del tb
+
+
+def find_app_by_string(module: ModuleType, app_name: str) -> Flask:
+ """Check if the given string is a variable name or a function. Call
+ a function to get the app instance, or return the variable directly.
+ """
+ from . import Flask
+
+ # Parse app_name as a single expression to determine if it's a valid
+ # attribute name or function call.
+ try:
+ expr = ast.parse(app_name.strip(), mode="eval").body
+ except SyntaxError:
+ raise NoAppException(
+ f"Failed to parse {app_name!r} as an attribute name or function call."
+ ) from None
+
+ if isinstance(expr, ast.Name):
+ name = expr.id
+ args = []
+ kwargs = {}
+ elif isinstance(expr, ast.Call):
+ # Ensure the function name is an attribute name only.
+ if not isinstance(expr.func, ast.Name):
+ raise NoAppException(
+ f"Function reference must be a simple name: {app_name!r}."
+ )
+
+ name = expr.func.id
+
+ # Parse the positional and keyword arguments as literals.
+ try:
+ args = [ast.literal_eval(arg) for arg in expr.args]
+ kwargs = {
+ kw.arg: ast.literal_eval(kw.value)
+ for kw in expr.keywords
+ if kw.arg is not None
+ }
+ except ValueError:
+ # literal_eval gives cryptic error messages, show a generic
+ # message with the full expression instead.
+ raise NoAppException(
+ f"Failed to parse arguments as literal values: {app_name!r}."
+ ) from None
+ else:
+ raise NoAppException(
+ f"Failed to parse {app_name!r} as an attribute name or function call."
+ )
+
+ try:
+ attr = getattr(module, name)
+ except AttributeError as e:
+ raise NoAppException(
+ f"Failed to find attribute {name!r} in {module.__name__!r}."
+ ) from e
+
+ # If the attribute is a function, call it with any args and kwargs
+ # to get the real application.
+ if inspect.isfunction(attr):
+ try:
+ app = attr(*args, **kwargs)
+ except TypeError as e:
+ if not _called_with_wrong_args(attr):
+ raise
+
+ raise NoAppException(
+ f"The factory {app_name!r} in module"
+ f" {module.__name__!r} could not be called with the"
+ " specified arguments."
+ ) from e
+ else:
+ app = attr
+
+ if isinstance(app, Flask):
+ return app
+
+ raise NoAppException(
+ "A valid Flask application was not obtained from"
+ f" '{module.__name__}:{app_name}'."
+ )
+
+
+def prepare_import(path: str) -> str:
+ """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)
+
+ fname, ext = os.path.splitext(path)
+ if ext == ".py":
+ path = fname
+
+ 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])
+
+
+@t.overload
+def locate_app(
+ module_name: str, app_name: str | None, raise_if_not_found: t.Literal[True] = True
+) -> Flask:
+ ...
+
+
+@t.overload
+def locate_app(
+ module_name: str, app_name: str | None, raise_if_not_found: t.Literal[False] = ...
+) -> Flask | None:
+ ...
+
+
+def locate_app(
+ module_name: str, app_name: str | None, raise_if_not_found: bool = True
+) -> Flask | None:
+ 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()[2].tb_next: # type: ignore[union-attr]
+ raise NoAppException(
+ f"While importing {module_name!r}, an ImportError was"
+ f" raised:\n\n{traceback.format_exc()}"
+ ) from None
+ elif raise_if_not_found:
+ raise NoAppException(f"Could not import {module_name!r}.") from None
+ else:
+ return None
+
+ module = sys.modules[module_name]
+
+ if app_name is None:
+ return find_best_app(module)
+ else:
+ return find_app_by_string(module, app_name)
+
+
+def get_version(ctx: click.Context, param: click.Parameter, value: t.Any) -> None:
+ if not value or ctx.resilient_parsing:
+ return
+
+ flask_version = importlib.metadata.version("flask")
+ werkzeug_version = importlib.metadata.version("werkzeug")
+
+ click.echo(
+ f"Python {platform.python_version()}\n"
+ f"Flask {flask_version}\n"
+ f"Werkzeug {werkzeug_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 ScriptInfo:
+ """Helper 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: str | None = None,
+ create_app: t.Callable[..., Flask] | None = None,
+ set_debug_flag: bool = True,
+ ) -> None:
+ #: Optionally the import path for the Flask application.
+ self.app_import_path = app_import_path
+ #: 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: dict[t.Any, t.Any] = {}
+ self.set_debug_flag = set_debug_flag
+ self._loaded_app: Flask | None = None
+
+ def load_app(self) -> Flask:
+ """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.
+ """
+ if self._loaded_app is not None:
+ return self._loaded_app
+
+ if self.create_app is not None:
+ app: Flask | None = self.create_app()
+ else:
+ if self.app_import_path:
+ path, name = (
+ re.split(r":(?![\\/])", self.app_import_path, maxsplit=1) + [None]
+ )[:2]
+ import_name = prepare_import(path)
+ app = locate_app(import_name, name)
+ else:
+ for path in ("wsgi.py", "app.py"):
+ import_name = prepare_import(path)
+ app = locate_app(import_name, None, raise_if_not_found=False)
+
+ if app is not None:
+ break
+
+ if app is None:
+ raise NoAppException(
+ "Could not locate a Flask application. Use the"
+ " 'flask --app' option, 'FLASK_APP' environment"
+ " variable, or a 'wsgi.py' or 'app.py' file in the"
+ " current directory."
+ )
+
+ if self.set_debug_flag:
+ # Update the app's debug flag through the descriptor so that
+ # other values repopulate as well.
+ app.debug = get_debug_flag()
+
+ self._loaded_app = app
+ return app
+
+
+pass_script_info = click.make_pass_decorator(ScriptInfo, ensure=True)
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+
+
+def with_appcontext(f: F) -> F:
+ """Wraps a callback so that it's guaranteed to be executed with the
+ script's application context.
+
+ Custom commands (and their options) registered under ``app.cli`` or
+ ``blueprint.cli`` will always have an app context available, this
+ decorator is not required in that case.
+
+ .. versionchanged:: 2.2
+ The app context is active for subcommands as well as the
+ decorated callback. The app context is always available to
+ ``app.cli`` command and parameter callbacks.
+ """
+
+ @click.pass_context
+ def decorator(ctx: click.Context, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
+ if not current_app:
+ app = ctx.ensure_object(ScriptInfo).load_app()
+ ctx.with_resource(app.app_context())
+
+ return ctx.invoke(f, *args, **kwargs)
+
+ return update_wrapper(decorator, f) # type: ignore[return-value]
+
+
+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( # type: ignore[override]
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Callable[[t.Callable[..., t.Any]], click.Command]:
+ """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: t.Callable[..., t.Any]) -> click.Command:
+ if wrap_for_ctx:
+ f = with_appcontext(f)
+ return super(AppGroup, self).command(*args, **kwargs)(f) # type: ignore[no-any-return]
+
+ return decorator
+
+ def group( # type: ignore[override]
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Callable[[t.Callable[..., t.Any]], click.Group]:
+ """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 super().group(*args, **kwargs) # type: ignore[no-any-return]
+
+
+def _set_app(ctx: click.Context, param: click.Option, value: str | None) -> str | None:
+ if value is None:
+ return None
+
+ info = ctx.ensure_object(ScriptInfo)
+ info.app_import_path = value
+ return value
+
+
+# This option is eager so the app will be available if --help is given.
+# --help is also eager, so --app must be before it in the param list.
+# no_args_is_help bypasses eager processing, so this option must be
+# processed manually in that case to ensure FLASK_APP gets picked up.
+_app_option = click.Option(
+ ["-A", "--app"],
+ metavar="IMPORT",
+ help=(
+ "The Flask application or factory function to load, in the form 'module:name'."
+ " Module can be a dotted import or file path. Name is not required if it is"
+ " 'app', 'application', 'create_app', or 'make_app', and can be 'name(args)' to"
+ " pass arguments."
+ ),
+ is_eager=True,
+ expose_value=False,
+ callback=_set_app,
+)
+
+
+def _set_debug(ctx: click.Context, param: click.Option, value: bool) -> bool | None:
+ # If the flag isn't provided, it will default to False. Don't use
+ # that, let debug be set by env in that case.
+ source = ctx.get_parameter_source(param.name) # type: ignore[arg-type]
+
+ if source is not None and source in (
+ ParameterSource.DEFAULT,
+ ParameterSource.DEFAULT_MAP,
+ ):
+ return None
+
+ # Set with env var instead of ScriptInfo.load so that it can be
+ # accessed early during a factory function.
+ os.environ["FLASK_DEBUG"] = "1" if value else "0"
+ return value
+
+
+_debug_option = click.Option(
+ ["--debug/--no-debug"],
+ help="Set debug mode.",
+ expose_value=False,
+ callback=_set_debug,
+)
+
+
+def _env_file_callback(
+ ctx: click.Context, param: click.Option, value: str | None
+) -> str | None:
+ if value is None:
+ return None
+
+ import importlib
+
+ try:
+ importlib.import_module("dotenv")
+ except ImportError:
+ raise click.BadParameter(
+ "python-dotenv must be installed to load an env file.",
+ ctx=ctx,
+ param=param,
+ ) from None
+
+ # Don't check FLASK_SKIP_DOTENV, that only disables automatically
+ # loading .env and .flaskenv files.
+ load_dotenv(value)
+ return value
+
+
+# This option is eager so env vars are loaded as early as possible to be
+# used by other options.
+_env_file_option = click.Option(
+ ["-e", "--env-file"],
+ type=click.Path(exists=True, dir_okay=False),
+ help="Load environment variables from this file. python-dotenv must be installed.",
+ is_eager=True,
+ expose_value=False,
+ callback=_env_file_callback,
+)
+
+
+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. see :ref:`custom-scripts`.
+
+ :param add_default_commands: if this is True then the default run and
+ shell commands will 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.
+ :param set_debug_flag: Set the app's debug flag.
+
+ .. versionchanged:: 2.2
+ Added the ``-A/--app``, ``--debug/--no-debug``, ``-e/--env-file`` options.
+
+ .. versionchanged:: 2.2
+ An app context is pushed when running ``app.cli`` commands, so
+ ``@with_appcontext`` is no longer required for those commands.
+
+ .. 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: bool = True,
+ create_app: t.Callable[..., Flask] | None = None,
+ add_version_option: bool = True,
+ load_dotenv: bool = True,
+ set_debug_flag: bool = True,
+ **extra: t.Any,
+ ) -> None:
+ params = list(extra.pop("params", None) or ())
+ # Processing is done with option callbacks instead of a group
+ # callback. This allows users to make a custom group callback
+ # without losing the behavior. --env-file must come first so
+ # that it is eagerly evaluated before --app.
+ params.extend((_env_file_option, _app_option, _debug_option))
+
+ if add_version_option:
+ params.append(version_option)
+
+ if "context_settings" not in extra:
+ extra["context_settings"] = {}
+
+ extra["context_settings"].setdefault("auto_envvar_prefix", "FLASK")
+
+ super().__init__(params=params, **extra)
+
+ self.create_app = create_app
+ self.load_dotenv = load_dotenv
+ self.set_debug_flag = set_debug_flag
+
+ 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) -> None:
+ if self._loaded_plugin_commands:
+ return
+
+ if sys.version_info >= (3, 10):
+ from importlib import metadata
+ else:
+ # Use a backport on Python < 3.10. We technically have
+ # importlib.metadata on 3.8+, but the API changed in 3.10,
+ # so use the backport for consistency.
+ import importlib_metadata as metadata
+
+ for ep in metadata.entry_points(group="flask.commands"):
+ self.add_command(ep.load(), ep.name)
+
+ self._loaded_plugin_commands = True
+
+ def get_command(self, ctx: click.Context, name: str) -> click.Command | None:
+ self._load_plugin_commands()
+ # Look up built-in and plugin commands, which should be
+ # available even if the app fails to load.
+ rv = super().get_command(ctx, name)
+
+ if rv is not None:
+ return rv
+
+ info = ctx.ensure_object(ScriptInfo)
+
+ # Look up commands provided by the app, showing an error and
+ # continuing if the app couldn't be loaded.
+ try:
+ app = info.load_app()
+ except NoAppException as e:
+ click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
+ return None
+
+ # Push an app context for the loaded app unless it is already
+ # active somehow. This makes the context available to parameter
+ # and command callbacks without needing @with_appcontext.
+ if not current_app or current_app._get_current_object() is not app: # type: ignore[attr-defined]
+ ctx.with_resource(app.app_context())
+
+ return app.cli.get_command(ctx, name)
+
+ def list_commands(self, ctx: click.Context) -> list[str]:
+ self._load_plugin_commands()
+ # Start with the built-in and plugin commands.
+ rv = set(super().list_commands(ctx))
+ info = ctx.ensure_object(ScriptInfo)
+
+ # Add commands provided by the app, showing an error and
+ # continuing if the app couldn't be loaded.
+ try:
+ rv.update(info.load_app().cli.list_commands(ctx))
+ except NoAppException as e:
+ # When an app couldn't be loaded, show the error message
+ # without the traceback.
+ click.secho(f"Error: {e.format_message()}\n", err=True, fg="red")
+ except Exception:
+ # When any other errors occurred during loading, show the
+ # full traceback.
+ click.secho(f"{traceback.format_exc()}\n", err=True, fg="red")
+
+ return sorted(rv)
+
+ def make_context(
+ self,
+ info_name: str | None,
+ args: list[str],
+ parent: click.Context | None = None,
+ **extra: t.Any,
+ ) -> click.Context:
+ # Set a flag to tell app.run to become a no-op. If app.run was
+ # not in a __name__ == __main__ guard, it would start the server
+ # when importing, blocking whatever command is being called.
+ os.environ["FLASK_RUN_FROM_CLI"] = "true"
+
+ # Attempt to load .env and .flask env files. The --env-file
+ # option can cause another file to be loaded.
+ if get_load_dotenv(self.load_dotenv):
+ load_dotenv()
+
+ if "obj" not in extra and "obj" not in self.context_settings:
+ extra["obj"] = ScriptInfo(
+ create_app=self.create_app, set_debug_flag=self.set_debug_flag
+ )
+
+ return super().make_context(info_name, args, parent=parent, **extra)
+
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
+ if not args and self.no_args_is_help:
+ # Attempt to load --env-file and --app early in case they
+ # were given as env vars. Otherwise no_args_is_help will not
+ # see commands from app.cli.
+ _env_file_option.handle_parse_result(ctx, {}, [])
+ _app_option.handle_parse_result(ctx, {}, [])
+
+ return super().parse_args(ctx, args)
+
+
+def _path_is_ancestor(path: str, other: str) -> bool:
+ """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: str | os.PathLike[str] | None = None) -> bool:
+ """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.
+
+ 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.
+
+ .. versionchanged:: 2.0
+ The current directory is not changed to the location of the
+ loaded file.
+
+ .. versionchanged:: 2.0
+ When loading the env files, set the default encoding to UTF-8.
+
+ .. versionchanged:: 1.1.0
+ Returns ``False`` when python-dotenv is not installed, or when
+ the given path isn't a file.
+
+ .. versionadded:: 1.0
+ """
+ try:
+ import dotenv
+ except ImportError:
+ if path or os.path.isfile(".env") or os.path.isfile(".flaskenv"):
+ click.secho(
+ " * Tip: There are .env or .flaskenv files present."
+ ' Do "pip install python-dotenv" to use them.',
+ fg="yellow",
+ err=True,
+ )
+
+ return False
+
+ # Always return after attempting to load a given path, don't load
+ # the default files.
+ if path is not None:
+ if os.path.isfile(path):
+ return dotenv.load_dotenv(path, encoding="utf-8")
+
+ return False
+
+ loaded = False
+
+ for name in (".env", ".flaskenv"):
+ path = dotenv.find_dotenv(name, usecwd=True)
+
+ if not path:
+ continue
+
+ dotenv.load_dotenv(path, encoding="utf-8")
+ loaded = True
+
+ return loaded # True if at least one file was located and loaded.
+
+
+def show_server_banner(debug: bool, app_import_path: str | None) -> None:
+ """Show extra startup messages the first time the server is run,
+ ignoring the reloader.
+ """
+ if is_running_from_reloader():
+ return
+
+ if app_import_path is not None:
+ click.echo(f" * Serving Flask app '{app_import_path}'")
+
+ if debug is not None:
+ click.echo(f" * Debug mode: {'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) -> None:
+ self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
+
+ def convert(
+ self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None
+ ) -> t.Any:
+ try:
+ import ssl
+ except ImportError:
+ raise click.BadParameter(
+ 'Using "--cert" requires Python to be compiled with SSL support.',
+ ctx,
+ param,
+ ) from None
+
+ try:
+ return self.path_type(value, param, ctx)
+ except click.BadParameter:
+ value = click.STRING(value, param, ctx).lower()
+
+ if value == "adhoc":
+ try:
+ import cryptography # noqa: F401
+ except ImportError:
+ raise click.BadParameter(
+ "Using ad-hoc certificates requires the cryptography library.",
+ ctx,
+ param,
+ ) from None
+
+ return value
+
+ obj = import_string(value, silent=True)
+
+ if isinstance(obj, ssl.SSLContext):
+ return obj
+
+ raise
+
+
+def _validate_key(ctx: click.Context, param: click.Parameter, value: t.Any) -> t.Any:
+ """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"
+
+ try:
+ import ssl
+ except ImportError:
+ is_context = False
+ 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
+
+
+class SeparatedPathType(click.Path):
+ """Click option type that accepts a list of values separated by the
+ OS's path separator (``:``, ``;`` on Windows). Each value is
+ validated as a :class:`click.Path` type.
+ """
+
+ def convert(
+ self, value: t.Any, param: click.Parameter | None, ctx: click.Context | None
+ ) -> t.Any:
+ items = self.split_envvar_value(value)
+ # can't call no-arg super() inside list comprehension until Python 3.12
+ super_convert = super().convert
+ return [super_convert(item, param, ctx) for item in items]
+
+
+@click.command("run", short_help="Run 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.",
+ is_eager=True,
+)
+@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(
+ "--with-threads/--without-threads",
+ default=True,
+ help="Enable or disable multithreading.",
+)
+@click.option(
+ "--extra-files",
+ default=None,
+ type=SeparatedPathType(),
+ help=(
+ "Extra files that trigger a reload on change. Multiple paths"
+ f" are separated by {os.path.pathsep!r}."
+ ),
+)
+@click.option(
+ "--exclude-patterns",
+ default=None,
+ type=SeparatedPathType(),
+ help=(
+ "Files matching these fnmatch patterns will not trigger a reload"
+ " on change. Multiple patterns are separated by"
+ f" {os.path.pathsep!r}."
+ ),
+)
+@pass_script_info
+def run_command(
+ info: ScriptInfo,
+ host: str,
+ port: int,
+ reload: bool,
+ debugger: bool,
+ with_threads: bool,
+ cert: ssl.SSLContext | tuple[str, str | None] | t.Literal["adhoc"] | None,
+ extra_files: list[str] | None,
+ exclude_patterns: list[str] | None,
+) -> None:
+ """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 with the '--debug'
+ option.
+ """
+ try:
+ app: WSGIApplication = info.load_app()
+ except Exception as e:
+ if is_running_from_reloader():
+ # When reloading, print out the error immediately, but raise
+ # it later so the debugger or server can handle it.
+ traceback.print_exc()
+ err = e
+
+ def app(
+ environ: WSGIEnvironment, start_response: StartResponse
+ ) -> cabc.Iterable[bytes]:
+ raise err from None
+
+ else:
+ # When not reloading, raise the error immediately so the
+ # command fails.
+ raise e from None
+
+ debug = get_debug_flag()
+
+ if reload is None:
+ reload = debug
+
+ if debugger is None:
+ debugger = debug
+
+ show_server_banner(debug, info.app_import_path)
+
+ run_simple(
+ host,
+ port,
+ app,
+ use_reloader=reload,
+ use_debugger=debugger,
+ threaded=with_threads,
+ ssl_context=cert,
+ extra_files=extra_files,
+ exclude_patterns=exclude_patterns,
+ )
+
+
+run_command.params.insert(0, _debug_option)
+
+
+@click.command("shell", short_help="Run a shell in the app context.")
+@with_appcontext
+def shell_command() -> None:
+ """Run an interactive Python shell in the context of a given
+ Flask application. The application will populate the default
+ namespace of this shell according to its configuration.
+
+ This is useful for executing small snippets of management code
+ without having to manually configure the application.
+ """
+ import code
+
+ banner = (
+ f"Python {sys.version} on {sys.platform}\n"
+ f"App: {current_app.import_name}\n"
+ f"Instance: {current_app.instance_path}"
+ )
+ ctx: dict[str, t.Any] = {}
+
+ # 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) as f:
+ eval(compile(f.read(), startup, "exec"), ctx)
+
+ ctx.update(current_app.make_shell_context())
+
+ # Site, customize, or startup script can set a hook to call when
+ # entering interactive mode. The default one sets up readline with
+ # tab and history completion.
+ interactive_hook = getattr(sys, "__interactivehook__", None)
+
+ if interactive_hook is not None:
+ try:
+ import readline
+ from rlcompleter import Completer
+ except ImportError:
+ pass
+ else:
+ # rlcompleter uses __main__.__dict__ by default, which is
+ # flask.__main__. Use the shell context instead.
+ readline.set_completer(Completer(ctx).complete)
+
+ interactive_hook()
+
+ 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", "domain", "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: str, all_methods: bool) -> None:
+ """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"}
+ host_matching = current_app.url_map.host_matching
+ has_domain = any(rule.host if host_matching else rule.subdomain for rule in rules)
+ rows = []
+
+ for rule in rules:
+ row = [
+ rule.endpoint,
+ ", ".join(sorted((rule.methods or set()) - ignored_methods)),
+ ]
+
+ if has_domain:
+ row.append((rule.host if host_matching else rule.subdomain) or "")
+
+ row.append(rule.rule)
+ rows.append(row)
+
+ headers = ["Endpoint", "Methods"]
+ sorts = ["endpoint", "methods"]
+
+ if has_domain:
+ headers.append("Host" if host_matching else "Subdomain")
+ sorts.append("domain")
+
+ headers.append("Rule")
+ sorts.append("rule")
+
+ try:
+ rows.sort(key=itemgetter(sorts.index(sort)))
+ except ValueError:
+ pass
+
+ rows.insert(0, headers)
+ widths = [max(len(row[i]) for row in rows) for i in range(len(headers))]
+ rows.insert(1, ["-" * w for w in widths])
+ template = " ".join(f"{{{i}:<{w}}}" for i, w in enumerate(widths))
+
+ for row in rows:
+ click.echo(template.format(*row))
+
+
+cli = FlaskGroup(
+ name="flask",
+ help="""\
+A general utility script for Flask applications.
+
+An application to load must be given with the '--app' option,
+'FLASK_APP' environment variable, or with a 'wsgi.py' or 'app.py' file
+in the current directory.
+""",
+)
+
+
+def main() -> None:
+ cli.main()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/venv/Lib/site-packages/flask/config.py b/venv/Lib/site-packages/flask/config.py
new file mode 100644
index 0000000..f2f4147
--- /dev/null
+++ b/venv/Lib/site-packages/flask/config.py
@@ -0,0 +1,372 @@
+from __future__ import annotations
+
+import errno
+import json
+import os
+import types
+import typing as t
+
+from werkzeug.utils import import_string
+
+if t.TYPE_CHECKING:
+ import typing_extensions as te
+
+ from .sansio.app import App
+
+
+T = t.TypeVar("T")
+
+
+class ConfigAttribute(t.Generic[T]):
+ """Makes an attribute forward to the config"""
+
+ def __init__(
+ self, name: str, get_converter: t.Callable[[t.Any], T] | None = None
+ ) -> None:
+ self.__name__ = name
+ self.get_converter = get_converter
+
+ @t.overload
+ def __get__(self, obj: None, owner: None) -> te.Self:
+ ...
+
+ @t.overload
+ def __get__(self, obj: App, owner: type[App]) -> T:
+ ...
+
+ def __get__(self, obj: App | None, owner: type[App] | None = None) -> T | te.Self:
+ 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 # type: ignore[no-any-return]
+
+ def __set__(self, obj: App, value: t.Any) -> None:
+ obj.config[self.__name__] = value
+
+
+class Config(dict): # type: ignore[type-arg]
+ """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: str | os.PathLike[str],
+ defaults: dict[str, t.Any] | None = None,
+ ) -> None:
+ super().__init__(defaults or {})
+ self.root_path = root_path
+
+ def from_envvar(self, variable_name: str, silent: bool = False) -> bool:
+ """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: ``True`` if the file was loaded successfully.
+ """
+ rv = os.environ.get(variable_name)
+ if not rv:
+ if silent:
+ return False
+ raise RuntimeError(
+ f"The environment variable {variable_name!r} is not set"
+ " and as such configuration could not be loaded. Set"
+ " this variable and make it point to a configuration"
+ " file"
+ )
+ return self.from_pyfile(rv, silent=silent)
+
+ def from_prefixed_env(
+ self, prefix: str = "FLASK", *, loads: t.Callable[[str], t.Any] = json.loads
+ ) -> bool:
+ """Load any environment variables that start with ``FLASK_``,
+ dropping the prefix from the env key for the config key. Values
+ are passed through a loading function to attempt to convert them
+ to more specific types than strings.
+
+ Keys are loaded in :func:`sorted` order.
+
+ The default loading function attempts to parse values as any
+ valid JSON type, including dicts and lists.
+
+ Specific items in nested dicts can be set by separating the
+ keys with double underscores (``__``). If an intermediate key
+ doesn't exist, it will be initialized to an empty dict.
+
+ :param prefix: Load env vars that start with this prefix,
+ separated with an underscore (``_``).
+ :param loads: Pass each string value to this function and use
+ the returned value as the config value. If any error is
+ raised it is ignored and the value remains a string. The
+ default is :func:`json.loads`.
+
+ .. versionadded:: 2.1
+ """
+ prefix = f"{prefix}_"
+ len_prefix = len(prefix)
+
+ for key in sorted(os.environ):
+ if not key.startswith(prefix):
+ continue
+
+ value = os.environ[key]
+
+ try:
+ value = loads(value)
+ except Exception:
+ # Keep the value as a string if loading failed.
+ pass
+
+ # Change to key.removeprefix(prefix) on Python >= 3.9.
+ key = key[len_prefix:]
+
+ if "__" not in key:
+ # A non-nested key, set directly.
+ self[key] = value
+ continue
+
+ # Traverse nested dictionaries with keys separated by "__".
+ current = self
+ *parts, tail = key.split("__")
+
+ for part in parts:
+ # If an intermediate dict does not exist, create it.
+ if part not in current:
+ current[part] = {}
+
+ current = current[part]
+
+ current[tail] = value
+
+ return True
+
+ def from_pyfile(
+ self, filename: str | os.PathLike[str], silent: bool = False
+ ) -> bool:
+ """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.
+ :return: ``True`` if the file was loaded successfully.
+
+ .. 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 OSError as e:
+ if silent and e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
+ return False
+ e.strerror = f"Unable to load configuration file ({e.strerror})"
+ raise
+ self.from_object(d)
+ return True
+
+ def from_object(self, obj: object | str) -> None:
+ """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)
+
+ Nothing is done to the object before loading. If the object is a
+ class and has ``@property`` attributes, it needs to be
+ instantiated before being passed to this method.
+
+ 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, str):
+ obj = import_string(obj)
+ for key in dir(obj):
+ if key.isupper():
+ self[key] = getattr(obj, key)
+
+ def from_file(
+ self,
+ filename: str | os.PathLike[str],
+ load: t.Callable[[t.IO[t.Any]], t.Mapping[str, t.Any]],
+ silent: bool = False,
+ text: bool = True,
+ ) -> bool:
+ """Update the values in the config from a file that is loaded
+ using the ``load`` parameter. The loaded data is passed to the
+ :meth:`from_mapping` method.
+
+ .. code-block:: python
+
+ import json
+ app.config.from_file("config.json", load=json.load)
+
+ import tomllib
+ app.config.from_file("config.toml", load=tomllib.load, text=False)
+
+ :param filename: The path to the data file. This can be an
+ absolute path or relative to the config root path.
+ :param load: A callable that takes a file handle and returns a
+ mapping of loaded data from the file.
+ :type load: ``Callable[[Reader], Mapping]`` where ``Reader``
+ implements a ``read`` method.
+ :param silent: Ignore the file if it doesn't exist.
+ :param text: Open the file in text or binary mode.
+ :return: ``True`` if the file was loaded successfully.
+
+ .. versionchanged:: 2.3
+ The ``text`` parameter was added.
+
+ .. versionadded:: 2.0
+ """
+ filename = os.path.join(self.root_path, filename)
+
+ try:
+ with open(filename, "r" if text else "rb") as f:
+ obj = load(f)
+ except OSError as e:
+ if silent and e.errno in (errno.ENOENT, errno.EISDIR):
+ return False
+
+ e.strerror = f"Unable to load configuration file ({e.strerror})"
+ raise
+
+ return self.from_mapping(obj)
+
+ def from_mapping(
+ self, mapping: t.Mapping[str, t.Any] | None = None, **kwargs: t.Any
+ ) -> bool:
+ """Updates the config like :meth:`update` ignoring items with
+ non-upper keys.
+
+ :return: Always returns ``True``.
+
+ .. versionadded:: 0.11
+ """
+ mappings: dict[str, t.Any] = {}
+ if mapping is not None:
+ mappings.update(mapping)
+ mappings.update(kwargs)
+ for key, value in mappings.items():
+ if key.isupper():
+ self[key] = value
+ return True
+
+ def get_namespace(
+ self, namespace: str, lowercase: bool = True, trim_namespace: bool = True
+ ) -> dict[str, t.Any]:
+ """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 self.items():
+ 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) -> str:
+ return f"<{type(self).__name__} {dict.__repr__(self)}>"
diff --git a/venv/Lib/site-packages/flask/ctx.py b/venv/Lib/site-packages/flask/ctx.py
new file mode 100644
index 0000000..9b164d3
--- /dev/null
+++ b/venv/Lib/site-packages/flask/ctx.py
@@ -0,0 +1,449 @@
+from __future__ import annotations
+
+import contextvars
+import sys
+import typing as t
+from functools import update_wrapper
+from types import TracebackType
+
+from werkzeug.exceptions import HTTPException
+
+from . import typing as ft
+from .globals import _cv_app
+from .globals import _cv_request
+from .signals import appcontext_popped
+from .signals import appcontext_pushed
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import WSGIEnvironment
+
+ from .app import Flask
+ from .sessions import SessionMixin
+ from .wrappers import Request
+
+
+# a singleton sentinel value for parameter defaults
+_sentinel = object()
+
+
+class _AppCtxGlobals:
+ """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
+ """
+
+ # Define attr methods to let mypy know this is a namespace object
+ # that has arbitrary attributes.
+
+ def __getattr__(self, name: str) -> t.Any:
+ try:
+ return self.__dict__[name]
+ except KeyError:
+ raise AttributeError(name) from None
+
+ def __setattr__(self, name: str, value: t.Any) -> None:
+ self.__dict__[name] = value
+
+ def __delattr__(self, name: str) -> None:
+ try:
+ del self.__dict__[name]
+ except KeyError:
+ raise AttributeError(name) from None
+
+ def get(self, name: str, default: t.Any | None = None) -> t.Any:
+ """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: str, default: t.Any = _sentinel) -> t.Any:
+ """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 raising 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: str, default: t.Any = None) -> t.Any:
+ """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: str) -> bool:
+ return item in self.__dict__
+
+ def __iter__(self) -> t.Iterator[str]:
+ return iter(self.__dict__)
+
+ def __repr__(self) -> str:
+ ctx = _cv_app.get(None)
+ if ctx is not None:
+ return f""
+ return object.__repr__(self)
+
+
+def after_this_request(
+ f: ft.AfterRequestCallable[t.Any],
+) -> ft.AfterRequestCallable[t.Any]:
+ """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
+ """
+ ctx = _cv_request.get(None)
+
+ if ctx is None:
+ raise RuntimeError(
+ "'after_this_request' can only be used when a request"
+ " context is active, such as in a view function."
+ )
+
+ ctx._after_request_functions.append(f)
+ return f
+
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+
+
+def copy_current_request_context(f: F) -> 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. The current session is also
+ included in the copied request context.
+
+ 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 or
+ # flask.session like you would otherwise in the view function.
+ ...
+ gevent.spawn(do_some_work)
+ return 'Regular response'
+
+ .. versionadded:: 0.10
+ """
+ ctx = _cv_request.get(None)
+
+ if ctx is None:
+ raise RuntimeError(
+ "'copy_current_request_context' can only be used when a"
+ " request context is active, such as in a view function."
+ )
+
+ ctx = ctx.copy()
+
+ def wrapper(*args: t.Any, **kwargs: t.Any) -> t.Any:
+ with ctx: # type: ignore[union-attr]
+ return ctx.app.ensure_sync(f)(*args, **kwargs) # type: ignore[union-attr]
+
+ return update_wrapper(wrapper, f) # type: ignore[return-value]
+
+
+def has_request_context() -> bool:
+ """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 _cv_request.get(None) is not None
+
+
+def has_app_context() -> bool:
+ """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 _cv_app.get(None) is not None
+
+
+class AppContext:
+ """The app context contains application-specific information. An app
+ context is created and pushed at the beginning of each request if
+ one is not already active. An app context is also pushed when
+ running CLI commands.
+ """
+
+ def __init__(self, app: Flask) -> None:
+ self.app = app
+ self.url_adapter = app.create_url_adapter(None)
+ self.g: _AppCtxGlobals = app.app_ctx_globals_class()
+ self._cv_tokens: list[contextvars.Token[AppContext]] = []
+
+ def push(self) -> None:
+ """Binds the app context to the current context."""
+ self._cv_tokens.append(_cv_app.set(self))
+ appcontext_pushed.send(self.app, _async_wrapper=self.app.ensure_sync)
+
+ def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
+ """Pops the app context."""
+ try:
+ if len(self._cv_tokens) == 1:
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_appcontext(exc)
+ finally:
+ ctx = _cv_app.get()
+ _cv_app.reset(self._cv_tokens.pop())
+
+ if ctx is not self:
+ raise AssertionError(
+ f"Popped wrong app context. ({ctx!r} instead of {self!r})"
+ )
+
+ appcontext_popped.send(self.app, _async_wrapper=self.app.ensure_sync)
+
+ def __enter__(self) -> AppContext:
+ self.push()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type | None,
+ exc_value: BaseException | None,
+ tb: TracebackType | None,
+ ) -> None:
+ self.pop(exc_value)
+
+
+class RequestContext:
+ """The request context contains per-request information. The Flask
+ app creates and pushes it at the beginning of the request, then pops
+ it at the end of the request. 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. When using the interactive debugger, the context will be
+ restored so ``request`` is still accessible. Similarly, the test
+ client can preserve the context after the request ends. However,
+ teardown functions may already have closed some resources such as
+ database connections.
+ """
+
+ def __init__(
+ self,
+ app: Flask,
+ environ: WSGIEnvironment,
+ request: Request | None = None,
+ session: SessionMixin | None = None,
+ ) -> None:
+ self.app = app
+ if request is None:
+ request = app.request_class(environ)
+ request.json_module = app.json
+ self.request: Request = request
+ self.url_adapter = None
+ try:
+ self.url_adapter = app.create_url_adapter(self.request)
+ except HTTPException as e:
+ self.request.routing_exception = e
+ self.flashes: list[tuple[str, str]] | None = None
+ self.session: SessionMixin | None = session
+ # 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: list[ft.AfterRequestCallable[t.Any]] = []
+
+ self._cv_tokens: list[
+ tuple[contextvars.Token[RequestContext], AppContext | None]
+ ] = []
+
+ def copy(self) -> RequestContext:
+ """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
+
+ .. versionchanged:: 1.1
+ The current session object is used instead of reloading the original
+ data. This prevents `flask.session` pointing to an out-of-date object.
+ """
+ return self.__class__(
+ self.app,
+ environ=self.request.environ,
+ request=self.request,
+ session=self.session,
+ )
+
+ def match_request(self) -> None:
+ """Can be overridden by a subclass to hook into the matching
+ of the request.
+ """
+ try:
+ result = self.url_adapter.match(return_rule=True) # type: ignore
+ self.request.url_rule, self.request.view_args = result # type: ignore
+ except HTTPException as e:
+ self.request.routing_exception = e
+
+ def push(self) -> None:
+ # Before we push the request context we have to ensure that there
+ # is an application context.
+ app_ctx = _cv_app.get(None)
+
+ if app_ctx is None or app_ctx.app is not self.app:
+ app_ctx = self.app.app_context()
+ app_ctx.push()
+ else:
+ app_ctx = None
+
+ self._cv_tokens.append((_cv_request.set(self), app_ctx))
+
+ # 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)
+
+ # Match the request URL after loading the session, so that the
+ # session is available in custom URL converters.
+ if self.url_adapter is not None:
+ self.match_request()
+
+ def pop(self, exc: BaseException | None = _sentinel) -> None: # type: ignore
+ """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.
+ """
+ clear_request = len(self._cv_tokens) == 1
+
+ try:
+ if clear_request:
+ if exc is _sentinel:
+ exc = sys.exc_info()[1]
+ self.app.do_teardown_request(exc)
+
+ request_close = getattr(self.request, "close", None)
+ if request_close is not None:
+ request_close()
+ finally:
+ ctx = _cv_request.get()
+ token, app_ctx = self._cv_tokens.pop()
+ _cv_request.reset(token)
+
+ # 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:
+ ctx.request.environ["werkzeug.request"] = None
+
+ if app_ctx is not None:
+ app_ctx.pop(exc)
+
+ if ctx is not self:
+ raise AssertionError(
+ f"Popped wrong request context. ({ctx!r} instead of {self!r})"
+ )
+
+ def __enter__(self) -> RequestContext:
+ self.push()
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type | None,
+ exc_value: BaseException | None,
+ tb: TracebackType | None,
+ ) -> None:
+ self.pop(exc_value)
+
+ def __repr__(self) -> str:
+ return (
+ f"<{type(self).__name__} {self.request.url!r}"
+ f" [{self.request.method}] of {self.app.name}>"
+ )
diff --git a/venv/Lib/site-packages/flask/debughelpers.py b/venv/Lib/site-packages/flask/debughelpers.py
new file mode 100644
index 0000000..2c8c4c4
--- /dev/null
+++ b/venv/Lib/site-packages/flask/debughelpers.py
@@ -0,0 +1,178 @@
+from __future__ import annotations
+
+import typing as t
+
+from jinja2.loaders import BaseLoader
+from werkzeug.routing import RequestRedirect
+
+from .blueprints import Blueprint
+from .globals import request_ctx
+from .sansio.app import App
+
+if t.TYPE_CHECKING:
+ from .sansio.scaffold import Scaffold
+ from .wrappers import Request
+
+
+class UnexpectedUnicodeError(AssertionError, UnicodeError):
+ """Raised in places where we want some better error reporting for
+ unexpected unicode or binary data.
+ """
+
+
+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: Request, key: str) -> None:
+ form_matches = request.form.getlist(key)
+ buf = [
+ f"You tried to access the file {key!r} in the request.files"
+ " dictionary but it does not exist. The mimetype for the"
+ f" request is {request.mimetype!r} 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.'
+ ]
+ if form_matches:
+ names = ", ".join(repr(x) for x in form_matches)
+ buf.append(
+ "\n\nThe browser instead transmitted some file names. "
+ f"This was submitted: {names}"
+ )
+ self.msg = "".join(buf)
+
+ def __str__(self) -> str:
+ return self.msg
+
+
+class FormDataRoutingRedirect(AssertionError):
+ """This exception is raised in debug mode if a routing redirect
+ would cause the browser to drop the method or body. This happens
+ when method is not GET, HEAD or OPTIONS and the status code is not
+ 307 or 308.
+ """
+
+ def __init__(self, request: Request) -> None:
+ exc = request.routing_exception
+ assert isinstance(exc, RequestRedirect)
+ buf = [
+ f"A request was sent to '{request.url}', but routing issued"
+ f" a redirect to the canonical URL '{exc.new_url}'."
+ ]
+
+ if f"{request.base_url}/" == exc.new_url.partition("?")[0]:
+ buf.append(
+ " The URL was defined with a trailing slash. Flask"
+ " will redirect to the URL with a trailing slash if it"
+ " was accessed without one."
+ )
+
+ buf.append(
+ " Send requests to the canonical URL, or use 307 or 308 for"
+ " routing redirects. Otherwise, browsers will drop form"
+ " data.\n\n"
+ "This exception is only raised in debug mode."
+ )
+ super().__init__("".join(buf))
+
+
+def attach_enctype_error_multidict(request: Request) -> None:
+ """Patch ``request.files.__getitem__`` to raise a descriptive error
+ about ``enctype=multipart/form-data``.
+
+ :param request: The request to patch.
+ :meta private:
+ """
+ oldcls = request.files.__class__
+
+ class newcls(oldcls): # type: ignore[valid-type, misc]
+ def __getitem__(self, key: str) -> t.Any:
+ try:
+ return super().__getitem__(key)
+ except KeyError as e:
+ if key not in request.form:
+ raise
+
+ raise DebugFilesKeyError(request, key).with_traceback(
+ e.__traceback__
+ ) from None
+
+ newcls.__name__ = oldcls.__name__
+ newcls.__module__ = oldcls.__module__
+ request.files.__class__ = newcls
+
+
+def _dump_loader_info(loader: BaseLoader) -> t.Iterator[str]:
+ yield f"class: {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) for x in value):
+ continue
+ yield f"{key}:"
+ for item in value:
+ yield f" - {item}"
+ continue
+ elif not isinstance(value, (str, int, float, bool)):
+ continue
+ yield f"{key}: {value!r}"
+
+
+def explain_template_loading_attempts(
+ app: App,
+ template: str,
+ attempts: list[
+ tuple[
+ BaseLoader,
+ Scaffold,
+ tuple[str, str | None, t.Callable[[], bool] | None] | None,
+ ]
+ ],
+) -> None:
+ """This should help developers understand what failed"""
+ info = [f"Locating template {template!r}:"]
+ total_found = 0
+ blueprint = None
+ if request_ctx and request_ctx.request.blueprint is not None:
+ blueprint = request_ctx.request.blueprint
+
+ for idx, (loader, srcobj, triple) in enumerate(attempts):
+ if isinstance(srcobj, App):
+ src_info = f"application {srcobj.import_name!r}"
+ elif isinstance(srcobj, Blueprint):
+ src_info = f"blueprint {srcobj.name!r} ({srcobj.import_name})"
+ else:
+ src_info = repr(srcobj)
+
+ info.append(f"{idx + 1:5}: trying loader of {src_info}")
+
+ for line in _dump_loader_info(loader):
+ info.append(f" {line}")
+
+ if triple is None:
+ detail = "no match"
+ else:
+ detail = f"found ({triple[1] or ''!r})"
+ total_found += 1
+ info.append(f" -> {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"
+ f" to the blueprint {blueprint!r}."
+ )
+ info.append(" Maybe you did not place a template in the right folder?")
+ info.append(" See https://flask.palletsprojects.com/blueprints/#templates")
+
+ app.logger.info("\n".join(info))
diff --git a/venv/Lib/site-packages/flask/globals.py b/venv/Lib/site-packages/flask/globals.py
new file mode 100644
index 0000000..e2c410c
--- /dev/null
+++ b/venv/Lib/site-packages/flask/globals.py
@@ -0,0 +1,51 @@
+from __future__ import annotations
+
+import typing as t
+from contextvars import ContextVar
+
+from werkzeug.local import LocalProxy
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+ from .ctx import _AppCtxGlobals
+ from .ctx import AppContext
+ from .ctx import RequestContext
+ from .sessions import SessionMixin
+ from .wrappers import Request
+
+
+_no_app_msg = """\
+Working outside of application context.
+
+This typically means that you attempted to use functionality that needed
+the current application. To solve this, set up an application context
+with app.app_context(). See the documentation for more information.\
+"""
+_cv_app: ContextVar[AppContext] = ContextVar("flask.app_ctx")
+app_ctx: AppContext = LocalProxy( # type: ignore[assignment]
+ _cv_app, unbound_message=_no_app_msg
+)
+current_app: Flask = LocalProxy( # type: ignore[assignment]
+ _cv_app, "app", unbound_message=_no_app_msg
+)
+g: _AppCtxGlobals = LocalProxy( # type: ignore[assignment]
+ _cv_app, "g", unbound_message=_no_app_msg
+)
+
+_no_req_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.\
+"""
+_cv_request: ContextVar[RequestContext] = ContextVar("flask.request_ctx")
+request_ctx: RequestContext = LocalProxy( # type: ignore[assignment]
+ _cv_request, unbound_message=_no_req_msg
+)
+request: Request = LocalProxy( # type: ignore[assignment]
+ _cv_request, "request", unbound_message=_no_req_msg
+)
+session: SessionMixin = LocalProxy( # type: ignore[assignment]
+ _cv_request, "session", unbound_message=_no_req_msg
+)
diff --git a/venv/Lib/site-packages/flask/helpers.py b/venv/Lib/site-packages/flask/helpers.py
new file mode 100644
index 0000000..359a842
--- /dev/null
+++ b/venv/Lib/site-packages/flask/helpers.py
@@ -0,0 +1,621 @@
+from __future__ import annotations
+
+import importlib.util
+import os
+import sys
+import typing as t
+from datetime import datetime
+from functools import lru_cache
+from functools import update_wrapper
+
+import werkzeug.utils
+from werkzeug.exceptions import abort as _wz_abort
+from werkzeug.utils import redirect as _wz_redirect
+from werkzeug.wrappers import Response as BaseResponse
+
+from .globals import _cv_request
+from .globals import current_app
+from .globals import request
+from .globals import request_ctx
+from .globals import session
+from .signals import message_flashed
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .wrappers import Response
+
+
+def get_debug_flag() -> bool:
+ """Get whether debug mode should be enabled for the app, indicated by the
+ :envvar:`FLASK_DEBUG` environment variable. The default is ``False``.
+ """
+ val = os.environ.get("FLASK_DEBUG")
+ return bool(val and val.lower() not in {"0", "false", "no"})
+
+
+def get_load_dotenv(default: bool = True) -> bool:
+ """Get whether the user has disabled loading default dotenv files by
+ setting :envvar:`FLASK_SKIP_DOTENV`. The default is ``True``, load
+ the files.
+
+ :param default: What to return if the env var isn't set.
+ """
+ val = os.environ.get("FLASK_SKIP_DOTENV")
+
+ if not val:
+ return default
+
+ return val.lower() in ("0", "false", "no")
+
+
+def stream_with_context(
+ generator_or_function: t.Iterator[t.AnyStr] | t.Callable[..., t.Iterator[t.AnyStr]],
+) -> t.Iterator[t.AnyStr]:
+ """Request contexts disappear when the response is started on the server.
+ This is done for efficiency reasons and to make it less likely to encounter
+ memory leaks with badly written WSGI middlewares. The downside is that if
+ you are using streamed responses, the generator cannot access request bound
+ information any more.
+
+ This function however can help you keep the context around for longer::
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ @stream_with_context
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(generate())
+
+ Alternatively it can also be used around a specific generator::
+
+ from flask import stream_with_context, request, Response
+
+ @app.route('/stream')
+ def streamed_response():
+ def generate():
+ yield 'Hello '
+ yield request.args['name']
+ yield '!'
+ return Response(stream_with_context(generate()))
+
+ .. versionadded:: 0.9
+ """
+ try:
+ gen = iter(generator_or_function) # type: ignore[arg-type]
+ except TypeError:
+
+ def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any:
+ gen = generator_or_function(*args, **kwargs) # type: ignore[operator]
+ return stream_with_context(gen)
+
+ return update_wrapper(decorator, generator_or_function) # type: ignore[arg-type]
+
+ def generator() -> t.Iterator[t.AnyStr | None]:
+ ctx = _cv_request.get(None)
+ if ctx is None:
+ raise RuntimeError(
+ "'stream_with_context' can only be used when a request"
+ " context is active, such as in a view function."
+ )
+ with ctx:
+ # Dummy sentinel. Has to be inside the context block or we're
+ # not actually keeping the context around.
+ yield None
+
+ # The try/finally is here so that if someone passes a WSGI level
+ # iterator in we're still running the cleanup logic. Generators
+ # don't need that because they are closed on their destruction
+ # automatically.
+ try:
+ yield from gen
+ finally:
+ if hasattr(gen, "close"):
+ gen.close()
+
+ # The trick is to start the generator. Then the code execution runs until
+ # the first dummy None is yielded at which point the context was already
+ # pushed. This item is discarded. Then when the iteration continues the
+ # real generator is executed.
+ wrapped_g = generator()
+ next(wrapped_g)
+ return wrapped_g # type: ignore[return-value]
+
+
+def make_response(*args: t.Any) -> Response:
+ """Sometimes it is necessary to set additional headers in a view. Because
+ views do not have to return response objects but can return a value that
+ is converted into a response object by Flask itself, it becomes tricky to
+ add headers to it. This function can be called instead of using a return
+ and you will get a response object which you can use to attach headers.
+
+ If view looked like this and you want to add a new header::
+
+ def index():
+ return render_template('index.html', foo=42)
+
+ You can now do something like this::
+
+ def index():
+ response = make_response(render_template('index.html', foo=42))
+ response.headers['X-Parachutes'] = 'parachutes are cool'
+ return response
+
+ This function accepts the very same arguments you can return from a
+ view function. This for example creates a response with a 404 error
+ code::
+
+ response = make_response(render_template('not_found.html'), 404)
+
+ The other use case of this function is to force the return value of a
+ view function into a response which is helpful with view
+ decorators::
+
+ response = make_response(view_function())
+ response.headers['X-Parachutes'] = 'parachutes are cool'
+
+ Internally this function does the following things:
+
+ - if no arguments are passed, it creates a new response argument
+ - if one argument is passed, :meth:`flask.Flask.make_response`
+ is invoked with it.
+ - if more than one argument is passed, the arguments are passed
+ to the :meth:`flask.Flask.make_response` function as tuple.
+
+ .. versionadded:: 0.6
+ """
+ if not args:
+ return current_app.response_class()
+ if len(args) == 1:
+ args = args[0]
+ return current_app.make_response(args)
+
+
+def url_for(
+ endpoint: str,
+ *,
+ _anchor: str | None = None,
+ _method: str | None = None,
+ _scheme: str | None = None,
+ _external: bool | None = None,
+ **values: t.Any,
+) -> str:
+ """Generate a URL to the given endpoint with the given values.
+
+ This requires an active request or application context, and calls
+ :meth:`current_app.url_for() `. See that method
+ for full documentation.
+
+ :param endpoint: The endpoint name associated with the URL to
+ generate. If this starts with a ``.``, the current blueprint
+ name (if any) will be used.
+ :param _anchor: If given, append this as ``#anchor`` to the URL.
+ :param _method: If given, generate the URL associated with this
+ method for the endpoint.
+ :param _scheme: If given, the URL will have this scheme if it is
+ external.
+ :param _external: If given, prefer the URL to be internal (False) or
+ require it to be external (True). External URLs include the
+ scheme and domain. When not in an active request, URLs are
+ external by default.
+ :param values: Values to use for the variable parts of the URL rule.
+ Unknown keys are appended as query string arguments, like
+ ``?a=b&c=d``.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.url_for``, allowing an app to override the
+ behavior.
+
+ .. versionchanged:: 0.10
+ The ``_scheme`` parameter was added.
+
+ .. versionchanged:: 0.9
+ The ``_anchor`` and ``_method`` parameters were added.
+
+ .. versionchanged:: 0.9
+ Calls ``app.handle_url_build_error`` on build errors.
+ """
+ return current_app.url_for(
+ endpoint,
+ _anchor=_anchor,
+ _method=_method,
+ _scheme=_scheme,
+ _external=_external,
+ **values,
+ )
+
+
+def redirect(
+ location: str, code: int = 302, Response: type[BaseResponse] | None = None
+) -> BaseResponse:
+ """Create a redirect response object.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`~flask.Flask.redirect` method, otherwise it will use
+ :func:`werkzeug.utils.redirect`.
+
+ :param location: The URL to redirect to.
+ :param code: The status code for the redirect.
+ :param Response: The response class to use. Not used when
+ ``current_app`` is active, which uses ``app.response_class``.
+
+ .. versionadded:: 2.2
+ Calls ``current_app.redirect`` if available instead of always
+ using Werkzeug's default ``redirect``.
+ """
+ if current_app:
+ return current_app.redirect(location, code=code)
+
+ return _wz_redirect(location, code=code, Response=Response)
+
+
+def abort(code: int | BaseResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
+ """Raise an :exc:`~werkzeug.exceptions.HTTPException` for the given
+ status code.
+
+ If :data:`~flask.current_app` is available, it will call its
+ :attr:`~flask.Flask.aborter` object, otherwise it will use
+ :func:`werkzeug.exceptions.abort`.
+
+ :param code: The status code for the exception, which must be
+ registered in ``app.aborter``.
+ :param args: Passed to the exception.
+ :param kwargs: Passed to the exception.
+
+ .. versionadded:: 2.2
+ Calls ``current_app.aborter`` if available instead of always
+ using Werkzeug's default ``abort``.
+ """
+ if current_app:
+ current_app.aborter(code, *args, **kwargs)
+
+ _wz_abort(code, *args, **kwargs)
+
+
+def get_template_attribute(template_name: str, attribute: str) -> t.Any:
+ """Loads a macro (or variable) a template exports. This can be used to
+ invoke a macro from within Python code. If you for example have a
+ template named :file:`_cider.html` with the following contents:
+
+ .. sourcecode:: html+jinja
+
+ {% macro hello(name) %}Hello {{ name }}!{% endmacro %}
+
+ You can access this from Python code like this::
+
+ hello = get_template_attribute('_cider.html', 'hello')
+ return hello('World')
+
+ .. versionadded:: 0.2
+
+ :param template_name: the name of the template
+ :param attribute: the name of the variable of macro to access
+ """
+ return getattr(current_app.jinja_env.get_template(template_name).module, attribute)
+
+
+def flash(message: str, category: str = "message") -> None:
+ """Flashes a message to the next request. In order to remove the
+ flashed message from the session and to display it to the user,
+ the template has to call :func:`get_flashed_messages`.
+
+ .. versionchanged:: 0.3
+ `category` parameter added.
+
+ :param message: the message to be flashed.
+ :param category: the category for the message. The following values
+ are recommended: ``'message'`` for any kind of message,
+ ``'error'`` for errors, ``'info'`` for information
+ messages and ``'warning'`` for warnings. However any
+ kind of string can be used as category.
+ """
+ # Original implementation:
+ #
+ # session.setdefault('_flashes', []).append((category, message))
+ #
+ # This assumed that changes made to mutable structures in the session are
+ # always in sync with the session object, which is not true for session
+ # implementations that use external storage for keeping their keys/values.
+ flashes = session.get("_flashes", [])
+ flashes.append((category, message))
+ session["_flashes"] = flashes
+ app = current_app._get_current_object() # type: ignore
+ message_flashed.send(
+ app,
+ _async_wrapper=app.ensure_sync,
+ message=message,
+ category=category,
+ )
+
+
+def get_flashed_messages(
+ with_categories: bool = False, category_filter: t.Iterable[str] = ()
+) -> list[str] | list[tuple[str, str]]:
+ """Pulls all flashed messages from the session and returns them.
+ Further calls in the same request to the function will return
+ the same messages. By default just the messages are returned,
+ but when `with_categories` is set to ``True``, the return value will
+ be a list of tuples in the form ``(category, message)`` instead.
+
+ Filter the flashed messages to one or more categories by providing those
+ categories in `category_filter`. This allows rendering categories in
+ separate html blocks. The `with_categories` and `category_filter`
+ arguments are distinct:
+
+ * `with_categories` controls whether categories are returned with message
+ text (``True`` gives a tuple, where ``False`` gives just the message text).
+ * `category_filter` filters the messages down to only those matching the
+ provided categories.
+
+ See :doc:`/patterns/flashing` for examples.
+
+ .. versionchanged:: 0.3
+ `with_categories` parameter added.
+
+ .. versionchanged:: 0.9
+ `category_filter` parameter added.
+
+ :param with_categories: set to ``True`` to also receive categories.
+ :param category_filter: filter of categories to limit return values. Only
+ categories in the list will be returned.
+ """
+ flashes = request_ctx.flashes
+ if flashes is None:
+ flashes = session.pop("_flashes") if "_flashes" in session else []
+ request_ctx.flashes = flashes
+ if category_filter:
+ flashes = list(filter(lambda f: f[0] in category_filter, flashes))
+ if not with_categories:
+ return [x[1] for x in flashes]
+ return flashes
+
+
+def _prepare_send_file_kwargs(**kwargs: t.Any) -> dict[str, t.Any]:
+ if kwargs.get("max_age") is None:
+ kwargs["max_age"] = current_app.get_send_file_max_age
+
+ kwargs.update(
+ environ=request.environ,
+ use_x_sendfile=current_app.config["USE_X_SENDFILE"],
+ response_class=current_app.response_class,
+ _root_path=current_app.root_path, # type: ignore
+ )
+ return kwargs
+
+
+def send_file(
+ path_or_file: os.PathLike[t.AnyStr] | str | t.BinaryIO,
+ mimetype: str | None = None,
+ as_attachment: bool = False,
+ download_name: str | None = None,
+ conditional: bool = True,
+ etag: bool | str = True,
+ last_modified: datetime | int | float | None = None,
+ max_age: None | (int | t.Callable[[str | None], int | None]) = None,
+) -> Response:
+ """Send the contents of a file to the client.
+
+ The first argument can be a file path or a file-like object. Paths
+ are preferred in most cases because Werkzeug can manage the file and
+ get extra information from the path. Passing a file-like object
+ requires that the file is opened in binary mode, and is mostly
+ useful when building a file in memory with :class:`io.BytesIO`.
+
+ Never pass file paths provided by a user. The path is assumed to be
+ trusted, so a user could craft a path to access a file you didn't
+ intend. Use :func:`send_from_directory` to safely serve
+ user-requested paths from within a directory.
+
+ If the WSGI server sets a ``file_wrapper`` in ``environ``, it is
+ used, otherwise Werkzeug's built-in wrapper is used. Alternatively,
+ if the HTTP server supports ``X-Sendfile``, configuring Flask with
+ ``USE_X_SENDFILE = True`` will tell the server to send the given
+ path, which is much more efficient than reading it in Python.
+
+ :param path_or_file: The path to the file to send, relative to the
+ current working directory if a relative path is given.
+ Alternatively, a file-like object opened in binary mode. Make
+ sure the file pointer is seeked to the start of the data.
+ :param mimetype: The MIME type to send for the file. If not
+ provided, it will try to detect it from the file name.
+ :param as_attachment: Indicate to a browser that it should offer to
+ save the file instead of displaying it.
+ :param download_name: The default name browsers will use when saving
+ the file. Defaults to the passed file name.
+ :param conditional: Enable conditional and range responses based on
+ request headers. Requires passing a file path and ``environ``.
+ :param etag: Calculate an ETag for the file, which requires passing
+ a file path. Can also be a string to use instead.
+ :param last_modified: The last modified time to send for the file,
+ in seconds. If not provided, it will try to detect it from the
+ file path.
+ :param max_age: How long the client should cache the file, in
+ seconds. If set, ``Cache-Control`` will be ``public``, otherwise
+ it will be ``no-cache`` to prefer conditional caching.
+
+ .. versionchanged:: 2.0
+ ``download_name`` replaces the ``attachment_filename``
+ parameter. If ``as_attachment=False``, it is passed with
+ ``Content-Disposition: inline`` instead.
+
+ .. versionchanged:: 2.0
+ ``max_age`` replaces the ``cache_timeout`` parameter.
+ ``conditional`` is enabled and ``max_age`` is not set by
+ default.
+
+ .. versionchanged:: 2.0
+ ``etag`` replaces the ``add_etags`` parameter. It can be a
+ string to use instead of generating one.
+
+ .. versionchanged:: 2.0
+ Passing a file-like object that inherits from
+ :class:`~io.TextIOBase` will raise a :exc:`ValueError` rather
+ than sending an empty file.
+
+ .. versionadded:: 2.0
+ Moved the implementation to Werkzeug. This is now a wrapper to
+ pass some Flask-specific arguments.
+
+ .. versionchanged:: 1.1
+ ``filename`` may be a :class:`~os.PathLike` object.
+
+ .. versionchanged:: 1.1
+ Passing a :class:`~io.BytesIO` object supports range requests.
+
+ .. versionchanged:: 1.0.3
+ Filenames are encoded with ASCII instead of Latin-1 for broader
+ compatibility with WSGI servers.
+
+ .. versionchanged:: 1.0
+ UTF-8 filenames as specified in :rfc:`2231` are supported.
+
+ .. versionchanged:: 0.12
+ The filename is no longer automatically inferred from file
+ objects. If you want to use automatic MIME and etag support,
+ pass a filename via ``filename_or_fp`` or
+ ``attachment_filename``.
+
+ .. versionchanged:: 0.12
+ ``attachment_filename`` is preferred over ``filename`` for MIME
+ detection.
+
+ .. versionchanged:: 0.9
+ ``cache_timeout`` defaults to
+ :meth:`Flask.get_send_file_max_age`.
+
+ .. versionchanged:: 0.7
+ MIME guessing and etag support for file-like objects was
+ removed because it was unreliable. Pass a filename if you are
+ able to, otherwise attach an etag yourself.
+
+ .. versionchanged:: 0.5
+ The ``add_etags``, ``cache_timeout`` and ``conditional``
+ parameters were added. The default behavior is to add etags.
+
+ .. versionadded:: 0.2
+ """
+ return werkzeug.utils.send_file( # type: ignore[return-value]
+ **_prepare_send_file_kwargs(
+ path_or_file=path_or_file,
+ environ=request.environ,
+ mimetype=mimetype,
+ as_attachment=as_attachment,
+ download_name=download_name,
+ conditional=conditional,
+ etag=etag,
+ last_modified=last_modified,
+ max_age=max_age,
+ )
+ )
+
+
+def send_from_directory(
+ directory: os.PathLike[str] | str,
+ path: os.PathLike[str] | str,
+ **kwargs: t.Any,
+) -> Response:
+ """Send a file from within a directory using :func:`send_file`.
+
+ .. code-block:: python
+
+ @app.route("/uploads/")
+ def download_file(name):
+ return send_from_directory(
+ app.config['UPLOAD_FOLDER'], name, as_attachment=True
+ )
+
+ This is a secure way to serve files from a folder, such as static
+ files or uploads. Uses :func:`~werkzeug.security.safe_join` to
+ ensure the path coming from the client is not maliciously crafted to
+ point outside the specified directory.
+
+ If the final path does not point to an existing regular file,
+ raises a 404 :exc:`~werkzeug.exceptions.NotFound` error.
+
+ :param directory: The directory that ``path`` must be located under,
+ relative to the current application's root path.
+ :param path: The path to the file to send, relative to
+ ``directory``.
+ :param kwargs: Arguments to pass to :func:`send_file`.
+
+ .. versionchanged:: 2.0
+ ``path`` replaces the ``filename`` parameter.
+
+ .. versionadded:: 2.0
+ Moved the implementation to Werkzeug. This is now a wrapper to
+ pass some Flask-specific arguments.
+
+ .. versionadded:: 0.5
+ """
+ return werkzeug.utils.send_from_directory( # type: ignore[return-value]
+ directory, path, **_prepare_send_file_kwargs(**kwargs)
+ )
+
+
+def get_root_path(import_name: str) -> str:
+ """Find the root path of a package, or the path that contains a
+ module. If it cannot be found, returns the current working
+ directory.
+
+ Not to be confused with the value returned by :func:`find_package`.
+
+ :meta private:
+ """
+ # Module already imported and has a file attribute. Use that first.
+ mod = sys.modules.get(import_name)
+
+ if mod is not None and hasattr(mod, "__file__") and mod.__file__ is not None:
+ return os.path.dirname(os.path.abspath(mod.__file__))
+
+ # Next attempt: check the loader.
+ try:
+ spec = importlib.util.find_spec(import_name)
+
+ if spec is None:
+ raise ValueError
+ except (ImportError, ValueError):
+ loader = None
+ else:
+ loader = spec.loader
+
+ # Loader does not exist or we're referring to an unloaded main
+ # module or a main module without path (interactive sessions), go
+ # with the current working directory.
+ if loader is None:
+ return os.getcwd()
+
+ if hasattr(loader, "get_filename"):
+ filepath = loader.get_filename(import_name)
+ else:
+ # Fall back to imports.
+ __import__(import_name)
+ mod = sys.modules[import_name]
+ filepath = getattr(mod, "__file__", None)
+
+ # If we don't have a file path it might be because it is a
+ # namespace package. In this case pick the root path from the
+ # first module that is contained in the package.
+ if filepath is None:
+ raise RuntimeError(
+ "No root path can be found for the provided module"
+ f" {import_name!r}. This can happen because the module"
+ " came from an import hook that does not provide file"
+ " name information or because it's a namespace package."
+ " In this case the root path needs to be explicitly"
+ " provided."
+ )
+
+ # filepath is import_name.py for a module, or __init__.py for a package.
+ return os.path.dirname(os.path.abspath(filepath)) # type: ignore[no-any-return]
+
+
+@lru_cache(maxsize=None)
+def _split_blueprint_path(name: str) -> list[str]:
+ out: list[str] = [name]
+
+ if "." in name:
+ out.extend(_split_blueprint_path(name.rpartition(".")[0]))
+
+ return out
diff --git a/venv/Lib/site-packages/flask/json/__init__.py b/venv/Lib/site-packages/flask/json/__init__.py
new file mode 100644
index 0000000..c0941d0
--- /dev/null
+++ b/venv/Lib/site-packages/flask/json/__init__.py
@@ -0,0 +1,170 @@
+from __future__ import annotations
+
+import json as _json
+import typing as t
+
+from ..globals import current_app
+from .provider import _default
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from ..wrappers import Response
+
+
+def dumps(obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.dumps() `
+ method, otherwise it will use :func:`json.dumps`.
+
+ :param obj: The data to serialize.
+ :param kwargs: Arguments passed to the ``dumps`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.dumps``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0.2
+ :class:`decimal.Decimal` is supported by converting to a string.
+
+ .. versionchanged:: 2.0
+ ``encoding`` will be removed in Flask 2.1.
+
+ .. versionchanged:: 1.0.3
+ ``app`` can be passed directly, rather than requiring an app
+ context for configuration.
+ """
+ if current_app:
+ return current_app.json.dumps(obj, **kwargs)
+
+ kwargs.setdefault("default", _default)
+ return _json.dumps(obj, **kwargs)
+
+
+def dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+ """Serialize data as JSON and write to a file.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.dump() `
+ method, otherwise it will use :func:`json.dump`.
+
+ :param obj: The data to serialize.
+ :param fp: A file opened for writing text. Should use the UTF-8
+ encoding to be valid JSON.
+ :param kwargs: Arguments passed to the ``dump`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.dump``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0
+ Writing to a binary file, and the ``encoding`` argument, will be
+ removed in Flask 2.1.
+ """
+ if current_app:
+ current_app.json.dump(obj, fp, **kwargs)
+ else:
+ kwargs.setdefault("default", _default)
+ _json.dump(obj, fp, **kwargs)
+
+
+def loads(s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.loads() `
+ method, otherwise it will use :func:`json.loads`.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: Arguments passed to the ``loads`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.loads``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0
+ ``encoding`` will be removed in Flask 2.1. The data must be a
+ string or UTF-8 bytes.
+
+ .. versionchanged:: 1.0.3
+ ``app`` can be passed directly, rather than requiring an app
+ context for configuration.
+ """
+ if current_app:
+ return current_app.json.loads(s, **kwargs)
+
+ return _json.loads(s, **kwargs)
+
+
+def load(fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON read from a file.
+
+ If :data:`~flask.current_app` is available, it will use its
+ :meth:`app.json.load() `
+ method, otherwise it will use :func:`json.load`.
+
+ :param fp: A file opened for reading text or UTF-8 bytes.
+ :param kwargs: Arguments passed to the ``load`` implementation.
+
+ .. versionchanged:: 2.3
+ The ``app`` parameter was removed.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.load``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.2
+ The ``app`` parameter will be removed in Flask 2.3.
+
+ .. versionchanged:: 2.0
+ ``encoding`` will be removed in Flask 2.1. The file must be text
+ mode, or binary mode with UTF-8 bytes.
+ """
+ if current_app:
+ return current_app.json.load(fp, **kwargs)
+
+ return _json.load(fp, **kwargs)
+
+
+def jsonify(*args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with the ``application/json``
+ mimetype. A dict or list returned from a view will be converted to a
+ JSON response automatically without needing to call this.
+
+ This requires an active request or application context, and calls
+ :meth:`app.json.response() `.
+
+ In debug mode, the output is formatted with indentation to make it
+ easier to read. This may also be controlled by the provider.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+
+ .. versionchanged:: 2.2
+ Calls ``current_app.json.response``, allowing an app to override
+ the behavior.
+
+ .. versionchanged:: 2.0.2
+ :class:`decimal.Decimal` is supported by converting to a string.
+
+ .. versionchanged:: 0.11
+ Added support for serializing top-level arrays. This was a
+ security risk in ancient browsers. See :ref:`security-json`.
+
+ .. versionadded:: 0.2
+ """
+ return current_app.json.response(*args, **kwargs) # type: ignore[return-value]
diff --git a/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..99cface
Binary files /dev/null and b/venv/Lib/site-packages/flask/json/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc b/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc
new file mode 100644
index 0000000..c4fcd34
Binary files /dev/null and b/venv/Lib/site-packages/flask/json/__pycache__/provider.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc b/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc
new file mode 100644
index 0000000..7a24d86
Binary files /dev/null and b/venv/Lib/site-packages/flask/json/__pycache__/tag.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/json/provider.py b/venv/Lib/site-packages/flask/json/provider.py
new file mode 100644
index 0000000..f9b2e8f
--- /dev/null
+++ b/venv/Lib/site-packages/flask/json/provider.py
@@ -0,0 +1,215 @@
+from __future__ import annotations
+
+import dataclasses
+import decimal
+import json
+import typing as t
+import uuid
+import weakref
+from datetime import date
+
+from werkzeug.http import http_date
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.sansio.response import Response
+
+ from ..sansio.app import App
+
+
+class JSONProvider:
+ """A standard set of JSON operations for an application. Subclasses
+ of this can be used to customize JSON behavior or use different
+ JSON libraries.
+
+ To implement a provider for a specific library, subclass this base
+ class and implement at least :meth:`dumps` and :meth:`loads`. All
+ other methods have default implementations.
+
+ To use a different provider, either subclass ``Flask`` and set
+ :attr:`~flask.Flask.json_provider_class` to a provider class, or set
+ :attr:`app.json ` to an instance of the class.
+
+ :param app: An application instance. This will be stored as a
+ :class:`weakref.proxy` on the :attr:`_app` attribute.
+
+ .. versionadded:: 2.2
+ """
+
+ def __init__(self, app: App) -> None:
+ self._app: App = weakref.proxy(app)
+
+ def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON.
+
+ :param obj: The data to serialize.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ raise NotImplementedError
+
+ def dump(self, obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+ """Serialize data as JSON and write to a file.
+
+ :param obj: The data to serialize.
+ :param fp: A file opened for writing text. Should use the UTF-8
+ encoding to be valid JSON.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ fp.write(self.dumps(obj, **kwargs))
+
+ def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ raise NotImplementedError
+
+ def load(self, fp: t.IO[t.AnyStr], **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON read from a file.
+
+ :param fp: A file opened for reading text or UTF-8 bytes.
+ :param kwargs: May be passed to the underlying JSON library.
+ """
+ return self.loads(fp.read(), **kwargs)
+
+ def _prepare_response_obj(
+ self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
+ ) -> t.Any:
+ if args and kwargs:
+ raise TypeError("app.json.response() takes either args or kwargs, not both")
+
+ if not args and not kwargs:
+ return None
+
+ if len(args) == 1:
+ return args[0]
+
+ return args or kwargs
+
+ def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with the ``application/json``
+ mimetype.
+
+ The :func:`~flask.json.jsonify` function calls this method for
+ the current application.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+ """
+ obj = self._prepare_response_obj(args, kwargs)
+ return self._app.response_class(self.dumps(obj), mimetype="application/json")
+
+
+def _default(o: t.Any) -> t.Any:
+ if isinstance(o, date):
+ return http_date(o)
+
+ if isinstance(o, (decimal.Decimal, uuid.UUID)):
+ return str(o)
+
+ if dataclasses and dataclasses.is_dataclass(o):
+ return dataclasses.asdict(o)
+
+ if hasattr(o, "__html__"):
+ return str(o.__html__())
+
+ raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
+
+
+class DefaultJSONProvider(JSONProvider):
+ """Provide JSON operations using Python's built-in :mod:`json`
+ library. Serializes the following additional data types:
+
+ - :class:`datetime.datetime` and :class:`datetime.date` are
+ serialized to :rfc:`822` strings. This is the same as the HTTP
+ date format.
+ - :class:`uuid.UUID` is serialized to a string.
+ - :class:`dataclasses.dataclass` is passed to
+ :func:`dataclasses.asdict`.
+ - :class:`~markupsafe.Markup` (or any object with a ``__html__``
+ method) will call the ``__html__`` method to get a string.
+ """
+
+ default: t.Callable[[t.Any], t.Any] = staticmethod(_default) # type: ignore[assignment]
+ """Apply this function to any object that :meth:`json.dumps` does
+ not know how to serialize. It should return a valid JSON type or
+ raise a ``TypeError``.
+ """
+
+ ensure_ascii = True
+ """Replace non-ASCII characters with escape sequences. This may be
+ more compatible with some clients, but can be disabled for better
+ performance and size.
+ """
+
+ sort_keys = True
+ """Sort the keys in any serialized dicts. This may be useful for
+ some caching situations, but can be disabled for better performance.
+ When enabled, keys must all be strings, they are not converted
+ before sorting.
+ """
+
+ compact: bool | None = None
+ """If ``True``, or ``None`` out of debug mode, the :meth:`response`
+ output will not add indentation, newlines, or spaces. If ``False``,
+ or ``None`` in debug mode, it will use a non-compact representation.
+ """
+
+ mimetype = "application/json"
+ """The mimetype set in :meth:`response`."""
+
+ def dumps(self, obj: t.Any, **kwargs: t.Any) -> str:
+ """Serialize data as JSON to a string.
+
+ Keyword arguments are passed to :func:`json.dumps`. Sets some
+ parameter defaults from the :attr:`default`,
+ :attr:`ensure_ascii`, and :attr:`sort_keys` attributes.
+
+ :param obj: The data to serialize.
+ :param kwargs: Passed to :func:`json.dumps`.
+ """
+ kwargs.setdefault("default", self.default)
+ kwargs.setdefault("ensure_ascii", self.ensure_ascii)
+ kwargs.setdefault("sort_keys", self.sort_keys)
+ return json.dumps(obj, **kwargs)
+
+ def loads(self, s: str | bytes, **kwargs: t.Any) -> t.Any:
+ """Deserialize data as JSON from a string or bytes.
+
+ :param s: Text or UTF-8 bytes.
+ :param kwargs: Passed to :func:`json.loads`.
+ """
+ return json.loads(s, **kwargs)
+
+ def response(self, *args: t.Any, **kwargs: t.Any) -> Response:
+ """Serialize the given arguments as JSON, and return a
+ :class:`~flask.Response` object with it. The response mimetype
+ will be "application/json" and can be changed with
+ :attr:`mimetype`.
+
+ If :attr:`compact` is ``False`` or debug mode is enabled, the
+ output will be formatted to be easier to read.
+
+ Either positional or keyword arguments can be given, not both.
+ If no arguments are given, ``None`` is serialized.
+
+ :param args: A single value to serialize, or multiple values to
+ treat as a list to serialize.
+ :param kwargs: Treat as a dict to serialize.
+ """
+ obj = self._prepare_response_obj(args, kwargs)
+ dump_args: dict[str, t.Any] = {}
+
+ if (self.compact is None and self._app.debug) or self.compact is False:
+ dump_args.setdefault("indent", 2)
+ else:
+ dump_args.setdefault("separators", (",", ":"))
+
+ return self._app.response_class(
+ f"{self.dumps(obj, **dump_args)}\n", mimetype=self.mimetype
+ )
diff --git a/venv/Lib/site-packages/flask/json/tag.py b/venv/Lib/site-packages/flask/json/tag.py
new file mode 100644
index 0000000..2bb986b
--- /dev/null
+++ b/venv/Lib/site-packages/flask/json/tag.py
@@ -0,0 +1,326 @@
+"""
+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 see an example that adds support for
+:class:`~collections.OrderedDict`. Dicts don't have an order in 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``.
+
+.. code-block:: python
+
+ 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)
+"""
+from __future__ import annotations
+
+import typing as t
+from base64 import b64decode
+from base64 import b64encode
+from datetime import datetime
+from uuid import UUID
+
+from markupsafe import Markup
+from werkzeug.http import http_date
+from werkzeug.http import parse_date
+
+from ..json import dumps
+from ..json import loads
+
+
+class JSONTag:
+ """Base class for defining type tags for :class:`TaggedJSONSerializer`."""
+
+ __slots__ = ("serializer",)
+
+ #: The tag to mark the serialized object with. If empty, this tag is
+ #: only used as an intermediate step during tagging.
+ key: str = ""
+
+ def __init__(self, serializer: TaggedJSONSerializer) -> None:
+ """Create a tagger for the given serializer."""
+ self.serializer = serializer
+
+ def check(self, value: t.Any) -> bool:
+ """Check if the given value should be tagged by this tag."""
+ raise NotImplementedError
+
+ def to_json(self, value: t.Any) -> t.Any:
+ """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: t.Any) -> t.Any:
+ """Convert the JSON representation back to the correct type. The tag
+ will already be removed."""
+ raise NotImplementedError
+
+ def tag(self, value: t.Any) -> dict[str, t.Any]:
+ """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: t.Any) -> bool:
+ return (
+ isinstance(value, dict)
+ and len(value) == 1
+ and next(iter(value)) in self.serializer.tags
+ )
+
+ def to_json(self, value: t.Any) -> t.Any:
+ key = next(iter(value))
+ return {f"{key}__": self.serializer.tag(value[key])}
+
+ def to_python(self, value: t.Any) -> t.Any:
+ key = next(iter(value))
+ return {key[:-2]: value[key]}
+
+
+class PassDict(JSONTag):
+ __slots__ = ()
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, dict)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ # JSON objects may only have string keys, so don't bother tagging the
+ # key here.
+ return {k: self.serializer.tag(v) for k, v in value.items()}
+
+ tag = to_json
+
+
+class TagTuple(JSONTag):
+ __slots__ = ()
+ key = " t"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, tuple)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return [self.serializer.tag(item) for item in value]
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return tuple(value)
+
+
+class PassList(JSONTag):
+ __slots__ = ()
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, list)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return [self.serializer.tag(item) for item in value]
+
+ tag = to_json
+
+
+class TagBytes(JSONTag):
+ __slots__ = ()
+ key = " b"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, bytes)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return b64encode(value).decode("ascii")
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return b64decode(value)
+
+
+class TagMarkup(JSONTag):
+ """Serialize anything matching the :class:`~markupsafe.Markup` API by
+ having a ``__html__`` method to the result of that method. Always
+ deserializes to an instance of :class:`~markupsafe.Markup`."""
+
+ __slots__ = ()
+ key = " m"
+
+ def check(self, value: t.Any) -> bool:
+ return callable(getattr(value, "__html__", None))
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return str(value.__html__())
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return Markup(value)
+
+
+class TagUUID(JSONTag):
+ __slots__ = ()
+ key = " u"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, UUID)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return value.hex
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return UUID(value)
+
+
+class TagDateTime(JSONTag):
+ __slots__ = ()
+ key = " d"
+
+ def check(self, value: t.Any) -> bool:
+ return isinstance(value, datetime)
+
+ def to_json(self, value: t.Any) -> t.Any:
+ return http_date(value)
+
+ def to_python(self, value: t.Any) -> t.Any:
+ return parse_date(value)
+
+
+class TaggedJSONSerializer:
+ """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:`~markupsafe.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) -> None:
+ self.tags: dict[str, JSONTag] = {}
+ self.order: list[JSONTag] = []
+
+ for cls in self.default_tags:
+ self.register(cls)
+
+ def register(
+ self,
+ tag_class: type[JSONTag],
+ force: bool = False,
+ index: int | None = None,
+ ) -> 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:
+ if not force and key in self.tags:
+ raise KeyError(f"Tag '{key}' is already registered.")
+
+ self.tags[key] = tag
+
+ if index is None:
+ self.order.append(tag)
+ else:
+ self.order.insert(index, tag)
+
+ def tag(self, value: t.Any) -> t.Any:
+ """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: dict[str, t.Any]) -> t.Any:
+ """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 _untag_scan(self, value: t.Any) -> t.Any:
+ if isinstance(value, dict):
+ # untag each item recursively
+ value = {k: self._untag_scan(v) for k, v in value.items()}
+ # untag the dict itself
+ value = self.untag(value)
+ elif isinstance(value, list):
+ # untag each item recursively
+ value = [self._untag_scan(item) for item in value]
+
+ return value
+
+ def dumps(self, value: t.Any) -> str:
+ """Tag the value and dump it to a compact JSON string."""
+ return dumps(self.tag(value), separators=(",", ":"))
+
+ def loads(self, value: str) -> t.Any:
+ """Load data from a JSON string and deserialized any tagged objects."""
+ return self._untag_scan(loads(value))
diff --git a/venv/Lib/site-packages/flask/logging.py b/venv/Lib/site-packages/flask/logging.py
new file mode 100644
index 0000000..0cb8f43
--- /dev/null
+++ b/venv/Lib/site-packages/flask/logging.py
@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+import logging
+import sys
+import typing as t
+
+from werkzeug.local import LocalProxy
+
+from .globals import request
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .sansio.app import App
+
+
+@LocalProxy
+def wsgi_errors_stream() -> t.TextIO:
+ """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``.
+ """
+ if request:
+ return request.environ["wsgi.errors"] # type: ignore[no-any-return]
+
+ return sys.stderr
+
+
+def has_level_handler(logger: logging.Logger) -> bool:
+ """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 # type: ignore
+
+ 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) # type: ignore
+default_handler.setFormatter(
+ logging.Formatter("[%(asctime)s] %(levelname)s in %(module)s: %(message)s")
+)
+
+
+def create_logger(app: App) -> logging.Logger:
+ """Get the Flask app's logger and configure it if needed.
+
+ The logger name will be the same as
+ :attr:`app.import_name `.
+
+ 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(app.name)
+
+ if app.debug and not logger.level:
+ logger.setLevel(logging.DEBUG)
+
+ if not has_level_handler(logger):
+ logger.addHandler(default_handler)
+
+ return logger
diff --git a/venv/Lib/site-packages/flask/py.typed b/venv/Lib/site-packages/flask/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/flask/sansio/README.md b/venv/Lib/site-packages/flask/sansio/README.md
new file mode 100644
index 0000000..623ac19
--- /dev/null
+++ b/venv/Lib/site-packages/flask/sansio/README.md
@@ -0,0 +1,6 @@
+# Sansio
+
+This folder contains code that can be used by alternative Flask
+implementations, for example Quart. The code therefore cannot do any
+IO, nor be part of a likely IO path. Finally this code cannot use the
+Flask globals.
diff --git a/venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc b/venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc
new file mode 100644
index 0000000..599d9a4
Binary files /dev/null and b/venv/Lib/site-packages/flask/sansio/__pycache__/app.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc b/venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc
new file mode 100644
index 0000000..9491969
Binary files /dev/null and b/venv/Lib/site-packages/flask/sansio/__pycache__/blueprints.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc b/venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc
new file mode 100644
index 0000000..9c74102
Binary files /dev/null and b/venv/Lib/site-packages/flask/sansio/__pycache__/scaffold.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask/sansio/app.py b/venv/Lib/site-packages/flask/sansio/app.py
new file mode 100644
index 0000000..21a79ba
--- /dev/null
+++ b/venv/Lib/site-packages/flask/sansio/app.py
@@ -0,0 +1,968 @@
+from __future__ import annotations
+
+import logging
+import os
+import sys
+import typing as t
+from datetime import timedelta
+from itertools import chain
+
+from werkzeug.exceptions import Aborter
+from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import BadRequestKeyError
+from werkzeug.routing import BuildError
+from werkzeug.routing import Map
+from werkzeug.routing import Rule
+from werkzeug.sansio.response import Response
+from werkzeug.utils import cached_property
+from werkzeug.utils import redirect as _wz_redirect
+
+from .. import typing as ft
+from ..config import Config
+from ..config import ConfigAttribute
+from ..ctx import _AppCtxGlobals
+from ..helpers import _split_blueprint_path
+from ..helpers import get_debug_flag
+from ..json.provider import DefaultJSONProvider
+from ..json.provider import JSONProvider
+from ..logging import create_logger
+from ..templating import DispatchingJinjaLoader
+from ..templating import Environment
+from .scaffold import _endpoint_from_view_func
+from .scaffold import find_package
+from .scaffold import Scaffold
+from .scaffold import setupmethod
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.wrappers import Response as BaseResponse
+
+ from ..testing import FlaskClient
+ from ..testing import FlaskCliRunner
+ from .blueprints import Blueprint
+
+T_shell_context_processor = t.TypeVar(
+ "T_shell_context_processor", bound=ft.ShellContextProcessorCallable
+)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+
+
+def _make_timedelta(value: timedelta | int | None) -> timedelta | None:
+ if value is None or isinstance(value, timedelta):
+ return value
+
+ return timedelta(seconds=value)
+
+
+class App(Scaffold):
+ """The flask object implements a WSGI application and acts as the central
+ object. It is passed the name of the module or package of the
+ application. Once it is created it will act as a central registry for
+ the view functions, the URL rules, template configuration and much more.
+
+ The name of the package is used to resolve resources from inside the
+ package or the folder the module is contained in depending on if the
+ package parameter resolves to an actual python package (a folder with
+ an :file:`__init__.py` file inside) or a standard module (just a ``.py`` file).
+
+ For more information about resource loading, see :func:`open_resource`.
+
+ Usually you create a :class:`Flask` instance in your main module or
+ in the :file:`__init__.py` file of your package like this::
+
+ from flask import Flask
+ app = Flask(__name__)
+
+ .. admonition:: About the First Parameter
+
+ The idea of the first parameter is to give Flask an idea of what
+ belongs to your application. This name is used to find resources
+ on the filesystem, can be used by extensions to improve debugging
+ information and a lot more.
+
+ So it's important what you provide there. If you are using a single
+ module, `__name__` is always the correct value. If you however are
+ using a package, it's usually recommended to hardcode the name of
+ your package there.
+
+ For example if your application is defined in :file:`yourapplication/app.py`
+ you should create it with one of the two versions below::
+
+ app = Flask('yourapplication')
+ app = Flask(__name__.split('.')[0])
+
+ Why is that? The application will work even with `__name__`, thanks
+ to how resources are looked up. However it will make debugging more
+ painful. Certain extensions can make assumptions based on the
+ import name of your application. For example the Flask-SQLAlchemy
+ extension will look for the code in your application that triggered
+ an SQL query in debug mode. If the import name is not properly set
+ up, that debugging information is lost. (For example it would only
+ pick up SQL queries in `yourapplication.app` and not
+ `yourapplication.views.frontend`)
+
+ .. versionadded:: 0.7
+ The `static_url_path`, `static_folder`, and `template_folder`
+ parameters were added.
+
+ .. versionadded:: 0.8
+ The `instance_path` and `instance_relative_config` parameters were
+ added.
+
+ .. versionadded:: 0.11
+ The `root_path` parameter was added.
+
+ .. versionadded:: 1.0
+ The ``host_matching`` and ``static_host`` parameters were added.
+
+ .. versionadded:: 1.0
+ The ``subdomain_matching`` parameter was added. Subdomain
+ matching needs to be enabled manually now. Setting
+ :data:`SERVER_NAME` does not implicitly enable it.
+
+ :param import_name: the name of the application package
+ :param static_url_path: can be used to specify a different path for the
+ static files on the web. Defaults to the name
+ of the `static_folder` folder.
+ :param static_folder: The folder with static files that is served at
+ ``static_url_path``. Relative to the application ``root_path``
+ or an absolute path. Defaults to ``'static'``.
+ :param static_host: the host to use when adding the static route.
+ Defaults to None. Required when using ``host_matching=True``
+ with a ``static_folder`` configured.
+ :param host_matching: set ``url_map.host_matching`` attribute.
+ Defaults to False.
+ :param subdomain_matching: consider the subdomain relative to
+ :data:`SERVER_NAME` when matching routes. Defaults to False.
+ :param template_folder: the folder that contains the templates that should
+ be used by the application. Defaults to
+ ``'templates'`` folder in the root path of the
+ application.
+ :param instance_path: An alternative instance path for the application.
+ By default the folder ``'instance'`` next to the
+ package or module is assumed to be the instance
+ path.
+ :param instance_relative_config: if set to ``True`` relative filenames
+ for loading the config are assumed to
+ be relative to the instance path instead
+ of the application root.
+ :param root_path: The path to the root of the application files.
+ This should only be set manually when it can't be detected
+ automatically, such as for namespace packages.
+ """
+
+ #: The class of the object assigned to :attr:`aborter`, created by
+ #: :meth:`create_aborter`. That object is called by
+ #: :func:`flask.abort` to raise HTTP errors, and can be
+ #: called directly as well.
+ #:
+ #: Defaults to :class:`werkzeug.exceptions.Aborter`.
+ #:
+ #: .. versionadded:: 2.2
+ aborter_class = Aborter
+
+ #: The class that is used for the Jinja environment.
+ #:
+ #: .. versionadded:: 0.11
+ jinja_environment = Environment
+
+ #: The class that is used for the :data:`~flask.g` instance.
+ #:
+ #: Example use cases for a custom class:
+ #:
+ #: 1. Store arbitrary attributes on flask.g.
+ #: 2. Add a property for lazy per-request database connectors.
+ #: 3. Return None instead of AttributeError on unexpected attributes.
+ #: 4. Raise exception if an unexpected attr is set, a "controlled" flask.g.
+ #:
+ #: In Flask 0.9 this property was called `request_globals_class` but it
+ #: was changed in 0.10 to :attr:`app_ctx_globals_class` because the
+ #: flask.g object is now application context scoped.
+ #:
+ #: .. versionadded:: 0.10
+ app_ctx_globals_class = _AppCtxGlobals
+
+ #: The class that is used for the ``config`` attribute of this app.
+ #: Defaults to :class:`~flask.Config`.
+ #:
+ #: Example use cases for a custom class:
+ #:
+ #: 1. Default values for certain config options.
+ #: 2. Access to config values through attributes in addition to keys.
+ #:
+ #: .. versionadded:: 0.11
+ config_class = Config
+
+ #: The testing flag. Set this to ``True`` to enable the test mode of
+ #: Flask extensions (and in the future probably also Flask itself).
+ #: For example this might activate test helpers that have an
+ #: additional runtime cost which should not be enabled by default.
+ #:
+ #: If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the
+ #: default it's implicitly enabled.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: ``TESTING`` configuration key. Defaults to ``False``.
+ testing = ConfigAttribute[bool]("TESTING")
+
+ #: If a secret key is set, cryptographic components can use this to
+ #: sign cookies and other things. Set this to a complex random value
+ #: when you want to use the secure cookie for instance.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: :data:`SECRET_KEY` configuration key. Defaults to ``None``.
+ secret_key = ConfigAttribute[t.Union[str, bytes, None]]("SECRET_KEY")
+
+ #: A :class:`~datetime.timedelta` which is used to set the expiration
+ #: date of a permanent session. The default is 31 days which makes a
+ #: permanent session survive for roughly one month.
+ #:
+ #: This attribute can also be configured from the config with the
+ #: ``PERMANENT_SESSION_LIFETIME`` configuration key. Defaults to
+ #: ``timedelta(days=31)``
+ permanent_session_lifetime = ConfigAttribute[timedelta](
+ "PERMANENT_SESSION_LIFETIME",
+ get_converter=_make_timedelta, # type: ignore[arg-type]
+ )
+
+ json_provider_class: type[JSONProvider] = DefaultJSONProvider
+ """A subclass of :class:`~flask.json.provider.JSONProvider`. An
+ instance is created and assigned to :attr:`app.json` when creating
+ the app.
+
+ The default, :class:`~flask.json.provider.DefaultJSONProvider`, uses
+ Python's built-in :mod:`json` library. A different provider can use
+ a different JSON library.
+
+ .. versionadded:: 2.2
+ """
+
+ #: Options that are passed to the Jinja environment in
+ #: :meth:`create_jinja_environment`. Changing these options after
+ #: the environment is created (accessing :attr:`jinja_env`) will
+ #: have no effect.
+ #:
+ #: .. versionchanged:: 1.1.0
+ #: This is a ``dict`` instead of an ``ImmutableDict`` to allow
+ #: easier configuration.
+ #:
+ jinja_options: dict[str, t.Any] = {}
+
+ #: The rule object to use for URL rules created. This is used by
+ #: :meth:`add_url_rule`. Defaults to :class:`werkzeug.routing.Rule`.
+ #:
+ #: .. versionadded:: 0.7
+ url_rule_class = Rule
+
+ #: The map object to use for storing the URL rules and routing
+ #: configuration parameters. Defaults to :class:`werkzeug.routing.Map`.
+ #:
+ #: .. versionadded:: 1.1.0
+ url_map_class = Map
+
+ #: The :meth:`test_client` method creates an instance of this test
+ #: client class. Defaults to :class:`~flask.testing.FlaskClient`.
+ #:
+ #: .. versionadded:: 0.7
+ test_client_class: type[FlaskClient] | None = None
+
+ #: The :class:`~click.testing.CliRunner` subclass, by default
+ #: :class:`~flask.testing.FlaskCliRunner` that is used by
+ #: :meth:`test_cli_runner`. Its ``__init__`` method should take a
+ #: Flask app object as the first argument.
+ #:
+ #: .. versionadded:: 1.0
+ test_cli_runner_class: type[FlaskCliRunner] | None = None
+
+ default_config: dict[str, t.Any]
+ response_class: type[Response]
+
+ def __init__(
+ self,
+ import_name: str,
+ static_url_path: str | None = None,
+ static_folder: str | os.PathLike[str] | None = "static",
+ static_host: str | None = None,
+ host_matching: bool = False,
+ subdomain_matching: bool = False,
+ template_folder: str | os.PathLike[str] | None = "templates",
+ instance_path: str | None = None,
+ instance_relative_config: bool = False,
+ root_path: str | None = None,
+ ):
+ super().__init__(
+ import_name=import_name,
+ static_folder=static_folder,
+ static_url_path=static_url_path,
+ template_folder=template_folder,
+ root_path=root_path,
+ )
+
+ if instance_path is None:
+ instance_path = self.auto_find_instance_path()
+ elif not os.path.isabs(instance_path):
+ raise ValueError(
+ "If an instance path is provided it must be absolute."
+ " A relative path was given instead."
+ )
+
+ #: Holds the path to the instance folder.
+ #:
+ #: .. versionadded:: 0.8
+ self.instance_path = instance_path
+
+ #: The configuration dictionary as :class:`Config`. This behaves
+ #: exactly like a regular dictionary but supports additional methods
+ #: to load a config from files.
+ self.config = self.make_config(instance_relative_config)
+
+ #: An instance of :attr:`aborter_class` created by
+ #: :meth:`make_aborter`. This is called by :func:`flask.abort`
+ #: to raise HTTP errors, and can be called directly as well.
+ #:
+ #: .. versionadded:: 2.2
+ #: Moved from ``flask.abort``, which calls this object.
+ self.aborter = self.make_aborter()
+
+ self.json: JSONProvider = self.json_provider_class(self)
+ """Provides access to JSON methods. Functions in ``flask.json``
+ will call methods on this provider when the application context
+ is active. Used for handling JSON requests and responses.
+
+ An instance of :attr:`json_provider_class`. Can be customized by
+ changing that attribute on a subclass, or by assigning to this
+ attribute afterwards.
+
+ The default, :class:`~flask.json.provider.DefaultJSONProvider`,
+ uses Python's built-in :mod:`json` library. A different provider
+ can use a different JSON library.
+
+ .. versionadded:: 2.2
+ """
+
+ #: A list of functions that are called by
+ #: :meth:`handle_url_build_error` when :meth:`.url_for` raises a
+ #: :exc:`~werkzeug.routing.BuildError`. Each function is called
+ #: with ``error``, ``endpoint`` and ``values``. If a function
+ #: returns ``None`` or raises a ``BuildError``, it is skipped.
+ #: Otherwise, its return value is returned by ``url_for``.
+ #:
+ #: .. versionadded:: 0.9
+ self.url_build_error_handlers: list[
+ t.Callable[[Exception, str, dict[str, t.Any]], str]
+ ] = []
+
+ #: A list of functions that are called when the application context
+ #: is destroyed. Since the application context is also torn down
+ #: if the request ends this is the place to store code that disconnects
+ #: from databases.
+ #:
+ #: .. versionadded:: 0.9
+ self.teardown_appcontext_funcs: list[ft.TeardownCallable] = []
+
+ #: A list of shell context processor functions that should be run
+ #: when a shell context is created.
+ #:
+ #: .. versionadded:: 0.11
+ self.shell_context_processors: list[ft.ShellContextProcessorCallable] = []
+
+ #: Maps registered blueprint names to blueprint objects. The
+ #: dict retains the order the blueprints were registered in.
+ #: Blueprints can be registered multiple times, this dict does
+ #: not track how often they were attached.
+ #:
+ #: .. versionadded:: 0.7
+ self.blueprints: dict[str, Blueprint] = {}
+
+ #: a place where extensions can store application specific state. For
+ #: example this is where an extension could store database engines and
+ #: similar things.
+ #:
+ #: The key must match the name of the extension module. For example in
+ #: case of a "Flask-Foo" extension in `flask_foo`, the key would be
+ #: ``'foo'``.
+ #:
+ #: .. versionadded:: 0.7
+ self.extensions: dict[str, t.Any] = {}
+
+ #: The :class:`~werkzeug.routing.Map` for this instance. You can use
+ #: this to change the routing converters after the class was created
+ #: but before any routes are connected. Example::
+ #:
+ #: from werkzeug.routing import BaseConverter
+ #:
+ #: class ListConverter(BaseConverter):
+ #: def to_python(self, value):
+ #: return value.split(',')
+ #: def to_url(self, values):
+ #: return ','.join(super(ListConverter, self).to_url(value)
+ #: for value in values)
+ #:
+ #: app = Flask(__name__)
+ #: app.url_map.converters['list'] = ListConverter
+ self.url_map = self.url_map_class(host_matching=host_matching)
+
+ self.subdomain_matching = subdomain_matching
+
+ # tracks internally if the application already handled at least one
+ # request.
+ self._got_first_request = False
+
+ # Set the name of the Click group in case someone wants to add
+ # the app's commands to another CLI tool.
+ self.cli.name = self.name
+
+ def _check_setup_finished(self, f_name: str) -> None:
+ if self._got_first_request:
+ raise AssertionError(
+ f"The setup method '{f_name}' can no longer be called"
+ " on the application. It has already handled its first"
+ " request, any changes will not be applied"
+ " consistently.\n"
+ "Make sure all imports, decorators, functions, etc."
+ " needed to set up the application are done before"
+ " running it."
+ )
+
+ @cached_property
+ def name(self) -> str: # type: ignore
+ """The name of the application. This is usually the import name
+ with the difference that it's guessed from the run file if the
+ import name is main. This name is used as a display name when
+ Flask needs the name of the application. It can be set and overridden
+ to change the value.
+
+ .. versionadded:: 0.8
+ """
+ if self.import_name == "__main__":
+ fn: str | None = getattr(sys.modules["__main__"], "__file__", None)
+ if fn is None:
+ return "__main__"
+ return os.path.splitext(os.path.basename(fn))[0]
+ return self.import_name
+
+ @cached_property
+ def logger(self) -> logging.Logger:
+ """A standard Python :class:`~logging.Logger` for the app, with
+ the same name as :attr:`name`.
+
+ In debug mode, the logger's :attr:`~logging.Logger.level` will
+ be set to :data:`~logging.DEBUG`.
+
+ If there are no handlers configured, a default handler will be
+ added. See :doc:`/logging` for more information.
+
+ .. versionchanged:: 1.1.0
+ The logger takes the same name as :attr:`name` rather than
+ hard-coding ``"flask.app"``.
+
+ .. versionchanged:: 1.0.0
+ Behavior was simplified. The logger is always named
+ ``"flask.app"``. The level is only set during configuration,
+ it doesn't check ``app.debug`` each time. Only one format is
+ used, not different ones depending on ``app.debug``. No
+ handlers are removed, and a handler is only added if no
+ handlers are already configured.
+
+ .. versionadded:: 0.3
+ """
+ return create_logger(self)
+
+ @cached_property
+ def jinja_env(self) -> Environment:
+ """The Jinja environment used to load templates.
+
+ The environment is created the first time this property is
+ accessed. Changing :attr:`jinja_options` after that will have no
+ effect.
+ """
+ return self.create_jinja_environment()
+
+ def create_jinja_environment(self) -> Environment:
+ raise NotImplementedError()
+
+ def make_config(self, instance_relative: bool = False) -> Config:
+ """Used to create the config attribute by the Flask constructor.
+ The `instance_relative` parameter is passed in from the constructor
+ of Flask (there named `instance_relative_config`) and indicates if
+ the config should be relative to the instance path or the root path
+ of the application.
+
+ .. versionadded:: 0.8
+ """
+ root_path = self.root_path
+ if instance_relative:
+ root_path = self.instance_path
+ defaults = dict(self.default_config)
+ defaults["DEBUG"] = get_debug_flag()
+ return self.config_class(root_path, defaults)
+
+ def make_aborter(self) -> Aborter:
+ """Create the object to assign to :attr:`aborter`. That object
+ is called by :func:`flask.abort` to raise HTTP errors, and can
+ be called directly as well.
+
+ By default, this creates an instance of :attr:`aborter_class`,
+ which defaults to :class:`werkzeug.exceptions.Aborter`.
+
+ .. versionadded:: 2.2
+ """
+ return self.aborter_class()
+
+ def auto_find_instance_path(self) -> str:
+ """Tries to locate the instance path if it was not provided to the
+ constructor of the application class. It will basically calculate
+ the path to a folder named ``instance`` next to your main file or
+ the package.
+
+ .. versionadded:: 0.8
+ """
+ prefix, package_path = find_package(self.import_name)
+ if prefix is None:
+ return os.path.join(package_path, "instance")
+ return os.path.join(prefix, "var", f"{self.name}-instance")
+
+ def create_global_jinja_loader(self) -> DispatchingJinjaLoader:
+ """Creates the loader for the Jinja2 environment. Can be used to
+ override just the loader and keeping the rest unchanged. It's
+ discouraged to override this function. Instead one should override
+ the :meth:`jinja_loader` function instead.
+
+ The global loader dispatches between the loaders of the application
+ and the individual blueprints.
+
+ .. versionadded:: 0.7
+ """
+ return DispatchingJinjaLoader(self)
+
+ def select_jinja_autoescape(self, filename: str) -> bool:
+ """Returns ``True`` if autoescaping should be active for the given
+ template name. If no template name is given, returns `True`.
+
+ .. versionchanged:: 2.2
+ Autoescaping is now enabled by default for ``.svg`` files.
+
+ .. versionadded:: 0.5
+ """
+ if filename is None:
+ return True
+ return filename.endswith((".html", ".htm", ".xml", ".xhtml", ".svg"))
+
+ @property
+ def debug(self) -> bool:
+ """Whether debug mode is enabled. When using ``flask run`` to start the
+ development server, an interactive debugger will be shown for unhandled
+ exceptions, and the server will be reloaded when code changes. This maps to the
+ :data:`DEBUG` config key. It may not behave as expected if set late.
+
+ **Do not enable debug mode when deploying in production.**
+
+ Default: ``False``
+ """
+ return self.config["DEBUG"] # type: ignore[no-any-return]
+
+ @debug.setter
+ def debug(self, value: bool) -> None:
+ self.config["DEBUG"] = value
+
+ if self.config["TEMPLATES_AUTO_RELOAD"] is None:
+ self.jinja_env.auto_reload = value
+
+ @setupmethod
+ def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
+ """Register a :class:`~flask.Blueprint` on the application. Keyword
+ arguments passed to this method will override the defaults set on the
+ blueprint.
+
+ Calls the blueprint's :meth:`~flask.Blueprint.register` method after
+ recording the blueprint in the application's :attr:`blueprints`.
+
+ :param blueprint: The blueprint to register.
+ :param url_prefix: Blueprint routes will be prefixed with this.
+ :param subdomain: Blueprint routes will match on this subdomain.
+ :param url_defaults: Blueprint routes will use these default values for
+ view arguments.
+ :param options: Additional keyword arguments are passed to
+ :class:`~flask.blueprints.BlueprintSetupState`. They can be
+ accessed in :meth:`~flask.Blueprint.record` callbacks.
+
+ .. versionchanged:: 2.0.1
+ The ``name`` option can be used to change the (pre-dotted)
+ name the blueprint is registered with. This allows the same
+ blueprint to be registered multiple times with unique names
+ for ``url_for``.
+
+ .. versionadded:: 0.7
+ """
+ blueprint.register(self, options)
+
+ def iter_blueprints(self) -> t.ValuesView[Blueprint]:
+ """Iterates over all blueprints by the order they were registered.
+
+ .. versionadded:: 0.11
+ """
+ return self.blueprints.values()
+
+ @setupmethod
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ provide_automatic_options: bool | None = None,
+ **options: t.Any,
+ ) -> None:
+ if endpoint is None:
+ endpoint = _endpoint_from_view_func(view_func) # type: ignore
+ options["endpoint"] = endpoint
+ methods = options.pop("methods", None)
+
+ # if the methods are not given and the view_func object knows its
+ # methods we can use that instead. If neither exists, we go with
+ # a tuple of only ``GET`` as default.
+ if methods is None:
+ methods = getattr(view_func, "methods", None) or ("GET",)
+ if isinstance(methods, str):
+ raise TypeError(
+ "Allowed methods must be a list of strings, for"
+ ' example: @app.route(..., methods=["POST"])'
+ )
+ methods = {item.upper() for item in methods}
+
+ # Methods that should always be added
+ required_methods = set(getattr(view_func, "required_methods", ()))
+
+ # starting with Flask 0.8 the view_func object can disable and
+ # force-enable the automatic options handling.
+ if provide_automatic_options is None:
+ provide_automatic_options = getattr(
+ view_func, "provide_automatic_options", None
+ )
+
+ if provide_automatic_options is None:
+ if "OPTIONS" not in methods:
+ provide_automatic_options = True
+ required_methods.add("OPTIONS")
+ else:
+ provide_automatic_options = False
+
+ # Add the required methods now.
+ methods |= required_methods
+
+ rule_obj = self.url_rule_class(rule, methods=methods, **options)
+ rule_obj.provide_automatic_options = provide_automatic_options # type: ignore[attr-defined]
+
+ self.url_map.add(rule_obj)
+ if view_func is not None:
+ old_func = self.view_functions.get(endpoint)
+ if old_func is not None and old_func != view_func:
+ raise AssertionError(
+ "View function mapping is overwriting an existing"
+ f" endpoint function: {endpoint}"
+ )
+ self.view_functions[endpoint] = view_func
+
+ @setupmethod
+ def template_filter(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_filter], T_template_filter]:
+ """A decorator that is used to register custom template filter.
+ You can specify a name for the filter, otherwise the function
+ name will be used. Example::
+
+ @app.template_filter()
+ def reverse(s):
+ return s[::-1]
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_filter) -> T_template_filter:
+ self.add_template_filter(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_template_filter(
+ self, f: ft.TemplateFilterCallable, name: str | None = None
+ ) -> None:
+ """Register a custom template filter. Works exactly like the
+ :meth:`template_filter` decorator.
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+ self.jinja_env.filters[name or f.__name__] = f
+
+ @setupmethod
+ def template_test(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_test], T_template_test]:
+ """A decorator that is used to register custom template test.
+ You can specify a name for the test, otherwise the function
+ name will be used. Example::
+
+ @app.template_test()
+ def is_prime(n):
+ if n == 2:
+ return True
+ for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
+ if n % i == 0:
+ return False
+ return True
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_test) -> T_template_test:
+ self.add_template_test(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_template_test(
+ self, f: ft.TemplateTestCallable, name: str | None = None
+ ) -> None:
+ """Register a custom template test. Works exactly like the
+ :meth:`template_test` decorator.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+ self.jinja_env.tests[name or f.__name__] = f
+
+ @setupmethod
+ def template_global(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_global], T_template_global]:
+ """A decorator that is used to register a custom template global function.
+ You can specify a name for the global function, otherwise the function
+ name will be used. Example::
+
+ @app.template_global()
+ def double(n):
+ return 2 * n
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global function, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_global) -> T_template_global:
+ self.add_template_global(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_template_global(
+ self, f: ft.TemplateGlobalCallable, name: str | None = None
+ ) -> None:
+ """Register a custom template global function. Works exactly like the
+ :meth:`template_global` decorator.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global function, otherwise the
+ function name will be used.
+ """
+ self.jinja_env.globals[name or f.__name__] = f
+
+ @setupmethod
+ def teardown_appcontext(self, f: T_teardown) -> T_teardown:
+ """Registers a function to be called when the application
+ context is popped. The application context is typically popped
+ after the request context for each request, at the end of CLI
+ commands, or after a manually pushed context ends.
+
+ .. code-block:: python
+
+ with app.app_context():
+ ...
+
+ When the ``with`` block exits (or ``ctx.pop()`` is called), the
+ teardown functions are called just before the app context is
+ made inactive. Since a request context typically also manages an
+ application context it would also be called when you pop a
+ request context.
+
+ When a teardown function was called because of an unhandled
+ exception it will be passed an error object. If an
+ :meth:`errorhandler` is registered, it will handle the exception
+ and the teardown will not receive it.
+
+ Teardown functions must avoid raising exceptions. If they
+ execute code that might fail they must surround that code with a
+ ``try``/``except`` block and log any errors.
+
+ The return values of teardown functions are ignored.
+
+ .. versionadded:: 0.9
+ """
+ self.teardown_appcontext_funcs.append(f)
+ return f
+
+ @setupmethod
+ def shell_context_processor(
+ self, f: T_shell_context_processor
+ ) -> T_shell_context_processor:
+ """Registers a shell context processor function.
+
+ .. versionadded:: 0.11
+ """
+ self.shell_context_processors.append(f)
+ return f
+
+ def _find_error_handler(
+ self, e: Exception, blueprints: list[str]
+ ) -> ft.ErrorHandlerCallable | None:
+ """Return a registered error handler for an exception in this order:
+ blueprint handler for a specific code, app handler for a specific code,
+ blueprint handler for an exception class, app handler for an exception
+ class, or ``None`` if a suitable handler is not found.
+ """
+ exc_class, code = self._get_exc_class_and_code(type(e))
+ names = (*blueprints, None)
+
+ for c in (code, None) if code is not None else (None,):
+ for name in names:
+ handler_map = self.error_handler_spec[name][c]
+
+ if not handler_map:
+ continue
+
+ for cls in exc_class.__mro__:
+ handler = handler_map.get(cls)
+
+ if handler is not None:
+ return handler
+ return None
+
+ def trap_http_exception(self, e: Exception) -> bool:
+ """Checks if an HTTP exception should be trapped or not. By default
+ this will return ``False`` for all exceptions except for a bad request
+ key error if ``TRAP_BAD_REQUEST_ERRORS`` is set to ``True``. It
+ also returns ``True`` if ``TRAP_HTTP_EXCEPTIONS`` is set to ``True``.
+
+ This is called for all HTTP exceptions raised by a view function.
+ If it returns ``True`` for any exception the error handler for this
+ exception is not called and it shows up as regular exception in the
+ traceback. This is helpful for debugging implicitly raised HTTP
+ exceptions.
+
+ .. versionchanged:: 1.0
+ Bad request errors are not trapped by default in debug mode.
+
+ .. versionadded:: 0.8
+ """
+ if self.config["TRAP_HTTP_EXCEPTIONS"]:
+ return True
+
+ trap_bad_request = self.config["TRAP_BAD_REQUEST_ERRORS"]
+
+ # if unset, trap key errors in debug mode
+ if (
+ trap_bad_request is None
+ and self.debug
+ and isinstance(e, BadRequestKeyError)
+ ):
+ return True
+
+ if trap_bad_request:
+ return isinstance(e, BadRequest)
+
+ return False
+
+ def should_ignore_error(self, error: BaseException | None) -> bool:
+ """This is called to figure out if an error should be ignored
+ or not as far as the teardown system is concerned. If this
+ function returns ``True`` then the teardown handlers will not be
+ passed the error.
+
+ .. versionadded:: 0.10
+ """
+ return False
+
+ def redirect(self, location: str, code: int = 302) -> BaseResponse:
+ """Create a redirect response object.
+
+ This is called by :func:`flask.redirect`, and can be called
+ directly as well.
+
+ :param location: The URL to redirect to.
+ :param code: The status code for the redirect.
+
+ .. versionadded:: 2.2
+ Moved from ``flask.redirect``, which calls this method.
+ """
+ return _wz_redirect(
+ location,
+ code=code,
+ Response=self.response_class, # type: ignore[arg-type]
+ )
+
+ def inject_url_defaults(self, endpoint: str, values: dict[str, t.Any]) -> None:
+ """Injects the URL defaults for the given endpoint directly into
+ the values dictionary passed. This is used internally and
+ automatically called on URL building.
+
+ .. versionadded:: 0.7
+ """
+ names: t.Iterable[str | None] = (None,)
+
+ # url_for may be called outside a request context, parse the
+ # passed endpoint instead of using request.blueprints.
+ if "." in endpoint:
+ names = chain(
+ names, reversed(_split_blueprint_path(endpoint.rpartition(".")[0]))
+ )
+
+ for name in names:
+ if name in self.url_default_functions:
+ for func in self.url_default_functions[name]:
+ func(endpoint, values)
+
+ def handle_url_build_error(
+ self, error: BuildError, endpoint: str, values: dict[str, t.Any]
+ ) -> str:
+ """Called by :meth:`.url_for` if a
+ :exc:`~werkzeug.routing.BuildError` was raised. If this returns
+ a value, it will be returned by ``url_for``, otherwise the error
+ will be re-raised.
+
+ Each function in :attr:`url_build_error_handlers` is called with
+ ``error``, ``endpoint`` and ``values``. If a function returns
+ ``None`` or raises a ``BuildError``, it is skipped. Otherwise,
+ its return value is returned by ``url_for``.
+
+ :param error: The active ``BuildError`` being handled.
+ :param endpoint: The endpoint being built.
+ :param values: The keyword arguments passed to ``url_for``.
+ """
+ for handler in self.url_build_error_handlers:
+ try:
+ rv = handler(error, endpoint, values)
+ except BuildError as e:
+ # make error available outside except block
+ error = e
+ else:
+ if rv is not None:
+ return rv
+
+ # Re-raise if called with an active exception, otherwise raise
+ # the passed in exception.
+ if error is sys.exc_info()[1]:
+ raise
+
+ raise error
diff --git a/venv/Lib/site-packages/flask/sansio/blueprints.py b/venv/Lib/site-packages/flask/sansio/blueprints.py
new file mode 100644
index 0000000..4f912cc
--- /dev/null
+++ b/venv/Lib/site-packages/flask/sansio/blueprints.py
@@ -0,0 +1,632 @@
+from __future__ import annotations
+
+import os
+import typing as t
+from collections import defaultdict
+from functools import update_wrapper
+
+from .. import typing as ft
+from .scaffold import _endpoint_from_view_func
+from .scaffold import _sentinel
+from .scaffold import Scaffold
+from .scaffold import setupmethod
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import App
+
+DeferredSetupFunction = t.Callable[["BlueprintSetupState"], None]
+T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any])
+T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
+T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_context_processor = t.TypeVar(
+ "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
+)
+T_template_filter = t.TypeVar("T_template_filter", bound=ft.TemplateFilterCallable)
+T_template_global = t.TypeVar("T_template_global", bound=ft.TemplateGlobalCallable)
+T_template_test = t.TypeVar("T_template_test", bound=ft.TemplateTestCallable)
+T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
+T_url_value_preprocessor = t.TypeVar(
+ "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
+)
+
+
+class BlueprintSetupState:
+ """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: Blueprint,
+ app: App,
+ options: t.Any,
+ first_registration: bool,
+ ) -> None:
+ #: 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
+
+ self.name = self.options.get("name", blueprint.name)
+ self.name_prefix = self.options.get("name_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: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ **options: t.Any,
+ ) -> None:
+ """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) # type: ignore
+ defaults = self.url_defaults
+ if "defaults" in options:
+ defaults = dict(defaults, **options.pop("defaults"))
+
+ self.app.add_url_rule(
+ rule,
+ f"{self.name_prefix}.{self.name}.{endpoint}".lstrip("."),
+ view_func,
+ defaults=defaults,
+ **options,
+ )
+
+
+class Blueprint(Scaffold):
+ """Represents a blueprint, a collection of routes and other
+ app-related functions that can be registered on a real application
+ later.
+
+ A blueprint is an object that allows defining application functions
+ without requiring an application object ahead of time. It uses the
+ same decorators as :class:`~flask.Flask`, but defers the need for an
+ application by recording them for later registration.
+
+ Decorating a function with a blueprint creates a deferred function
+ that is called with :class:`~flask.blueprints.BlueprintSetupState`
+ when the blueprint is registered on an application.
+
+ See :doc:`/blueprints` for more information.
+
+ :param name: The name of the blueprint. Will be prepended to each
+ endpoint name.
+ :param import_name: The name of the blueprint package, usually
+ ``__name__``. This helps locate the ``root_path`` for the
+ blueprint.
+ :param static_folder: A folder with static files that should be
+ served by the blueprint's static route. The path is relative to
+ the blueprint's root path. Blueprint static files are disabled
+ by default.
+ :param static_url_path: The url to serve static files from.
+ Defaults to ``static_folder``. If the blueprint does not have
+ a ``url_prefix``, the app's static route will take precedence,
+ and the blueprint's static files won't be accessible.
+ :param template_folder: A folder with templates that should be added
+ to the app's template search path. The path is relative to the
+ blueprint's root path. Blueprint templates are disabled by
+ default. Blueprint templates have a lower precedence than those
+ in the app's templates folder.
+ :param url_prefix: A path to prepend to all of the blueprint's URLs,
+ to make them distinct from the rest of the app's routes.
+ :param subdomain: A subdomain that blueprint routes will match on by
+ default.
+ :param url_defaults: A dict of default values that blueprint routes
+ will receive by default.
+ :param root_path: By default, the blueprint will automatically set
+ this based on ``import_name``. In certain situations this
+ automatic detection can fail, so the path can be specified
+ manually instead.
+
+ .. versionchanged:: 1.1.0
+ Blueprints have a ``cli`` group to register nested CLI commands.
+ The ``cli_group`` parameter controls the name of the group under
+ the ``flask`` command.
+
+ .. versionadded:: 0.7
+ """
+
+ _got_registered_once = False
+
+ def __init__(
+ self,
+ name: str,
+ import_name: str,
+ static_folder: str | os.PathLike[str] | None = None,
+ static_url_path: str | None = None,
+ template_folder: str | os.PathLike[str] | None = None,
+ url_prefix: str | None = None,
+ subdomain: str | None = None,
+ url_defaults: dict[str, t.Any] | None = None,
+ root_path: str | None = None,
+ cli_group: str | None = _sentinel, # type: ignore[assignment]
+ ):
+ super().__init__(
+ import_name=import_name,
+ static_folder=static_folder,
+ static_url_path=static_url_path,
+ template_folder=template_folder,
+ root_path=root_path,
+ )
+
+ if not name:
+ raise ValueError("'name' may not be empty.")
+
+ if "." in name:
+ raise ValueError("'name' may not contain a dot '.' character.")
+
+ self.name = name
+ self.url_prefix = url_prefix
+ self.subdomain = subdomain
+ self.deferred_functions: list[DeferredSetupFunction] = []
+
+ if url_defaults is None:
+ url_defaults = {}
+
+ self.url_values_defaults = url_defaults
+ self.cli_group = cli_group
+ self._blueprints: list[tuple[Blueprint, dict[str, t.Any]]] = []
+
+ def _check_setup_finished(self, f_name: str) -> None:
+ if self._got_registered_once:
+ raise AssertionError(
+ f"The setup method '{f_name}' can no longer be called on the blueprint"
+ f" '{self.name}'. It has already been registered at least once, any"
+ " changes will not be applied consistently.\n"
+ "Make sure all imports, decorators, functions, etc. needed to set up"
+ " the blueprint are done before registering it."
+ )
+
+ @setupmethod
+ def record(self, func: DeferredSetupFunction) -> None:
+ """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.
+ """
+ self.deferred_functions.append(func)
+
+ @setupmethod
+ def record_once(self, func: DeferredSetupFunction) -> None:
+ """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: BlueprintSetupState) -> None:
+ if state.first_registration:
+ func(state)
+
+ self.record(update_wrapper(wrapper, func))
+
+ def make_setup_state(
+ self, app: App, options: dict[str, t.Any], first_registration: bool = False
+ ) -> BlueprintSetupState:
+ """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)
+
+ @setupmethod
+ def register_blueprint(self, blueprint: Blueprint, **options: t.Any) -> None:
+ """Register a :class:`~flask.Blueprint` on this blueprint. Keyword
+ arguments passed to this method will override the defaults set
+ on the blueprint.
+
+ .. versionchanged:: 2.0.1
+ The ``name`` option can be used to change the (pre-dotted)
+ name the blueprint is registered with. This allows the same
+ blueprint to be registered multiple times with unique names
+ for ``url_for``.
+
+ .. versionadded:: 2.0
+ """
+ if blueprint is self:
+ raise ValueError("Cannot register a blueprint on itself")
+ self._blueprints.append((blueprint, options))
+
+ def register(self, app: App, options: dict[str, t.Any]) -> None:
+ """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`.
+
+ .. versionchanged:: 2.3
+ Nested blueprints now correctly apply subdomains.
+
+ .. versionchanged:: 2.1
+ Registering the same blueprint with the same name multiple
+ times is an error.
+
+ .. versionchanged:: 2.0.1
+ Nested blueprints are registered with their dotted name.
+ This allows different blueprints with the same name to be
+ nested at different locations.
+
+ .. versionchanged:: 2.0.1
+ The ``name`` option can be used to change the (pre-dotted)
+ name the blueprint is registered with. This allows the same
+ blueprint to be registered multiple times with unique names
+ for ``url_for``.
+ """
+ name_prefix = options.get("name_prefix", "")
+ self_name = options.get("name", self.name)
+ name = f"{name_prefix}.{self_name}".lstrip(".")
+
+ if name in app.blueprints:
+ bp_desc = "this" if app.blueprints[name] is self else "a different"
+ existing_at = f" '{name}'" if self_name != name else ""
+
+ raise ValueError(
+ f"The name '{self_name}' is already registered for"
+ f" {bp_desc} blueprint{existing_at}. Use 'name=' to"
+ f" provide a unique name."
+ )
+
+ first_bp_registration = not any(bp is self for bp in app.blueprints.values())
+ first_name_registration = name not in app.blueprints
+
+ app.blueprints[name] = self
+ self._got_registered_once = True
+ state = self.make_setup_state(app, options, first_bp_registration)
+
+ if self.has_static_folder:
+ state.add_url_rule(
+ f"{self.static_url_path}/",
+ view_func=self.send_static_file, # type: ignore[attr-defined]
+ endpoint="static",
+ )
+
+ # Merge blueprint data into parent.
+ if first_bp_registration or first_name_registration:
+ self._merge_blueprint_funcs(app, name)
+
+ for deferred in self.deferred_functions:
+ deferred(state)
+
+ cli_resolved_group = options.get("cli_group", self.cli_group)
+
+ if self.cli.commands:
+ if cli_resolved_group is None:
+ app.cli.commands.update(self.cli.commands)
+ elif cli_resolved_group is _sentinel:
+ self.cli.name = name
+ app.cli.add_command(self.cli)
+ else:
+ self.cli.name = cli_resolved_group
+ app.cli.add_command(self.cli)
+
+ for blueprint, bp_options in self._blueprints:
+ bp_options = bp_options.copy()
+ bp_url_prefix = bp_options.get("url_prefix")
+ bp_subdomain = bp_options.get("subdomain")
+
+ if bp_subdomain is None:
+ bp_subdomain = blueprint.subdomain
+
+ if state.subdomain is not None and bp_subdomain is not None:
+ bp_options["subdomain"] = bp_subdomain + "." + state.subdomain
+ elif bp_subdomain is not None:
+ bp_options["subdomain"] = bp_subdomain
+ elif state.subdomain is not None:
+ bp_options["subdomain"] = state.subdomain
+
+ if bp_url_prefix is None:
+ bp_url_prefix = blueprint.url_prefix
+
+ if state.url_prefix is not None and bp_url_prefix is not None:
+ bp_options["url_prefix"] = (
+ state.url_prefix.rstrip("/") + "/" + bp_url_prefix.lstrip("/")
+ )
+ elif bp_url_prefix is not None:
+ bp_options["url_prefix"] = bp_url_prefix
+ elif state.url_prefix is not None:
+ bp_options["url_prefix"] = state.url_prefix
+
+ bp_options["name_prefix"] = name
+ blueprint.register(app, bp_options)
+
+ def _merge_blueprint_funcs(self, app: App, name: str) -> None:
+ def extend(
+ bp_dict: dict[ft.AppOrBlueprintKey, list[t.Any]],
+ parent_dict: dict[ft.AppOrBlueprintKey, list[t.Any]],
+ ) -> None:
+ for key, values in bp_dict.items():
+ key = name if key is None else f"{name}.{key}"
+ parent_dict[key].extend(values)
+
+ for key, value in self.error_handler_spec.items():
+ key = name if key is None else f"{name}.{key}"
+ value = defaultdict(
+ dict,
+ {
+ code: {exc_class: func for exc_class, func in code_values.items()}
+ for code, code_values in value.items()
+ },
+ )
+ app.error_handler_spec[key] = value
+
+ for endpoint, func in self.view_functions.items():
+ app.view_functions[endpoint] = func
+
+ extend(self.before_request_funcs, app.before_request_funcs)
+ extend(self.after_request_funcs, app.after_request_funcs)
+ extend(
+ self.teardown_request_funcs,
+ app.teardown_request_funcs,
+ )
+ extend(self.url_default_functions, app.url_default_functions)
+ extend(self.url_value_preprocessors, app.url_value_preprocessors)
+ extend(self.template_context_processors, app.template_context_processors)
+
+ @setupmethod
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ provide_automatic_options: bool | None = None,
+ **options: t.Any,
+ ) -> None:
+ """Register a URL rule with the blueprint. See :meth:`.Flask.add_url_rule` for
+ full documentation.
+
+ The URL rule is prefixed with the blueprint's URL prefix. The endpoint name,
+ used with :func:`url_for`, is prefixed with the blueprint's name.
+ """
+ if endpoint and "." in endpoint:
+ raise ValueError("'endpoint' may not contain a dot '.' character.")
+
+ if view_func and hasattr(view_func, "__name__") and "." in view_func.__name__:
+ raise ValueError("'view_func' name may not contain a dot '.' character.")
+
+ self.record(
+ lambda s: s.add_url_rule(
+ rule,
+ endpoint,
+ view_func,
+ provide_automatic_options=provide_automatic_options,
+ **options,
+ )
+ )
+
+ @setupmethod
+ def app_template_filter(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_filter], T_template_filter]:
+ """Register a template filter, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_filter`.
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_filter) -> T_template_filter:
+ self.add_app_template_filter(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_app_template_filter(
+ self, f: ft.TemplateFilterCallable, name: str | None = None
+ ) -> None:
+ """Register a template filter, available in any template rendered by the
+ application. Works like the :meth:`app_template_filter` decorator. Equivalent to
+ :meth:`.Flask.add_template_filter`.
+
+ :param name: the optional name of the filter, otherwise the
+ function name will be used.
+ """
+
+ def register_template(state: BlueprintSetupState) -> None:
+ state.app.jinja_env.filters[name or f.__name__] = f
+
+ self.record_once(register_template)
+
+ @setupmethod
+ def app_template_test(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_test], T_template_test]:
+ """Register a template test, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_test`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_test) -> T_template_test:
+ self.add_app_template_test(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_app_template_test(
+ self, f: ft.TemplateTestCallable, name: str | None = None
+ ) -> None:
+ """Register a template test, available in any template rendered by the
+ application. Works like the :meth:`app_template_test` decorator. Equivalent to
+ :meth:`.Flask.add_template_test`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the test, otherwise the
+ function name will be used.
+ """
+
+ def register_template(state: BlueprintSetupState) -> None:
+ state.app.jinja_env.tests[name or f.__name__] = f
+
+ self.record_once(register_template)
+
+ @setupmethod
+ def app_template_global(
+ self, name: str | None = None
+ ) -> t.Callable[[T_template_global], T_template_global]:
+ """Register a template global, available in any template rendered by the
+ application. Equivalent to :meth:`.Flask.template_global`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global, otherwise the
+ function name will be used.
+ """
+
+ def decorator(f: T_template_global) -> T_template_global:
+ self.add_app_template_global(f, name=name)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_app_template_global(
+ self, f: ft.TemplateGlobalCallable, name: str | None = None
+ ) -> None:
+ """Register a template global, available in any template rendered by the
+ application. Works like the :meth:`app_template_global` decorator. Equivalent to
+ :meth:`.Flask.add_template_global`.
+
+ .. versionadded:: 0.10
+
+ :param name: the optional name of the global, otherwise the
+ function name will be used.
+ """
+
+ def register_template(state: BlueprintSetupState) -> None:
+ state.app.jinja_env.globals[name or f.__name__] = f
+
+ self.record_once(register_template)
+
+ @setupmethod
+ def before_app_request(self, f: T_before_request) -> T_before_request:
+ """Like :meth:`before_request`, but before every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.before_request`.
+ """
+ self.record_once(
+ lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def after_app_request(self, f: T_after_request) -> T_after_request:
+ """Like :meth:`after_request`, but after every request, not only those handled
+ by the blueprint. Equivalent to :meth:`.Flask.after_request`.
+ """
+ self.record_once(
+ lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def teardown_app_request(self, f: T_teardown) -> T_teardown:
+ """Like :meth:`teardown_request`, but after every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.teardown_request`.
+ """
+ self.record_once(
+ lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def app_context_processor(
+ self, f: T_template_context_processor
+ ) -> T_template_context_processor:
+ """Like :meth:`context_processor`, but for templates rendered by every view, not
+ only by the blueprint. Equivalent to :meth:`.Flask.context_processor`.
+ """
+ self.record_once(
+ lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def app_errorhandler(
+ self, code: type[Exception] | int
+ ) -> t.Callable[[T_error_handler], T_error_handler]:
+ """Like :meth:`errorhandler`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.errorhandler`.
+ """
+
+ def decorator(f: T_error_handler) -> T_error_handler:
+ def from_blueprint(state: BlueprintSetupState) -> None:
+ state.app.errorhandler(code)(f)
+
+ self.record_once(from_blueprint)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def app_url_value_preprocessor(
+ self, f: T_url_value_preprocessor
+ ) -> T_url_value_preprocessor:
+ """Like :meth:`url_value_preprocessor`, but for every request, not only those
+ handled by the blueprint. Equivalent to :meth:`.Flask.url_value_preprocessor`.
+ """
+ self.record_once(
+ lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
+ )
+ return f
+
+ @setupmethod
+ def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
+ """Like :meth:`url_defaults`, but for every request, not only those handled by
+ the blueprint. Equivalent to :meth:`.Flask.url_defaults`.
+ """
+ self.record_once(
+ lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
+ )
+ return f
diff --git a/venv/Lib/site-packages/flask/sansio/scaffold.py b/venv/Lib/site-packages/flask/sansio/scaffold.py
new file mode 100644
index 0000000..5355700
--- /dev/null
+++ b/venv/Lib/site-packages/flask/sansio/scaffold.py
@@ -0,0 +1,805 @@
+from __future__ import annotations
+
+import importlib.util
+import os
+import pathlib
+import sys
+import typing as t
+from collections import defaultdict
+from functools import update_wrapper
+
+import click
+from jinja2 import BaseLoader
+from jinja2 import FileSystemLoader
+from werkzeug.exceptions import default_exceptions
+from werkzeug.exceptions import HTTPException
+from werkzeug.utils import cached_property
+
+from .. import typing as ft
+from ..cli import AppGroup
+from ..helpers import get_root_path
+from ..templating import _default_template_ctx_processor
+
+# a singleton sentinel value for parameter defaults
+_sentinel = object()
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable[t.Any])
+T_before_request = t.TypeVar("T_before_request", bound=ft.BeforeRequestCallable)
+T_error_handler = t.TypeVar("T_error_handler", bound=ft.ErrorHandlerCallable)
+T_teardown = t.TypeVar("T_teardown", bound=ft.TeardownCallable)
+T_template_context_processor = t.TypeVar(
+ "T_template_context_processor", bound=ft.TemplateContextProcessorCallable
+)
+T_url_defaults = t.TypeVar("T_url_defaults", bound=ft.URLDefaultCallable)
+T_url_value_preprocessor = t.TypeVar(
+ "T_url_value_preprocessor", bound=ft.URLValuePreprocessorCallable
+)
+T_route = t.TypeVar("T_route", bound=ft.RouteCallable)
+
+
+def setupmethod(f: F) -> F:
+ f_name = f.__name__
+
+ def wrapper_func(self: Scaffold, *args: t.Any, **kwargs: t.Any) -> t.Any:
+ self._check_setup_finished(f_name)
+ return f(self, *args, **kwargs)
+
+ return t.cast(F, update_wrapper(wrapper_func, f))
+
+
+class Scaffold:
+ """Common behavior shared between :class:`~flask.Flask` and
+ :class:`~flask.blueprints.Blueprint`.
+
+ :param import_name: The import name of the module where this object
+ is defined. Usually :attr:`__name__` should be used.
+ :param static_folder: Path to a folder of static files to serve.
+ If this is set, a static route will be added.
+ :param static_url_path: URL prefix for the static route.
+ :param template_folder: Path to a folder containing template files.
+ for rendering. If this is set, a Jinja loader will be added.
+ :param root_path: The path that static, template, and resource files
+ are relative to. Typically not set, it is discovered based on
+ the ``import_name``.
+
+ .. versionadded:: 2.0
+ """
+
+ name: str
+ _static_folder: str | None = None
+ _static_url_path: str | None = None
+
+ def __init__(
+ self,
+ import_name: str,
+ static_folder: str | os.PathLike[str] | None = None,
+ static_url_path: str | None = None,
+ template_folder: str | os.PathLike[str] | None = None,
+ root_path: str | None = None,
+ ):
+ #: The name of the package or module that this object belongs
+ #: to. Do not change this once it is set by the constructor.
+ self.import_name = import_name
+
+ self.static_folder = static_folder # type: ignore
+ self.static_url_path = static_url_path
+
+ #: The path to the templates folder, relative to
+ #: :attr:`root_path`, to add to the template loader. ``None`` if
+ #: templates should not be added.
+ self.template_folder = template_folder
+
+ if root_path is None:
+ root_path = get_root_path(self.import_name)
+
+ #: Absolute path to the package on the filesystem. Used to look
+ #: up resources contained in the package.
+ self.root_path = root_path
+
+ #: The Click command group for registering CLI commands for this
+ #: object. The commands are available from the ``flask`` command
+ #: once the application has been discovered and blueprints have
+ #: been registered.
+ self.cli: click.Group = AppGroup()
+
+ #: A dictionary mapping endpoint names to view functions.
+ #:
+ #: To register a view function, use the :meth:`route` decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.view_functions: dict[str, ft.RouteCallable] = {}
+
+ #: A data structure of registered error handlers, in the format
+ #: ``{scope: {code: {class: handler}}}``. The ``scope`` key is
+ #: the name of a blueprint the handlers are active for, or
+ #: ``None`` for all requests. The ``code`` key is the HTTP
+ #: status code for ``HTTPException``, or ``None`` for
+ #: other exceptions. The innermost dictionary maps exception
+ #: classes to handler functions.
+ #:
+ #: To register an error handler, use the :meth:`errorhandler`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.error_handler_spec: dict[
+ ft.AppOrBlueprintKey,
+ dict[int | None, dict[type[Exception], ft.ErrorHandlerCallable]],
+ ] = defaultdict(lambda: defaultdict(dict))
+
+ #: A data structure of functions to call at the beginning of
+ #: each request, in the format ``{scope: [functions]}``. The
+ #: ``scope`` key is the name of a blueprint the functions are
+ #: active for, or ``None`` for all requests.
+ #:
+ #: To register a function, use the :meth:`before_request`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.before_request_funcs: dict[
+ ft.AppOrBlueprintKey, list[ft.BeforeRequestCallable]
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call at the end of each
+ #: request, in the format ``{scope: [functions]}``. The
+ #: ``scope`` key is the name of a blueprint the functions are
+ #: active for, or ``None`` for all requests.
+ #:
+ #: To register a function, use the :meth:`after_request`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.after_request_funcs: dict[
+ ft.AppOrBlueprintKey, list[ft.AfterRequestCallable[t.Any]]
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call at the end of each
+ #: request even if an exception is raised, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the :meth:`teardown_request`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.teardown_request_funcs: dict[
+ ft.AppOrBlueprintKey, list[ft.TeardownCallable]
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call to pass extra context
+ #: values when rendering templates, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the :meth:`context_processor`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.template_context_processors: dict[
+ ft.AppOrBlueprintKey, list[ft.TemplateContextProcessorCallable]
+ ] = defaultdict(list, {None: [_default_template_ctx_processor]})
+
+ #: A data structure of functions to call to modify the keyword
+ #: arguments passed to the view function, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the
+ #: :meth:`url_value_preprocessor` decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.url_value_preprocessors: dict[
+ ft.AppOrBlueprintKey,
+ list[ft.URLValuePreprocessorCallable],
+ ] = defaultdict(list)
+
+ #: A data structure of functions to call to modify the keyword
+ #: arguments when generating URLs, in the format
+ #: ``{scope: [functions]}``. The ``scope`` key is the name of a
+ #: blueprint the functions are active for, or ``None`` for all
+ #: requests.
+ #:
+ #: To register a function, use the :meth:`url_defaults`
+ #: decorator.
+ #:
+ #: This data structure is internal. It should not be modified
+ #: directly and its format may change at any time.
+ self.url_default_functions: dict[
+ ft.AppOrBlueprintKey, list[ft.URLDefaultCallable]
+ ] = defaultdict(list)
+
+ def __repr__(self) -> str:
+ return f"<{type(self).__name__} {self.name!r}>"
+
+ def _check_setup_finished(self, f_name: str) -> None:
+ raise NotImplementedError
+
+ @property
+ def static_folder(self) -> str | None:
+ """The absolute path to the configured static folder. ``None``
+ if no static folder is set.
+ """
+ if self._static_folder is not None:
+ return os.path.join(self.root_path, self._static_folder)
+ else:
+ return None
+
+ @static_folder.setter
+ def static_folder(self, value: str | os.PathLike[str] | None) -> None:
+ if value is not None:
+ value = os.fspath(value).rstrip(r"\/")
+
+ self._static_folder = value
+
+ @property
+ def has_static_folder(self) -> bool:
+ """``True`` if :attr:`static_folder` is set.
+
+ .. versionadded:: 0.5
+ """
+ return self.static_folder is not None
+
+ @property
+ def static_url_path(self) -> str | None:
+ """The URL prefix that the static route will be accessible from.
+
+ If it was not configured during init, it is derived from
+ :attr:`static_folder`.
+ """
+ if self._static_url_path is not None:
+ return self._static_url_path
+
+ if self.static_folder is not None:
+ basename = os.path.basename(self.static_folder)
+ return f"/{basename}".rstrip("/")
+
+ return None
+
+ @static_url_path.setter
+ def static_url_path(self, value: str | None) -> None:
+ if value is not None:
+ value = value.rstrip("/")
+
+ self._static_url_path = value
+
+ @cached_property
+ def jinja_loader(self) -> BaseLoader | None:
+ """The Jinja loader for this object's templates. By default this
+ is a class :class:`jinja2.loaders.FileSystemLoader` to
+ :attr:`template_folder` if it is set.
+
+ .. versionadded:: 0.5
+ """
+ if self.template_folder is not None:
+ return FileSystemLoader(os.path.join(self.root_path, self.template_folder))
+ else:
+ return None
+
+ def _method_route(
+ self,
+ method: str,
+ rule: str,
+ options: dict[str, t.Any],
+ ) -> t.Callable[[T_route], T_route]:
+ if "methods" in options:
+ raise TypeError("Use the 'route' decorator to use the 'methods' argument.")
+
+ return self.route(rule, methods=[method], **options)
+
+ @setupmethod
+ def get(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["GET"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("GET", rule, options)
+
+ @setupmethod
+ def post(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["POST"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("POST", rule, options)
+
+ @setupmethod
+ def put(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["PUT"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("PUT", rule, options)
+
+ @setupmethod
+ def delete(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["DELETE"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("DELETE", rule, options)
+
+ @setupmethod
+ def patch(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Shortcut for :meth:`route` with ``methods=["PATCH"]``.
+
+ .. versionadded:: 2.0
+ """
+ return self._method_route("PATCH", rule, options)
+
+ @setupmethod
+ def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
+ """Decorate a view function to register it with the given URL
+ rule and options. Calls :meth:`add_url_rule`, which has more
+ details about the implementation.
+
+ .. code-block:: python
+
+ @app.route("/")
+ def index():
+ return "Hello, World!"
+
+ See :ref:`url-route-registrations`.
+
+ The endpoint name for the route defaults to the name of the view
+ function if the ``endpoint`` parameter isn't passed.
+
+ The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` and
+ ``OPTIONS`` are added automatically.
+
+ :param rule: The URL rule string.
+ :param options: Extra options passed to the
+ :class:`~werkzeug.routing.Rule` object.
+ """
+
+ def decorator(f: T_route) -> T_route:
+ endpoint = options.pop("endpoint", None)
+ self.add_url_rule(rule, endpoint, f, **options)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def add_url_rule(
+ self,
+ rule: str,
+ endpoint: str | None = None,
+ view_func: ft.RouteCallable | None = None,
+ provide_automatic_options: bool | None = None,
+ **options: t.Any,
+ ) -> None:
+ """Register a rule for routing incoming requests and building
+ URLs. The :meth:`route` decorator is a shortcut to call this
+ with the ``view_func`` argument. These are equivalent:
+
+ .. code-block:: python
+
+ @app.route("/")
+ def index():
+ ...
+
+ .. code-block:: python
+
+ def index():
+ ...
+
+ app.add_url_rule("/", view_func=index)
+
+ See :ref:`url-route-registrations`.
+
+ The endpoint name for the route defaults to the name of the view
+ function if the ``endpoint`` parameter isn't passed. An error
+ will be raised if a function has already been registered for the
+ endpoint.
+
+ The ``methods`` parameter defaults to ``["GET"]``. ``HEAD`` is
+ always added automatically, and ``OPTIONS`` is added
+ automatically by default.
+
+ ``view_func`` does not necessarily need to be passed, but if the
+ rule should participate in routing an endpoint name must be
+ associated with a view function at some point with the
+ :meth:`endpoint` decorator.
+
+ .. code-block:: python
+
+ app.add_url_rule("/", endpoint="index")
+
+ @app.endpoint("index")
+ def index():
+ ...
+
+ If ``view_func`` has a ``required_methods`` attribute, those
+ methods are added to the passed and automatic methods. If it
+ has a ``provide_automatic_methods`` attribute, it is used as the
+ default if the parameter is not passed.
+
+ :param rule: The URL rule string.
+ :param endpoint: The endpoint name to associate with the rule
+ and view function. Used when routing and building URLs.
+ Defaults to ``view_func.__name__``.
+ :param view_func: The view function to associate with the
+ endpoint name.
+ :param provide_automatic_options: Add the ``OPTIONS`` method and
+ respond to ``OPTIONS`` requests automatically.
+ :param options: Extra options passed to the
+ :class:`~werkzeug.routing.Rule` object.
+ """
+ raise NotImplementedError
+
+ @setupmethod
+ def endpoint(self, endpoint: str) -> t.Callable[[F], F]:
+ """Decorate a view function to register it for the given
+ endpoint. Used if a rule is added without a ``view_func`` with
+ :meth:`add_url_rule`.
+
+ .. code-block:: python
+
+ app.add_url_rule("/ex", endpoint="example")
+
+ @app.endpoint("example")
+ def example():
+ ...
+
+ :param endpoint: The endpoint name to associate with the view
+ function.
+ """
+
+ def decorator(f: F) -> F:
+ self.view_functions[endpoint] = f
+ return f
+
+ return decorator
+
+ @setupmethod
+ def before_request(self, f: T_before_request) -> T_before_request:
+ """Register a function to run before each request.
+
+ For example, this can be used to open a database connection, or
+ to load the logged in user from the session.
+
+ .. code-block:: python
+
+ @app.before_request
+ def load_user():
+ if "user_id" in session:
+ g.user = db.session.get(session["user_id"])
+
+ The function will be called without any arguments. If it returns
+ a non-``None`` value, the value is handled as if it was the
+ return value from the view, and further request handling is
+ stopped.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes before every request. When used on a blueprint, this executes before
+ every request that the blueprint handles. To register with a blueprint and
+ execute before every request, use :meth:`.Blueprint.before_app_request`.
+ """
+ self.before_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def after_request(self, f: T_after_request) -> T_after_request:
+ """Register a function to run after each request to this object.
+
+ The function is called with the response object, and must return
+ a response object. This allows the functions to modify or
+ replace the response before it is sent.
+
+ If a function raises an exception, any remaining
+ ``after_request`` functions will not be called. Therefore, this
+ should not be used for actions that must execute, such as to
+ close resources. Use :meth:`teardown_request` for that.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.after_app_request`.
+ """
+ self.after_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def teardown_request(self, f: T_teardown) -> T_teardown:
+ """Register a function to be called when the request context is
+ popped. Typically this happens at the end of each request, but
+ contexts may be pushed manually as well during testing.
+
+ .. code-block:: python
+
+ with app.test_request_context():
+ ...
+
+ When the ``with`` block exits (or ``ctx.pop()`` is called), the
+ teardown functions are called just before the request context is
+ made inactive.
+
+ When a teardown function was called because of an unhandled
+ exception it will be passed an error object. If an
+ :meth:`errorhandler` is registered, it will handle the exception
+ and the teardown will not receive it.
+
+ Teardown functions must avoid raising exceptions. If they
+ execute code that might fail they must surround that code with a
+ ``try``/``except`` block and log any errors.
+
+ The return values of teardown functions are ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ executes after every request. When used on a blueprint, this executes after
+ every request that the blueprint handles. To register with a blueprint and
+ execute after every request, use :meth:`.Blueprint.teardown_app_request`.
+ """
+ self.teardown_request_funcs.setdefault(None, []).append(f)
+ return f
+
+ @setupmethod
+ def context_processor(
+ self,
+ f: T_template_context_processor,
+ ) -> T_template_context_processor:
+ """Registers a template context processor function. These functions run before
+ rendering a template. The keys of the returned dict are added as variables
+ available in the template.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every rendered template. When used on a blueprint, this is called
+ for templates rendered from the blueprint's views. To register with a blueprint
+ and affect every template, use :meth:`.Blueprint.app_context_processor`.
+ """
+ self.template_context_processors[None].append(f)
+ return f
+
+ @setupmethod
+ def url_value_preprocessor(
+ self,
+ f: T_url_value_preprocessor,
+ ) -> T_url_value_preprocessor:
+ """Register a URL value preprocessor function for all view
+ functions in the application. These functions will be called before the
+ :meth:`before_request` functions.
+
+ The function can modify the values captured from the matched url before
+ they are passed to the view. For example, this can be used to pop a
+ common language code value and place it in ``g`` rather than pass it to
+ every view.
+
+ The function is passed the endpoint name and values dict. The return
+ value is ignored.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_value_preprocessor`.
+ """
+ self.url_value_preprocessors[None].append(f)
+ return f
+
+ @setupmethod
+ def url_defaults(self, f: T_url_defaults) -> T_url_defaults:
+ """Callback function for URL defaults for all view functions of the
+ application. It's called with the endpoint and values and should
+ update the values passed in place.
+
+ This is available on both app and blueprint objects. When used on an app, this
+ is called for every request. When used on a blueprint, this is called for
+ requests that the blueprint handles. To register with a blueprint and affect
+ every request, use :meth:`.Blueprint.app_url_defaults`.
+ """
+ self.url_default_functions[None].append(f)
+ return f
+
+ @setupmethod
+ def errorhandler(
+ self, code_or_exception: type[Exception] | int
+ ) -> t.Callable[[T_error_handler], T_error_handler]:
+ """Register a function to handle errors by code or exception class.
+
+ A decorator that is used to register a function given an
+ error code. Example::
+
+ @app.errorhandler(404)
+ def page_not_found(error):
+ return 'This page does not exist', 404
+
+ You can also register handlers for arbitrary exceptions::
+
+ @app.errorhandler(DatabaseError)
+ def special_exception_handler(error):
+ return 'Database connection failed', 500
+
+ This is available on both app and blueprint objects. When used on an app, this
+ can handle errors from every request. When used on a blueprint, this can handle
+ errors from requests that the blueprint handles. To register with a blueprint
+ and affect every request, use :meth:`.Blueprint.app_errorhandler`.
+
+ .. versionadded:: 0.7
+ Use :meth:`register_error_handler` instead of modifying
+ :attr:`error_handler_spec` directly, for application wide error
+ handlers.
+
+ .. versionadded:: 0.7
+ One can now additionally also register custom exception types
+ that do not necessarily have to be a subclass of the
+ :class:`~werkzeug.exceptions.HTTPException` class.
+
+ :param code_or_exception: the code as integer for the handler, or
+ an arbitrary exception
+ """
+
+ def decorator(f: T_error_handler) -> T_error_handler:
+ self.register_error_handler(code_or_exception, f)
+ return f
+
+ return decorator
+
+ @setupmethod
+ def register_error_handler(
+ self,
+ code_or_exception: type[Exception] | int,
+ f: ft.ErrorHandlerCallable,
+ ) -> None:
+ """Alternative error attach function to the :meth:`errorhandler`
+ decorator that is more straightforward to use for non decorator
+ usage.
+
+ .. versionadded:: 0.7
+ """
+ exc_class, code = self._get_exc_class_and_code(code_or_exception)
+ self.error_handler_spec[None][code][exc_class] = f
+
+ @staticmethod
+ def _get_exc_class_and_code(
+ exc_class_or_code: type[Exception] | int,
+ ) -> tuple[type[Exception], int | None]:
+ """Get the exception class being handled. For HTTP status codes
+ or ``HTTPException`` subclasses, return both the exception and
+ status code.
+
+ :param exc_class_or_code: Any exception class, or an HTTP status
+ code as an integer.
+ """
+ exc_class: type[Exception]
+
+ if isinstance(exc_class_or_code, int):
+ try:
+ exc_class = default_exceptions[exc_class_or_code]
+ except KeyError:
+ raise ValueError(
+ f"'{exc_class_or_code}' is not a recognized HTTP"
+ " error code. Use a subclass of HTTPException with"
+ " that code instead."
+ ) from None
+ else:
+ exc_class = exc_class_or_code
+
+ if isinstance(exc_class, Exception):
+ raise TypeError(
+ f"{exc_class!r} is an instance, not a class. Handlers"
+ " can only be registered for Exception classes or HTTP"
+ " error codes."
+ )
+
+ if not issubclass(exc_class, Exception):
+ raise ValueError(
+ f"'{exc_class.__name__}' is not a subclass of Exception."
+ " Handlers can only be registered for Exception classes"
+ " or HTTP error codes."
+ )
+
+ if issubclass(exc_class, HTTPException):
+ return exc_class, exc_class.code
+ else:
+ return exc_class, None
+
+
+def _endpoint_from_view_func(view_func: ft.RouteCallable) -> str:
+ """Internal helper that returns the default endpoint for a given
+ function. This always is the function name.
+ """
+ assert view_func is not None, "expected view func if endpoint is not provided."
+ return view_func.__name__
+
+
+def _path_is_relative_to(path: pathlib.PurePath, base: str) -> bool:
+ # Path.is_relative_to doesn't exist until Python 3.9
+ try:
+ path.relative_to(base)
+ return True
+ except ValueError:
+ return False
+
+
+def _find_package_path(import_name: str) -> str:
+ """Find the path that contains the package or module."""
+ root_mod_name, _, _ = import_name.partition(".")
+
+ try:
+ root_spec = importlib.util.find_spec(root_mod_name)
+
+ if root_spec is None:
+ raise ValueError("not found")
+ except (ImportError, ValueError):
+ # ImportError: the machinery told us it does not exist
+ # ValueError:
+ # - the module name was invalid
+ # - the module name is __main__
+ # - we raised `ValueError` due to `root_spec` being `None`
+ return os.getcwd()
+
+ if root_spec.submodule_search_locations:
+ if root_spec.origin is None or root_spec.origin == "namespace":
+ # namespace package
+ package_spec = importlib.util.find_spec(import_name)
+
+ if package_spec is not None and package_spec.submodule_search_locations:
+ # Pick the path in the namespace that contains the submodule.
+ package_path = pathlib.Path(
+ os.path.commonpath(package_spec.submodule_search_locations)
+ )
+ search_location = next(
+ location
+ for location in root_spec.submodule_search_locations
+ if _path_is_relative_to(package_path, location)
+ )
+ else:
+ # Pick the first path.
+ search_location = root_spec.submodule_search_locations[0]
+
+ return os.path.dirname(search_location)
+ else:
+ # package with __init__.py
+ return os.path.dirname(os.path.dirname(root_spec.origin))
+ else:
+ # module
+ return os.path.dirname(root_spec.origin) # type: ignore[type-var, return-value]
+
+
+def find_package(import_name: str) -> tuple[str | None, str]:
+ """Find the prefix that a package is installed under, and the path
+ that it would be imported from.
+
+ The prefix is the directory containing the standard directory
+ hierarchy (lib, bin, etc.). If the package is not installed to the
+ system (:attr:`sys.prefix`) or a virtualenv (``site-packages``),
+ ``None`` is returned.
+
+ The path is the entry in :attr:`sys.path` that contains the package
+ for import. If the package is not installed, it's assumed that the
+ package was imported from the current working directory.
+ """
+ package_path = _find_package_path(import_name)
+ py_prefix = os.path.abspath(sys.prefix)
+
+ # installed to the system
+ if _path_is_relative_to(pathlib.PurePath(package_path), py_prefix):
+ return py_prefix, package_path
+
+ site_parent, site_folder = os.path.split(package_path)
+
+ # installed to a virtualenv
+ if site_folder.lower() == "site-packages":
+ parent, folder = os.path.split(site_parent)
+
+ # Windows (prefix/lib/site-packages)
+ if folder.lower() == "lib":
+ return parent, package_path
+
+ # Unix (prefix/lib/pythonX.Y/site-packages)
+ if os.path.basename(parent).lower() == "lib":
+ return os.path.dirname(parent), package_path
+
+ # something else (prefix/site-packages)
+ return site_parent, package_path
+
+ # not installed
+ return None, package_path
diff --git a/venv/Lib/site-packages/flask/sessions.py b/venv/Lib/site-packages/flask/sessions.py
new file mode 100644
index 0000000..bb753eb
--- /dev/null
+++ b/venv/Lib/site-packages/flask/sessions.py
@@ -0,0 +1,371 @@
+from __future__ import annotations
+
+import hashlib
+import typing as t
+from collections.abc import MutableMapping
+from datetime import datetime
+from datetime import timezone
+
+from itsdangerous import BadSignature
+from itsdangerous import URLSafeTimedSerializer
+from werkzeug.datastructures import CallbackDict
+
+from .json.tag import TaggedJSONSerializer
+
+if t.TYPE_CHECKING: # pragma: no cover
+ import typing_extensions as te
+
+ from .app import Flask
+ from .wrappers import Request
+ from .wrappers import Response
+
+
+# TODO generic when Python > 3.8
+class SessionMixin(MutableMapping): # type: ignore[type-arg]
+ """Expands a basic dictionary with session attributes."""
+
+ @property
+ def permanent(self) -> bool:
+ """This reflects the ``'_permanent'`` key in the dict."""
+ return self.get("_permanent", False)
+
+ @permanent.setter
+ def permanent(self, value: bool) -> None:
+ 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
+
+
+# TODO generic when Python > 3.8
+class SecureCookieSession(CallbackDict, SessionMixin): # type: ignore[type-arg]
+ """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: t.Any = None) -> None:
+ def on_update(self: te.Self) -> None:
+ self.modified = True
+ self.accessed = True
+
+ super().__init__(initial, on_update)
+
+ def __getitem__(self, key: str) -> t.Any:
+ self.accessed = True
+ return super().__getitem__(key)
+
+ def get(self, key: str, default: t.Any = None) -> t.Any:
+ self.accessed = True
+ return super().get(key, default)
+
+ def setdefault(self, key: str, default: t.Any = None) -> t.Any:
+ self.accessed = True
+ return super().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: t.Any, **kwargs: t.Any) -> t.NoReturn:
+ 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 # type: ignore # noqa: B950
+ del _fail
+
+
+class SessionInterface:
+ """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()
+
+ Multiple requests with the same session may be sent and handled
+ concurrently. When implementing a new session interface, consider
+ whether reads or writes to the backing store must be synchronized.
+ There is no guarantee on the order in which the session for each
+ request is opened or saved, it will occur in the order that requests
+ begin and end processing.
+
+ .. 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: Flask) -> NullSession:
+ """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: object) -> bool:
+ """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_name(self, app: Flask) -> str:
+ """The name of the session cookie. Uses``app.config["SESSION_COOKIE_NAME"]``."""
+ return app.config["SESSION_COOKIE_NAME"] # type: ignore[no-any-return]
+
+ def get_cookie_domain(self, app: Flask) -> str | None:
+ """The value of the ``Domain`` parameter on the session cookie. If not set,
+ browsers will only send the cookie to the exact domain it was set from.
+ Otherwise, they will send it to any subdomain of the given value as well.
+
+ Uses the :data:`SESSION_COOKIE_DOMAIN` config.
+
+ .. versionchanged:: 2.3
+ Not set by default, does not fall back to ``SERVER_NAME``.
+ """
+ return app.config["SESSION_COOKIE_DOMAIN"] # type: ignore[no-any-return]
+
+ def get_cookie_path(self, app: Flask) -> str:
+ """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"] # type: ignore[no-any-return]
+
+ def get_cookie_httponly(self, app: Flask) -> bool:
+ """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"] # type: ignore[no-any-return]
+
+ def get_cookie_secure(self, app: Flask) -> bool:
+ """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"] # type: ignore[no-any-return]
+
+ def get_cookie_samesite(self, app: Flask) -> str | None:
+ """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"] # type: ignore[no-any-return]
+
+ def get_expiration_time(self, app: Flask, session: SessionMixin) -> datetime | None:
+ """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.now(timezone.utc) + app.permanent_session_lifetime
+ return None
+
+ def should_set_cookie(self, app: Flask, session: SessionMixin) -> bool:
+ """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: Flask, request: Request) -> SessionMixin | None:
+ """This is called at the beginning of each request, after
+ pushing the request context, before matching the URL.
+
+ This must return an object which implements a dictionary-like
+ interface as well as the :class:`SessionMixin` interface.
+
+ This will return ``None`` to indicate that loading failed in
+ some way that is not immediately an error. The request
+ context will fall back to using :meth:`make_null_session`
+ in this case.
+ """
+ raise NotImplementedError()
+
+ def save_session(
+ self, app: Flask, session: SessionMixin, response: Response
+ ) -> None:
+ """This is called at the end of each request, after generating
+ a response, before removing the request context. It is skipped
+ if :meth:`is_null_session` returns ``True``.
+ """
+ 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: Flask) -> URLSafeTimedSerializer | None:
+ 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: Flask, request: Request) -> SecureCookieSession | None:
+ s = self.get_signing_serializer(app)
+ if s is None:
+ return None
+ val = request.cookies.get(self.get_cookie_name(app))
+ if not val:
+ return self.session_class()
+ max_age = int(app.permanent_session_lifetime.total_seconds())
+ 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: Flask, session: SessionMixin, response: Response
+ ) -> None:
+ name = self.get_cookie_name(app)
+ domain = self.get_cookie_domain(app)
+ path = self.get_cookie_path(app)
+ secure = self.get_cookie_secure(app)
+ samesite = self.get_cookie_samesite(app)
+ httponly = self.get_cookie_httponly(app)
+
+ # Add a "Vary: Cookie" header if the session was accessed at all.
+ if session.accessed:
+ response.vary.add("Cookie")
+
+ # 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(
+ name,
+ domain=domain,
+ path=path,
+ secure=secure,
+ samesite=samesite,
+ httponly=httponly,
+ )
+ response.vary.add("Cookie")
+
+ return
+
+ if not self.should_set_cookie(app, session):
+ return
+
+ expires = self.get_expiration_time(app, session)
+ val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
+ response.set_cookie(
+ name,
+ val, # type: ignore
+ expires=expires,
+ httponly=httponly,
+ domain=domain,
+ path=path,
+ secure=secure,
+ samesite=samesite,
+ )
+ response.vary.add("Cookie")
diff --git a/venv/Lib/site-packages/flask/signals.py b/venv/Lib/site-packages/flask/signals.py
new file mode 100644
index 0000000..444fda9
--- /dev/null
+++ b/venv/Lib/site-packages/flask/signals.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+from blinker import Namespace
+
+# This namespace is only for signals provided by Flask itself.
+_signals = Namespace()
+
+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")
diff --git a/venv/Lib/site-packages/flask/templating.py b/venv/Lib/site-packages/flask/templating.py
new file mode 100644
index 0000000..618a3b3
--- /dev/null
+++ b/venv/Lib/site-packages/flask/templating.py
@@ -0,0 +1,219 @@
+from __future__ import annotations
+
+import typing as t
+
+from jinja2 import BaseLoader
+from jinja2 import Environment as BaseEnvironment
+from jinja2 import Template
+from jinja2 import TemplateNotFound
+
+from .globals import _cv_app
+from .globals import _cv_request
+from .globals import current_app
+from .globals import request
+from .helpers import stream_with_context
+from .signals import before_render_template
+from .signals import template_rendered
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from .app import Flask
+ from .sansio.app import App
+ from .sansio.scaffold import Scaffold
+
+
+def _default_template_ctx_processor() -> dict[str, t.Any]:
+ """Default template context processor. Injects `request`,
+ `session` and `g`.
+ """
+ appctx = _cv_app.get(None)
+ reqctx = _cv_request.get(None)
+ rv: dict[str, t.Any] = {}
+ 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: App, **options: t.Any) -> None:
+ 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: App) -> None:
+ self.app = app
+
+ def get_source(
+ self, environment: BaseEnvironment, template: str
+ ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
+ 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: BaseEnvironment, template: str
+ ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
+ attempts = []
+ rv: tuple[str, str | None, t.Callable[[], bool] | None] | None
+ trv: None | (tuple[str, str | None, t.Callable[[], bool] | None]) = 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: BaseEnvironment, template: str
+ ) -> tuple[str, str | None, t.Callable[[], bool] | None]:
+ 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: str) -> t.Iterator[tuple[Scaffold, BaseLoader]]:
+ 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) -> list[str]:
+ 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(app: Flask, template: Template, context: dict[str, t.Any]) -> str:
+ app.update_template_context(context)
+ before_render_template.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+ rv = template.render(context)
+ template_rendered.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+ return rv
+
+
+def render_template(
+ template_name_or_list: str | Template | list[str | Template],
+ **context: t.Any,
+) -> str:
+ """Render a template by name with the given context.
+
+ :param template_name_or_list: The name of the template to render. If
+ a list is given, the first name to exist will be rendered.
+ :param context: The variables to make available in the template.
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.get_or_select_template(template_name_or_list)
+ return _render(app, template, context)
+
+
+def render_template_string(source: str, **context: t.Any) -> str:
+ """Render a template from the given source string with the given
+ context.
+
+ :param source: The source code of the template to render.
+ :param context: The variables to make available in the template.
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.from_string(source)
+ return _render(app, template, context)
+
+
+def _stream(
+ app: Flask, template: Template, context: dict[str, t.Any]
+) -> t.Iterator[str]:
+ app.update_template_context(context)
+ before_render_template.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+
+ def generate() -> t.Iterator[str]:
+ yield from template.generate(context)
+ template_rendered.send(
+ app, _async_wrapper=app.ensure_sync, template=template, context=context
+ )
+
+ rv = generate()
+
+ # If a request context is active, keep it while generating.
+ if request:
+ rv = stream_with_context(rv)
+
+ return rv
+
+
+def stream_template(
+ template_name_or_list: str | Template | list[str | Template],
+ **context: t.Any,
+) -> t.Iterator[str]:
+ """Render a template by name with the given context as a stream.
+ This returns an iterator of strings, which can be used as a
+ streaming response from a view.
+
+ :param template_name_or_list: The name of the template to render. If
+ a list is given, the first name to exist will be rendered.
+ :param context: The variables to make available in the template.
+
+ .. versionadded:: 2.2
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.get_or_select_template(template_name_or_list)
+ return _stream(app, template, context)
+
+
+def stream_template_string(source: str, **context: t.Any) -> t.Iterator[str]:
+ """Render a template from the given source string with the given
+ context as a stream. This returns an iterator of strings, which can
+ be used as a streaming response from a view.
+
+ :param source: The source code of the template to render.
+ :param context: The variables to make available in the template.
+
+ .. versionadded:: 2.2
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+ template = app.jinja_env.from_string(source)
+ return _stream(app, template, context)
diff --git a/venv/Lib/site-packages/flask/testing.py b/venv/Lib/site-packages/flask/testing.py
new file mode 100644
index 0000000..a27b7c8
--- /dev/null
+++ b/venv/Lib/site-packages/flask/testing.py
@@ -0,0 +1,298 @@
+from __future__ import annotations
+
+import importlib.metadata
+import typing as t
+from contextlib import contextmanager
+from contextlib import ExitStack
+from copy import copy
+from types import TracebackType
+from urllib.parse import urlsplit
+
+import werkzeug.test
+from click.testing import CliRunner
+from werkzeug.test import Client
+from werkzeug.wrappers import Request as BaseRequest
+
+from .cli import ScriptInfo
+from .sessions import SessionMixin
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import WSGIEnvironment
+ from werkzeug.test import TestResponse
+
+ from .app import Flask
+
+
+class EnvironBuilder(werkzeug.test.EnvironBuilder):
+ """An :class:`~werkzeug.test.EnvironBuilder`, that takes 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`.
+ """
+
+ def __init__(
+ self,
+ app: Flask,
+ path: str = "/",
+ base_url: str | None = None,
+ subdomain: str | None = None,
+ url_scheme: str | None = None,
+ *args: t.Any,
+ **kwargs: t.Any,
+ ) -> None:
+ 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 = f"{subdomain}.{http_host}"
+
+ if url_scheme is None:
+ url_scheme = app.config["PREFERRED_URL_SCHEME"]
+
+ url = urlsplit(path)
+ base_url = (
+ f"{url.scheme or url_scheme}://{url.netloc or http_host}"
+ f"/{app_root.lstrip('/')}"
+ )
+ path = url.path
+
+ if url.query:
+ sep = b"?" if isinstance(url.query, bytes) else "?"
+ path += sep + url.query
+
+ self.app = app
+ super().__init__(path, base_url, *args, **kwargs)
+
+ def json_dumps(self, obj: t.Any, **kwargs: t.Any) -> str: # type: ignore
+ """Serialize ``obj`` to a JSON-formatted string.
+
+ The serialization will be configured according to the config associated
+ with this EnvironBuilder's ``app``.
+ """
+ return self.app.json.dumps(obj, **kwargs)
+
+
+_werkzeug_version = ""
+
+
+def _get_werkzeug_version() -> str:
+ global _werkzeug_version
+
+ if not _werkzeug_version:
+ _werkzeug_version = importlib.metadata.version("werkzeug")
+
+ return _werkzeug_version
+
+
+class FlaskClient(Client):
+ """Works like a regular Werkzeug test client but has knowledge about
+ Flask's contexts to defer the cleanup of the request context until
+ the end of a ``with`` block. 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 :doc:`/testing` chapter.
+ """
+
+ application: Flask
+
+ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
+ super().__init__(*args, **kwargs)
+ self.preserve_context = False
+ self._new_contexts: list[t.ContextManager[t.Any]] = []
+ self._context_stack = ExitStack()
+ self.environ_base = {
+ "REMOTE_ADDR": "127.0.0.1",
+ "HTTP_USER_AGENT": f"Werkzeug/{_get_werkzeug_version()}",
+ }
+
+ @contextmanager
+ def session_transaction(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> t.Iterator[SessionMixin]:
+ """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._cookies is None:
+ raise TypeError(
+ "Cookies are disabled. Create a client with 'use_cookies=True'."
+ )
+
+ app = self.application
+ ctx = app.test_request_context(*args, **kwargs)
+ self._add_cookies_to_wsgi(ctx.request.environ)
+
+ with ctx:
+ sess = app.session_interface.open_session(app, ctx.request)
+
+ if sess is None:
+ raise RuntimeError("Session backend did not open a session.")
+
+ yield sess
+ resp = app.response_class()
+
+ if app.session_interface.is_null_session(sess):
+ return
+
+ with ctx:
+ app.session_interface.save_session(app, sess, resp)
+
+ self._update_cookies_from_response(
+ ctx.request.host.partition(":")[0],
+ ctx.request.path,
+ resp.headers.getlist("Set-Cookie"),
+ )
+
+ def _copy_environ(self, other: WSGIEnvironment) -> WSGIEnvironment:
+ out = {**self.environ_base, **other}
+
+ if self.preserve_context:
+ out["werkzeug.debug.preserve_context"] = self._new_contexts.append
+
+ return out
+
+ def _request_from_builder_args(
+ self, args: tuple[t.Any, ...], kwargs: dict[str, t.Any]
+ ) -> BaseRequest:
+ kwargs["environ_base"] = self._copy_environ(kwargs.get("environ_base", {}))
+ builder = EnvironBuilder(self.application, *args, **kwargs)
+
+ try:
+ return builder.get_request()
+ finally:
+ builder.close()
+
+ def open(
+ self,
+ *args: t.Any,
+ buffered: bool = False,
+ follow_redirects: bool = False,
+ **kwargs: t.Any,
+ ) -> TestResponse:
+ if args and isinstance(
+ args[0], (werkzeug.test.EnvironBuilder, dict, BaseRequest)
+ ):
+ if isinstance(args[0], werkzeug.test.EnvironBuilder):
+ builder = copy(args[0])
+ builder.environ_base = self._copy_environ(builder.environ_base or {}) # type: ignore[arg-type]
+ request = builder.get_request()
+ elif isinstance(args[0], dict):
+ request = EnvironBuilder.from_environ(
+ args[0], app=self.application, environ_base=self._copy_environ({})
+ ).get_request()
+ else:
+ # isinstance(args[0], BaseRequest)
+ request = copy(args[0])
+ request.environ = self._copy_environ(request.environ)
+ else:
+ # request is None
+ request = self._request_from_builder_args(args, kwargs)
+
+ # Pop any previously preserved contexts. This prevents contexts
+ # from being preserved across redirects or multiple requests
+ # within a single block.
+ self._context_stack.close()
+
+ response = super().open(
+ request,
+ buffered=buffered,
+ follow_redirects=follow_redirects,
+ )
+ response.json_module = self.application.json # type: ignore[assignment]
+
+ # Re-push contexts that were preserved during the request.
+ while self._new_contexts:
+ cm = self._new_contexts.pop()
+ self._context_stack.enter_context(cm)
+
+ return response
+
+ def __enter__(self) -> FlaskClient:
+ if self.preserve_context:
+ raise RuntimeError("Cannot nest client invocations")
+ self.preserve_context = True
+ return self
+
+ def __exit__(
+ self,
+ exc_type: type | None,
+ exc_value: BaseException | None,
+ tb: TracebackType | None,
+ ) -> None:
+ self.preserve_context = False
+ self._context_stack.close()
+
+
+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: Flask, **kwargs: t.Any) -> None:
+ self.app = app
+ super().__init__(**kwargs)
+
+ def invoke( # type: ignore
+ self, cli: t.Any = None, args: t.Any = None, **kwargs: t.Any
+ ) -> t.Any:
+ """Invokes a CLI command in an isolated environment. See
+ :meth:`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().invoke(cli, args, **kwargs)
diff --git a/venv/Lib/site-packages/flask/typing.py b/venv/Lib/site-packages/flask/typing.py
new file mode 100644
index 0000000..cf6d4ae
--- /dev/null
+++ b/venv/Lib/site-packages/flask/typing.py
@@ -0,0 +1,90 @@
+from __future__ import annotations
+
+import typing as t
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from _typeshed.wsgi import WSGIApplication # noqa: F401
+ from werkzeug.datastructures import Headers # noqa: F401
+ from werkzeug.sansio.response import Response # noqa: F401
+
+# The possible types that are directly convertible or are a Response object.
+ResponseValue = t.Union[
+ "Response",
+ str,
+ bytes,
+ t.List[t.Any],
+ # Only dict is actually accepted, but Mapping allows for TypedDict.
+ t.Mapping[str, t.Any],
+ t.Iterator[str],
+ t.Iterator[bytes],
+]
+
+# the possible types for an individual HTTP header
+# This should be a Union, but mypy doesn't pass unless it's a TypeVar.
+HeaderValue = t.Union[str, t.List[str], t.Tuple[str, ...]]
+
+# the possible types for HTTP headers
+HeadersValue = t.Union[
+ "Headers",
+ t.Mapping[str, HeaderValue],
+ t.Sequence[t.Tuple[str, HeaderValue]],
+]
+
+# The possible types returned by a route function.
+ResponseReturnValue = t.Union[
+ ResponseValue,
+ t.Tuple[ResponseValue, HeadersValue],
+ t.Tuple[ResponseValue, int],
+ t.Tuple[ResponseValue, int, HeadersValue],
+ "WSGIApplication",
+]
+
+# Allow any subclass of werkzeug.Response, such as the one from Flask,
+# as a callback argument. Using werkzeug.Response directly makes a
+# callback annotated with flask.Response fail type checking.
+ResponseClass = t.TypeVar("ResponseClass", bound="Response")
+
+AppOrBlueprintKey = t.Optional[str] # The App key is None, whereas blueprints are named
+AfterRequestCallable = t.Union[
+ t.Callable[[ResponseClass], ResponseClass],
+ t.Callable[[ResponseClass], t.Awaitable[ResponseClass]],
+]
+BeforeFirstRequestCallable = t.Union[
+ t.Callable[[], None], t.Callable[[], t.Awaitable[None]]
+]
+BeforeRequestCallable = t.Union[
+ t.Callable[[], t.Optional[ResponseReturnValue]],
+ t.Callable[[], t.Awaitable[t.Optional[ResponseReturnValue]]],
+]
+ShellContextProcessorCallable = t.Callable[[], t.Dict[str, t.Any]]
+TeardownCallable = t.Union[
+ t.Callable[[t.Optional[BaseException]], None],
+ t.Callable[[t.Optional[BaseException]], t.Awaitable[None]],
+]
+TemplateContextProcessorCallable = t.Union[
+ t.Callable[[], t.Dict[str, t.Any]],
+ t.Callable[[], t.Awaitable[t.Dict[str, t.Any]]],
+]
+TemplateFilterCallable = t.Callable[..., t.Any]
+TemplateGlobalCallable = t.Callable[..., t.Any]
+TemplateTestCallable = t.Callable[..., bool]
+URLDefaultCallable = t.Callable[[str, t.Dict[str, t.Any]], None]
+URLValuePreprocessorCallable = t.Callable[
+ [t.Optional[str], t.Optional[t.Dict[str, t.Any]]], None
+]
+
+# This should take Exception, but that either breaks typing the argument
+# with a specific exception, or decorating multiple times with different
+# exceptions (and using a union type on the argument).
+# https://github.com/pallets/flask/issues/4095
+# https://github.com/pallets/flask/issues/4295
+# https://github.com/pallets/flask/issues/4297
+ErrorHandlerCallable = t.Union[
+ t.Callable[[t.Any], ResponseReturnValue],
+ t.Callable[[t.Any], t.Awaitable[ResponseReturnValue]],
+]
+
+RouteCallable = t.Union[
+ t.Callable[..., ResponseReturnValue],
+ t.Callable[..., t.Awaitable[ResponseReturnValue]],
+]
diff --git a/venv/Lib/site-packages/flask/views.py b/venv/Lib/site-packages/flask/views.py
new file mode 100644
index 0000000..794fdc0
--- /dev/null
+++ b/venv/Lib/site-packages/flask/views.py
@@ -0,0 +1,191 @@
+from __future__ import annotations
+
+import typing as t
+
+from . import typing as ft
+from .globals import current_app
+from .globals import request
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+
+http_method_funcs = frozenset(
+ ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
+)
+
+
+class View:
+ """Subclass this class and override :meth:`dispatch_request` to
+ create a generic class-based view. Call :meth:`as_view` to create a
+ view function that creates an instance of the class with the given
+ arguments and calls its ``dispatch_request`` method with any URL
+ variables.
+
+ See :doc:`views` for a detailed guide.
+
+ .. code-block:: python
+
+ class Hello(View):
+ init_every_request = False
+
+ def dispatch_request(self, name):
+ return f"Hello, {name}!"
+
+ app.add_url_rule(
+ "/hello/", view_func=Hello.as_view("hello")
+ )
+
+ Set :attr:`methods` on the class to change what methods the view
+ accepts.
+
+ Set :attr:`decorators` on the class to apply a list of decorators to
+ the generated view function. Decorators applied to the class itself
+ will not be applied to the generated view function!
+
+ Set :attr:`init_every_request` to ``False`` for efficiency, unless
+ you need to store request-global data on ``self``.
+ """
+
+ #: The methods this view is registered for. Uses the same default
+ #: (``["GET", "HEAD", "OPTIONS"]``) as ``route`` and
+ #: ``add_url_rule`` by default.
+ methods: t.ClassVar[t.Collection[str] | None] = None
+
+ #: Control whether the ``OPTIONS`` method is handled automatically.
+ #: Uses the same default (``True``) as ``route`` and
+ #: ``add_url_rule`` by default.
+ provide_automatic_options: t.ClassVar[bool | None] = None
+
+ #: A list of decorators to apply, in order, to the generated view
+ #: function. Remember that ``@decorator`` syntax is applied bottom
+ #: to top, so the first decorator in the list would be the bottom
+ #: decorator.
+ #:
+ #: .. versionadded:: 0.8
+ decorators: t.ClassVar[list[t.Callable[[F], F]]] = []
+
+ #: Create a new instance of this view class for every request by
+ #: default. If a view subclass sets this to ``False``, the same
+ #: instance is used for every request.
+ #:
+ #: A single instance is more efficient, especially if complex setup
+ #: is done during init. However, storing data on ``self`` is no
+ #: longer safe across requests, and :data:`~flask.g` should be used
+ #: instead.
+ #:
+ #: .. versionadded:: 2.2
+ init_every_request: t.ClassVar[bool] = True
+
+ def dispatch_request(self) -> ft.ResponseReturnValue:
+ """The actual view function behavior. Subclasses must override
+ this and return a valid response. Any variables from the URL
+ rule are passed as keyword arguments.
+ """
+ raise NotImplementedError()
+
+ @classmethod
+ def as_view(
+ cls, name: str, *class_args: t.Any, **class_kwargs: t.Any
+ ) -> ft.RouteCallable:
+ """Convert the class into a view function that can be registered
+ for a route.
+
+ By default, the generated view will create a new instance of the
+ view class for every request and call its
+ :meth:`dispatch_request` method. If the view class sets
+ :attr:`init_every_request` to ``False``, the same instance will
+ be used for every request.
+
+ Except for ``name``, all other arguments passed to this method
+ are forwarded to the view class ``__init__`` method.
+
+ .. versionchanged:: 2.2
+ Added the ``init_every_request`` class attribute.
+ """
+ if cls.init_every_request:
+
+ def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+ self = view.view_class( # type: ignore[attr-defined]
+ *class_args, **class_kwargs
+ )
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
+
+ else:
+ self = cls(*class_args, **class_kwargs)
+
+ def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+ return current_app.ensure_sync(self.dispatch_request)(**kwargs) # type: ignore[no-any-return]
+
+ 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 # type: ignore
+ view.__name__ = name
+ view.__doc__ = cls.__doc__
+ view.__module__ = cls.__module__
+ view.methods = cls.methods # type: ignore
+ view.provide_automatic_options = cls.provide_automatic_options # type: ignore
+ return view
+
+
+class MethodView(View):
+ """Dispatches request methods to the corresponding instance methods.
+ For example, if you implement a ``get`` method, it will be used to
+ handle ``GET`` requests.
+
+ This can be useful for defining a REST API.
+
+ :attr:`methods` is automatically set based on the methods defined on
+ the class.
+
+ See :doc:`views` for a detailed guide.
+
+ .. code-block:: python
+
+ class CounterAPI(MethodView):
+ def get(self):
+ return str(session.get("counter", 0))
+
+ def post(self):
+ session["counter"] = session.get("counter", 0) + 1
+ return redirect(url_for("counter"))
+
+ app.add_url_rule(
+ "/counter", view_func=CounterAPI.as_view("counter")
+ )
+ """
+
+ def __init_subclass__(cls, **kwargs: t.Any) -> None:
+ super().__init_subclass__(**kwargs)
+
+ if "methods" not in cls.__dict__:
+ methods = set()
+
+ for base in cls.__bases__:
+ if getattr(base, "methods", None):
+ methods.update(base.methods) # type: ignore[attr-defined]
+
+ for key in http_method_funcs:
+ if hasattr(cls, key):
+ methods.add(key.upper())
+
+ if methods:
+ cls.methods = methods
+
+ def dispatch_request(self, **kwargs: t.Any) -> ft.ResponseReturnValue:
+ 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, f"Unimplemented method {request.method!r}"
+ return current_app.ensure_sync(meth)(**kwargs) # type: ignore[no-any-return]
diff --git a/venv/Lib/site-packages/flask/wrappers.py b/venv/Lib/site-packages/flask/wrappers.py
new file mode 100644
index 0000000..c1eca80
--- /dev/null
+++ b/venv/Lib/site-packages/flask/wrappers.py
@@ -0,0 +1,174 @@
+from __future__ import annotations
+
+import typing as t
+
+from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import HTTPException
+from werkzeug.wrappers import Request as RequestBase
+from werkzeug.wrappers import Response as ResponseBase
+
+from . import json
+from .globals import current_app
+from .helpers import _split_blueprint_path
+
+if t.TYPE_CHECKING: # pragma: no cover
+ from werkzeug.routing import Rule
+
+
+class Request(RequestBase):
+ """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.
+ """
+
+ json_module: t.Any = json
+
+ #: 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: Rule | None = None
+
+ #: A dict of view arguments that matched the request. If an exception
+ #: happened when matching, this will be ``None``.
+ view_args: dict[str, t.Any] | None = 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: HTTPException | None = None
+
+ @property
+ def max_content_length(self) -> int | None: # type: ignore[override]
+ """Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
+ if current_app:
+ return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return]
+ else:
+ return None
+
+ @property
+ def endpoint(self) -> str | None:
+ """The endpoint that matched the request URL.
+
+ This will be ``None`` if matching failed or has not been
+ performed yet.
+
+ This in combination with :attr:`view_args` can be used to
+ reconstruct the same URL or a modified URL.
+ """
+ if self.url_rule is not None:
+ return self.url_rule.endpoint
+
+ return None
+
+ @property
+ def blueprint(self) -> str | None:
+ """The registered name of the current blueprint.
+
+ This will be ``None`` if the endpoint is not part of a
+ blueprint, or if URL matching failed or has not been performed
+ yet.
+
+ This does not necessarily match the name the blueprint was
+ created with. It may have been nested, or registered with a
+ different name.
+ """
+ endpoint = self.endpoint
+
+ if endpoint is not None and "." in endpoint:
+ return endpoint.rpartition(".")[0]
+
+ return None
+
+ @property
+ def blueprints(self) -> list[str]:
+ """The registered names of the current blueprint upwards through
+ parent blueprints.
+
+ This will be an empty list if there is no current blueprint, or
+ if URL matching failed.
+
+ .. versionadded:: 2.0.1
+ """
+ name = self.blueprint
+
+ if name is None:
+ return []
+
+ return _split_blueprint_path(name)
+
+ def _load_form_data(self) -> None:
+ super()._load_form_data()
+
+ # 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)
+
+ def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
+ try:
+ return super().on_json_loading_failed(e)
+ except BadRequest as e:
+ if current_app and current_app.debug:
+ raise
+
+ raise BadRequest() from e
+
+
+class Response(ResponseBase):
+ """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: str | None = "text/html"
+
+ json_module = json
+
+ autocorrect_location_header = False
+
+ @property
+ def max_cookie_size(self) -> int: # type: ignore
+ """Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
+
+ See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
+ Werkzeug's docs.
+ """
+ if current_app:
+ return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return]
+
+ # return Werkzeug's default when not in an app context
+ return super().max_cookie_size
diff --git a/venv/Lib/site-packages/flask_login/__about__.py b/venv/Lib/site-packages/flask_login/__about__.py
new file mode 100644
index 0000000..1918d54
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/__about__.py
@@ -0,0 +1,10 @@
+__title__ = "Flask-Login"
+__description__ = "User session management for Flask"
+__url__ = "https://github.com/maxcountryman/flask-login"
+__version_info__ = ("0", "6", "3")
+__version__ = ".".join(__version_info__)
+__author__ = "Matthew Frazier"
+__author_email__ = "leafstormrush@gmail.com"
+__maintainer__ = "Max Countryman"
+__license__ = "MIT"
+__copyright__ = "(c) 2011 by Matthew Frazier"
diff --git a/venv/Lib/site-packages/flask_login/__init__.py b/venv/Lib/site-packages/flask_login/__init__.py
new file mode 100644
index 0000000..fbe9c3e
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/__init__.py
@@ -0,0 +1,94 @@
+from .__about__ import __version__
+from .config import AUTH_HEADER_NAME
+from .config import COOKIE_DURATION
+from .config import COOKIE_HTTPONLY
+from .config import COOKIE_NAME
+from .config import COOKIE_SECURE
+from .config import ID_ATTRIBUTE
+from .config import LOGIN_MESSAGE
+from .config import LOGIN_MESSAGE_CATEGORY
+from .config import REFRESH_MESSAGE
+from .config import REFRESH_MESSAGE_CATEGORY
+from .login_manager import LoginManager
+from .mixins import AnonymousUserMixin
+from .mixins import UserMixin
+from .signals import session_protected
+from .signals import user_accessed
+from .signals import user_loaded_from_cookie
+from .signals import user_loaded_from_request
+from .signals import user_logged_in
+from .signals import user_logged_out
+from .signals import user_login_confirmed
+from .signals import user_needs_refresh
+from .signals import user_unauthorized
+from .test_client import FlaskLoginClient
+from .utils import confirm_login
+from .utils import current_user
+from .utils import decode_cookie
+from .utils import encode_cookie
+from .utils import fresh_login_required
+from .utils import login_fresh
+from .utils import login_remembered
+from .utils import login_required
+from .utils import login_url
+from .utils import login_user
+from .utils import logout_user
+from .utils import make_next_param
+from .utils import set_login_view
+
+__all__ = [
+ "__version__",
+ "AUTH_HEADER_NAME",
+ "COOKIE_DURATION",
+ "COOKIE_HTTPONLY",
+ "COOKIE_NAME",
+ "COOKIE_SECURE",
+ "ID_ATTRIBUTE",
+ "LOGIN_MESSAGE",
+ "LOGIN_MESSAGE_CATEGORY",
+ "REFRESH_MESSAGE",
+ "REFRESH_MESSAGE_CATEGORY",
+ "LoginManager",
+ "AnonymousUserMixin",
+ "UserMixin",
+ "session_protected",
+ "user_accessed",
+ "user_loaded_from_cookie",
+ "user_loaded_from_request",
+ "user_logged_in",
+ "user_logged_out",
+ "user_login_confirmed",
+ "user_needs_refresh",
+ "user_unauthorized",
+ "FlaskLoginClient",
+ "confirm_login",
+ "current_user",
+ "decode_cookie",
+ "encode_cookie",
+ "fresh_login_required",
+ "login_fresh",
+ "login_remembered",
+ "login_required",
+ "login_url",
+ "login_user",
+ "logout_user",
+ "make_next_param",
+ "set_login_view",
+]
+
+
+def __getattr__(name):
+ if name == "user_loaded_from_header":
+ import warnings
+ from .signals import _user_loaded_from_header
+
+ warnings.warn(
+ "'user_loaded_from_header' is deprecated and will be"
+ " removed in Flask-Login 0.7. Use"
+ " 'user_loaded_from_request' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _user_loaded_from_header
+
+ raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/__about__.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/__about__.cpython-312.pyc
new file mode 100644
index 0000000..493c5ad
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/__about__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..868f431
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/config.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/config.cpython-312.pyc
new file mode 100644
index 0000000..ba4d252
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/config.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/login_manager.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/login_manager.cpython-312.pyc
new file mode 100644
index 0000000..367db92
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/login_manager.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/mixins.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/mixins.cpython-312.pyc
new file mode 100644
index 0000000..e94a3c2
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/mixins.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/signals.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/signals.cpython-312.pyc
new file mode 100644
index 0000000..2612ac3
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/signals.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/test_client.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/test_client.cpython-312.pyc
new file mode 100644
index 0000000..44829a3
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/test_client.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/__pycache__/utils.cpython-312.pyc b/venv/Lib/site-packages/flask_login/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000..929b87d
Binary files /dev/null and b/venv/Lib/site-packages/flask_login/__pycache__/utils.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_login/config.py b/venv/Lib/site-packages/flask_login/config.py
new file mode 100644
index 0000000..fe2db2c
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/config.py
@@ -0,0 +1,55 @@
+from datetime import timedelta
+
+#: The default name of the "remember me" cookie (``remember_token``)
+COOKIE_NAME = "remember_token"
+
+#: The default time before the "remember me" cookie expires (365 days).
+COOKIE_DURATION = timedelta(days=365)
+
+#: Whether the "remember me" cookie requires Secure; defaults to ``False``
+COOKIE_SECURE = False
+
+#: Whether the "remember me" cookie uses HttpOnly or not; defaults to ``True``
+COOKIE_HTTPONLY = True
+
+#: Whether the "remember me" cookie requires same origin; defaults to ``None``
+COOKIE_SAMESITE = None
+
+#: The default flash message to display when users need to log in.
+LOGIN_MESSAGE = "Please log in to access this page."
+
+#: The default flash message category to display when users need to log in.
+LOGIN_MESSAGE_CATEGORY = "message"
+
+#: The default flash message to display when users need to reauthenticate.
+REFRESH_MESSAGE = "Please reauthenticate to access this page."
+
+#: The default flash message category to display when users need to
+#: reauthenticate.
+REFRESH_MESSAGE_CATEGORY = "message"
+
+#: The default attribute to retreive the str id of the user
+ID_ATTRIBUTE = "get_id"
+
+#: Default name of the auth header (``Authorization``)
+AUTH_HEADER_NAME = "Authorization"
+
+#: A set of session keys that are populated by Flask-Login. Use this set to
+#: purge keys safely and accurately.
+SESSION_KEYS = {
+ "_user_id",
+ "_remember",
+ "_remember_seconds",
+ "_id",
+ "_fresh",
+ "next",
+}
+
+#: A set of HTTP methods which are exempt from `login_required` and
+#: `fresh_login_required`. By default, this is just ``OPTIONS``.
+EXEMPT_METHODS = {"OPTIONS"}
+
+#: If true, the page the user is attempting to access is stored in the session
+#: rather than a url parameter when redirecting to the login view; defaults to
+#: ``False``.
+USE_SESSION_FOR_NEXT = False
diff --git a/venv/Lib/site-packages/flask_login/login_manager.py b/venv/Lib/site-packages/flask_login/login_manager.py
new file mode 100644
index 0000000..49d0844
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/login_manager.py
@@ -0,0 +1,543 @@
+from datetime import datetime
+from datetime import timedelta
+
+from flask import abort
+from flask import current_app
+from flask import flash
+from flask import g
+from flask import has_app_context
+from flask import redirect
+from flask import request
+from flask import session
+
+from .config import AUTH_HEADER_NAME
+from .config import COOKIE_DURATION
+from .config import COOKIE_HTTPONLY
+from .config import COOKIE_NAME
+from .config import COOKIE_SAMESITE
+from .config import COOKIE_SECURE
+from .config import ID_ATTRIBUTE
+from .config import LOGIN_MESSAGE
+from .config import LOGIN_MESSAGE_CATEGORY
+from .config import REFRESH_MESSAGE
+from .config import REFRESH_MESSAGE_CATEGORY
+from .config import SESSION_KEYS
+from .config import USE_SESSION_FOR_NEXT
+from .mixins import AnonymousUserMixin
+from .signals import session_protected
+from .signals import user_accessed
+from .signals import user_loaded_from_cookie
+from .signals import user_loaded_from_request
+from .signals import user_needs_refresh
+from .signals import user_unauthorized
+from .utils import _create_identifier
+from .utils import _user_context_processor
+from .utils import decode_cookie
+from .utils import encode_cookie
+from .utils import expand_login_view
+from .utils import login_url as make_login_url
+from .utils import make_next_param
+
+
+class LoginManager:
+ """This object is used to hold the settings used for logging in. Instances
+ of :class:`LoginManager` are *not* bound to specific apps, so you can
+ create one in the main body of your code and then bind it to your
+ app in a factory function.
+ """
+
+ def __init__(self, app=None, add_context_processor=True):
+ #: A class or factory function that produces an anonymous user, which
+ #: is used when no one is logged in.
+ self.anonymous_user = AnonymousUserMixin
+
+ #: The name of the view to redirect to when the user needs to log in.
+ #: (This can be an absolute URL as well, if your authentication
+ #: machinery is external to your application.)
+ self.login_view = None
+
+ #: Names of views to redirect to when the user needs to log in,
+ #: per blueprint. If the key value is set to None the value of
+ #: :attr:`login_view` will be used instead.
+ self.blueprint_login_views = {}
+
+ #: The message to flash when a user is redirected to the login page.
+ self.login_message = LOGIN_MESSAGE
+
+ #: The message category to flash when a user is redirected to the login
+ #: page.
+ self.login_message_category = LOGIN_MESSAGE_CATEGORY
+
+ #: The name of the view to redirect to when the user needs to
+ #: reauthenticate.
+ self.refresh_view = None
+
+ #: The message to flash when a user is redirected to the 'needs
+ #: refresh' page.
+ self.needs_refresh_message = REFRESH_MESSAGE
+
+ #: The message category to flash when a user is redirected to the
+ #: 'needs refresh' page.
+ self.needs_refresh_message_category = REFRESH_MESSAGE_CATEGORY
+
+ #: The mode to use session protection in. This can be either
+ #: ``'basic'`` (the default) or ``'strong'``, or ``None`` to disable
+ #: it.
+ self.session_protection = "basic"
+
+ #: If present, used to translate flash messages ``self.login_message``
+ #: and ``self.needs_refresh_message``
+ self.localize_callback = None
+
+ self.unauthorized_callback = None
+
+ self.needs_refresh_callback = None
+
+ self.id_attribute = ID_ATTRIBUTE
+
+ self._user_callback = None
+
+ self._header_callback = None
+
+ self._request_callback = None
+
+ self._session_identifier_generator = _create_identifier
+
+ if app is not None:
+ self.init_app(app, add_context_processor)
+
+ def setup_app(self, app, add_context_processor=True): # pragma: no cover
+ """
+ This method has been deprecated. Please use
+ :meth:`LoginManager.init_app` instead.
+ """
+ import warnings
+
+ warnings.warn(
+ "'setup_app' is deprecated and will be removed in"
+ " Flask-Login 0.7. Use 'init_app' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self.init_app(app, add_context_processor)
+
+ def init_app(self, app, add_context_processor=True):
+ """
+ Configures an application. This registers an `after_request` call, and
+ attaches this `LoginManager` to it as `app.login_manager`.
+
+ :param app: The :class:`flask.Flask` object to configure.
+ :type app: :class:`flask.Flask`
+ :param add_context_processor: Whether to add a context processor to
+ the app that adds a `current_user` variable to the template.
+ Defaults to ``True``.
+ :type add_context_processor: bool
+ """
+ app.login_manager = self
+ app.after_request(self._update_remember_cookie)
+
+ if add_context_processor:
+ app.context_processor(_user_context_processor)
+
+ def unauthorized(self):
+ """
+ This is called when the user is required to log in. If you register a
+ callback with :meth:`LoginManager.unauthorized_handler`, then it will
+ be called. Otherwise, it will take the following actions:
+
+ - Flash :attr:`LoginManager.login_message` to the user.
+
+ - If the app is using blueprints find the login view for
+ the current blueprint using `blueprint_login_views`. If the app
+ is not using blueprints or the login view for the current
+ blueprint is not specified use the value of `login_view`.
+
+ - Redirect the user to the login view. (The page they were
+ attempting to access will be passed in the ``next`` query
+ string variable, so you can redirect there if present instead
+ of the homepage. Alternatively, it will be added to the session
+ as ``next`` if USE_SESSION_FOR_NEXT is set.)
+
+ If :attr:`LoginManager.login_view` is not defined, then it will simply
+ raise a HTTP 401 (Unauthorized) error instead.
+
+ This should be returned from a view or before/after_request function,
+ otherwise the redirect will have no effect.
+ """
+ user_unauthorized.send(current_app._get_current_object())
+
+ if self.unauthorized_callback:
+ return self.unauthorized_callback()
+
+ if request.blueprint in self.blueprint_login_views:
+ login_view = self.blueprint_login_views[request.blueprint]
+ else:
+ login_view = self.login_view
+
+ if not login_view:
+ abort(401)
+
+ if self.login_message:
+ if self.localize_callback is not None:
+ flash(
+ self.localize_callback(self.login_message),
+ category=self.login_message_category,
+ )
+ else:
+ flash(self.login_message, category=self.login_message_category)
+
+ config = current_app.config
+ if config.get("USE_SESSION_FOR_NEXT", USE_SESSION_FOR_NEXT):
+ login_url = expand_login_view(login_view)
+ session["_id"] = self._session_identifier_generator()
+ session["next"] = make_next_param(login_url, request.url)
+ redirect_url = make_login_url(login_view)
+ else:
+ redirect_url = make_login_url(login_view, next_url=request.url)
+
+ return redirect(redirect_url)
+
+ def user_loader(self, callback):
+ """
+ This sets the callback for reloading a user from the session. The
+ function you set should take a user ID (a ``str``) and return a
+ user object, or ``None`` if the user does not exist.
+
+ :param callback: The callback for retrieving a user object.
+ :type callback: callable
+ """
+ self._user_callback = callback
+ return self.user_callback
+
+ @property
+ def user_callback(self):
+ """Gets the user_loader callback set by user_loader decorator."""
+ return self._user_callback
+
+ def request_loader(self, callback):
+ """
+ This sets the callback for loading a user from a Flask request.
+ The function you set should take Flask request object and
+ return a user object, or `None` if the user does not exist.
+
+ :param callback: The callback for retrieving a user object.
+ :type callback: callable
+ """
+ self._request_callback = callback
+ return self.request_callback
+
+ @property
+ def request_callback(self):
+ """Gets the request_loader callback set by request_loader decorator."""
+ return self._request_callback
+
+ def unauthorized_handler(self, callback):
+ """
+ This will set the callback for the `unauthorized` method, which among
+ other things is used by `login_required`. It takes no arguments, and
+ should return a response to be sent to the user instead of their
+ normal view.
+
+ :param callback: The callback for unauthorized users.
+ :type callback: callable
+ """
+ self.unauthorized_callback = callback
+ return callback
+
+ def needs_refresh_handler(self, callback):
+ """
+ This will set the callback for the `needs_refresh` method, which among
+ other things is used by `fresh_login_required`. It takes no arguments,
+ and should return a response to be sent to the user instead of their
+ normal view.
+
+ :param callback: The callback for unauthorized users.
+ :type callback: callable
+ """
+ self.needs_refresh_callback = callback
+ return callback
+
+ def needs_refresh(self):
+ """
+ This is called when the user is logged in, but they need to be
+ reauthenticated because their session is stale. If you register a
+ callback with `needs_refresh_handler`, then it will be called.
+ Otherwise, it will take the following actions:
+
+ - Flash :attr:`LoginManager.needs_refresh_message` to the user.
+
+ - Redirect the user to :attr:`LoginManager.refresh_view`. (The page
+ they were attempting to access will be passed in the ``next``
+ query string variable, so you can redirect there if present
+ instead of the homepage.)
+
+ If :attr:`LoginManager.refresh_view` is not defined, then it will
+ simply raise a HTTP 401 (Unauthorized) error instead.
+
+ This should be returned from a view or before/after_request function,
+ otherwise the redirect will have no effect.
+ """
+ user_needs_refresh.send(current_app._get_current_object())
+
+ if self.needs_refresh_callback:
+ return self.needs_refresh_callback()
+
+ if not self.refresh_view:
+ abort(401)
+
+ if self.needs_refresh_message:
+ if self.localize_callback is not None:
+ flash(
+ self.localize_callback(self.needs_refresh_message),
+ category=self.needs_refresh_message_category,
+ )
+ else:
+ flash(
+ self.needs_refresh_message,
+ category=self.needs_refresh_message_category,
+ )
+
+ config = current_app.config
+ if config.get("USE_SESSION_FOR_NEXT", USE_SESSION_FOR_NEXT):
+ login_url = expand_login_view(self.refresh_view)
+ session["_id"] = self._session_identifier_generator()
+ session["next"] = make_next_param(login_url, request.url)
+ redirect_url = make_login_url(self.refresh_view)
+ else:
+ login_url = self.refresh_view
+ redirect_url = make_login_url(login_url, next_url=request.url)
+
+ return redirect(redirect_url)
+
+ def header_loader(self, callback):
+ """
+ This function has been deprecated. Please use
+ :meth:`LoginManager.request_loader` instead.
+
+ This sets the callback for loading a user from a header value.
+ The function you set should take an authentication token and
+ return a user object, or `None` if the user does not exist.
+
+ :param callback: The callback for retrieving a user object.
+ :type callback: callable
+ """
+ import warnings
+
+ warnings.warn(
+ "'header_loader' is deprecated and will be removed in"
+ " Flask-Login 0.7. Use 'request_loader' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ self._header_callback = callback
+ return callback
+
+ def _update_request_context_with_user(self, user=None):
+ """Store the given user as ctx.user."""
+
+ if user is None:
+ user = self.anonymous_user()
+
+ g._login_user = user
+
+ def _load_user(self):
+ """Loads user from session or remember_me cookie as applicable"""
+
+ if self._user_callback is None and self._request_callback is None:
+ raise Exception(
+ "Missing user_loader or request_loader. Refer to "
+ "http://flask-login.readthedocs.io/#how-it-works "
+ "for more info."
+ )
+
+ user_accessed.send(current_app._get_current_object())
+
+ # Check SESSION_PROTECTION
+ if self._session_protection_failed():
+ return self._update_request_context_with_user()
+
+ user = None
+
+ # Load user from Flask Session
+ user_id = session.get("_user_id")
+ if user_id is not None and self._user_callback is not None:
+ user = self._user_callback(user_id)
+
+ # Load user from Remember Me Cookie or Request Loader
+ if user is None:
+ config = current_app.config
+ cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
+ header_name = config.get("AUTH_HEADER_NAME", AUTH_HEADER_NAME)
+ has_cookie = (
+ cookie_name in request.cookies and session.get("_remember") != "clear"
+ )
+ if has_cookie:
+ cookie = request.cookies[cookie_name]
+ user = self._load_user_from_remember_cookie(cookie)
+ elif self._request_callback:
+ user = self._load_user_from_request(request)
+ elif header_name in request.headers:
+ header = request.headers[header_name]
+ user = self._load_user_from_header(header)
+
+ return self._update_request_context_with_user(user)
+
+ def _session_protection_failed(self):
+ sess = session._get_current_object()
+ ident = self._session_identifier_generator()
+
+ app = current_app._get_current_object()
+ mode = app.config.get("SESSION_PROTECTION", self.session_protection)
+
+ if not mode or mode not in ["basic", "strong"]:
+ return False
+
+ # if the sess is empty, it's an anonymous user or just logged out
+ # so we can skip this
+ if sess and ident != sess.get("_id", None):
+ if mode == "basic" or sess.permanent:
+ if sess.get("_fresh") is not False:
+ sess["_fresh"] = False
+ session_protected.send(app)
+ return False
+ elif mode == "strong":
+ for k in SESSION_KEYS:
+ sess.pop(k, None)
+
+ sess["_remember"] = "clear"
+ session_protected.send(app)
+ return True
+
+ return False
+
+ def _load_user_from_remember_cookie(self, cookie):
+ user_id = decode_cookie(cookie)
+ if user_id is not None:
+ session["_user_id"] = user_id
+ session["_fresh"] = False
+ user = None
+ if self._user_callback:
+ user = self._user_callback(user_id)
+ if user is not None:
+ app = current_app._get_current_object()
+ user_loaded_from_cookie.send(app, user=user)
+ return user
+ return None
+
+ def _load_user_from_header(self, header):
+ if self._header_callback:
+ user = self._header_callback(header)
+ if user is not None:
+ app = current_app._get_current_object()
+
+ from .signals import _user_loaded_from_header
+
+ _user_loaded_from_header.send(app, user=user)
+ return user
+ return None
+
+ def _load_user_from_request(self, request):
+ if self._request_callback:
+ user = self._request_callback(request)
+ if user is not None:
+ app = current_app._get_current_object()
+ user_loaded_from_request.send(app, user=user)
+ return user
+ return None
+
+ def _update_remember_cookie(self, response):
+ # Don't modify the session unless there's something to do.
+ if "_remember" not in session and current_app.config.get(
+ "REMEMBER_COOKIE_REFRESH_EACH_REQUEST"
+ ):
+ session["_remember"] = "set"
+
+ if "_remember" in session:
+ operation = session.pop("_remember", None)
+
+ if operation == "set" and "_user_id" in session:
+ self._set_cookie(response)
+ elif operation == "clear":
+ self._clear_cookie(response)
+
+ return response
+
+ def _set_cookie(self, response):
+ # cookie settings
+ config = current_app.config
+ cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
+ domain = config.get("REMEMBER_COOKIE_DOMAIN")
+ path = config.get("REMEMBER_COOKIE_PATH", "/")
+
+ secure = config.get("REMEMBER_COOKIE_SECURE", COOKIE_SECURE)
+ httponly = config.get("REMEMBER_COOKIE_HTTPONLY", COOKIE_HTTPONLY)
+ samesite = config.get("REMEMBER_COOKIE_SAMESITE", COOKIE_SAMESITE)
+
+ if "_remember_seconds" in session:
+ duration = timedelta(seconds=session["_remember_seconds"])
+ else:
+ duration = config.get("REMEMBER_COOKIE_DURATION", COOKIE_DURATION)
+
+ # prepare data
+ data = encode_cookie(str(session["_user_id"]))
+
+ if isinstance(duration, int):
+ duration = timedelta(seconds=duration)
+
+ try:
+ expires = datetime.utcnow() + duration
+ except TypeError as e:
+ raise Exception(
+ "REMEMBER_COOKIE_DURATION must be a datetime.timedelta,"
+ f" instead got: {duration}"
+ ) from e
+
+ # actually set it
+ response.set_cookie(
+ cookie_name,
+ value=data,
+ expires=expires,
+ domain=domain,
+ path=path,
+ secure=secure,
+ httponly=httponly,
+ samesite=samesite,
+ )
+
+ def _clear_cookie(self, response):
+ config = current_app.config
+ cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
+ domain = config.get("REMEMBER_COOKIE_DOMAIN")
+ path = config.get("REMEMBER_COOKIE_PATH", "/")
+ response.delete_cookie(cookie_name, domain=domain, path=path)
+
+ @property
+ def _login_disabled(self):
+ """Legacy property, use app.config['LOGIN_DISABLED'] instead."""
+ import warnings
+
+ warnings.warn(
+ "'_login_disabled' is deprecated and will be removed in"
+ " Flask-Login 0.7. Use 'LOGIN_DISABLED' in 'app.config'"
+ " instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ if has_app_context():
+ return current_app.config.get("LOGIN_DISABLED", False)
+ return False
+
+ @_login_disabled.setter
+ def _login_disabled(self, newvalue):
+ """Legacy property setter, use app.config['LOGIN_DISABLED'] instead."""
+ import warnings
+
+ warnings.warn(
+ "'_login_disabled' is deprecated and will be removed in"
+ " Flask-Login 0.7. Use 'LOGIN_DISABLED' in 'app.config'"
+ " instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ current_app.config["LOGIN_DISABLED"] = newvalue
diff --git a/venv/Lib/site-packages/flask_login/mixins.py b/venv/Lib/site-packages/flask_login/mixins.py
new file mode 100644
index 0000000..0b3a71b
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/mixins.py
@@ -0,0 +1,65 @@
+class UserMixin:
+ """
+ This provides default implementations for the methods that Flask-Login
+ expects user objects to have.
+ """
+
+ # Python 3 implicitly set __hash__ to None if we override __eq__
+ # We set it back to its default implementation
+ __hash__ = object.__hash__
+
+ @property
+ def is_active(self):
+ return True
+
+ @property
+ def is_authenticated(self):
+ return self.is_active
+
+ @property
+ def is_anonymous(self):
+ return False
+
+ def get_id(self):
+ try:
+ return str(self.id)
+ except AttributeError:
+ raise NotImplementedError("No `id` attribute - override `get_id`") from None
+
+ def __eq__(self, other):
+ """
+ Checks the equality of two `UserMixin` objects using `get_id`.
+ """
+ if isinstance(other, UserMixin):
+ return self.get_id() == other.get_id()
+ return NotImplemented
+
+ def __ne__(self, other):
+ """
+ Checks the inequality of two `UserMixin` objects using `get_id`.
+ """
+ equal = self.__eq__(other)
+ if equal is NotImplemented:
+ return NotImplemented
+ return not equal
+
+
+class AnonymousUserMixin:
+ """
+ This is the default object for representing an anonymous user.
+ """
+
+ @property
+ def is_authenticated(self):
+ return False
+
+ @property
+ def is_active(self):
+ return False
+
+ @property
+ def is_anonymous(self):
+ return True
+
+ def get_id(self):
+ return
diff --git a/venv/Lib/site-packages/flask_login/signals.py b/venv/Lib/site-packages/flask_login/signals.py
new file mode 100644
index 0000000..cf9157f
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/signals.py
@@ -0,0 +1,61 @@
+from flask.signals import Namespace
+
+_signals = Namespace()
+
+#: Sent when a user is logged in. In addition to the app (which is the
+#: sender), it is passed `user`, which is the user being logged in.
+user_logged_in = _signals.signal("logged-in")
+
+#: Sent when a user is logged out. In addition to the app (which is the
+#: sender), it is passed `user`, which is the user being logged out.
+user_logged_out = _signals.signal("logged-out")
+
+#: Sent when the user is loaded from the cookie. In addition to the app (which
+#: is the sender), it is passed `user`, which is the user being reloaded.
+user_loaded_from_cookie = _signals.signal("loaded-from-cookie")
+
+#: Sent when the user is loaded from the header. In addition to the app (which
+#: is the #: sender), it is passed `user`, which is the user being reloaded.
+_user_loaded_from_header = _signals.signal("loaded-from-header")
+
+#: Sent when the user is loaded from the request. In addition to the app (which
+#: is the #: sender), it is passed `user`, which is the user being reloaded.
+user_loaded_from_request = _signals.signal("loaded-from-request")
+
+#: Sent when a user's login is confirmed, marking it as fresh. (It is not
+#: called for a normal login.)
+#: It receives no additional arguments besides the app.
+user_login_confirmed = _signals.signal("login-confirmed")
+
+#: Sent when the `unauthorized` method is called on a `LoginManager`. It
+#: receives no additional arguments besides the app.
+user_unauthorized = _signals.signal("unauthorized")
+
+#: Sent when the `needs_refresh` method is called on a `LoginManager`. It
+#: receives no additional arguments besides the app.
+user_needs_refresh = _signals.signal("needs-refresh")
+
+#: Sent whenever the user is accessed/loaded
+#: receives no additional arguments besides the app.
+user_accessed = _signals.signal("accessed")
+
+#: Sent whenever session protection takes effect, and a session is either
+#: marked non-fresh or deleted. It receives no additional arguments besides
+#: the app.
+session_protected = _signals.signal("session-protected")
+
+
+def __getattr__(name):
+ if name == "user_loaded_from_header":
+ import warnings
+
+ warnings.warn(
+ "'user_loaded_from_header' is deprecated and will be"
+ " removed in Flask-Login 0.7. Use"
+ " 'user_loaded_from_request' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return _user_loaded_from_header
+
+ raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask_login/test_client.py b/venv/Lib/site-packages/flask_login/test_client.py
new file mode 100644
index 0000000..be2a8bf
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/test_client.py
@@ -0,0 +1,19 @@
+from flask.testing import FlaskClient
+
+
+class FlaskLoginClient(FlaskClient):
+ """
+ A Flask test client that knows how to log in users
+ using the Flask-Login extension.
+ """
+
+ def __init__(self, *args, **kwargs):
+ user = kwargs.pop("user", None)
+ fresh = kwargs.pop("fresh_login", True)
+
+ super().__init__(*args, **kwargs)
+
+ if user:
+ with self.session_transaction() as sess:
+ sess["_user_id"] = user.get_id()
+ sess["_fresh"] = fresh
diff --git a/venv/Lib/site-packages/flask_login/utils.py b/venv/Lib/site-packages/flask_login/utils.py
new file mode 100644
index 0000000..37b2056
--- /dev/null
+++ b/venv/Lib/site-packages/flask_login/utils.py
@@ -0,0 +1,415 @@
+import hmac
+from functools import wraps
+from hashlib import sha512
+from urllib.parse import parse_qs
+from urllib.parse import urlencode
+from urllib.parse import urlsplit
+from urllib.parse import urlunsplit
+
+from flask import current_app
+from flask import g
+from flask import has_request_context
+from flask import request
+from flask import session
+from flask import url_for
+from werkzeug.local import LocalProxy
+
+from .config import COOKIE_NAME
+from .config import EXEMPT_METHODS
+from .signals import user_logged_in
+from .signals import user_logged_out
+from .signals import user_login_confirmed
+
+#: A proxy for the current user. If no user is logged in, this will be an
+#: anonymous user
+current_user = LocalProxy(lambda: _get_user())
+
+
+def encode_cookie(payload, key=None):
+ """
+ This will encode a ``str`` value into a cookie, and sign that cookie
+ with the app's secret key.
+
+ :param payload: The value to encode, as `str`.
+ :type payload: str
+
+ :param key: The key to use when creating the cookie digest. If not
+ specified, the SECRET_KEY value from app config will be used.
+ :type key: str
+ """
+ return f"{payload}|{_cookie_digest(payload, key=key)}"
+
+
+def decode_cookie(cookie, key=None):
+ """
+ This decodes a cookie given by `encode_cookie`. If verification of the
+ cookie fails, ``None`` will be implicitly returned.
+
+ :param cookie: An encoded cookie.
+ :type cookie: str
+
+ :param key: The key to use when creating the cookie digest. If not
+ specified, the SECRET_KEY value from app config will be used.
+ :type key: str
+ """
+ try:
+ payload, digest = cookie.rsplit("|", 1)
+ if hasattr(digest, "decode"):
+ digest = digest.decode("ascii") # pragma: no cover
+ except ValueError:
+ return
+
+ if hmac.compare_digest(_cookie_digest(payload, key=key), digest):
+ return payload
+
+
+def make_next_param(login_url, current_url):
+ """
+ Reduces the scheme and host from a given URL so it can be passed to
+ the given `login` URL more efficiently.
+
+ :param login_url: The login URL being redirected to.
+ :type login_url: str
+ :param current_url: The URL to reduce.
+ :type current_url: str
+ """
+ l_url = urlsplit(login_url)
+ c_url = urlsplit(current_url)
+
+ if (not l_url.scheme or l_url.scheme == c_url.scheme) and (
+ not l_url.netloc or l_url.netloc == c_url.netloc
+ ):
+ return urlunsplit(("", "", c_url.path, c_url.query, ""))
+ return current_url
+
+
+def expand_login_view(login_view):
+ """
+ Returns the url for the login view, expanding the view name to a url if
+ needed.
+
+ :param login_view: The name of the login view or a URL for the login view.
+ :type login_view: str
+ """
+ if login_view.startswith(("https://", "http://", "/")):
+ return login_view
+
+ return url_for(login_view)
+
+
+def login_url(login_view, next_url=None, next_field="next"):
+ """
+ Creates a URL for redirecting to a login page. If only `login_view` is
+ provided, this will just return the URL for it. If `next_url` is provided,
+ however, this will append a ``next=URL`` parameter to the query string
+ so that the login view can redirect back to that URL. Flask-Login's default
+ unauthorized handler uses this function when redirecting to your login url.
+ To force the host name used, set `FORCE_HOST_FOR_REDIRECTS` to a host. This
+ prevents from redirecting to external sites if request headers Host or
+ X-Forwarded-For are present.
+
+ :param login_view: The name of the login view. (Alternately, the actual
+ URL to the login view.)
+ :type login_view: str
+ :param next_url: The URL to give the login view for redirection.
+ :type next_url: str
+ :param next_field: What field to store the next URL in. (It defaults to
+ ``next``.)
+ :type next_field: str
+ """
+ base = expand_login_view(login_view)
+
+ if next_url is None:
+ return base
+
+ parsed_result = urlsplit(base)
+ md = parse_qs(parsed_result.query, keep_blank_values=True)
+ md[next_field] = make_next_param(base, next_url)
+ netloc = current_app.config.get("FORCE_HOST_FOR_REDIRECTS") or parsed_result.netloc
+ parsed_result = parsed_result._replace(
+ netloc=netloc, query=urlencode(md, doseq=True)
+ )
+ return urlunsplit(parsed_result)
+
+
+def login_fresh():
+ """
+ This returns ``True`` if the current login is fresh.
+ """
+ return session.get("_fresh", False)
+
+
+def login_remembered():
+ """
+ This returns ``True`` if the current login is remembered across sessions.
+ """
+ config = current_app.config
+ cookie_name = config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
+ has_cookie = cookie_name in request.cookies and session.get("_remember") != "clear"
+ if has_cookie:
+ cookie = request.cookies[cookie_name]
+ user_id = decode_cookie(cookie)
+ return user_id is not None
+ return False
+
+
+def login_user(user, remember=False, duration=None, force=False, fresh=True):
+ """
+ Logs a user in. You should pass the actual user object to this. If the
+ user's `is_active` property is ``False``, they will not be logged in
+ unless `force` is ``True``.
+
+ This will return ``True`` if the log in attempt succeeds, and ``False`` if
+ it fails (i.e. because the user is inactive).
+
+ :param user: The user object to log in.
+ :type user: object
+ :param remember: Whether to remember the user after their session expires.
+ Defaults to ``False``.
+ :type remember: bool
+ :param duration: The amount of time before the remember cookie expires. If
+ ``None`` the value set in the settings is used. Defaults to ``None``.
+ :type duration: :class:`datetime.timedelta`
+ :param force: If the user is inactive, setting this to ``True`` will log
+ them in regardless. Defaults to ``False``.
+ :type force: bool
+ :param fresh: setting this to ``False`` will log in the user with a session
+ marked as not "fresh". Defaults to ``True``.
+ :type fresh: bool
+ """
+ if not force and not user.is_active:
+ return False
+
+ user_id = getattr(user, current_app.login_manager.id_attribute)()
+ session["_user_id"] = user_id
+ session["_fresh"] = fresh
+ session["_id"] = current_app.login_manager._session_identifier_generator()
+
+ if remember:
+ session["_remember"] = "set"
+ if duration is not None:
+ try:
+ # equal to timedelta.total_seconds() but works with Python 2.6
+ session["_remember_seconds"] = (
+ duration.microseconds
+ + (duration.seconds + duration.days * 24 * 3600) * 10**6
+ ) / 10.0**6
+ except AttributeError as e:
+ raise Exception(
+ f"duration must be a datetime.timedelta, instead got: {duration}"
+ ) from e
+
+ current_app.login_manager._update_request_context_with_user(user)
+ user_logged_in.send(current_app._get_current_object(), user=_get_user())
+ return True
+
+
+def logout_user():
+ """
+ Logs a user out. (You do not need to pass the actual user.) This will
+ also clean up the remember me cookie if it exists.
+ """
+
+ user = _get_user()
+
+ if "_user_id" in session:
+ session.pop("_user_id")
+
+ if "_fresh" in session:
+ session.pop("_fresh")
+
+ if "_id" in session:
+ session.pop("_id")
+
+ cookie_name = current_app.config.get("REMEMBER_COOKIE_NAME", COOKIE_NAME)
+ if cookie_name in request.cookies:
+ session["_remember"] = "clear"
+ if "_remember_seconds" in session:
+ session.pop("_remember_seconds")
+
+ user_logged_out.send(current_app._get_current_object(), user=user)
+
+ current_app.login_manager._update_request_context_with_user()
+ return True
+
+
+def confirm_login():
+ """
+ This sets the current session as fresh. Sessions become stale when they
+ are reloaded from a cookie.
+ """
+ session["_fresh"] = True
+ session["_id"] = current_app.login_manager._session_identifier_generator()
+ user_login_confirmed.send(current_app._get_current_object())
+
+
+def login_required(func):
+ """
+ If you decorate a view with this, it will ensure that the current user is
+ logged in and authenticated before calling the actual view. (If they are
+ not, it calls the :attr:`LoginManager.unauthorized` callback.) For
+ example::
+
+ @app.route('/post')
+ @login_required
+ def post():
+ pass
+
+ If there are only certain times you need to require that your user is
+ logged in, you can do so with::
+
+ if not current_user.is_authenticated:
+ return current_app.login_manager.unauthorized()
+
+ ...which is essentially the code that this function adds to your views.
+
+ It can be convenient to globally turn off authentication when unit testing.
+ To enable this, if the application configuration variable `LOGIN_DISABLED`
+ is set to `True`, this decorator will be ignored.
+
+ .. Note ::
+
+ Per `W3 guidelines for CORS preflight requests
+ `_,
+ HTTP ``OPTIONS`` requests are exempt from login checks.
+
+ :param func: The view function to decorate.
+ :type func: function
+ """
+
+ @wraps(func)
+ def decorated_view(*args, **kwargs):
+ if request.method in EXEMPT_METHODS or current_app.config.get("LOGIN_DISABLED"):
+ pass
+ elif not current_user.is_authenticated:
+ return current_app.login_manager.unauthorized()
+
+ # flask 1.x compatibility
+ # current_app.ensure_sync is only available in Flask >= 2.0
+ if callable(getattr(current_app, "ensure_sync", None)):
+ return current_app.ensure_sync(func)(*args, **kwargs)
+ return func(*args, **kwargs)
+
+ return decorated_view
+
+
+def fresh_login_required(func):
+ """
+ If you decorate a view with this, it will ensure that the current user's
+ login is fresh - i.e. their session was not restored from a 'remember me'
+ cookie. Sensitive operations, like changing a password or e-mail, should
+ be protected with this, to impede the efforts of cookie thieves.
+
+ If the user is not authenticated, :meth:`LoginManager.unauthorized` is
+ called as normal. If they are authenticated, but their session is not
+ fresh, it will call :meth:`LoginManager.needs_refresh` instead. (In that
+ case, you will need to provide a :attr:`LoginManager.refresh_view`.)
+
+ Behaves identically to the :func:`login_required` decorator with respect
+ to configuration variables.
+
+ .. Note ::
+
+ Per `W3 guidelines for CORS preflight requests
+ `_,
+ HTTP ``OPTIONS`` requests are exempt from login checks.
+
+ :param func: The view function to decorate.
+ :type func: function
+ """
+
+ @wraps(func)
+ def decorated_view(*args, **kwargs):
+ if request.method in EXEMPT_METHODS or current_app.config.get("LOGIN_DISABLED"):
+ pass
+ elif not current_user.is_authenticated:
+ return current_app.login_manager.unauthorized()
+ elif not login_fresh():
+ return current_app.login_manager.needs_refresh()
+ try:
+ # current_app.ensure_sync available in Flask >= 2.0
+ return current_app.ensure_sync(func)(*args, **kwargs)
+ except AttributeError: # pragma: no cover
+ return func(*args, **kwargs)
+
+ return decorated_view
+
+
+def set_login_view(login_view, blueprint=None):
+ """
+ Sets the login view for the app or blueprint. If a blueprint is passed,
+ the login view is set for this blueprint on ``blueprint_login_views``.
+
+ :param login_view: The user object to log in.
+ :type login_view: str
+ :param blueprint: The blueprint which this login view should be set on.
+ Defaults to ``None``.
+ :type blueprint: object
+ """
+
+ num_login_views = len(current_app.login_manager.blueprint_login_views)
+ if blueprint is not None or num_login_views != 0:
+ (current_app.login_manager.blueprint_login_views[blueprint.name]) = login_view
+
+ if (
+ current_app.login_manager.login_view is not None
+ and None not in current_app.login_manager.blueprint_login_views
+ ):
+ (
+ current_app.login_manager.blueprint_login_views[None]
+ ) = current_app.login_manager.login_view
+
+ current_app.login_manager.login_view = None
+ else:
+ current_app.login_manager.login_view = login_view
+
+
+def _get_user():
+ if has_request_context():
+ if "_login_user" not in g:
+ current_app.login_manager._load_user()
+
+ return g._login_user
+
+ return None
+
+
+def _cookie_digest(payload, key=None):
+ key = _secret_key(key)
+
+ return hmac.new(key, payload.encode("utf-8"), sha512).hexdigest()
+
+
+def _get_remote_addr():
+ address = request.headers.get("X-Forwarded-For", request.remote_addr)
+ if address is not None:
+ # An 'X-Forwarded-For' header includes a comma separated list of the
+ # addresses, the first address being the actual remote address.
+ address = address.encode("utf-8").split(b",")[0].strip()
+ return address
+
+
+def _create_identifier():
+ user_agent = request.headers.get("User-Agent")
+ if user_agent is not None:
+ user_agent = user_agent.encode("utf-8")
+ base = f"{_get_remote_addr()}|{user_agent}"
+ if str is bytes:
+ base = str(base, "utf-8", errors="replace") # pragma: no cover
+ h = sha512()
+ h.update(base.encode("utf8"))
+ return h.hexdigest()
+
+
+def _user_context_processor():
+ return dict(current_user=_get_user())
+
+
+def _secret_key(key=None):
+ if key is None:
+ key = current_app.config["SECRET_KEY"]
+
+ if isinstance(key, str): # pragma: no cover
+ key = key.encode("latin1") # ensure bytes
+
+ return key
diff --git a/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/INSTALLER b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/LICENSE.rst b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/LICENSE.rst
new file mode 100644
index 0000000..9d227a0
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/LICENSE.rst
@@ -0,0 +1,28 @@
+Copyright 2010 Pallets
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/METADATA b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/METADATA
new file mode 100644
index 0000000..92f239c
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/METADATA
@@ -0,0 +1,109 @@
+Metadata-Version: 2.1
+Name: Flask-SQLAlchemy
+Version: 3.1.1
+Summary: Add SQLAlchemy support to your Flask application.
+Maintainer-email: Pallets
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+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: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Requires-Dist: flask>=2.2.5
+Requires-Dist: sqlalchemy>=2.0.16
+Project-URL: Changes, https://flask-sqlalchemy.palletsprojects.com/changes/
+Project-URL: Chat, https://discord.gg/pallets
+Project-URL: Documentation, https://flask-sqlalchemy.palletsprojects.com
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Issue Tracker, https://github.com/pallets-eco/flask-sqlalchemy/issues/
+Project-URL: Source Code, https://github.com/pallets-eco/flask-sqlalchemy/
+
+Flask-SQLAlchemy
+================
+
+Flask-SQLAlchemy is an extension for `Flask`_ that adds support for
+`SQLAlchemy`_ to your application. It aims to simplify using SQLAlchemy
+with Flask by providing useful defaults and extra helpers that make it
+easier to accomplish common tasks.
+
+.. _Flask: https://palletsprojects.com/p/flask/
+.. _SQLAlchemy: https://www.sqlalchemy.org
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+ $ pip install -U Flask-SQLAlchemy
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+A Simple Example
+----------------
+
+.. code-block:: python
+
+ from flask import Flask
+ from flask_sqlalchemy import SQLAlchemy
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
+
+ app = Flask(__name__)
+ app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///example.sqlite"
+
+ class Base(DeclarativeBase):
+ pass
+
+ db = SQLAlchemy(app, model_class=Base)
+
+ class User(db.Model):
+ id: Mapped[int] = mapped_column(db.Integer, primary_key=True)
+ username: Mapped[str] = mapped_column(db.String, unique=True, nullable=False)
+
+ with app.app_context():
+ db.create_all()
+
+ db.session.add(User(username="example"))
+ db.session.commit()
+
+ users = db.session.execute(db.select(User)).scalars()
+
+
+Contributing
+------------
+
+For guidance on setting up a development environment and how to make a
+contribution to Flask-SQLAlchemy, see the `contributing guidelines`_.
+
+.. _contributing guidelines: https://github.com/pallets-eco/flask-sqlalchemy/blob/main/CONTRIBUTING.rst
+
+
+Donate
+------
+
+The Pallets organization develops and supports Flask-SQLAlchemy and
+other popular packages. 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://palletsprojects.com/donate
+
+
+Links
+-----
+
+- Documentation: https://flask-sqlalchemy.palletsprojects.com/
+- Changes: https://flask-sqlalchemy.palletsprojects.com/changes/
+- PyPI Releases: https://pypi.org/project/Flask-SQLAlchemy/
+- Source Code: https://github.com/pallets-eco/flask-sqlalchemy/
+- Issue Tracker: https://github.com/pallets-eco/flask-sqlalchemy/issues/
+- Website: https://palletsprojects.com/
+- Twitter: https://twitter.com/PalletsTeam
+- Chat: https://discord.gg/pallets
+
diff --git a/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/RECORD b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/RECORD
new file mode 100644
index 0000000..95c47ac
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/RECORD
@@ -0,0 +1,27 @@
+flask_sqlalchemy-3.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+flask_sqlalchemy-3.1.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
+flask_sqlalchemy-3.1.1.dist-info/METADATA,sha256=lBxR1akBt7n9XBjIVTL2OV52OhCfFrb-Mqtoe0DCbR8,3432
+flask_sqlalchemy-3.1.1.dist-info/RECORD,,
+flask_sqlalchemy-3.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask_sqlalchemy-3.1.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
+flask_sqlalchemy/__init__.py,sha256=he_w4qQQVS2Z1ms5GCTptDTXNOXBXw0n8zSuWCp8n6Y,653
+flask_sqlalchemy/__pycache__/__init__.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/cli.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/extension.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/model.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/pagination.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/query.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/record_queries.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/session.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/table.cpython-312.pyc,,
+flask_sqlalchemy/__pycache__/track_modifications.cpython-312.pyc,,
+flask_sqlalchemy/cli.py,sha256=pg3QDxP36GW2qnwe_CpPtkRhPchyVSGM6zlBNWuNCFE,484
+flask_sqlalchemy/extension.py,sha256=71tP_kNtb5VgZdafy_OH1sWdZOA6PaT7cJqX7tKgZ-k,38261
+flask_sqlalchemy/model.py,sha256=_mSisC2Eni0TgTyFWeN_O4LIexTeP_sVTdxh03yMK50,11461
+flask_sqlalchemy/pagination.py,sha256=JFpllrqkRkwacb8DAmQWaz9wsvQa0dypfSkhUDSC2ws,11119
+flask_sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask_sqlalchemy/query.py,sha256=Uls9qbmnpb9Vba43EDfsRP17eHJ0X4VG7SE22tH5R3g,3748
+flask_sqlalchemy/record_queries.py,sha256=ouS1ayj16h76LJprx13iYdoFZbm6m8OncrOgAVbG1Sk,3520
+flask_sqlalchemy/session.py,sha256=pBbtN8iDc8yuGVt0k18BvZHh2uEI7QPzZXO7eXrRi1g,3426
+flask_sqlalchemy/table.py,sha256=wAPOy8qwyAxpMwOIUJY4iMOultzz2W0D6xvBkQ7U2CE,859
+flask_sqlalchemy/track_modifications.py,sha256=yieyozj7IiVzwnAGZ-ZrgqrzjrUfG0kPrXBfW_hStSU,2755
diff --git a/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/REQUESTED b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/WHEEL b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/WHEEL
new file mode 100644
index 0000000..3b5e64b
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy-3.1.1.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__init__.py b/venv/Lib/site-packages/flask_sqlalchemy/__init__.py
new file mode 100644
index 0000000..c2fa059
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/__init__.py
@@ -0,0 +1,26 @@
+from __future__ import annotations
+
+import typing as t
+
+from .extension import SQLAlchemy
+
+__all__ = [
+ "SQLAlchemy",
+]
+
+
+def __getattr__(name: str) -> t.Any:
+ if name == "__version__":
+ import importlib.metadata
+ import warnings
+
+ warnings.warn(
+ "The '__version__' attribute is deprecated and will be removed in"
+ " Flask-SQLAlchemy 3.2. Use feature detection or"
+ " 'importlib.metadata.version(\"flask-sqlalchemy\")' instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ return importlib.metadata.version("flask-sqlalchemy")
+
+ raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..7dacea5
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/cli.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/cli.cpython-312.pyc
new file mode 100644
index 0000000..c73a33a
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/cli.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/extension.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/extension.cpython-312.pyc
new file mode 100644
index 0000000..7d7634f
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/extension.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/model.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/model.cpython-312.pyc
new file mode 100644
index 0000000..0c16640
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/model.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/pagination.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/pagination.cpython-312.pyc
new file mode 100644
index 0000000..9917885
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/pagination.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/query.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/query.cpython-312.pyc
new file mode 100644
index 0000000..6666613
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/query.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/record_queries.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/record_queries.cpython-312.pyc
new file mode 100644
index 0000000..414fa09
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/record_queries.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/session.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/session.cpython-312.pyc
new file mode 100644
index 0000000..e0dffd8
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/session.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/table.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/table.cpython-312.pyc
new file mode 100644
index 0000000..fe08ae2
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/table.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/track_modifications.cpython-312.pyc b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/track_modifications.cpython-312.pyc
new file mode 100644
index 0000000..72ce6a5
Binary files /dev/null and b/venv/Lib/site-packages/flask_sqlalchemy/__pycache__/track_modifications.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/cli.py b/venv/Lib/site-packages/flask_sqlalchemy/cli.py
new file mode 100644
index 0000000..d7d7e4b
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/cli.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+import typing as t
+
+from flask import current_app
+
+
+def add_models_to_shell() -> dict[str, t.Any]:
+ """Registered with :meth:`~flask.Flask.shell_context_processor` if
+ ``add_models_to_shell`` is enabled. Adds the ``db`` instance and all model classes
+ to ``flask shell``.
+ """
+ db = current_app.extensions["sqlalchemy"]
+ out = {m.class_.__name__: m.class_ for m in db.Model._sa_registry.mappers}
+ out["db"] = db
+ return out
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/extension.py b/venv/Lib/site-packages/flask_sqlalchemy/extension.py
new file mode 100644
index 0000000..43e1b9a
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/extension.py
@@ -0,0 +1,1008 @@
+from __future__ import annotations
+
+import os
+import types
+import typing as t
+import warnings
+from weakref import WeakKeyDictionary
+
+import sqlalchemy as sa
+import sqlalchemy.event as sa_event
+import sqlalchemy.exc as sa_exc
+import sqlalchemy.orm as sa_orm
+from flask import abort
+from flask import current_app
+from flask import Flask
+from flask import has_app_context
+
+from .model import _QueryProperty
+from .model import BindMixin
+from .model import DefaultMeta
+from .model import DefaultMetaNoName
+from .model import Model
+from .model import NameMixin
+from .pagination import Pagination
+from .pagination import SelectPagination
+from .query import Query
+from .session import _app_ctx_id
+from .session import Session
+from .table import _Table
+
+_O = t.TypeVar("_O", bound=object) # Based on sqlalchemy.orm._typing.py
+
+
+# Type accepted for model_class argument
+_FSA_MCT = t.TypeVar(
+ "_FSA_MCT",
+ bound=t.Union[
+ t.Type[Model],
+ sa_orm.DeclarativeMeta,
+ t.Type[sa_orm.DeclarativeBase],
+ t.Type[sa_orm.DeclarativeBaseNoMeta],
+ ],
+)
+
+
+# Type returned by make_declarative_base
+class _FSAModel(Model):
+ metadata: sa.MetaData
+
+
+def _get_2x_declarative_bases(
+ model_class: _FSA_MCT,
+) -> list[t.Type[t.Union[sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta]]]:
+ return [
+ b
+ for b in model_class.__bases__
+ if issubclass(b, (sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta))
+ ]
+
+
+class SQLAlchemy:
+ """Integrates SQLAlchemy with Flask. This handles setting up one or more engines,
+ associating tables and models with specific engines, and cleaning up connections and
+ sessions after each request.
+
+ Only the engine configuration is specific to each application, other things like
+ the model, table, metadata, and session are shared for all applications using that
+ extension instance. Call :meth:`init_app` to configure the extension on an
+ application.
+
+ After creating the extension, create model classes by subclassing :attr:`Model`, and
+ table classes with :attr:`Table`. These can be accessed before :meth:`init_app` is
+ called, making it possible to define the models separately from the application.
+
+ Accessing :attr:`session` and :attr:`engine` requires an active Flask application
+ context. This includes methods like :meth:`create_all` which use the engine.
+
+ This class also provides access to names in SQLAlchemy's ``sqlalchemy`` and
+ ``sqlalchemy.orm`` modules. For example, you can use ``db.Column`` and
+ ``db.relationship`` instead of importing ``sqlalchemy.Column`` and
+ ``sqlalchemy.orm.relationship``. This can be convenient when defining models.
+
+ :param app: Call :meth:`init_app` on this Flask application now.
+ :param metadata: Use this as the default :class:`sqlalchemy.schema.MetaData`. Useful
+ for setting a naming convention.
+ :param session_options: Arguments used by :attr:`session` to create each session
+ instance. A ``scopefunc`` key will be passed to the scoped session, not the
+ session instance. See :class:`sqlalchemy.orm.sessionmaker` for a list of
+ arguments.
+ :param query_class: Use this as the default query class for models and dynamic
+ relationships. The query interface is considered legacy in SQLAlchemy.
+ :param model_class: Use this as the model base class when creating the declarative
+ model class :attr:`Model`. Can also be a fully created declarative model class
+ for further customization.
+ :param engine_options: Default arguments used when creating every engine. These are
+ lower precedence than application config. See :func:`sqlalchemy.create_engine`
+ for a list of arguments.
+ :param add_models_to_shell: Add the ``db`` instance and all model classes to
+ ``flask shell``.
+
+ .. versionchanged:: 3.1.0
+ The ``metadata`` parameter can still be used with SQLAlchemy 1.x classes,
+ but is ignored when using SQLAlchemy 2.x style of declarative classes.
+ Instead, specify metadata on your Base class.
+
+ .. versionchanged:: 3.1.0
+ Added the ``disable_autonaming`` parameter.
+
+ .. versionchanged:: 3.1.0
+ Changed ``model_class`` parameter to accepta SQLAlchemy 2.x
+ declarative base subclass.
+
+ .. versionchanged:: 3.0
+ An active Flask application context is always required to access ``session`` and
+ ``engine``.
+
+ .. versionchanged:: 3.0
+ Separate ``metadata`` are used for each bind key.
+
+ .. versionchanged:: 3.0
+ The ``engine_options`` parameter is applied as defaults before per-engine
+ configuration.
+
+ .. versionchanged:: 3.0
+ The session class can be customized in ``session_options``.
+
+ .. versionchanged:: 3.0
+ Added the ``add_models_to_shell`` parameter.
+
+ .. versionchanged:: 3.0
+ Engines are created when calling ``init_app`` rather than the first time they
+ are accessed.
+
+ .. versionchanged:: 3.0
+ All parameters except ``app`` are keyword-only.
+
+ .. versionchanged:: 3.0
+ The extension instance is stored directly as ``app.extensions["sqlalchemy"]``.
+
+ .. versionchanged:: 3.0
+ Setup methods are renamed with a leading underscore. They are considered
+ internal interfaces which may change at any time.
+
+ .. versionchanged:: 3.0
+ Removed the ``use_native_unicode`` parameter and config.
+
+ .. versionchanged:: 2.4
+ Added the ``engine_options`` parameter.
+
+ .. versionchanged:: 2.1
+ Added the ``metadata``, ``query_class``, and ``model_class`` parameters.
+
+ .. versionchanged:: 2.1
+ Use the same query class across ``session``, ``Model.query`` and
+ ``Query``.
+
+ .. versionchanged:: 0.16
+ ``scopefunc`` is accepted in ``session_options``.
+
+ .. versionchanged:: 0.10
+ Added the ``session_options`` parameter.
+ """
+
+ def __init__(
+ self,
+ app: Flask | None = None,
+ *,
+ metadata: sa.MetaData | None = None,
+ session_options: dict[str, t.Any] | None = None,
+ query_class: type[Query] = Query,
+ model_class: _FSA_MCT = Model, # type: ignore[assignment]
+ engine_options: dict[str, t.Any] | None = None,
+ add_models_to_shell: bool = True,
+ disable_autonaming: bool = False,
+ ):
+ if session_options is None:
+ session_options = {}
+
+ self.Query = query_class
+ """The default query class used by ``Model.query`` and ``lazy="dynamic"``
+ relationships.
+
+ .. warning::
+ The query interface is considered legacy in SQLAlchemy.
+
+ Customize this by passing the ``query_class`` parameter to the extension.
+ """
+
+ self.session = self._make_scoped_session(session_options)
+ """A :class:`sqlalchemy.orm.scoping.scoped_session` that creates instances of
+ :class:`.Session` scoped to the current Flask application context. The session
+ will be removed, returning the engine connection to the pool, when the
+ application context exits.
+
+ Customize this by passing ``session_options`` to the extension.
+
+ This requires that a Flask application context is active.
+
+ .. versionchanged:: 3.0
+ The session is scoped to the current app context.
+ """
+
+ self.metadatas: dict[str | None, sa.MetaData] = {}
+ """Map of bind keys to :class:`sqlalchemy.schema.MetaData` instances. The
+ ``None`` key refers to the default metadata, and is available as
+ :attr:`metadata`.
+
+ Customize the default metadata by passing the ``metadata`` parameter to the
+ extension. This can be used to set a naming convention. When metadata for
+ another bind key is created, it copies the default's naming convention.
+
+ .. versionadded:: 3.0
+ """
+
+ if metadata is not None:
+ if len(_get_2x_declarative_bases(model_class)) > 0:
+ warnings.warn(
+ "When using SQLAlchemy 2.x style of declarative classes,"
+ " the `metadata` should be an attribute of the base class."
+ "The metadata passed into SQLAlchemy() is ignored.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ else:
+ metadata.info["bind_key"] = None
+ self.metadatas[None] = metadata
+
+ self.Table = self._make_table_class()
+ """A :class:`sqlalchemy.schema.Table` class that chooses a metadata
+ automatically.
+
+ Unlike the base ``Table``, the ``metadata`` argument is not required. If it is
+ not given, it is selected based on the ``bind_key`` argument.
+
+ :param bind_key: Used to select a different metadata.
+ :param args: Arguments passed to the base class. These are typically the table's
+ name, columns, and constraints.
+ :param kwargs: Arguments passed to the base class.
+
+ .. versionchanged:: 3.0
+ This is a subclass of SQLAlchemy's ``Table`` rather than a function.
+ """
+
+ self.Model = self._make_declarative_base(
+ model_class, disable_autonaming=disable_autonaming
+ )
+ """A SQLAlchemy declarative model class. Subclass this to define database
+ models.
+
+ If a model does not set ``__tablename__``, it will be generated by converting
+ the class name from ``CamelCase`` to ``snake_case``. It will not be generated
+ if the model looks like it uses single-table inheritance.
+
+ If a model or parent class sets ``__bind_key__``, it will use that metadata and
+ database engine. Otherwise, it will use the default :attr:`metadata` and
+ :attr:`engine`. This is ignored if the model sets ``metadata`` or ``__table__``.
+
+ For code using the SQLAlchemy 1.x API, customize this model by subclassing
+ :class:`.Model` and passing the ``model_class`` parameter to the extension.
+ A fully created declarative model class can be
+ passed as well, to use a custom metaclass.
+
+ For code using the SQLAlchemy 2.x API, customize this model by subclassing
+ :class:`sqlalchemy.orm.DeclarativeBase` or
+ :class:`sqlalchemy.orm.DeclarativeBaseNoMeta`
+ and passing the ``model_class`` parameter to the extension.
+ """
+
+ if engine_options is None:
+ engine_options = {}
+
+ self._engine_options = engine_options
+ self._app_engines: WeakKeyDictionary[Flask, dict[str | None, sa.engine.Engine]]
+ self._app_engines = WeakKeyDictionary()
+ self._add_models_to_shell = add_models_to_shell
+
+ if app is not None:
+ self.init_app(app)
+
+ def __repr__(self) -> str:
+ if not has_app_context():
+ return f"<{type(self).__name__}>"
+
+ message = f"{type(self).__name__} {self.engine.url}"
+
+ if len(self.engines) > 1:
+ message = f"{message} +{len(self.engines) - 1}"
+
+ return f"<{message}>"
+
+ def init_app(self, app: Flask) -> None:
+ """Initialize a Flask application for use with this extension instance. This
+ must be called before accessing the database engine or session with the app.
+
+ This sets default configuration values, then configures the extension on the
+ application and creates the engines for each bind key. Therefore, this must be
+ called after the application has been configured. Changes to application config
+ after this call will not be reflected.
+
+ The following keys from ``app.config`` are used:
+
+ - :data:`.SQLALCHEMY_DATABASE_URI`
+ - :data:`.SQLALCHEMY_ENGINE_OPTIONS`
+ - :data:`.SQLALCHEMY_ECHO`
+ - :data:`.SQLALCHEMY_BINDS`
+ - :data:`.SQLALCHEMY_RECORD_QUERIES`
+ - :data:`.SQLALCHEMY_TRACK_MODIFICATIONS`
+
+ :param app: The Flask application to initialize.
+ """
+ if "sqlalchemy" in app.extensions:
+ raise RuntimeError(
+ "A 'SQLAlchemy' instance has already been registered on this Flask app."
+ " Import and use that instance instead."
+ )
+
+ app.extensions["sqlalchemy"] = self
+ app.teardown_appcontext(self._teardown_session)
+
+ if self._add_models_to_shell:
+ from .cli import add_models_to_shell
+
+ app.shell_context_processor(add_models_to_shell)
+
+ basic_uri: str | sa.engine.URL | None = app.config.setdefault(
+ "SQLALCHEMY_DATABASE_URI", None
+ )
+ basic_engine_options = self._engine_options.copy()
+ basic_engine_options.update(
+ app.config.setdefault("SQLALCHEMY_ENGINE_OPTIONS", {})
+ )
+ echo: bool = app.config.setdefault("SQLALCHEMY_ECHO", False)
+ config_binds: dict[
+ str | None, str | sa.engine.URL | dict[str, t.Any]
+ ] = app.config.setdefault("SQLALCHEMY_BINDS", {})
+ engine_options: dict[str | None, dict[str, t.Any]] = {}
+
+ # Build the engine config for each bind key.
+ for key, value in config_binds.items():
+ engine_options[key] = self._engine_options.copy()
+
+ if isinstance(value, (str, sa.engine.URL)):
+ engine_options[key]["url"] = value
+ else:
+ engine_options[key].update(value)
+
+ # Build the engine config for the default bind key.
+ if basic_uri is not None:
+ basic_engine_options["url"] = basic_uri
+
+ if "url" in basic_engine_options:
+ engine_options.setdefault(None, {}).update(basic_engine_options)
+
+ if not engine_options:
+ raise RuntimeError(
+ "Either 'SQLALCHEMY_DATABASE_URI' or 'SQLALCHEMY_BINDS' must be set."
+ )
+
+ engines = self._app_engines.setdefault(app, {})
+
+ # Dispose existing engines in case init_app is called again.
+ if engines:
+ for engine in engines.values():
+ engine.dispose()
+
+ engines.clear()
+
+ # Create the metadata and engine for each bind key.
+ for key, options in engine_options.items():
+ self._make_metadata(key)
+ options.setdefault("echo", echo)
+ options.setdefault("echo_pool", echo)
+ self._apply_driver_defaults(options, app)
+ engines[key] = self._make_engine(key, options, app)
+
+ if app.config.setdefault("SQLALCHEMY_RECORD_QUERIES", False):
+ from . import record_queries
+
+ for engine in engines.values():
+ record_queries._listen(engine)
+
+ if app.config.setdefault("SQLALCHEMY_TRACK_MODIFICATIONS", False):
+ from . import track_modifications
+
+ track_modifications._listen(self.session)
+
+ def _make_scoped_session(
+ self, options: dict[str, t.Any]
+ ) -> sa_orm.scoped_session[Session]:
+ """Create a :class:`sqlalchemy.orm.scoping.scoped_session` around the factory
+ from :meth:`_make_session_factory`. The result is available as :attr:`session`.
+
+ The scope function can be customized using the ``scopefunc`` key in the
+ ``session_options`` parameter to the extension. By default it uses the current
+ thread or greenlet id.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ :param options: The ``session_options`` parameter from ``__init__``. Keyword
+ arguments passed to the session factory. A ``scopefunc`` key is popped.
+
+ .. versionchanged:: 3.0
+ The session is scoped to the current app context.
+
+ .. versionchanged:: 3.0
+ Renamed from ``create_scoped_session``, this method is internal.
+ """
+ scope = options.pop("scopefunc", _app_ctx_id)
+ factory = self._make_session_factory(options)
+ return sa_orm.scoped_session(factory, scope)
+
+ def _make_session_factory(
+ self, options: dict[str, t.Any]
+ ) -> sa_orm.sessionmaker[Session]:
+ """Create the SQLAlchemy :class:`sqlalchemy.orm.sessionmaker` used by
+ :meth:`_make_scoped_session`.
+
+ To customize, pass the ``session_options`` parameter to :class:`SQLAlchemy`. To
+ customize the session class, subclass :class:`.Session` and pass it as the
+ ``class_`` key.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ :param options: The ``session_options`` parameter from ``__init__``. Keyword
+ arguments passed to the session factory.
+
+ .. versionchanged:: 3.0
+ The session class can be customized.
+
+ .. versionchanged:: 3.0
+ Renamed from ``create_session``, this method is internal.
+ """
+ options.setdefault("class_", Session)
+ options.setdefault("query_cls", self.Query)
+ return sa_orm.sessionmaker(db=self, **options)
+
+ def _teardown_session(self, exc: BaseException | None) -> None:
+ """Remove the current session at the end of the request.
+
+ :meta private:
+
+ .. versionadded:: 3.0
+ """
+ self.session.remove()
+
+ def _make_metadata(self, bind_key: str | None) -> sa.MetaData:
+ """Get or create a :class:`sqlalchemy.schema.MetaData` for the given bind key.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ :param bind_key: The name of the metadata being created.
+
+ .. versionadded:: 3.0
+ """
+ if bind_key in self.metadatas:
+ return self.metadatas[bind_key]
+
+ if bind_key is not None:
+ # Copy the naming convention from the default metadata.
+ naming_convention = self._make_metadata(None).naming_convention
+ else:
+ naming_convention = None
+
+ # Set the bind key in info to be used by session.get_bind.
+ metadata = sa.MetaData(
+ naming_convention=naming_convention, info={"bind_key": bind_key}
+ )
+ self.metadatas[bind_key] = metadata
+ return metadata
+
+ def _make_table_class(self) -> type[_Table]:
+ """Create a SQLAlchemy :class:`sqlalchemy.schema.Table` class that chooses a
+ metadata automatically based on the ``bind_key``. The result is available as
+ :attr:`Table`.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ .. versionadded:: 3.0
+ """
+
+ class Table(_Table):
+ def __new__(
+ cls, *args: t.Any, bind_key: str | None = None, **kwargs: t.Any
+ ) -> Table:
+ # If a metadata arg is passed, go directly to the base Table. Also do
+ # this for no args so the correct error is shown.
+ if not args or (len(args) >= 2 and isinstance(args[1], sa.MetaData)):
+ return super().__new__(cls, *args, **kwargs)
+
+ metadata = self._make_metadata(bind_key)
+ return super().__new__(cls, *[args[0], metadata, *args[1:]], **kwargs)
+
+ return Table
+
+ def _make_declarative_base(
+ self,
+ model_class: _FSA_MCT,
+ disable_autonaming: bool = False,
+ ) -> t.Type[_FSAModel]:
+ """Create a SQLAlchemy declarative model class. The result is available as
+ :attr:`Model`.
+
+ To customize, subclass :class:`.Model` and pass it as ``model_class`` to
+ :class:`SQLAlchemy`. To customize at the metaclass level, pass an already
+ created declarative model class as ``model_class``.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ :param model_class: A model base class, or an already created declarative model
+ class.
+
+ :param disable_autonaming: Turns off automatic tablename generation in models.
+
+ .. versionchanged:: 3.1.0
+ Added support for passing SQLAlchemy 2.x base class as model class.
+ Added optional ``disable_autonaming`` parameter.
+
+ .. versionchanged:: 3.0
+ Renamed with a leading underscore, this method is internal.
+
+ .. versionchanged:: 2.3
+ ``model`` can be an already created declarative model class.
+ """
+ model: t.Type[_FSAModel]
+ declarative_bases = _get_2x_declarative_bases(model_class)
+ if len(declarative_bases) > 1:
+ # raise error if more than one declarative base is found
+ raise ValueError(
+ "Only one declarative base can be passed to SQLAlchemy."
+ " Got: {}".format(model_class.__bases__)
+ )
+ elif len(declarative_bases) == 1:
+ body = dict(model_class.__dict__)
+ body["__fsa__"] = self
+ mixin_classes = [BindMixin, NameMixin, Model]
+ if disable_autonaming:
+ mixin_classes.remove(NameMixin)
+ model = types.new_class(
+ "FlaskSQLAlchemyBase",
+ (*mixin_classes, *model_class.__bases__),
+ {"metaclass": type(declarative_bases[0])},
+ lambda ns: ns.update(body),
+ )
+ elif not isinstance(model_class, sa_orm.DeclarativeMeta):
+ metadata = self._make_metadata(None)
+ metaclass = DefaultMetaNoName if disable_autonaming else DefaultMeta
+ model = sa_orm.declarative_base(
+ metadata=metadata, cls=model_class, name="Model", metaclass=metaclass
+ )
+ else:
+ model = model_class # type: ignore[assignment]
+
+ if None not in self.metadatas:
+ # Use the model's metadata as the default metadata.
+ model.metadata.info["bind_key"] = None
+ self.metadatas[None] = model.metadata
+ else:
+ # Use the passed in default metadata as the model's metadata.
+ model.metadata = self.metadatas[None]
+
+ model.query_class = self.Query
+ model.query = _QueryProperty() # type: ignore[assignment]
+ model.__fsa__ = self
+ return model
+
+ def _apply_driver_defaults(self, options: dict[str, t.Any], app: Flask) -> None:
+ """Apply driver-specific configuration to an engine.
+
+ SQLite in-memory databases use ``StaticPool`` and disable ``check_same_thread``.
+ File paths are relative to the app's :attr:`~flask.Flask.instance_path`,
+ which is created if it doesn't exist.
+
+ MySQL sets ``charset="utf8mb4"``, and ``pool_timeout`` defaults to 2 hours.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ :param options: Arguments passed to the engine.
+ :param app: The application that the engine configuration belongs to.
+
+ .. versionchanged:: 3.0
+ SQLite paths are relative to ``app.instance_path``. It does not use
+ ``NullPool`` if ``pool_size`` is 0. Driver-level URIs are supported.
+
+ .. versionchanged:: 3.0
+ MySQL sets ``charset="utf8mb4". It does not set ``pool_size`` to 10. It
+ does not set ``pool_recycle`` if not using a queue pool.
+
+ .. versionchanged:: 3.0
+ Renamed from ``apply_driver_hacks``, this method is internal. It does not
+ return anything.
+
+ .. versionchanged:: 2.5
+ Returns ``(sa_url, options)``.
+ """
+ url = sa.engine.make_url(options["url"])
+
+ if url.drivername in {"sqlite", "sqlite+pysqlite"}:
+ if url.database is None or url.database in {"", ":memory:"}:
+ options["poolclass"] = sa.pool.StaticPool
+
+ if "connect_args" not in options:
+ options["connect_args"] = {}
+
+ options["connect_args"]["check_same_thread"] = False
+ else:
+ # the url might look like sqlite:///file:path?uri=true
+ is_uri = url.query.get("uri", False)
+
+ if is_uri:
+ db_str = url.database[5:]
+ else:
+ db_str = url.database
+
+ if not os.path.isabs(db_str):
+ os.makedirs(app.instance_path, exist_ok=True)
+ db_str = os.path.join(app.instance_path, db_str)
+
+ if is_uri:
+ db_str = f"file:{db_str}"
+
+ options["url"] = url.set(database=db_str)
+ elif url.drivername.startswith("mysql"):
+ # set queue defaults only when using queue pool
+ if (
+ "pool_class" not in options
+ or options["pool_class"] is sa.pool.QueuePool
+ ):
+ options.setdefault("pool_recycle", 7200)
+
+ if "charset" not in url.query:
+ options["url"] = url.update_query_dict({"charset": "utf8mb4"})
+
+ def _make_engine(
+ self, bind_key: str | None, options: dict[str, t.Any], app: Flask
+ ) -> sa.engine.Engine:
+ """Create the :class:`sqlalchemy.engine.Engine` for the given bind key and app.
+
+ To customize, use :data:`.SQLALCHEMY_ENGINE_OPTIONS` or
+ :data:`.SQLALCHEMY_BINDS` config. Pass ``engine_options`` to :class:`SQLAlchemy`
+ to set defaults for all engines.
+
+ This method is used for internal setup. Its signature may change at any time.
+
+ :meta private:
+
+ :param bind_key: The name of the engine being created.
+ :param options: Arguments passed to the engine.
+ :param app: The application that the engine configuration belongs to.
+
+ .. versionchanged:: 3.0
+ Renamed from ``create_engine``, this method is internal.
+ """
+ return sa.engine_from_config(options, prefix="")
+
+ @property
+ def metadata(self) -> sa.MetaData:
+ """The default metadata used by :attr:`Model` and :attr:`Table` if no bind key
+ is set.
+ """
+ return self.metadatas[None]
+
+ @property
+ def engines(self) -> t.Mapping[str | None, sa.engine.Engine]:
+ """Map of bind keys to :class:`sqlalchemy.engine.Engine` instances for current
+ application. The ``None`` key refers to the default engine, and is available as
+ :attr:`engine`.
+
+ To customize, set the :data:`.SQLALCHEMY_BINDS` config, and set defaults by
+ passing the ``engine_options`` parameter to the extension.
+
+ This requires that a Flask application context is active.
+
+ .. versionadded:: 3.0
+ """
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+
+ if app not in self._app_engines:
+ raise RuntimeError(
+ "The current Flask app is not registered with this 'SQLAlchemy'"
+ " instance. Did you forget to call 'init_app', or did you create"
+ " multiple 'SQLAlchemy' instances?"
+ )
+
+ return self._app_engines[app]
+
+ @property
+ def engine(self) -> sa.engine.Engine:
+ """The default :class:`~sqlalchemy.engine.Engine` for the current application,
+ used by :attr:`session` if the :attr:`Model` or :attr:`Table` being queried does
+ not set a bind key.
+
+ To customize, set the :data:`.SQLALCHEMY_ENGINE_OPTIONS` config, and set
+ defaults by passing the ``engine_options`` parameter to the extension.
+
+ This requires that a Flask application context is active.
+ """
+ return self.engines[None]
+
+ def get_engine(
+ self, bind_key: str | None = None, **kwargs: t.Any
+ ) -> sa.engine.Engine:
+ """Get the engine for the given bind key for the current application.
+ This requires that a Flask application context is active.
+
+ :param bind_key: The name of the engine.
+
+ .. deprecated:: 3.0
+ Will be removed in Flask-SQLAlchemy 3.2. Use ``engines[key]`` instead.
+
+ .. versionchanged:: 3.0
+ Renamed the ``bind`` parameter to ``bind_key``. Removed the ``app``
+ parameter.
+ """
+ warnings.warn(
+ "'get_engine' is deprecated and will be removed in Flask-SQLAlchemy"
+ " 3.2. Use 'engine' or 'engines[key]' instead. If you're using"
+ " Flask-Migrate or Alembic, you'll need to update your 'env.py' file.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
+
+ if "bind" in kwargs:
+ bind_key = kwargs.pop("bind")
+
+ return self.engines[bind_key]
+
+ def get_or_404(
+ self,
+ entity: type[_O],
+ ident: t.Any,
+ *,
+ description: str | None = None,
+ **kwargs: t.Any,
+ ) -> _O:
+ """Like :meth:`session.get() ` but aborts with a
+ ``404 Not Found`` error instead of returning ``None``.
+
+ :param entity: The model class to query.
+ :param ident: The primary key to query.
+ :param description: A custom message to show on the error page.
+ :param kwargs: Extra arguments passed to ``session.get()``.
+
+ .. versionchanged:: 3.1
+ Pass extra keyword arguments to ``session.get()``.
+
+ .. versionadded:: 3.0
+ """
+ value = self.session.get(entity, ident, **kwargs)
+
+ if value is None:
+ abort(404, description=description)
+
+ return value
+
+ def first_or_404(
+ self, statement: sa.sql.Select[t.Any], *, description: str | None = None
+ ) -> t.Any:
+ """Like :meth:`Result.scalar() `, but aborts
+ with a ``404 Not Found`` error instead of returning ``None``.
+
+ :param statement: The ``select`` statement to execute.
+ :param description: A custom message to show on the error page.
+
+ .. versionadded:: 3.0
+ """
+ value = self.session.execute(statement).scalar()
+
+ if value is None:
+ abort(404, description=description)
+
+ return value
+
+ def one_or_404(
+ self, statement: sa.sql.Select[t.Any], *, description: str | None = None
+ ) -> t.Any:
+ """Like :meth:`Result.scalar_one() `,
+ but aborts with a ``404 Not Found`` error instead of raising ``NoResultFound``
+ or ``MultipleResultsFound``.
+
+ :param statement: The ``select`` statement to execute.
+ :param description: A custom message to show on the error page.
+
+ .. versionadded:: 3.0
+ """
+ try:
+ return self.session.execute(statement).scalar_one()
+ except (sa_exc.NoResultFound, sa_exc.MultipleResultsFound):
+ abort(404, description=description)
+
+ def paginate(
+ self,
+ select: sa.sql.Select[t.Any],
+ *,
+ page: int | None = None,
+ per_page: int | None = None,
+ max_per_page: int | None = None,
+ error_out: bool = True,
+ count: bool = True,
+ ) -> Pagination:
+ """Apply an offset and limit to a select statment based on the current page and
+ number of items per page, returning a :class:`.Pagination` object.
+
+ The statement should select a model class, like ``select(User)``. This applies
+ ``unique()`` and ``scalars()`` modifiers to the result, so compound selects will
+ not return the expected results.
+
+ :param select: The ``select`` statement to paginate.
+ :param page: The current page, used to calculate the offset. Defaults to the
+ ``page`` query arg during a request, or 1 otherwise.
+ :param per_page: The maximum number of items on a page, used to calculate the
+ offset and limit. Defaults to the ``per_page`` query arg during a request,
+ or 20 otherwise.
+ :param max_per_page: The maximum allowed value for ``per_page``, to limit a
+ user-provided value. Use ``None`` for no limit. Defaults to 100.
+ :param error_out: Abort with a ``404 Not Found`` error if no items are returned
+ and ``page`` is not 1, or if ``page`` or ``per_page`` is less than 1, or if
+ either are not ints.
+ :param count: Calculate the total number of values by issuing an extra count
+ query. For very complex queries this may be inaccurate or slow, so it can be
+ disabled and set manually if necessary.
+
+ .. versionchanged:: 3.0
+ The ``count`` query is more efficient.
+
+ .. versionadded:: 3.0
+ """
+ return SelectPagination(
+ select=select,
+ session=self.session(),
+ page=page,
+ per_page=per_page,
+ max_per_page=max_per_page,
+ error_out=error_out,
+ count=count,
+ )
+
+ def _call_for_binds(
+ self, bind_key: str | None | list[str | None], op_name: str
+ ) -> None:
+ """Call a method on each metadata.
+
+ :meta private:
+
+ :param bind_key: A bind key or list of keys. Defaults to all binds.
+ :param op_name: The name of the method to call.
+
+ .. versionchanged:: 3.0
+ Renamed from ``_execute_for_all_tables``.
+ """
+ if bind_key == "__all__":
+ keys: list[str | None] = list(self.metadatas)
+ elif bind_key is None or isinstance(bind_key, str):
+ keys = [bind_key]
+ else:
+ keys = bind_key
+
+ for key in keys:
+ try:
+ engine = self.engines[key]
+ except KeyError:
+ message = f"Bind key '{key}' is not in 'SQLALCHEMY_BINDS' config."
+
+ if key is None:
+ message = f"'SQLALCHEMY_DATABASE_URI' config is not set. {message}"
+
+ raise sa_exc.UnboundExecutionError(message) from None
+
+ metadata = self.metadatas[key]
+ getattr(metadata, op_name)(bind=engine)
+
+ def create_all(self, bind_key: str | None | list[str | None] = "__all__") -> None:
+ """Create tables that do not exist in the database by calling
+ ``metadata.create_all()`` for all or some bind keys. This does not
+ update existing tables, use a migration library for that.
+
+ This requires that a Flask application context is active.
+
+ :param bind_key: A bind key or list of keys to create the tables for. Defaults
+ to all binds.
+
+ .. versionchanged:: 3.0
+ Renamed the ``bind`` parameter to ``bind_key``. Removed the ``app``
+ parameter.
+
+ .. versionchanged:: 0.12
+ Added the ``bind`` and ``app`` parameters.
+ """
+ self._call_for_binds(bind_key, "create_all")
+
+ def drop_all(self, bind_key: str | None | list[str | None] = "__all__") -> None:
+ """Drop tables by calling ``metadata.drop_all()`` for all or some bind keys.
+
+ This requires that a Flask application context is active.
+
+ :param bind_key: A bind key or list of keys to drop the tables from. Defaults to
+ all binds.
+
+ .. versionchanged:: 3.0
+ Renamed the ``bind`` parameter to ``bind_key``. Removed the ``app``
+ parameter.
+
+ .. versionchanged:: 0.12
+ Added the ``bind`` and ``app`` parameters.
+ """
+ self._call_for_binds(bind_key, "drop_all")
+
+ def reflect(self, bind_key: str | None | list[str | None] = "__all__") -> None:
+ """Load table definitions from the database by calling ``metadata.reflect()``
+ for all or some bind keys.
+
+ This requires that a Flask application context is active.
+
+ :param bind_key: A bind key or list of keys to reflect the tables from. Defaults
+ to all binds.
+
+ .. versionchanged:: 3.0
+ Renamed the ``bind`` parameter to ``bind_key``. Removed the ``app``
+ parameter.
+
+ .. versionchanged:: 0.12
+ Added the ``bind`` and ``app`` parameters.
+ """
+ self._call_for_binds(bind_key, "reflect")
+
+ def _set_rel_query(self, kwargs: dict[str, t.Any]) -> None:
+ """Apply the extension's :attr:`Query` class as the default for relationships
+ and backrefs.
+
+ :meta private:
+ """
+ kwargs.setdefault("query_class", self.Query)
+
+ if "backref" in kwargs:
+ backref = kwargs["backref"]
+
+ if isinstance(backref, str):
+ backref = (backref, {})
+
+ backref[1].setdefault("query_class", self.Query)
+
+ def relationship(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> sa_orm.RelationshipProperty[t.Any]:
+ """A :func:`sqlalchemy.orm.relationship` that applies this extension's
+ :attr:`Query` class for dynamic relationships and backrefs.
+
+ .. versionchanged:: 3.0
+ The :attr:`Query` class is set on ``backref``.
+ """
+ self._set_rel_query(kwargs)
+ return sa_orm.relationship(*args, **kwargs)
+
+ def dynamic_loader(
+ self, argument: t.Any, **kwargs: t.Any
+ ) -> sa_orm.RelationshipProperty[t.Any]:
+ """A :func:`sqlalchemy.orm.dynamic_loader` that applies this extension's
+ :attr:`Query` class for relationships and backrefs.
+
+ .. versionchanged:: 3.0
+ The :attr:`Query` class is set on ``backref``.
+ """
+ self._set_rel_query(kwargs)
+ return sa_orm.dynamic_loader(argument, **kwargs)
+
+ def _relation(
+ self, *args: t.Any, **kwargs: t.Any
+ ) -> sa_orm.RelationshipProperty[t.Any]:
+ """A :func:`sqlalchemy.orm.relationship` that applies this extension's
+ :attr:`Query` class for dynamic relationships and backrefs.
+
+ SQLAlchemy 2.0 removes this name, use ``relationship`` instead.
+
+ :meta private:
+
+ .. versionchanged:: 3.0
+ The :attr:`Query` class is set on ``backref``.
+ """
+ self._set_rel_query(kwargs)
+ f = sa_orm.relationship
+ return f(*args, **kwargs)
+
+ def __getattr__(self, name: str) -> t.Any:
+ if name == "relation":
+ return self._relation
+
+ if name == "event":
+ return sa_event
+
+ if name.startswith("_"):
+ raise AttributeError(name)
+
+ for mod in (sa, sa_orm):
+ if hasattr(mod, name):
+ return getattr(mod, name)
+
+ raise AttributeError(name)
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/model.py b/venv/Lib/site-packages/flask_sqlalchemy/model.py
new file mode 100644
index 0000000..c6f9e5a
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/model.py
@@ -0,0 +1,330 @@
+from __future__ import annotations
+
+import re
+import typing as t
+
+import sqlalchemy as sa
+import sqlalchemy.orm as sa_orm
+
+from .query import Query
+
+if t.TYPE_CHECKING:
+ from .extension import SQLAlchemy
+
+
+class _QueryProperty:
+ """A class property that creates a query object for a model.
+
+ :meta private:
+ """
+
+ def __get__(self, obj: Model | None, cls: type[Model]) -> Query:
+ return cls.query_class(
+ cls, session=cls.__fsa__.session() # type: ignore[arg-type]
+ )
+
+
+class Model:
+ """The base class of the :attr:`.SQLAlchemy.Model` declarative model class.
+
+ To define models, subclass :attr:`db.Model <.SQLAlchemy.Model>`, not this. To
+ customize ``db.Model``, subclass this and pass it as ``model_class`` to
+ :class:`.SQLAlchemy`. To customize ``db.Model`` at the metaclass level, pass an
+ already created declarative model class as ``model_class``.
+ """
+
+ __fsa__: t.ClassVar[SQLAlchemy]
+ """Internal reference to the extension object.
+
+ :meta private:
+ """
+
+ query_class: t.ClassVar[type[Query]] = Query
+ """Query class used by :attr:`query`. Defaults to :attr:`.SQLAlchemy.Query`, which
+ defaults to :class:`.Query`.
+ """
+
+ query: t.ClassVar[Query] = _QueryProperty() # type: ignore[assignment]
+ """A SQLAlchemy query for a model. Equivalent to ``db.session.query(Model)``. Can be
+ customized per-model by overriding :attr:`query_class`.
+
+ .. warning::
+ The query interface is considered legacy in SQLAlchemy. Prefer using
+ ``session.execute(select())`` instead.
+ """
+
+ def __repr__(self) -> str:
+ state = sa.inspect(self)
+ assert state is not None
+
+ if state.transient:
+ pk = f"(transient {id(self)})"
+ elif state.pending:
+ pk = f"(pending {id(self)})"
+ else:
+ pk = ", ".join(map(str, state.identity))
+
+ return f"<{type(self).__name__} {pk}>"
+
+
+class BindMetaMixin(type):
+ """Metaclass mixin that sets a model's ``metadata`` based on its ``__bind_key__``.
+
+ If the model sets ``metadata`` or ``__table__`` directly, ``__bind_key__`` is
+ ignored. If the ``metadata`` is the same as the parent model, it will not be set
+ directly on the child model.
+ """
+
+ __fsa__: SQLAlchemy
+ metadata: sa.MetaData
+
+ def __init__(
+ cls, name: str, bases: tuple[type, ...], d: dict[str, t.Any], **kwargs: t.Any
+ ) -> None:
+ if not ("metadata" in cls.__dict__ or "__table__" in cls.__dict__):
+ bind_key = getattr(cls, "__bind_key__", None)
+ parent_metadata = getattr(cls, "metadata", None)
+ metadata = cls.__fsa__._make_metadata(bind_key)
+
+ if metadata is not parent_metadata:
+ cls.metadata = metadata
+
+ super().__init__(name, bases, d, **kwargs)
+
+
+class BindMixin:
+ """DeclarativeBase mixin to set a model's ``metadata`` based on ``__bind_key__``.
+
+ If no ``__bind_key__`` is specified, the model will use the default metadata
+ provided by ``DeclarativeBase`` or ``DeclarativeBaseNoMeta``.
+ If the model doesn't set ``metadata`` or ``__table__`` directly
+ and does set ``__bind_key__``, the model will use the metadata
+ for the specified bind key.
+ If the ``metadata`` is the same as the parent model, it will not be set
+ directly on the child model.
+
+ .. versionchanged:: 3.1.0
+ """
+
+ __fsa__: SQLAlchemy
+ metadata: sa.MetaData
+
+ @classmethod
+ def __init_subclass__(cls: t.Type[BindMixin], **kwargs: t.Dict[str, t.Any]) -> None:
+ if not ("metadata" in cls.__dict__ or "__table__" in cls.__dict__) and hasattr(
+ cls, "__bind_key__"
+ ):
+ bind_key = getattr(cls, "__bind_key__", None)
+ parent_metadata = getattr(cls, "metadata", None)
+ metadata = cls.__fsa__._make_metadata(bind_key)
+
+ if metadata is not parent_metadata:
+ cls.metadata = metadata
+
+ super().__init_subclass__(**kwargs)
+
+
+class NameMetaMixin(type):
+ """Metaclass mixin that sets a model's ``__tablename__`` by converting the
+ ``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models
+ that do not otherwise define ``__tablename__``. If a model does not define a primary
+ key, it will not generate a name or ``__table__``, for single-table inheritance.
+ """
+
+ metadata: sa.MetaData
+ __tablename__: str
+ __table__: sa.Table
+
+ def __init__(
+ cls, name: str, bases: tuple[type, ...], d: dict[str, t.Any], **kwargs: t.Any
+ ) -> None:
+ if should_set_tablename(cls):
+ cls.__tablename__ = camel_to_snake_case(cls.__name__)
+
+ super().__init__(name, bases, d, **kwargs)
+
+ # __table_cls__ has run. If no table was created, use the parent table.
+ if (
+ "__tablename__" not in cls.__dict__
+ and "__table__" in cls.__dict__
+ and cls.__dict__["__table__"] is None
+ ):
+ del cls.__table__
+
+ def __table_cls__(cls, *args: t.Any, **kwargs: t.Any) -> sa.Table | None:
+ """This is called by SQLAlchemy during mapper setup. It determines the final
+ table object that the model will use.
+
+ If no primary key is found, that indicates single-table inheritance, so no table
+ will be created and ``__tablename__`` will be unset.
+ """
+ schema = kwargs.get("schema")
+
+ if schema is None:
+ key = args[0]
+ else:
+ key = f"{schema}.{args[0]}"
+
+ # Check if a table with this name already exists. Allows reflected tables to be
+ # applied to models by name.
+ if key in cls.metadata.tables:
+ return sa.Table(*args, **kwargs)
+
+ # If a primary key is found, create a table for joined-table inheritance.
+ for arg in args:
+ if (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(
+ arg, sa.PrimaryKeyConstraint
+ ):
+ return sa.Table(*args, **kwargs)
+
+ # If no base classes define a table, return one that's missing a primary key
+ # so SQLAlchemy shows the correct error.
+ for base in cls.__mro__[1:-1]:
+ if "__table__" in base.__dict__:
+ break
+ else:
+ return sa.Table(*args, **kwargs)
+
+ # Single-table inheritance, use the parent table name. __init__ will unset
+ # __table__ based on this.
+ if "__tablename__" in cls.__dict__:
+ del cls.__tablename__
+
+ return None
+
+
+class NameMixin:
+ """DeclarativeBase mixin that sets a model's ``__tablename__`` by converting the
+ ``CamelCase`` class name to ``snake_case``. A name is set for non-abstract models
+ that do not otherwise define ``__tablename__``. If a model does not define a primary
+ key, it will not generate a name or ``__table__``, for single-table inheritance.
+
+ .. versionchanged:: 3.1.0
+ """
+
+ metadata: sa.MetaData
+ __tablename__: str
+ __table__: sa.Table
+
+ @classmethod
+ def __init_subclass__(cls: t.Type[NameMixin], **kwargs: t.Dict[str, t.Any]) -> None:
+ if should_set_tablename(cls):
+ cls.__tablename__ = camel_to_snake_case(cls.__name__)
+
+ super().__init_subclass__(**kwargs)
+
+ # __table_cls__ has run. If no table was created, use the parent table.
+ if (
+ "__tablename__" not in cls.__dict__
+ and "__table__" in cls.__dict__
+ and cls.__dict__["__table__"] is None
+ ):
+ del cls.__table__
+
+ @classmethod
+ def __table_cls__(cls, *args: t.Any, **kwargs: t.Any) -> sa.Table | None:
+ """This is called by SQLAlchemy during mapper setup. It determines the final
+ table object that the model will use.
+
+ If no primary key is found, that indicates single-table inheritance, so no table
+ will be created and ``__tablename__`` will be unset.
+ """
+ schema = kwargs.get("schema")
+
+ if schema is None:
+ key = args[0]
+ else:
+ key = f"{schema}.{args[0]}"
+
+ # Check if a table with this name already exists. Allows reflected tables to be
+ # applied to models by name.
+ if key in cls.metadata.tables:
+ return sa.Table(*args, **kwargs)
+
+ # If a primary key is found, create a table for joined-table inheritance.
+ for arg in args:
+ if (isinstance(arg, sa.Column) and arg.primary_key) or isinstance(
+ arg, sa.PrimaryKeyConstraint
+ ):
+ return sa.Table(*args, **kwargs)
+
+ # If no base classes define a table, return one that's missing a primary key
+ # so SQLAlchemy shows the correct error.
+ for base in cls.__mro__[1:-1]:
+ if "__table__" in base.__dict__:
+ break
+ else:
+ return sa.Table(*args, **kwargs)
+
+ # Single-table inheritance, use the parent table name. __init__ will unset
+ # __table__ based on this.
+ if "__tablename__" in cls.__dict__:
+ del cls.__tablename__
+
+ return None
+
+
+def should_set_tablename(cls: type) -> bool:
+ """Determine whether ``__tablename__`` should be generated for a model.
+
+ - If no class in the MRO sets a name, one should be generated.
+ - If a declared attr is found, it should be used instead.
+ - If a name is found, it should be used if the class is a mixin, otherwise one
+ should be generated.
+ - Abstract models should not have one generated.
+
+ Later, ``__table_cls__`` will determine if the model looks like single or
+ joined-table inheritance. If no primary key is found, the name will be unset.
+ """
+ if (
+ cls.__dict__.get("__abstract__", False)
+ or (
+ not issubclass(cls, (sa_orm.DeclarativeBase, sa_orm.DeclarativeBaseNoMeta))
+ and not any(isinstance(b, sa_orm.DeclarativeMeta) for b in cls.__mro__[1:])
+ )
+ or any(
+ (b is sa_orm.DeclarativeBase or b is sa_orm.DeclarativeBaseNoMeta)
+ for b in cls.__bases__
+ )
+ ):
+ return False
+
+ for base in cls.__mro__:
+ if "__tablename__" not in base.__dict__:
+ continue
+
+ if isinstance(base.__dict__["__tablename__"], sa_orm.declared_attr):
+ return False
+
+ return not (
+ base is cls
+ or base.__dict__.get("__abstract__", False)
+ or not (
+ # SQLAlchemy 1.x
+ isinstance(base, sa_orm.DeclarativeMeta)
+ # 2.x: DeclarativeBas uses this as metaclass
+ or isinstance(base, sa_orm.decl_api.DeclarativeAttributeIntercept)
+ # 2.x: DeclarativeBaseNoMeta doesn't use a metaclass
+ or issubclass(base, sa_orm.DeclarativeBaseNoMeta)
+ )
+ )
+
+ return True
+
+
+def camel_to_snake_case(name: str) -> str:
+ """Convert a ``CamelCase`` name to ``snake_case``."""
+ name = re.sub(r"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))", r"_\1", name)
+ return name.lower().lstrip("_")
+
+
+class DefaultMeta(BindMetaMixin, NameMetaMixin, sa_orm.DeclarativeMeta):
+ """SQLAlchemy declarative metaclass that provides ``__bind_key__`` and
+ ``__tablename__`` support.
+ """
+
+
+class DefaultMetaNoName(BindMetaMixin, sa_orm.DeclarativeMeta):
+ """SQLAlchemy declarative metaclass that provides ``__bind_key__`` and
+ ``__tablename__`` support.
+ """
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/pagination.py b/venv/Lib/site-packages/flask_sqlalchemy/pagination.py
new file mode 100644
index 0000000..3d49d6e
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/pagination.py
@@ -0,0 +1,364 @@
+from __future__ import annotations
+
+import typing as t
+from math import ceil
+
+import sqlalchemy as sa
+import sqlalchemy.orm as sa_orm
+from flask import abort
+from flask import request
+
+
+class Pagination:
+ """Apply an offset and limit to the query based on the current page and number of
+ items per page.
+
+ Don't create pagination objects manually. They are created by
+ :meth:`.SQLAlchemy.paginate` and :meth:`.Query.paginate`.
+
+ This is a base class, a subclass must implement :meth:`_query_items` and
+ :meth:`_query_count`. Those methods will use arguments passed as ``kwargs`` to
+ perform the queries.
+
+ :param page: The current page, used to calculate the offset. Defaults to the
+ ``page`` query arg during a request, or 1 otherwise.
+ :param per_page: The maximum number of items on a page, used to calculate the
+ offset and limit. Defaults to the ``per_page`` query arg during a request,
+ or 20 otherwise.
+ :param max_per_page: The maximum allowed value for ``per_page``, to limit a
+ user-provided value. Use ``None`` for no limit. Defaults to 100.
+ :param error_out: Abort with a ``404 Not Found`` error if no items are returned
+ and ``page`` is not 1, or if ``page`` or ``per_page`` is less than 1, or if
+ either are not ints.
+ :param count: Calculate the total number of values by issuing an extra count
+ query. For very complex queries this may be inaccurate or slow, so it can be
+ disabled and set manually if necessary.
+ :param kwargs: Information about the query to paginate. Different subclasses will
+ require different arguments.
+
+ .. versionchanged:: 3.0
+ Iterating over a pagination object iterates over its items.
+
+ .. versionchanged:: 3.0
+ Creating instances manually is not a public API.
+ """
+
+ def __init__(
+ self,
+ page: int | None = None,
+ per_page: int | None = None,
+ max_per_page: int | None = 100,
+ error_out: bool = True,
+ count: bool = True,
+ **kwargs: t.Any,
+ ) -> None:
+ self._query_args = kwargs
+ page, per_page = self._prepare_page_args(
+ page=page,
+ per_page=per_page,
+ max_per_page=max_per_page,
+ error_out=error_out,
+ )
+
+ self.page: int = page
+ """The current page."""
+
+ self.per_page: int = per_page
+ """The maximum number of items on a page."""
+
+ self.max_per_page: int | None = max_per_page
+ """The maximum allowed value for ``per_page``."""
+
+ items = self._query_items()
+
+ if not items and page != 1 and error_out:
+ abort(404)
+
+ self.items: list[t.Any] = items
+ """The items on the current page. Iterating over the pagination object is
+ equivalent to iterating over the items.
+ """
+
+ if count:
+ total = self._query_count()
+ else:
+ total = None
+
+ self.total: int | None = total
+ """The total number of items across all pages."""
+
+ @staticmethod
+ def _prepare_page_args(
+ *,
+ page: int | None = None,
+ per_page: int | None = None,
+ max_per_page: int | None = None,
+ error_out: bool = True,
+ ) -> tuple[int, int]:
+ if request:
+ if page is None:
+ try:
+ page = int(request.args.get("page", 1))
+ except (TypeError, ValueError):
+ if error_out:
+ abort(404)
+
+ page = 1
+
+ if per_page is None:
+ try:
+ per_page = int(request.args.get("per_page", 20))
+ except (TypeError, ValueError):
+ if error_out:
+ abort(404)
+
+ per_page = 20
+ else:
+ if page is None:
+ page = 1
+
+ if per_page is None:
+ per_page = 20
+
+ if max_per_page is not None:
+ per_page = min(per_page, max_per_page)
+
+ if page < 1:
+ if error_out:
+ abort(404)
+ else:
+ page = 1
+
+ if per_page < 1:
+ if error_out:
+ abort(404)
+ else:
+ per_page = 20
+
+ return page, per_page
+
+ @property
+ def _query_offset(self) -> int:
+ """The index of the first item to query, passed to ``offset()``.
+
+ :meta private:
+
+ .. versionadded:: 3.0
+ """
+ return (self.page - 1) * self.per_page
+
+ def _query_items(self) -> list[t.Any]:
+ """Execute the query to get the items on the current page.
+
+ Uses init arguments stored in :attr:`_query_args`.
+
+ :meta private:
+
+ .. versionadded:: 3.0
+ """
+ raise NotImplementedError
+
+ def _query_count(self) -> int:
+ """Execute the query to get the total number of items.
+
+ Uses init arguments stored in :attr:`_query_args`.
+
+ :meta private:
+
+ .. versionadded:: 3.0
+ """
+ raise NotImplementedError
+
+ @property
+ def first(self) -> int:
+ """The number of the first item on the page, starting from 1, or 0 if there are
+ no items.
+
+ .. versionadded:: 3.0
+ """
+ if len(self.items) == 0:
+ return 0
+
+ return (self.page - 1) * self.per_page + 1
+
+ @property
+ def last(self) -> int:
+ """The number of the last item on the page, starting from 1, inclusive, or 0 if
+ there are no items.
+
+ .. versionadded:: 3.0
+ """
+ first = self.first
+ return max(first, first + len(self.items) - 1)
+
+ @property
+ def pages(self) -> int:
+ """The total number of pages."""
+ if self.total == 0 or self.total is None:
+ return 0
+
+ return ceil(self.total / self.per_page)
+
+ @property
+ def has_prev(self) -> bool:
+ """``True`` if this is not the first page."""
+ return self.page > 1
+
+ @property
+ def prev_num(self) -> int | None:
+ """The previous page number, or ``None`` if this is the first page."""
+ if not self.has_prev:
+ return None
+
+ return self.page - 1
+
+ def prev(self, *, error_out: bool = False) -> Pagination:
+ """Query the :class:`Pagination` object for the previous page.
+
+ :param error_out: Abort with a ``404 Not Found`` error if no items are returned
+ and ``page`` is not 1, or if ``page`` or ``per_page`` is less than 1, or if
+ either are not ints.
+ """
+ p = type(self)(
+ page=self.page - 1,
+ per_page=self.per_page,
+ error_out=error_out,
+ count=False,
+ **self._query_args,
+ )
+ p.total = self.total
+ return p
+
+ @property
+ def has_next(self) -> bool:
+ """``True`` if this is not the last page."""
+ return self.page < self.pages
+
+ @property
+ def next_num(self) -> int | None:
+ """The next page number, or ``None`` if this is the last page."""
+ if not self.has_next:
+ return None
+
+ return self.page + 1
+
+ def next(self, *, error_out: bool = False) -> Pagination:
+ """Query the :class:`Pagination` object for the next page.
+
+ :param error_out: Abort with a ``404 Not Found`` error if no items are returned
+ and ``page`` is not 1, or if ``page`` or ``per_page`` is less than 1, or if
+ either are not ints.
+ """
+ p = type(self)(
+ page=self.page + 1,
+ per_page=self.per_page,
+ max_per_page=self.max_per_page,
+ error_out=error_out,
+ count=False,
+ **self._query_args,
+ )
+ p.total = self.total
+ return p
+
+ def iter_pages(
+ self,
+ *,
+ left_edge: int = 2,
+ left_current: int = 2,
+ right_current: int = 4,
+ right_edge: int = 2,
+ ) -> t.Iterator[int | None]:
+ """Yield page numbers for a pagination widget. Skipped pages between the edges
+ and middle are represented by a ``None``.
+
+ For example, if there are 20 pages and the current page is 7, the following
+ values are yielded.
+
+ .. code-block:: python
+
+ 1, 2, None, 5, 6, 7, 8, 9, 10, 11, None, 19, 20
+
+ :param left_edge: How many pages to show from the first page.
+ :param left_current: How many pages to show left of the current page.
+ :param right_current: How many pages to show right of the current page.
+ :param right_edge: How many pages to show from the last page.
+
+ .. versionchanged:: 3.0
+ Improved efficiency of calculating what to yield.
+
+ .. versionchanged:: 3.0
+ ``right_current`` boundary is inclusive.
+
+ .. versionchanged:: 3.0
+ All parameters are keyword-only.
+ """
+ pages_end = self.pages + 1
+
+ if pages_end == 1:
+ return
+
+ left_end = min(1 + left_edge, pages_end)
+ yield from range(1, left_end)
+
+ if left_end == pages_end:
+ return
+
+ mid_start = max(left_end, self.page - left_current)
+ mid_end = min(self.page + right_current + 1, pages_end)
+
+ if mid_start - left_end > 0:
+ yield None
+
+ yield from range(mid_start, mid_end)
+
+ if mid_end == pages_end:
+ return
+
+ right_start = max(mid_end, pages_end - right_edge)
+
+ if right_start - mid_end > 0:
+ yield None
+
+ yield from range(right_start, pages_end)
+
+ def __iter__(self) -> t.Iterator[t.Any]:
+ yield from self.items
+
+
+class SelectPagination(Pagination):
+ """Returned by :meth:`.SQLAlchemy.paginate`. Takes ``select`` and ``session``
+ arguments in addition to the :class:`Pagination` arguments.
+
+ .. versionadded:: 3.0
+ """
+
+ def _query_items(self) -> list[t.Any]:
+ select = self._query_args["select"]
+ select = select.limit(self.per_page).offset(self._query_offset)
+ session = self._query_args["session"]
+ return list(session.execute(select).unique().scalars())
+
+ def _query_count(self) -> int:
+ select = self._query_args["select"]
+ sub = select.options(sa_orm.lazyload("*")).order_by(None).subquery()
+ session = self._query_args["session"]
+ out = session.execute(sa.select(sa.func.count()).select_from(sub)).scalar()
+ return out # type: ignore[no-any-return]
+
+
+class QueryPagination(Pagination):
+ """Returned by :meth:`.Query.paginate`. Takes a ``query`` argument in addition to
+ the :class:`Pagination` arguments.
+
+ .. versionadded:: 3.0
+ """
+
+ def _query_items(self) -> list[t.Any]:
+ query = self._query_args["query"]
+ out = query.limit(self.per_page).offset(self._query_offset).all()
+ return out # type: ignore[no-any-return]
+
+ def _query_count(self) -> int:
+ # Query.count automatically disables eager loads
+ out = self._query_args["query"].order_by(None).count()
+ return out # type: ignore[no-any-return]
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/py.typed b/venv/Lib/site-packages/flask_sqlalchemy/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/query.py b/venv/Lib/site-packages/flask_sqlalchemy/query.py
new file mode 100644
index 0000000..35f927d
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/query.py
@@ -0,0 +1,105 @@
+from __future__ import annotations
+
+import typing as t
+
+import sqlalchemy.exc as sa_exc
+import sqlalchemy.orm as sa_orm
+from flask import abort
+
+from .pagination import Pagination
+from .pagination import QueryPagination
+
+
+class Query(sa_orm.Query): # type: ignore[type-arg]
+ """SQLAlchemy :class:`~sqlalchemy.orm.query.Query` subclass with some extra methods
+ useful for querying in a web application.
+
+ This is the default query class for :attr:`.Model.query`.
+
+ .. versionchanged:: 3.0
+ Renamed to ``Query`` from ``BaseQuery``.
+ """
+
+ def get_or_404(self, ident: t.Any, description: str | None = None) -> t.Any:
+ """Like :meth:`~sqlalchemy.orm.Query.get` but aborts with a ``404 Not Found``
+ error instead of returning ``None``.
+
+ :param ident: The primary key to query.
+ :param description: A custom message to show on the error page.
+ """
+ rv = self.get(ident)
+
+ if rv is None:
+ abort(404, description=description)
+
+ return rv
+
+ def first_or_404(self, description: str | None = None) -> t.Any:
+ """Like :meth:`~sqlalchemy.orm.Query.first` but aborts with a ``404 Not Found``
+ error instead of returning ``None``.
+
+ :param description: A custom message to show on the error page.
+ """
+ rv = self.first()
+
+ if rv is None:
+ abort(404, description=description)
+
+ return rv
+
+ def one_or_404(self, description: str | None = None) -> t.Any:
+ """Like :meth:`~sqlalchemy.orm.Query.one` but aborts with a ``404 Not Found``
+ error instead of raising ``NoResultFound`` or ``MultipleResultsFound``.
+
+ :param description: A custom message to show on the error page.
+
+ .. versionadded:: 3.0
+ """
+ try:
+ return self.one()
+ except (sa_exc.NoResultFound, sa_exc.MultipleResultsFound):
+ abort(404, description=description)
+
+ def paginate(
+ self,
+ *,
+ page: int | None = None,
+ per_page: int | None = None,
+ max_per_page: int | None = None,
+ error_out: bool = True,
+ count: bool = True,
+ ) -> Pagination:
+ """Apply an offset and limit to the query based on the current page and number
+ of items per page, returning a :class:`.Pagination` object.
+
+ :param page: The current page, used to calculate the offset. Defaults to the
+ ``page`` query arg during a request, or 1 otherwise.
+ :param per_page: The maximum number of items on a page, used to calculate the
+ offset and limit. Defaults to the ``per_page`` query arg during a request,
+ or 20 otherwise.
+ :param max_per_page: The maximum allowed value for ``per_page``, to limit a
+ user-provided value. Use ``None`` for no limit. Defaults to 100.
+ :param error_out: Abort with a ``404 Not Found`` error if no items are returned
+ and ``page`` is not 1, or if ``page`` or ``per_page`` is less than 1, or if
+ either are not ints.
+ :param count: Calculate the total number of values by issuing an extra count
+ query. For very complex queries this may be inaccurate or slow, so it can be
+ disabled and set manually if necessary.
+
+ .. versionchanged:: 3.0
+ All parameters are keyword-only.
+
+ .. versionchanged:: 3.0
+ The ``count`` query is more efficient.
+
+ .. versionchanged:: 3.0
+ ``max_per_page`` defaults to 100.
+ """
+ return QueryPagination(
+ query=self,
+ page=page,
+ per_page=per_page,
+ max_per_page=max_per_page,
+ error_out=error_out,
+ count=count,
+ )
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/record_queries.py b/venv/Lib/site-packages/flask_sqlalchemy/record_queries.py
new file mode 100644
index 0000000..e8273be
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/record_queries.py
@@ -0,0 +1,117 @@
+from __future__ import annotations
+
+import dataclasses
+import inspect
+import typing as t
+from time import perf_counter
+
+import sqlalchemy as sa
+import sqlalchemy.event as sa_event
+from flask import current_app
+from flask import g
+from flask import has_app_context
+
+
+def get_recorded_queries() -> list[_QueryInfo]:
+ """Get the list of recorded query information for the current session. Queries are
+ recorded if the config :data:`.SQLALCHEMY_RECORD_QUERIES` is enabled.
+
+ Each query info object has the following attributes:
+
+ ``statement``
+ The string of SQL generated by SQLAlchemy with parameter placeholders.
+ ``parameters``
+ The parameters sent with the SQL statement.
+ ``start_time`` / ``end_time``
+ Timing info about when the query started execution and when the results where
+ returned. Accuracy and value depends on the operating system.
+ ``duration``
+ The time the query took in seconds.
+ ``location``
+ A string description of where in your application code the query was executed.
+ This may not be possible to calculate, and the format is not stable.
+
+ .. versionchanged:: 3.0
+ Renamed from ``get_debug_queries``.
+
+ .. versionchanged:: 3.0
+ The info object is a dataclass instead of a tuple.
+
+ .. versionchanged:: 3.0
+ The info object attribute ``context`` is renamed to ``location``.
+
+ .. versionchanged:: 3.0
+ Not enabled automatically in debug or testing mode.
+ """
+ return g.get("_sqlalchemy_queries", []) # type: ignore[no-any-return]
+
+
+@dataclasses.dataclass
+class _QueryInfo:
+ """Information about an executed query. Returned by :func:`get_recorded_queries`.
+
+ .. versionchanged:: 3.0
+ Renamed from ``_DebugQueryTuple``.
+
+ .. versionchanged:: 3.0
+ Changed to a dataclass instead of a tuple.
+
+ .. versionchanged:: 3.0
+ ``context`` is renamed to ``location``.
+ """
+
+ statement: str | None
+ parameters: t.Any
+ start_time: float
+ end_time: float
+ location: str
+
+ @property
+ def duration(self) -> float:
+ return self.end_time - self.start_time
+
+
+def _listen(engine: sa.engine.Engine) -> None:
+ sa_event.listen(engine, "before_cursor_execute", _record_start, named=True)
+ sa_event.listen(engine, "after_cursor_execute", _record_end, named=True)
+
+
+def _record_start(context: sa.engine.ExecutionContext, **kwargs: t.Any) -> None:
+ if not has_app_context():
+ return
+
+ context._fsa_start_time = perf_counter() # type: ignore[attr-defined]
+
+
+def _record_end(context: sa.engine.ExecutionContext, **kwargs: t.Any) -> None:
+ if not has_app_context():
+ return
+
+ if "_sqlalchemy_queries" not in g:
+ g._sqlalchemy_queries = []
+
+ import_top = current_app.import_name.partition(".")[0]
+ import_dot = f"{import_top}."
+ frame = inspect.currentframe()
+
+ while frame:
+ name = frame.f_globals.get("__name__")
+
+ if name and (name == import_top or name.startswith(import_dot)):
+ code = frame.f_code
+ location = f"{code.co_filename}:{frame.f_lineno} ({code.co_name})"
+ break
+
+ frame = frame.f_back
+ else:
+ location = ""
+
+ g._sqlalchemy_queries.append(
+ _QueryInfo(
+ statement=context.statement,
+ parameters=context.parameters,
+ start_time=context._fsa_start_time, # type: ignore[attr-defined]
+ end_time=perf_counter(),
+ location=location,
+ )
+ )
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/session.py b/venv/Lib/site-packages/flask_sqlalchemy/session.py
new file mode 100644
index 0000000..631fffa
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/session.py
@@ -0,0 +1,111 @@
+from __future__ import annotations
+
+import typing as t
+
+import sqlalchemy as sa
+import sqlalchemy.exc as sa_exc
+import sqlalchemy.orm as sa_orm
+from flask.globals import app_ctx
+
+if t.TYPE_CHECKING:
+ from .extension import SQLAlchemy
+
+
+class Session(sa_orm.Session):
+ """A SQLAlchemy :class:`~sqlalchemy.orm.Session` class that chooses what engine to
+ use based on the bind key associated with the metadata associated with the thing
+ being queried.
+
+ To customize ``db.session``, subclass this and pass it as the ``class_`` key in the
+ ``session_options`` to :class:`.SQLAlchemy`.
+
+ .. versionchanged:: 3.0
+ Renamed from ``SignallingSession``.
+ """
+
+ def __init__(self, db: SQLAlchemy, **kwargs: t.Any) -> None:
+ super().__init__(**kwargs)
+ self._db = db
+ self._model_changes: dict[object, tuple[t.Any, str]] = {}
+
+ def get_bind(
+ self,
+ mapper: t.Any | None = None,
+ clause: t.Any | None = None,
+ bind: sa.engine.Engine | sa.engine.Connection | None = None,
+ **kwargs: t.Any,
+ ) -> sa.engine.Engine | sa.engine.Connection:
+ """Select an engine based on the ``bind_key`` of the metadata associated with
+ the model or table being queried. If no bind key is set, uses the default bind.
+
+ .. versionchanged:: 3.0.3
+ Fix finding the bind for a joined inheritance model.
+
+ .. versionchanged:: 3.0
+ The implementation more closely matches the base SQLAlchemy implementation.
+
+ .. versionchanged:: 2.1
+ Support joining an external transaction.
+ """
+ if bind is not None:
+ return bind
+
+ engines = self._db.engines
+
+ if mapper is not None:
+ try:
+ mapper = sa.inspect(mapper)
+ except sa_exc.NoInspectionAvailable as e:
+ if isinstance(mapper, type):
+ raise sa_orm.exc.UnmappedClassError(mapper) from e
+
+ raise
+
+ engine = _clause_to_engine(mapper.local_table, engines)
+
+ if engine is not None:
+ return engine
+
+ if clause is not None:
+ engine = _clause_to_engine(clause, engines)
+
+ if engine is not None:
+ return engine
+
+ if None in engines:
+ return engines[None]
+
+ return super().get_bind(mapper=mapper, clause=clause, bind=bind, **kwargs)
+
+
+def _clause_to_engine(
+ clause: sa.ClauseElement | None,
+ engines: t.Mapping[str | None, sa.engine.Engine],
+) -> sa.engine.Engine | None:
+ """If the clause is a table, return the engine associated with the table's
+ metadata's bind key.
+ """
+ table = None
+
+ if clause is not None:
+ if isinstance(clause, sa.Table):
+ table = clause
+ elif isinstance(clause, sa.UpdateBase) and isinstance(clause.table, sa.Table):
+ table = clause.table
+
+ if table is not None and "bind_key" in table.metadata.info:
+ key = table.metadata.info["bind_key"]
+
+ if key not in engines:
+ raise sa_exc.UnboundExecutionError(
+ f"Bind key '{key}' is not in 'SQLALCHEMY_BINDS' config."
+ )
+
+ return engines[key]
+
+ return None
+
+
+def _app_ctx_id() -> int:
+ """Get the id of the current Flask application context for the session scope."""
+ return id(app_ctx._get_current_object()) # type: ignore[attr-defined]
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/table.py b/venv/Lib/site-packages/flask_sqlalchemy/table.py
new file mode 100644
index 0000000..ab08a69
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/table.py
@@ -0,0 +1,39 @@
+from __future__ import annotations
+
+import typing as t
+
+import sqlalchemy as sa
+import sqlalchemy.sql.schema as sa_sql_schema
+
+
+class _Table(sa.Table):
+ @t.overload
+ def __init__(
+ self,
+ name: str,
+ *args: sa_sql_schema.SchemaItem,
+ bind_key: str | None = None,
+ **kwargs: t.Any,
+ ) -> None:
+ ...
+
+ @t.overload
+ def __init__(
+ self,
+ name: str,
+ metadata: sa.MetaData,
+ *args: sa_sql_schema.SchemaItem,
+ **kwargs: t.Any,
+ ) -> None:
+ ...
+
+ @t.overload
+ def __init__(
+ self, name: str, *args: sa_sql_schema.SchemaItem, **kwargs: t.Any
+ ) -> None:
+ ...
+
+ def __init__(
+ self, name: str, *args: sa_sql_schema.SchemaItem, **kwargs: t.Any
+ ) -> None:
+ super().__init__(name, *args, **kwargs) # type: ignore[arg-type]
diff --git a/venv/Lib/site-packages/flask_sqlalchemy/track_modifications.py b/venv/Lib/site-packages/flask_sqlalchemy/track_modifications.py
new file mode 100644
index 0000000..7028b65
--- /dev/null
+++ b/venv/Lib/site-packages/flask_sqlalchemy/track_modifications.py
@@ -0,0 +1,88 @@
+from __future__ import annotations
+
+import typing as t
+
+import sqlalchemy as sa
+import sqlalchemy.event as sa_event
+import sqlalchemy.orm as sa_orm
+from flask import current_app
+from flask import has_app_context
+from flask.signals import Namespace # type: ignore[attr-defined]
+
+if t.TYPE_CHECKING:
+ from .session import Session
+
+_signals = Namespace()
+
+models_committed = _signals.signal("models-committed")
+"""This Blinker signal is sent after the session is committed if there were changed
+models in the session.
+
+The sender is the application that emitted the changes. The receiver is passed the
+``changes`` argument with a list of tuples in the form ``(instance, operation)``.
+The operations are ``"insert"``, ``"update"``, and ``"delete"``.
+"""
+
+before_models_committed = _signals.signal("before-models-committed")
+"""This signal works exactly like :data:`models_committed` but is emitted before the
+commit takes place.
+"""
+
+
+def _listen(session: sa_orm.scoped_session[Session]) -> None:
+ sa_event.listen(session, "before_flush", _record_ops, named=True)
+ sa_event.listen(session, "before_commit", _record_ops, named=True)
+ sa_event.listen(session, "before_commit", _before_commit)
+ sa_event.listen(session, "after_commit", _after_commit)
+ sa_event.listen(session, "after_rollback", _after_rollback)
+
+
+def _record_ops(session: Session, **kwargs: t.Any) -> None:
+ if not has_app_context():
+ return
+
+ if not current_app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]:
+ return
+
+ for targets, operation in (
+ (session.new, "insert"),
+ (session.dirty, "update"),
+ (session.deleted, "delete"),
+ ):
+ for target in targets:
+ state = sa.inspect(target)
+ key = state.identity_key if state.has_identity else id(target)
+ session._model_changes[key] = (target, operation)
+
+
+def _before_commit(session: Session) -> None:
+ if not has_app_context():
+ return
+
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+
+ if not app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]:
+ return
+
+ if session._model_changes:
+ changes = list(session._model_changes.values())
+ before_models_committed.send(app, changes=changes)
+
+
+def _after_commit(session: Session) -> None:
+ if not has_app_context():
+ return
+
+ app = current_app._get_current_object() # type: ignore[attr-defined]
+
+ if not app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]:
+ return
+
+ if session._model_changes:
+ changes = list(session._model_changes.values())
+ models_committed.send(app, changes=changes)
+ session._model_changes.clear()
+
+
+def _after_rollback(session: Session) -> None:
+ session._model_changes.clear()
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/AUTHORS b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/AUTHORS
new file mode 100644
index 0000000..42a5c22
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/AUTHORS
@@ -0,0 +1,51 @@
+Original Authors
+----------------
+* Armin Rigo
+* Christian Tismer
+
+Contributors
+------------
+* Al Stone
+* Alexander Schmidt
+* Alexey Borzenkov
+* Andreas Schwab
+* Armin Ronacher
+* Bin Wang
+* Bob Ippolito
+* ChangBo Guo
+* Christoph Gohlke
+* Denis Bilenko
+* Dirk Mueller
+* Donovan Preston
+* Fantix King
+* Floris Bruynooghe
+* Fredrik Fornwall
+* Gerd Woetzel
+* Giel van Schijndel
+* Gökhan Karabulut
+* Gustavo Niemeyer
+* Guy Rozendorn
+* Hye-Shik Chang
+* Jared Kuolt
+* Jason Madden
+* Josh Snyder
+* Kyle Ambroff
+* Laszlo Boszormenyi
+* Mao Han
+* Marc Abramowitz
+* Marc Schlaich
+* Marcin Bachry
+* Matt Madison
+* Matt Turner
+* Michael Ellerman
+* Michael Matz
+* Ralf Schmitt
+* Robie Basak
+* Ronny Pfannschmidt
+* Samual M. Rushing
+* Tony Bowles
+* Tony Breeds
+* Trevor Bowen
+* Tulio Magno Quites Machado Filho
+* Ulrich Weigand
+* Victor Stinner
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/INSTALLER b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/LICENSE b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/LICENSE
new file mode 100644
index 0000000..b73a4a1
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/LICENSE
@@ -0,0 +1,30 @@
+The following files are derived from Stackless Python and are subject to the
+same license as Stackless Python:
+
+ src/greenlet/slp_platformselect.h
+ files in src/greenlet/platform/ directory
+
+See LICENSE.PSF and http://www.stackless.com/ for details.
+
+Unless otherwise noted, the files in greenlet have been released under the
+following MIT license:
+
+Copyright (c) Armin Rigo, Christian Tismer and contributors
+
+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.
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/LICENSE.PSF b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/LICENSE.PSF
new file mode 100644
index 0000000..d3b509a
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/LICENSE.PSF
@@ -0,0 +1,47 @@
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011 Python Software Foundation; All Rights Reserved" are retained in Python
+alone or in any derivative version prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/METADATA b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/METADATA
new file mode 100644
index 0000000..e87d0ab
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/METADATA
@@ -0,0 +1,102 @@
+Metadata-Version: 2.1
+Name: greenlet
+Version: 3.0.3
+Summary: Lightweight in-process concurrent programming
+Home-page: https://greenlet.readthedocs.io/
+Author: Alexey Borzenkov
+Author-email: snaury@gmail.com
+Maintainer: Jason Madden
+Maintainer-email: jason@seecoresoftware.com
+License: MIT License
+Project-URL: Bug Tracker, https://github.com/python-greenlet/greenlet/issues
+Project-URL: Source Code, https://github.com/python-greenlet/greenlet/
+Project-URL: Documentation, https://greenlet.readthedocs.io/
+Keywords: greenlet coroutine concurrency threads cooperative
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+License-File: LICENSE.PSF
+License-File: AUTHORS
+Provides-Extra: docs
+Requires-Dist: Sphinx ; extra == 'docs'
+Requires-Dist: furo ; extra == 'docs'
+Provides-Extra: test
+Requires-Dist: objgraph ; extra == 'test'
+Requires-Dist: psutil ; extra == 'test'
+
+.. This file is included into docs/history.rst
+
+
+Greenlets are lightweight coroutines for in-process concurrent
+programming.
+
+The "greenlet" package is a spin-off of `Stackless`_, a version of
+CPython that supports micro-threads called "tasklets". Tasklets run
+pseudo-concurrently (typically in a single or a few OS-level threads)
+and are synchronized with data exchanges on "channels".
+
+A "greenlet", on the other hand, is a still more primitive notion of
+micro-thread with no implicit scheduling; coroutines, in other words.
+This is useful when you want to control exactly when your code runs.
+You can build custom scheduled micro-threads on top of greenlet;
+however, it seems that greenlets are useful on their own as a way to
+make advanced control flow structures. For example, we can recreate
+generators; the difference with Python's own generators is that our
+generators can call nested functions and the nested functions can
+yield values too. (Additionally, you don't need a "yield" keyword. See
+the example in `test_generator.py
+`_).
+
+Greenlets are provided as a C extension module for the regular unmodified
+interpreter.
+
+.. _`Stackless`: http://www.stackless.com
+
+
+Who is using Greenlet?
+======================
+
+There are several libraries that use Greenlet as a more flexible
+alternative to Python's built in coroutine support:
+
+ - `Concurrence`_
+ - `Eventlet`_
+ - `Gevent`_
+
+.. _Concurrence: http://opensource.hyves.org/concurrence/
+.. _Eventlet: http://eventlet.net/
+.. _Gevent: http://www.gevent.org/
+
+Getting Greenlet
+================
+
+The easiest way to get Greenlet is to install it with pip::
+
+ pip install greenlet
+
+
+Source code archives and binary distributions are available on the
+python package index at https://pypi.org/project/greenlet
+
+The source code repository is hosted on github:
+https://github.com/python-greenlet/greenlet
+
+Documentation is available on readthedocs.org:
+https://greenlet.readthedocs.io
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/RECORD b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/RECORD
new file mode 100644
index 0000000..b2e8728
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/RECORD
@@ -0,0 +1,117 @@
+../../include/site/python3.12/greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755
+greenlet-3.0.3.dist-info/AUTHORS,sha256=swW28t2knVRxRkaEQNZtO7MP9Sgnompb7B6cNgJM8Gk,849
+greenlet-3.0.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+greenlet-3.0.3.dist-info/LICENSE,sha256=dpgx1uXfrywggC-sz_H6-0wgJd2PYlPfpH_K1Z1NCXk,1434
+greenlet-3.0.3.dist-info/LICENSE.PSF,sha256=5f88I8EQ5JTNfXNsEP2W1GJFe6_soxCEDbZScpjH1Gs,2424
+greenlet-3.0.3.dist-info/METADATA,sha256=LjLm176aHpnFw1eMigcZGdnc0OgCO7LKPy21O4WhAp0,3881
+greenlet-3.0.3.dist-info/RECORD,,
+greenlet-3.0.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+greenlet-3.0.3.dist-info/WHEEL,sha256=j9Aissza3750LQHFAQyYerNjmkEON1-8w_RaZNFtKSs,102
+greenlet-3.0.3.dist-info/top_level.txt,sha256=YSnRsCRoO61JGlP57o8iKL6rdLWDWuiyKD8ekpWUsDc,9
+greenlet/TBrokenGreenlet.cpp,sha256=YgKaHkQV6_dKBrgS0HKDSqZroskv0IwSZDo4bsiwz3w,1029
+greenlet/TExceptionState.cpp,sha256=Ctg2YfyEYNjOYbteRB_oIJa9lNGyC7N1F3h4XqqQdg8,1367
+greenlet/TGreenlet.cpp,sha256=1xwAzGNqO68AZ4D5lD5DHmGPBohM6nv4BYnLatgIL68,25637
+greenlet/TGreenletGlobals.cpp,sha256=qLi1icS1UDSbefTkolz9TycEi_GOUblsEznMp0HFywQ,3268
+greenlet/TMainGreenlet.cpp,sha256=FvWtGJDKb64DLy0n-ddcTF6xJDwczPMKSm9mXSsHJKg,3365
+greenlet/TPythonState.cpp,sha256=QUoIQzF0HYmAJO_nwX5gXSSlMNL1mkxlN24KJCXIrIQ,14861
+greenlet/TStackState.cpp,sha256=VclDR-qiMeJjuiJxL9_u24MJiTgdSaYvr8bWQdTEZjY,7389
+greenlet/TThreadStateDestroy.cpp,sha256=EqZ-GjksrWNC20CY_P0yXN43wVRMYEh659SmRRqBaI4,7214
+greenlet/TUserGreenlet.cpp,sha256=b_Bmh4WZdS6I1yM2AfHRtd535WovtpYMkpfu2GQpaDs,23618
+greenlet/__init__.py,sha256=Dw4tovn18bpPaWQ4SK7jDJe24uV4ao264UfaT0uufxU,1723
+greenlet/__pycache__/__init__.cpython-312.pyc,,
+greenlet/_greenlet.cp312-win_amd64.pyd,sha256=TDBFIev55v5uK7pgBKgFRc5_I9t1mOmhCgMg0VaRDKY,221696
+greenlet/greenlet.cpp,sha256=k9RZolayY79WgjPXwcA3Vcv48MuW7TAtogIZPaDD3gM,48815
+greenlet/greenlet.h,sha256=sz5pYRSQqedgOt2AMgxLZdTjO-qcr_JMvgiEJR9IAJ8,4755
+greenlet/greenlet_allocator.hpp,sha256=kxyWW4Qdwlrc7ufgdb5vd6Y7jhauQ699Kod0mqiO1iM,1582
+greenlet/greenlet_compiler_compat.hpp,sha256=m7wvwrZqBoCQpDMTP-Z7whdXIES7e3AuXBgvPHSsfxg,4140
+greenlet/greenlet_cpython_add_pending.hpp,sha256=apAwIhGlgYrnYn03zWL6Sxy68kltDeb1e0QupZfb3DQ,6043
+greenlet/greenlet_cpython_compat.hpp,sha256=ZpN8gewZeOtd6T-mLidA7zteQ_P4vG8T1za_KPvCijg,3621
+greenlet/greenlet_exceptions.hpp,sha256=Dt8YdaQn8AK9nBfwU9rrDoMlR2Lw5aLTQV6ZAsHmfsw,3683
+greenlet/greenlet_greenlet.hpp,sha256=Ct_EAx4OJL6FvF5g3jV1ybSxnqzLVaRdPi2EcYT1iq4,27728
+greenlet/greenlet_internal.hpp,sha256=ZXH5zemWCN8wH8zAqMUGycvz_3IulRL6Gf2hZA6CknE,2703
+greenlet/greenlet_refs.hpp,sha256=ECkHKV1CVamtzmWWGKXXMpw8lXLeIzastXM9tfqlsNI,33864
+greenlet/greenlet_slp_switch.hpp,sha256=kM1QHA2iV-gH4cFyN6lfIagHQxvJZjWOVJdIxRE3TlQ,3198
+greenlet/greenlet_thread_state.hpp,sha256=0UwJCNd86ifwM2yDd3QrNmHAECL-eNADHubwiB_XGA4,20614
+greenlet/greenlet_thread_state_dict_cleanup.hpp,sha256=tEN0rI1pZiEsdtr7Oda24gr52fGiHnYTLyM8Vme3Gns,3831
+greenlet/greenlet_thread_support.hpp,sha256=XUJ6ljWjf9OYyuOILiz8e_yHvT3fbaUiHdhiPNQUV4s,867
+greenlet/platform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+greenlet/platform/__pycache__/__init__.cpython-312.pyc,,
+greenlet/platform/setup_switch_x64_masm.cmd,sha256=ZpClUJeU0ujEPSTWNSepP0W2f9XiYQKA8QKSoVou8EU,143
+greenlet/platform/switch_aarch64_gcc.h,sha256=GKC0yWNXnbK2X--X6aguRCMj2Tg7hDU1Zkl3RljDvC8,4307
+greenlet/platform/switch_alpha_unix.h,sha256=Z-SvF8JQV3oxWT8JRbL9RFu4gRFxPdJ7cviM8YayMmw,671
+greenlet/platform/switch_amd64_unix.h,sha256=EcSFCBlodEBhqhKjcJqY_5Dn_jn7pKpkJlOvp7gFXLI,2748
+greenlet/platform/switch_arm32_gcc.h,sha256=Z3KkHszdgq6uU4YN3BxvKMG2AdDnovwCCNrqGWZ1Lyo,2479
+greenlet/platform/switch_arm32_ios.h,sha256=mm5_R9aXB92hyxzFRwB71M60H6AlvHjrpTrc72Pz3l8,1892
+greenlet/platform/switch_arm64_masm.asm,sha256=4kpTtfy7rfcr8j1CpJLAK21EtZpGDAJXWRU68HEy5A8,1245
+greenlet/platform/switch_arm64_masm.obj,sha256=DmLnIB_icoEHAz1naue_pJPTZgR9ElM7-Nmztr-o9_U,746
+greenlet/platform/switch_arm64_msvc.h,sha256=RqK5MHLmXI3Q-FQ7tm32KWnbDNZKnkJdq8CR89cz640,398
+greenlet/platform/switch_csky_gcc.h,sha256=kDikyiPpewP71KoBZQO_MukDTXTXBiC7x-hF0_2DL0w,1331
+greenlet/platform/switch_loongarch64_linux.h,sha256=7M-Dhc4Q8tRbJCJhalDLwU6S9Mx8MjmN1RbTDgIvQTM,779
+greenlet/platform/switch_m68k_gcc.h,sha256=VSa6NpZhvyyvF-Q58CTIWSpEDo4FKygOyTz00whctlw,928
+greenlet/platform/switch_mips_unix.h,sha256=E0tYsqc5anDY1BhenU1l8DW-nVHC_BElzLgJw3TGtPk,1426
+greenlet/platform/switch_ppc64_aix.h,sha256=_BL0iyRr3ZA5iPlr3uk9SJ5sNRWGYLrXcZ5z-CE9anE,3860
+greenlet/platform/switch_ppc64_linux.h,sha256=0rriT5XyxPb0GqsSSn_bP9iQsnjsPbBmu0yqo5goSyQ,3815
+greenlet/platform/switch_ppc_aix.h,sha256=pHA4slEjUFP3J3SYm1TAlNPhgb2G_PAtax5cO8BEe1A,2941
+greenlet/platform/switch_ppc_linux.h,sha256=YwrlKUzxlXuiKMQqr6MFAV1bPzWnmvk6X1AqJZEpOWU,2759
+greenlet/platform/switch_ppc_macosx.h,sha256=L8sB0c00V4G2_5cQCG3zX-23DKq3le_Dcj0sUDcACos,2624
+greenlet/platform/switch_ppc_unix.h,sha256=POy4bRBcH74Chfw4viFE9bVlZ-7BaNsFC0NnXr1L2tg,2652
+greenlet/platform/switch_riscv_unix.h,sha256=jX3vC_xZXiUho8tz4J6Ai8BNQB80yLn03fxkoMztVCU,740
+greenlet/platform/switch_s390_unix.h,sha256=RRlGu957ybmq95qNNY4Qw1mcaoT3eBnW5KbVwu48KX8,2763
+greenlet/platform/switch_sparc_sun_gcc.h,sha256=xZish9GsMHBienUbUMsX1-ZZ-as7hs36sVhYIE3ew8Y,2797
+greenlet/platform/switch_x32_unix.h,sha256=nM98PKtzTWc1lcM7TRMUZJzskVdR1C69U1UqZRWX0GE,1509
+greenlet/platform/switch_x64_masm.asm,sha256=nu6n2sWyXuXfpPx40d9YmLfHXUc1sHgeTvX1kUzuvEM,1841
+greenlet/platform/switch_x64_masm.obj,sha256=GNtTNxYdo7idFUYsQv-mrXWgyT5EJ93-9q90lN6svtQ,1078
+greenlet/platform/switch_x64_msvc.h,sha256=LIeasyKo_vHzspdMzMHbosRhrBfKI4BkQOh4qcTHyJw,1805
+greenlet/platform/switch_x86_msvc.h,sha256=TtGOwinbFfnn6clxMNkCz8i6OmgB6kVRrShoF5iT9to,12838
+greenlet/platform/switch_x86_unix.h,sha256=VplW9H0FF0cZHw1DhJdIUs5q6YLS4cwb2nYwjF83R1s,3059
+greenlet/slp_platformselect.h,sha256=JEnia_2HsTwdqvnnEsDxHQqalYvFJqx_CDsqvNUQYe8,3600
+greenlet/tests/__init__.py,sha256=F282jaIavKrhsYgHJEXtIQXKHdHpe9OJOPTK7R40JzI,9022
+greenlet/tests/__pycache__/__init__.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_clearing_run_switches.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_cpp_exception.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_initialstub_already_started.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_slp_switch.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_switch_three_greenlets.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_switch_three_greenlets2.cpython-312.pyc,,
+greenlet/tests/__pycache__/fail_switch_two_greenlets.cpython-312.pyc,,
+greenlet/tests/__pycache__/leakcheck.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_contextvars.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_cpp.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_extension_interface.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_gc.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_generator.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_generator_nested.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_greenlet.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_greenlet_trash.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_leaks.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_stack_saved.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_throw.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_tracing.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_version.cpython-312.pyc,,
+greenlet/tests/__pycache__/test_weakref.cpython-312.pyc,,
+greenlet/tests/_test_extension.c,sha256=vkeGA-6oeJcGILsD7oIrT1qZop2GaTOHXiNT7mcSl-0,5773
+greenlet/tests/_test_extension.cp312-win_amd64.pyd,sha256=z48TrtIKsg0QDnylgdNtW5Sr4nRDHk0_yAMLTHRguR4,13824
+greenlet/tests/_test_extension_cpp.cp312-win_amd64.pyd,sha256=WgEStnXbfa5YB0yScY0LlUjz-M2aySkp6q8EVRMlWTo,15872
+greenlet/tests/_test_extension_cpp.cpp,sha256=e0kVnaB8CCaEhE9yHtNyfqTjevsPDKKx-zgxk7PPK48,6565
+greenlet/tests/fail_clearing_run_switches.py,sha256=o433oA_nUCtOPaMEGc8VEhZIKa71imVHXFw7TsXaP8M,1263
+greenlet/tests/fail_cpp_exception.py,sha256=o_ZbipWikok8Bjc-vjiQvcb5FHh2nVW-McGKMLcMzh0,985
+greenlet/tests/fail_initialstub_already_started.py,sha256=txENn5IyzGx2p-XR1XB7qXmC8JX_4mKDEA8kYBXUQKc,1961
+greenlet/tests/fail_slp_switch.py,sha256=rJBZcZfTWR3e2ERQtPAud6YKShiDsP84PmwOJbp4ey0,524
+greenlet/tests/fail_switch_three_greenlets.py,sha256=zSitV7rkNnaoHYVzAGGLnxz-yPtohXJJzaE8ehFDQ0M,956
+greenlet/tests/fail_switch_three_greenlets2.py,sha256=FPJensn2EJxoropl03JSTVP3kgP33k04h6aDWWozrOk,1285
+greenlet/tests/fail_switch_two_greenlets.py,sha256=1CaI8s3504VbbF1vj1uBYuy-zxBHVzHPIAd1LIc8ONg,817
+greenlet/tests/leakcheck.py,sha256=inbfM7_oVzd8jIKGxCgo4JqpFZaDAnWPkSULJ8vIE1s,11964
+greenlet/tests/test_contextvars.py,sha256=0n5pR_lbpAppc5wFfK0e1SwYLM-fsSFp72B5_ArLPGE,10348
+greenlet/tests/test_cpp.py,sha256=hpxhFAdKJTpAVZP8CBGs1ZcrKdscI9BaDZk4btkI5d4,2736
+greenlet/tests/test_extension_interface.py,sha256=eJ3cwLacdK2WbsrC-4DgeyHdwLRcG4zx7rrkRtqSzC4,3829
+greenlet/tests/test_gc.py,sha256=PCOaRpIyjNnNlDogGL3FZU_lrdXuM-pv1rxeE5TP5mc,2923
+greenlet/tests/test_generator.py,sha256=tONXiTf98VGm347o1b-810daPiwdla5cbpFg6QI1R1g,1240
+greenlet/tests/test_generator_nested.py,sha256=7v4HOYrf1XZP39dk5IUMubdZ8yc3ynwZcqj9GUJyMSA,3718
+greenlet/tests/test_greenlet.py,sha256=95qgDR-xtB0jzEFLirNx7HPUdwHikVMvDdyUoCvyjOo,45354
+greenlet/tests/test_greenlet_trash.py,sha256=P6r-3K4fmXX8foW8BVgthuqVKjicHMDvxfK7Al4x028,7508
+greenlet/tests/test_leaks.py,sha256=wskLqCAvqZ3qTZkam_wXzd-E5zelUjlXS5Ss8KshtZY,17465
+greenlet/tests/test_stack_saved.py,sha256=eyzqNY2VCGuGlxhT_In6TvZ6Okb0AXFZVyBEnK1jDwA,446
+greenlet/tests/test_throw.py,sha256=u2TQ_WvvCd6N6JdXWIxVEcXkKu5fepDlz9dktYdmtng,3712
+greenlet/tests/test_tracing.py,sha256=VlwzMU0C1noospZhuUMyB7MHw200emIvGCN_6G2p2ZU,8250
+greenlet/tests/test_version.py,sha256=O9DpAITsOFgiRcjd4odQ7ejmwx_N9Q1zQENVcbtFHIc,1339
+greenlet/tests/test_weakref.py,sha256=F8M23btEF87bIbpptLNBORosbQqNZGiYeKMqYjWrsak,883
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/REQUESTED b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/WHEEL b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/WHEEL
new file mode 100644
index 0000000..1c1a93d
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.42.0)
+Root-Is-Purelib: false
+Tag: cp312-cp312-win_amd64
+
diff --git a/venv/Lib/site-packages/greenlet-3.0.3.dist-info/top_level.txt b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/top_level.txt
new file mode 100644
index 0000000..46725be
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet-3.0.3.dist-info/top_level.txt
@@ -0,0 +1 @@
+greenlet
diff --git a/venv/Lib/site-packages/greenlet/TBrokenGreenlet.cpp b/venv/Lib/site-packages/greenlet/TBrokenGreenlet.cpp
new file mode 100644
index 0000000..11a3bea
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TBrokenGreenlet.cpp
@@ -0,0 +1,45 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of greenlet::UserGreenlet.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+
+#include "greenlet_greenlet.hpp"
+
+namespace greenlet {
+
+void* BrokenGreenlet::operator new(size_t UNUSED(count))
+{
+ return allocator.allocate(1);
+}
+
+
+void BrokenGreenlet::operator delete(void* ptr)
+{
+ return allocator.deallocate(static_cast(ptr),
+ 1);
+}
+
+greenlet::PythonAllocator greenlet::BrokenGreenlet::allocator;
+
+bool
+BrokenGreenlet::force_slp_switch_error() const noexcept
+{
+ return this->_force_slp_switch_error;
+}
+
+UserGreenlet::switchstack_result_t BrokenGreenlet::g_switchstack(void)
+{
+ if (this->_force_switch_error) {
+ return switchstack_result_t(-1);
+ }
+ return UserGreenlet::g_switchstack();
+}
+
+}; //namespace greenlet
diff --git a/venv/Lib/site-packages/greenlet/TExceptionState.cpp b/venv/Lib/site-packages/greenlet/TExceptionState.cpp
new file mode 100644
index 0000000..ee6b191
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TExceptionState.cpp
@@ -0,0 +1,62 @@
+#ifndef GREENLET_EXCEPTION_STATE_CPP
+#define GREENLET_EXCEPTION_STATE_CPP
+
+#include
+#include "greenlet_greenlet.hpp"
+
+namespace greenlet {
+
+
+ExceptionState::ExceptionState()
+{
+ this->clear();
+}
+
+void ExceptionState::operator<<(const PyThreadState *const tstate) noexcept
+{
+ this->exc_info = tstate->exc_info;
+ this->exc_state = tstate->exc_state;
+}
+
+void ExceptionState::operator>>(PyThreadState *const tstate) noexcept
+{
+ tstate->exc_state = this->exc_state;
+ tstate->exc_info =
+ this->exc_info ? this->exc_info : &tstate->exc_state;
+ this->clear();
+}
+
+void ExceptionState::clear() noexcept
+{
+ this->exc_info = nullptr;
+ this->exc_state.exc_value = nullptr;
+#if !GREENLET_PY311
+ this->exc_state.exc_type = nullptr;
+ this->exc_state.exc_traceback = nullptr;
+#endif
+ this->exc_state.previous_item = nullptr;
+}
+
+int ExceptionState::tp_traverse(visitproc visit, void* arg) noexcept
+{
+ Py_VISIT(this->exc_state.exc_value);
+#if !GREENLET_PY311
+ Py_VISIT(this->exc_state.exc_type);
+ Py_VISIT(this->exc_state.exc_traceback);
+#endif
+ return 0;
+}
+
+void ExceptionState::tp_clear() noexcept
+{
+ Py_CLEAR(this->exc_state.exc_value);
+#if !GREENLET_PY311
+ Py_CLEAR(this->exc_state.exc_type);
+ Py_CLEAR(this->exc_state.exc_traceback);
+#endif
+}
+
+
+}; // namespace greenlet
+
+#endif // GREENLET_EXCEPTION_STATE_CPP
diff --git a/venv/Lib/site-packages/greenlet/TGreenlet.cpp b/venv/Lib/site-packages/greenlet/TGreenlet.cpp
new file mode 100644
index 0000000..51f8995
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TGreenlet.cpp
@@ -0,0 +1,714 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of greenlet::Greenlet.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+
+#include "greenlet_internal.hpp"
+#include "greenlet_greenlet.hpp"
+#include "greenlet_thread_state.hpp"
+
+#include "TGreenletGlobals.cpp"
+#include "TThreadStateDestroy.cpp"
+
+namespace greenlet {
+
+Greenlet::Greenlet(PyGreenlet* p)
+{
+ p ->pimpl = this;
+}
+
+Greenlet::~Greenlet()
+{
+ // XXX: Can't do this. tp_clear is a virtual function, and by the
+ // time we're here, we've sliced off our child classes.
+ //this->tp_clear();
+}
+
+Greenlet::Greenlet(PyGreenlet* p, const StackState& initial_stack)
+ : stack_state(initial_stack)
+{
+ // can't use a delegating constructor because of
+ // MSVC for Python 2.7
+ p->pimpl = this;
+}
+
+bool
+Greenlet::force_slp_switch_error() const noexcept
+{
+ return false;
+}
+
+void
+Greenlet::release_args()
+{
+ this->switch_args.CLEAR();
+}
+
+/**
+ * CAUTION: This will allocate memory and may trigger garbage
+ * collection and arbitrary Python code.
+ */
+OwnedObject
+Greenlet::throw_GreenletExit_during_dealloc(const ThreadState& UNUSED(current_thread_state))
+{
+ // If we're killed because we lost all references in the
+ // middle of a switch, that's ok. Don't reset the args/kwargs,
+ // we still want to pass them to the parent.
+ PyErr_SetString(mod_globs->PyExc_GreenletExit,
+ "Killing the greenlet because all references have vanished.");
+ // To get here it had to have run before
+ return this->g_switch();
+}
+
+inline void
+Greenlet::slp_restore_state() noexcept
+{
+#ifdef SLP_BEFORE_RESTORE_STATE
+ SLP_BEFORE_RESTORE_STATE();
+#endif
+ this->stack_state.copy_heap_to_stack(
+ this->thread_state()->borrow_current()->stack_state);
+}
+
+
+inline int
+Greenlet::slp_save_state(char *const stackref) noexcept
+{
+ // XXX: This used to happen in the middle, before saving, but
+ // after finding the next owner. Does that matter? This is
+ // only defined for Sparc/GCC where it flushes register
+ // windows to the stack (I think)
+#ifdef SLP_BEFORE_SAVE_STATE
+ SLP_BEFORE_SAVE_STATE();
+#endif
+ return this->stack_state.copy_stack_to_heap(stackref,
+ this->thread_state()->borrow_current()->stack_state);
+}
+
+/**
+ * CAUTION: This will allocate memory and may trigger garbage
+ * collection and arbitrary Python code.
+ */
+OwnedObject
+Greenlet::on_switchstack_or_initialstub_failure(
+ Greenlet* target,
+ const Greenlet::switchstack_result_t& err,
+ const bool target_was_me,
+ const bool was_initial_stub)
+{
+ // If we get here, either g_initialstub()
+ // failed, or g_switchstack() failed. Either one of those
+ // cases SHOULD leave us in the original greenlet with a valid stack.
+ if (!PyErr_Occurred()) {
+ PyErr_SetString(
+ PyExc_SystemError,
+ was_initial_stub
+ ? "Failed to switch stacks into a greenlet for the first time."
+ : "Failed to switch stacks into a running greenlet.");
+ }
+ this->release_args();
+
+ if (target && !target_was_me) {
+ target->murder_in_place();
+ }
+
+ assert(!err.the_new_current_greenlet);
+ assert(!err.origin_greenlet);
+ return OwnedObject();
+
+}
+
+OwnedGreenlet
+Greenlet::g_switchstack_success() noexcept
+{
+ PyThreadState* tstate = PyThreadState_GET();
+ // restore the saved state
+ this->python_state >> tstate;
+ this->exception_state >> tstate;
+
+ // The thread state hasn't been changed yet.
+ ThreadState* thread_state = this->thread_state();
+ OwnedGreenlet result(thread_state->get_current());
+ thread_state->set_current(this->self());
+ //assert(thread_state->borrow_current().borrow() == this->_self);
+ return result;
+}
+
+Greenlet::switchstack_result_t
+Greenlet::g_switchstack(void)
+{
+ // if any of these assertions fail, it's likely because we
+ // switched away and tried to switch back to us. Early stages of
+ // switching are not reentrant because we re-use ``this->args()``.
+ // Switching away would happen if we trigger a garbage collection
+ // (by just using some Python APIs that happen to allocate Python
+ // objects) and some garbage had weakref callbacks or __del__ that
+ // switches (people don't write code like that by hand, but with
+ // gevent it's possible without realizing it)
+ assert(this->args() || PyErr_Occurred());
+ { /* save state */
+ if (this->thread_state()->is_current(this->self())) {
+ // Hmm, nothing to do.
+ // TODO: Does this bypass trace events that are
+ // important?
+ return switchstack_result_t(0,
+ this, this->thread_state()->borrow_current());
+ }
+ BorrowedGreenlet current = this->thread_state()->borrow_current();
+ PyThreadState* tstate = PyThreadState_GET();
+
+ current->python_state << tstate;
+ current->exception_state << tstate;
+ this->python_state.will_switch_from(tstate);
+ switching_thread_state = this;
+ current->expose_frames();
+ }
+ assert(this->args() || PyErr_Occurred());
+ // If this is the first switch into a greenlet, this will
+ // return twice, once with 1 in the new greenlet, once with 0
+ // in the origin.
+ int err;
+ if (this->force_slp_switch_error()) {
+ err = -1;
+ }
+ else {
+ err = slp_switch();
+ }
+
+ if (err < 0) { /* error */
+ // Tested by
+ // test_greenlet.TestBrokenGreenlets.test_failed_to_slp_switch_into_running
+ //
+ // It's not clear if it's worth trying to clean up and
+ // continue here. Failing to switch stacks is a big deal which
+ // may not be recoverable (who knows what state the stack is in).
+ // Also, we've stolen references in preparation for calling
+ // ``g_switchstack_success()`` and we don't have a clean
+ // mechanism for backing that all out.
+ Py_FatalError("greenlet: Failed low-level slp_switch(). The stack is probably corrupt.");
+ }
+
+ // No stack-based variables are valid anymore.
+
+ // But the global is volatile so we can reload it without the
+ // compiler caching it from earlier.
+ Greenlet* greenlet_that_switched_in = switching_thread_state; // aka this
+ switching_thread_state = nullptr;
+ // except that no stack variables are valid, we would:
+ // assert(this == greenlet_that_switched_in);
+
+ // switchstack success is where we restore the exception state,
+ // etc. It returns the origin greenlet because its convenient.
+
+ OwnedGreenlet origin = greenlet_that_switched_in->g_switchstack_success();
+ assert(greenlet_that_switched_in->args() || PyErr_Occurred());
+ return switchstack_result_t(err, greenlet_that_switched_in, origin);
+}
+
+
+inline void
+Greenlet::check_switch_allowed() const
+{
+ // TODO: Make this take a parameter of the current greenlet,
+ // or current main greenlet, to make the check for
+ // cross-thread switching cheaper. Surely somewhere up the
+ // call stack we've already accessed the thread local variable.
+
+ // We expect to always have a main greenlet now; accessing the thread state
+ // created it. However, if we get here and cleanup has already
+ // begun because we're a greenlet that was running in a
+ // (now dead) thread, these invariants will not hold true. In
+ // fact, accessing `this->thread_state` may not even be possible.
+
+ // If the thread this greenlet was running in is dead,
+ // we'll still have a reference to a main greenlet, but the
+ // thread state pointer we have is bogus.
+ // TODO: Give the objects an API to determine if they belong
+ // to a dead thread.
+
+ const BorrowedMainGreenlet main_greenlet = this->find_main_greenlet_in_lineage();
+
+ if (!main_greenlet) {
+ throw PyErrOccurred(mod_globs->PyExc_GreenletError,
+ "cannot switch to a garbage collected greenlet");
+ }
+
+ if (!main_greenlet->thread_state()) {
+ throw PyErrOccurred(mod_globs->PyExc_GreenletError,
+ "cannot switch to a different thread (which happens to have exited)");
+ }
+
+ // The main greenlet we found was from the .parent lineage.
+ // That may or may not have any relationship to the main
+ // greenlet of the running thread. We can't actually access
+ // our this->thread_state members to try to check that,
+ // because it could be in the process of getting destroyed,
+ // but setting the main_greenlet->thread_state member to NULL
+ // may not be visible yet. So we need to check against the
+ // current thread state (once the cheaper checks are out of
+ // the way)
+ const BorrowedMainGreenlet current_main_greenlet = GET_THREAD_STATE().state().borrow_main_greenlet();
+ if (
+ // lineage main greenlet is not this thread's greenlet
+ current_main_greenlet != main_greenlet
+ || (
+ // atteched to some thread
+ this->main_greenlet()
+ // XXX: Same condition as above. Was this supposed to be
+ // this->main_greenlet()?
+ && current_main_greenlet != main_greenlet)
+ // switching into a known dead thread (XXX: which, if we get here,
+ // is bad, because we just accessed the thread state, which is
+ // gone!)
+ || (!current_main_greenlet->thread_state())) {
+ // CAUTION: This may trigger memory allocations, gc, and
+ // arbitrary Python code.
+ throw PyErrOccurred(mod_globs->PyExc_GreenletError,
+ "cannot switch to a different thread");
+ }
+}
+
+const OwnedObject
+Greenlet::context() const
+{
+ using greenlet::PythonStateContext;
+ OwnedObject result;
+
+ if (this->is_currently_running_in_some_thread()) {
+ /* Currently running greenlet: context is stored in the thread state,
+ not the greenlet object. */
+ if (GET_THREAD_STATE().state().is_current(this->self())) {
+ result = PythonStateContext::context(PyThreadState_GET());
+ }
+ else {
+ throw ValueError(
+ "cannot get context of a "
+ "greenlet that is running in a different thread");
+ }
+ }
+ else {
+ /* Greenlet is not running: just return context. */
+ result = this->python_state.context();
+ }
+ if (!result) {
+ result = OwnedObject::None();
+ }
+ return result;
+}
+
+
+void
+Greenlet::context(BorrowedObject given)
+{
+ using greenlet::PythonStateContext;
+ if (!given) {
+ throw AttributeError("can't delete context attribute");
+ }
+ if (given.is_None()) {
+ /* "Empty context" is stored as NULL, not None. */
+ given = nullptr;
+ }
+
+ //checks type, incrs refcnt
+ greenlet::refs::OwnedContext context(given);
+ PyThreadState* tstate = PyThreadState_GET();
+
+ if (this->is_currently_running_in_some_thread()) {
+ if (!GET_THREAD_STATE().state().is_current(this->self())) {
+ throw ValueError("cannot set context of a greenlet"
+ " that is running in a different thread");
+ }
+
+ /* Currently running greenlet: context is stored in the thread state,
+ not the greenlet object. */
+ OwnedObject octx = OwnedObject::consuming(PythonStateContext::context(tstate));
+ PythonStateContext::context(tstate, context.relinquish_ownership());
+ }
+ else {
+ /* Greenlet is not running: just set context. Note that the
+ greenlet may be dead.*/
+ this->python_state.context() = context;
+ }
+}
+
+/**
+ * CAUTION: May invoke arbitrary Python code.
+ *
+ * Figure out what the result of ``greenlet.switch(arg, kwargs)``
+ * should be and transfers ownership of it to the left-hand-side.
+ *
+ * If switch() was just passed an arg tuple, then we'll just return that.
+ * If only keyword arguments were passed, then we'll pass the keyword
+ * argument dict. Otherwise, we'll create a tuple of (args, kwargs) and
+ * return both.
+ *
+ * CAUTION: This may allocate a new tuple object, which may
+ * cause the Python garbage collector to run, which in turn may
+ * run arbitrary Python code that switches.
+ */
+OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept
+{
+ // Because this may invoke arbitrary Python code, which could
+ // result in switching back to us, we need to get the
+ // arguments locally on the stack.
+ assert(rhs);
+ OwnedObject args = rhs.args();
+ OwnedObject kwargs = rhs.kwargs();
+ rhs.CLEAR();
+ // We shouldn't be called twice for the same switch.
+ assert(args || kwargs);
+ assert(!rhs);
+
+ if (!kwargs) {
+ lhs = args;
+ }
+ else if (!PyDict_Size(kwargs.borrow())) {
+ lhs = args;
+ }
+ else if (!PySequence_Length(args.borrow())) {
+ lhs = kwargs;
+ }
+ else {
+ // PyTuple_Pack allocates memory, may GC, may run arbitrary
+ // Python code.
+ lhs = OwnedObject::consuming(PyTuple_Pack(2, args.borrow(), kwargs.borrow()));
+ }
+ return lhs;
+}
+
+static OwnedObject
+g_handle_exit(const OwnedObject& greenlet_result)
+{
+ if (!greenlet_result && mod_globs->PyExc_GreenletExit.PyExceptionMatches()) {
+ /* catch and ignore GreenletExit */
+ PyErrFetchParam val;
+ PyErr_Fetch(PyErrFetchParam(), val, PyErrFetchParam());
+ if (!val) {
+ return OwnedObject::None();
+ }
+ return OwnedObject(val);
+ }
+
+ if (greenlet_result) {
+ // package the result into a 1-tuple
+ // PyTuple_Pack increments the reference of its arguments,
+ // so we always need to decref the greenlet result;
+ // the owner will do that.
+ return OwnedObject::consuming(PyTuple_Pack(1, greenlet_result.borrow()));
+ }
+
+ return OwnedObject();
+}
+
+
+
+/**
+ * May run arbitrary Python code.
+ */
+OwnedObject
+Greenlet::g_switch_finish(const switchstack_result_t& err)
+{
+ assert(err.the_new_current_greenlet == this);
+
+ ThreadState& state = *this->thread_state();
+ // Because calling the trace function could do arbitrary things,
+ // including switching away from this greenlet and then maybe
+ // switching back, we need to capture the arguments now so that
+ // they don't change.
+ OwnedObject result;
+ if (this->args()) {
+ result <<= this->args();
+ }
+ else {
+ assert(PyErr_Occurred());
+ }
+ assert(!this->args());
+ try {
+ // Our only caller handles the bad error case
+ assert(err.status >= 0);
+ assert(state.borrow_current() == this->self());
+ if (OwnedObject tracefunc = state.get_tracefunc()) {
+ assert(result || PyErr_Occurred());
+ g_calltrace(tracefunc,
+ result ? mod_globs->event_switch : mod_globs->event_throw,
+ err.origin_greenlet,
+ this->self());
+ }
+ // The above could have invoked arbitrary Python code, but
+ // it couldn't switch back to this object and *also*
+ // throw an exception, so the args won't have changed.
+
+ if (PyErr_Occurred()) {
+ // We get here if we fell of the end of the run() function
+ // raising an exception. The switch itself was
+ // successful, but the function raised.
+ // valgrind reports that memory allocated here can still
+ // be reached after a test run.
+ throw PyErrOccurred::from_current();
+ }
+ return result;
+ }
+ catch (const PyErrOccurred&) {
+ /* Turn switch errors into switch throws */
+ /* Turn trace errors into switch throws */
+ this->release_args();
+ throw;
+ }
+}
+
+void
+Greenlet::g_calltrace(const OwnedObject& tracefunc,
+ const greenlet::refs::ImmortalEventName& event,
+ const BorrowedGreenlet& origin,
+ const BorrowedGreenlet& target)
+{
+ PyErrPieces saved_exc;
+ try {
+ TracingGuard tracing_guard;
+ // TODO: We have saved the active exception (if any) that's
+ // about to be raised. In the 'throw' case, we could provide
+ // the exception to the tracefunction, which seems very helpful.
+ tracing_guard.CallTraceFunction(tracefunc, event, origin, target);
+ }
+ catch (const PyErrOccurred&) {
+ // In case of exceptions trace function is removed,
+ // and any existing exception is replaced with the tracing
+ // exception.
+ GET_THREAD_STATE().state().set_tracefunc(Py_None);
+ throw;
+ }
+
+ saved_exc.PyErrRestore();
+ assert(
+ (event == mod_globs->event_throw && PyErr_Occurred())
+ || (event == mod_globs->event_switch && !PyErr_Occurred())
+ );
+}
+
+void
+Greenlet::murder_in_place()
+{
+ if (this->active()) {
+ assert(!this->is_currently_running_in_some_thread());
+ this->deactivate_and_free();
+ }
+}
+
+inline void
+Greenlet::deactivate_and_free()
+{
+ if (!this->active()) {
+ return;
+ }
+ // Throw away any saved stack.
+ this->stack_state = StackState();
+ assert(!this->stack_state.active());
+ // Throw away any Python references.
+ // We're holding a borrowed reference to the last
+ // frame we executed. Since we borrowed it, the
+ // normal traversal, clear, and dealloc functions
+ // ignore it, meaning it leaks. (The thread state
+ // object can't find it to clear it when that's
+ // deallocated either, because by definition if we
+ // got an object on this list, it wasn't
+ // running and the thread state doesn't have
+ // this frame.)
+ // So here, we *do* clear it.
+ this->python_state.tp_clear(true);
+}
+
+bool
+Greenlet::belongs_to_thread(const ThreadState* thread_state) const
+{
+ if (!this->thread_state() // not running anywhere, or thread
+ // exited
+ || !thread_state) { // same, or there is no thread state.
+ return false;
+ }
+ return true;
+}
+
+
+void
+Greenlet::deallocing_greenlet_in_thread(const ThreadState* current_thread_state)
+{
+ /* Cannot raise an exception to kill the greenlet if
+ it is not running in the same thread! */
+ if (this->belongs_to_thread(current_thread_state)) {
+ assert(current_thread_state);
+ // To get here it had to have run before
+ /* Send the greenlet a GreenletExit exception. */
+
+ // We don't care about the return value, only whether an
+ // exception happened.
+ this->throw_GreenletExit_during_dealloc(*current_thread_state);
+ return;
+ }
+
+ // Not the same thread! Temporarily save the greenlet
+ // into its thread's deleteme list, *if* it exists.
+ // If that thread has already exited, and processed its pending
+ // cleanup, we'll never be able to clean everything up: we won't
+ // be able to raise an exception.
+ // That's mostly OK! Since we can't add it to a list, our refcount
+ // won't increase, and we'll go ahead with the DECREFs later.
+ ThreadState *const thread_state = this->thread_state();
+ if (thread_state) {
+ thread_state->delete_when_thread_running(this->self());
+ }
+ else {
+ // The thread is dead, we can't raise an exception.
+ // We need to make it look non-active, though, so that dealloc
+ // finishes killing it.
+ this->deactivate_and_free();
+ }
+ return;
+}
+
+
+int
+Greenlet::tp_traverse(visitproc visit, void* arg)
+{
+
+ int result;
+ if ((result = this->exception_state.tp_traverse(visit, arg)) != 0) {
+ return result;
+ }
+ //XXX: This is ugly. But so is handling everything having to do
+ //with the top frame.
+ bool visit_top_frame = this->was_running_in_dead_thread();
+ // When true, the thread is dead. Our implicit weak reference to the
+ // frame is now all that's left; we consider ourselves to
+ // strongly own it now.
+ if ((result = this->python_state.tp_traverse(visit, arg, visit_top_frame)) != 0) {
+ return result;
+ }
+ return 0;
+}
+
+int
+Greenlet::tp_clear()
+{
+ bool own_top_frame = this->was_running_in_dead_thread();
+ this->exception_state.tp_clear();
+ this->python_state.tp_clear(own_top_frame);
+ return 0;
+}
+
+bool Greenlet::is_currently_running_in_some_thread() const
+{
+ return this->stack_state.active() && !this->python_state.top_frame();
+}
+
+#if GREENLET_PY312
+void GREENLET_NOINLINE(Greenlet::expose_frames)()
+{
+ if (!this->python_state.top_frame()) {
+ return;
+ }
+
+ _PyInterpreterFrame* last_complete_iframe = nullptr;
+ _PyInterpreterFrame* iframe = this->python_state.top_frame()->f_frame;
+ while (iframe) {
+ // We must make a copy before looking at the iframe contents,
+ // since iframe might point to a portion of the greenlet's C stack
+ // that was spilled when switching greenlets.
+ _PyInterpreterFrame iframe_copy;
+ this->stack_state.copy_from_stack(&iframe_copy, iframe, sizeof(*iframe));
+ if (!_PyFrame_IsIncomplete(&iframe_copy)) {
+ // If the iframe were OWNED_BY_CSTACK then it would always be
+ // incomplete. Since it's not incomplete, it's not on the C stack
+ // and we can access it through the original `iframe` pointer
+ // directly. This is important since GetFrameObject might
+ // lazily _create_ the frame object and we don't want the
+ // interpreter to lose track of it.
+ assert(iframe_copy.owner != FRAME_OWNED_BY_CSTACK);
+
+ // We really want to just write:
+ // PyFrameObject* frame = _PyFrame_GetFrameObject(iframe);
+ // but _PyFrame_GetFrameObject calls _PyFrame_MakeAndSetFrameObject
+ // which is not a visible symbol in libpython. The easiest
+ // way to get a public function to call it is using
+ // PyFrame_GetBack, which is defined as follows:
+ // assert(frame != NULL);
+ // assert(!_PyFrame_IsIncomplete(frame->f_frame));
+ // PyFrameObject *back = frame->f_back;
+ // if (back == NULL) {
+ // _PyInterpreterFrame *prev = frame->f_frame->previous;
+ // prev = _PyFrame_GetFirstComplete(prev);
+ // if (prev) {
+ // back = _PyFrame_GetFrameObject(prev);
+ // }
+ // }
+ // return (PyFrameObject*)Py_XNewRef(back);
+ if (!iframe->frame_obj) {
+ PyFrameObject dummy_frame;
+ _PyInterpreterFrame dummy_iframe;
+ dummy_frame.f_back = nullptr;
+ dummy_frame.f_frame = &dummy_iframe;
+ // force the iframe to be considered complete without
+ // needing to check its code object:
+ dummy_iframe.owner = FRAME_OWNED_BY_GENERATOR;
+ dummy_iframe.previous = iframe;
+ assert(!_PyFrame_IsIncomplete(&dummy_iframe));
+ // Drop the returned reference immediately; the iframe
+ // continues to hold a strong reference
+ Py_XDECREF(PyFrame_GetBack(&dummy_frame));
+ assert(iframe->frame_obj);
+ }
+
+ // This is a complete frame, so make the last one of those we saw
+ // point at it, bypassing any incomplete frames (which may have
+ // been on the C stack) in between the two. We're overwriting
+ // last_complete_iframe->previous and need that to be reversible,
+ // so we store the original previous ptr in the frame object
+ // (which we must have created on a previous iteration through
+ // this loop). The frame object has a bunch of storage that is
+ // only used when its iframe is OWNED_BY_FRAME_OBJECT, which only
+ // occurs when the frame object outlives the frame's execution,
+ // which can't have happened yet because the frame is currently
+ // executing as far as the interpreter is concerned. So, we can
+ // reuse it for our own purposes.
+ assert(iframe->owner == FRAME_OWNED_BY_THREAD
+ || iframe->owner == FRAME_OWNED_BY_GENERATOR);
+ if (last_complete_iframe) {
+ assert(last_complete_iframe->frame_obj);
+ memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
+ &last_complete_iframe->previous, sizeof(void *));
+ last_complete_iframe->previous = iframe;
+ }
+ last_complete_iframe = iframe;
+ }
+ // Frames that are OWNED_BY_FRAME_OBJECT are linked via the
+ // frame's f_back while all others are linked via the iframe's
+ // previous ptr. Since all the frames we traverse are running
+ // as far as the interpreter is concerned, we don't have to
+ // worry about the OWNED_BY_FRAME_OBJECT case.
+ iframe = iframe_copy.previous;
+ }
+
+ // Give the outermost complete iframe a null previous pointer to
+ // account for any potential incomplete/C-stack iframes between it
+ // and the actual top-of-stack
+ if (last_complete_iframe) {
+ assert(last_complete_iframe->frame_obj);
+ memcpy(&last_complete_iframe->frame_obj->_f_frame_data[0],
+ &last_complete_iframe->previous, sizeof(void *));
+ last_complete_iframe->previous = nullptr;
+ }
+}
+#else
+void Greenlet::expose_frames()
+{
+
+}
+#endif
+
+}; // namespace greenlet
diff --git a/venv/Lib/site-packages/greenlet/TGreenletGlobals.cpp b/venv/Lib/site-packages/greenlet/TGreenletGlobals.cpp
new file mode 100644
index 0000000..c71c963
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TGreenletGlobals.cpp
@@ -0,0 +1,94 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of GreenletGlobals.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+#ifndef T_GREENLET_GLOBALS
+#define T_GREENLET_GLOBALS
+
+#include "greenlet_refs.hpp"
+#include "greenlet_exceptions.hpp"
+#include "greenlet_thread_support.hpp"
+#include "greenlet_thread_state.hpp"
+
+namespace greenlet {
+
+// This encapsulates what were previously module global "constants"
+// established at init time.
+// This is a step towards Python3 style module state that allows
+// reloading.
+//
+// In an earlier iteration of this code, we used placement new to be
+// able to allocate this object statically still, so that references
+// to its members don't incur an extra pointer indirection.
+// But under some scenarios, that could result in crashes at
+// shutdown because apparently the destructor was getting run twice?
+class GreenletGlobals
+{
+
+public:
+ const greenlet::refs::ImmortalEventName event_switch;
+ const greenlet::refs::ImmortalEventName event_throw;
+ const greenlet::refs::ImmortalException PyExc_GreenletError;
+ const greenlet::refs::ImmortalException PyExc_GreenletExit;
+ const greenlet::refs::ImmortalObject empty_tuple;
+ const greenlet::refs::ImmortalObject empty_dict;
+ const greenlet::refs::ImmortalString str_run;
+ Mutex* const thread_states_to_destroy_lock;
+ greenlet::cleanup_queue_t thread_states_to_destroy;
+
+ GreenletGlobals() :
+ event_switch("switch"),
+ event_throw("throw"),
+ PyExc_GreenletError("greenlet.error"),
+ PyExc_GreenletExit("greenlet.GreenletExit", PyExc_BaseException),
+ empty_tuple(Require(PyTuple_New(0))),
+ empty_dict(Require(PyDict_New())),
+ str_run("run"),
+ thread_states_to_destroy_lock(new Mutex())
+ {}
+
+ ~GreenletGlobals()
+ {
+ // This object is (currently) effectively immortal, and not
+ // just because of those placement new tricks; if we try to
+ // deallocate the static object we allocated, and overwrote,
+ // we would be doing so at C++ teardown time, which is after
+ // the final Python GIL is released, and we can't use the API
+ // then.
+ // (The members will still be destructed, but they also don't
+ // do any deallocation.)
+ }
+
+ void queue_to_destroy(ThreadState* ts) const
+ {
+ // we're currently accessed through a static const object,
+ // implicitly marking our members as const, so code can't just
+ // call push_back (or pop_back) without casting away the
+ // const.
+ //
+ // Do that for callers.
+ greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy);
+ q.push_back(ts);
+ }
+
+ ThreadState* take_next_to_destroy() const
+ {
+ greenlet::cleanup_queue_t& q = const_cast(this->thread_states_to_destroy);
+ ThreadState* result = q.back();
+ q.pop_back();
+ return result;
+ }
+};
+
+}; // namespace greenlet
+
+static const greenlet::GreenletGlobals* mod_globs;
+
+#endif // T_GREENLET_GLOBALS
diff --git a/venv/Lib/site-packages/greenlet/TMainGreenlet.cpp b/venv/Lib/site-packages/greenlet/TMainGreenlet.cpp
new file mode 100644
index 0000000..c33aadb
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TMainGreenlet.cpp
@@ -0,0 +1,155 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of greenlet::MainGreenlet.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+
+#include "greenlet_greenlet.hpp"
+#include "greenlet_thread_state.hpp"
+
+
+// Protected by the GIL. Incremented when we create a main greenlet,
+// in a new thread, decremented when it is destroyed.
+static Py_ssize_t G_TOTAL_MAIN_GREENLETS;
+
+namespace greenlet {
+greenlet::PythonAllocator MainGreenlet::allocator;
+
+void* MainGreenlet::operator new(size_t UNUSED(count))
+{
+ return allocator.allocate(1);
+}
+
+
+void MainGreenlet::operator delete(void* ptr)
+{
+ return allocator.deallocate(static_cast(ptr),
+ 1);
+}
+
+
+MainGreenlet::MainGreenlet(PyGreenlet* p, ThreadState* state)
+ : Greenlet(p, StackState::make_main()),
+ _self(p),
+ _thread_state(state)
+{
+ G_TOTAL_MAIN_GREENLETS++;
+}
+
+MainGreenlet::~MainGreenlet()
+{
+ G_TOTAL_MAIN_GREENLETS--;
+ this->tp_clear();
+}
+
+ThreadState*
+MainGreenlet::thread_state() const noexcept
+{
+ return this->_thread_state;
+}
+
+void
+MainGreenlet::thread_state(ThreadState* t) noexcept
+{
+ assert(!t);
+ this->_thread_state = t;
+}
+
+BorrowedGreenlet
+MainGreenlet::self() const noexcept
+{
+ return BorrowedGreenlet(this->_self.borrow());
+}
+
+
+const BorrowedMainGreenlet
+MainGreenlet::main_greenlet() const
+{
+ return this->_self;
+}
+
+BorrowedMainGreenlet
+MainGreenlet::find_main_greenlet_in_lineage() const
+{
+ return BorrowedMainGreenlet(this->_self);
+}
+
+bool
+MainGreenlet::was_running_in_dead_thread() const noexcept
+{
+ return !this->_thread_state;
+}
+
+OwnedObject
+MainGreenlet::g_switch()
+{
+ try {
+ this->check_switch_allowed();
+ }
+ catch (const PyErrOccurred&) {
+ this->release_args();
+ throw;
+ }
+
+ switchstack_result_t err = this->g_switchstack();
+ if (err.status < 0) {
+ // XXX: This code path is untested, but it is shared
+ // with the UserGreenlet path that is tested.
+ return this->on_switchstack_or_initialstub_failure(
+ this,
+ err,
+ true, // target was me
+ false // was initial stub
+ );
+ }
+
+ return err.the_new_current_greenlet->g_switch_finish(err);
+}
+
+int
+MainGreenlet::tp_traverse(visitproc visit, void* arg)
+{
+ if (this->_thread_state) {
+ // we've already traversed main, (self), don't do it again.
+ int result = this->_thread_state->tp_traverse(visit, arg, false);
+ if (result) {
+ return result;
+ }
+ }
+ return Greenlet::tp_traverse(visit, arg);
+}
+
+const OwnedObject&
+MainGreenlet::run() const
+{
+ throw AttributeError("Main greenlets do not have a run attribute.");
+}
+
+void
+MainGreenlet::run(const BorrowedObject UNUSED(nrun))
+{
+ throw AttributeError("Main greenlets do not have a run attribute.");
+}
+
+void
+MainGreenlet::parent(const BorrowedObject raw_new_parent)
+{
+ if (!raw_new_parent) {
+ throw AttributeError("can't delete attribute");
+ }
+ throw AttributeError("cannot set the parent of a main greenlet");
+}
+
+const OwnedGreenlet
+MainGreenlet::parent() const
+{
+ return OwnedGreenlet(); // null becomes None
+}
+
+}; // namespace greenlet
diff --git a/venv/Lib/site-packages/greenlet/TPythonState.cpp b/venv/Lib/site-packages/greenlet/TPythonState.cpp
new file mode 100644
index 0000000..465d417
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TPythonState.cpp
@@ -0,0 +1,375 @@
+#ifndef GREENLET_PYTHON_STATE_CPP
+#define GREENLET_PYTHON_STATE_CPP
+
+#include
+#include "greenlet_greenlet.hpp"
+
+namespace greenlet {
+
+PythonState::PythonState()
+ : _top_frame()
+#if GREENLET_USE_CFRAME
+ ,cframe(nullptr)
+ ,use_tracing(0)
+#endif
+#if GREENLET_PY312
+ ,py_recursion_depth(0)
+ ,c_recursion_depth(0)
+#else
+ ,recursion_depth(0)
+#endif
+ ,trash_delete_nesting(0)
+#if GREENLET_PY311
+ ,current_frame(nullptr)
+ ,datastack_chunk(nullptr)
+ ,datastack_top(nullptr)
+ ,datastack_limit(nullptr)
+#endif
+{
+#if GREENLET_USE_CFRAME
+ /*
+ The PyThreadState->cframe pointer usually points to memory on
+ the stack, alloceted in a call into PyEval_EvalFrameDefault.
+
+ Initially, before any evaluation begins, it points to the
+ initial PyThreadState object's ``root_cframe`` object, which is
+ statically allocated for the lifetime of the thread.
+
+ A greenlet can last for longer than a call to
+ PyEval_EvalFrameDefault, so we can't set its ``cframe`` pointer
+ to be the current ``PyThreadState->cframe``; nor could we use
+ one from the greenlet parent for the same reason. Yet a further
+ no: we can't allocate one scoped to the greenlet and then
+ destroy it when the greenlet is deallocated, because inside the
+ interpreter the _PyCFrame objects form a linked list, and that too
+ can result in accessing memory beyond its dynamic lifetime (if
+ the greenlet doesn't actually finish before it dies, its entry
+ could still be in the list).
+
+ Using the ``root_cframe`` is problematic, though, because its
+ members are never modified by the interpreter and are set to 0,
+ meaning that its ``use_tracing`` flag is never updated. We don't
+ want to modify that value in the ``root_cframe`` ourself: it
+ *shouldn't* matter much because we should probably never get
+ back to the point where that's the only cframe on the stack;
+ even if it did matter, the major consequence of an incorrect
+ value for ``use_tracing`` is that if its true the interpreter
+ does some extra work --- however, it's just good code hygiene.
+
+ Our solution: before a greenlet runs, after its initial
+ creation, it uses the ``root_cframe`` just to have something to
+ put there. However, once the greenlet is actually switched to
+ for the first time, ``g_initialstub`` (which doesn't actually
+ "return" while the greenlet is running) stores a new _PyCFrame on
+ its local stack, and copies the appropriate values from the
+ currently running _PyCFrame; this is then made the _PyCFrame for the
+ newly-minted greenlet. ``g_initialstub`` then proceeds to call
+ ``glet.run()``, which results in ``PyEval_...`` adding the
+ _PyCFrame to the list. Switches continue as normal. Finally, when
+ the greenlet finishes, the call to ``glet.run()`` returns and
+ the _PyCFrame is taken out of the linked list and the stack value
+ is now unused and free to expire.
+
+ XXX: I think we can do better. If we're deallocing in the same
+ thread, can't we traverse the list and unlink our frame?
+ Can we just keep a reference to the thread state in case we
+ dealloc in another thread? (Is that even possible if we're still
+ running and haven't returned from g_initialstub?)
+ */
+ this->cframe = &PyThreadState_GET()->root_cframe;
+#endif
+}
+
+
+inline void PythonState::may_switch_away() noexcept
+{
+#if GREENLET_PY311
+ // PyThreadState_GetFrame is probably going to have to allocate a
+ // new frame object. That may trigger garbage collection. Because
+ // we call this during the early phases of a switch (it doesn't
+ // matter to which greenlet, as this has a global effect), if a GC
+ // triggers a switch away, two things can happen, both bad:
+ // - We might not get switched back to, halting forward progress.
+ // this is pathological, but possible.
+ // - We might get switched back to with a different set of
+ // arguments or a throw instead of a switch. That would corrupt
+ // our state (specifically, PyErr_Occurred() and this->args()
+ // would no longer agree).
+ //
+ // Thus, when we call this API, we need to have GC disabled.
+ // This method serves as a bottleneck we call when maybe beginning
+ // a switch. In this way, it is always safe -- no risk of GC -- to
+ // use ``_GetFrame()`` whenever we need to, just as it was in
+ // <=3.10 (because subsequent calls will be cached and not
+ // allocate memory).
+
+ GCDisabledGuard no_gc;
+ Py_XDECREF(PyThreadState_GetFrame(PyThreadState_GET()));
+#endif
+}
+
+void PythonState::operator<<(const PyThreadState *const tstate) noexcept
+{
+ this->_context.steal(tstate->context);
+#if GREENLET_USE_CFRAME
+ /*
+ IMPORTANT: ``cframe`` is a pointer into the STACK. Thus, because
+ the call to ``slp_switch()`` changes the contents of the stack,
+ you cannot read from ``ts_current->cframe`` after that call and
+ necessarily get the same values you get from reading it here.
+ Anything you need to restore from now to then must be saved in a
+ global/threadlocal variable (because we can't use stack
+ variables here either). For things that need to persist across
+ the switch, use `will_switch_from`.
+ */
+ this->cframe = tstate->cframe;
+ #if !GREENLET_PY312
+ this->use_tracing = tstate->cframe->use_tracing;
+ #endif
+#endif // GREENLET_USE_CFRAME
+#if GREENLET_PY311
+ #if GREENLET_PY312
+ this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
+ this->c_recursion_depth = C_RECURSION_LIMIT - tstate->c_recursion_remaining;
+ #else // not 312
+ this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
+ #endif // GREENLET_PY312
+ this->current_frame = tstate->cframe->current_frame;
+ this->datastack_chunk = tstate->datastack_chunk;
+ this->datastack_top = tstate->datastack_top;
+ this->datastack_limit = tstate->datastack_limit;
+
+ PyFrameObject *frame = PyThreadState_GetFrame((PyThreadState *)tstate);
+ Py_XDECREF(frame); // PyThreadState_GetFrame gives us a new
+ // reference.
+ this->_top_frame.steal(frame);
+ #if GREENLET_PY312
+ this->trash_delete_nesting = tstate->trash.delete_nesting;
+ #else // not 312
+ this->trash_delete_nesting = tstate->trash_delete_nesting;
+ #endif // GREENLET_PY312
+#else // Not 311
+ this->recursion_depth = tstate->recursion_depth;
+ this->_top_frame.steal(tstate->frame);
+ this->trash_delete_nesting = tstate->trash_delete_nesting;
+#endif // GREENLET_PY311
+}
+
+#if GREENLET_PY312
+void GREENLET_NOINLINE(PythonState::unexpose_frames)()
+{
+ if (!this->top_frame()) {
+ return;
+ }
+
+ // See GreenletState::expose_frames() and the comment on frames_were_exposed
+ // for more information about this logic.
+ _PyInterpreterFrame *iframe = this->_top_frame->f_frame;
+ while (iframe != nullptr) {
+ _PyInterpreterFrame *prev_exposed = iframe->previous;
+ assert(iframe->frame_obj);
+ memcpy(&iframe->previous, &iframe->frame_obj->_f_frame_data[0],
+ sizeof(void *));
+ iframe = prev_exposed;
+ }
+}
+#else
+void PythonState::unexpose_frames()
+{}
+#endif
+
+void PythonState::operator>>(PyThreadState *const tstate) noexcept
+{
+ tstate->context = this->_context.relinquish_ownership();
+ /* Incrementing this value invalidates the contextvars cache,
+ which would otherwise remain valid across switches */
+ tstate->context_ver++;
+#if GREENLET_USE_CFRAME
+ tstate->cframe = this->cframe;
+ /*
+ If we were tracing, we need to keep tracing.
+ There should never be the possibility of hitting the
+ root_cframe here. See note above about why we can't
+ just copy this from ``origin->cframe->use_tracing``.
+ */
+ #if !GREENLET_PY312
+ tstate->cframe->use_tracing = this->use_tracing;
+ #endif
+#endif // GREENLET_USE_CFRAME
+#if GREENLET_PY311
+ #if GREENLET_PY312
+ tstate->py_recursion_remaining = tstate->py_recursion_limit - this->py_recursion_depth;
+ tstate->c_recursion_remaining = C_RECURSION_LIMIT - this->c_recursion_depth;
+ this->unexpose_frames();
+ #else // \/ 3.11
+ tstate->recursion_remaining = tstate->recursion_limit - this->recursion_depth;
+ #endif // GREENLET_PY312
+ tstate->cframe->current_frame = this->current_frame;
+ tstate->datastack_chunk = this->datastack_chunk;
+ tstate->datastack_top = this->datastack_top;
+ tstate->datastack_limit = this->datastack_limit;
+ this->_top_frame.relinquish_ownership();
+ #if GREENLET_PY312
+ tstate->trash.delete_nesting = this->trash_delete_nesting;
+ #else // not 3.12
+ tstate->trash_delete_nesting = this->trash_delete_nesting;
+ #endif // GREENLET_PY312
+#else // not 3.11
+ tstate->frame = this->_top_frame.relinquish_ownership();
+ tstate->recursion_depth = this->recursion_depth;
+ tstate->trash_delete_nesting = this->trash_delete_nesting;
+#endif // GREENLET_PY311
+}
+
+inline void PythonState::will_switch_from(PyThreadState *const origin_tstate) noexcept
+{
+#if GREENLET_USE_CFRAME && !GREENLET_PY312
+ // The weird thing is, we don't actually save this for an
+ // effect on the current greenlet, it's saved for an
+ // effect on the target greenlet. That is, we want
+ // continuity of this setting across the greenlet switch.
+ this->use_tracing = origin_tstate->cframe->use_tracing;
+#endif
+}
+
+void PythonState::set_initial_state(const PyThreadState* const tstate) noexcept
+{
+ this->_top_frame = nullptr;
+#if GREENLET_PY312
+ this->py_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
+ // XXX: TODO: Comment from a reviewer:
+ // Should this be ``C_RECURSION_LIMIT - tstate->c_recursion_remaining``?
+ // But to me it looks more like that might not be the right
+ // initialization either?
+ this->c_recursion_depth = tstate->py_recursion_limit - tstate->py_recursion_remaining;
+#elif GREENLET_PY311
+ this->recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
+#else
+ this->recursion_depth = tstate->recursion_depth;
+#endif
+}
+// TODO: Better state management about when we own the top frame.
+int PythonState::tp_traverse(visitproc visit, void* arg, bool own_top_frame) noexcept
+{
+ Py_VISIT(this->_context.borrow());
+ if (own_top_frame) {
+ Py_VISIT(this->_top_frame.borrow());
+ }
+ return 0;
+}
+
+void PythonState::tp_clear(bool own_top_frame) noexcept
+{
+ PythonStateContext::tp_clear();
+ // If we get here owning a frame,
+ // we got dealloc'd without being finished. We may or may not be
+ // in the same thread.
+ if (own_top_frame) {
+ this->_top_frame.CLEAR();
+ }
+}
+
+#if GREENLET_USE_CFRAME
+void PythonState::set_new_cframe(_PyCFrame& frame) noexcept
+{
+ frame = *PyThreadState_GET()->cframe;
+ /* Make the target greenlet refer to the stack value. */
+ this->cframe = &frame;
+ /*
+ And restore the link to the previous frame so this one gets
+ unliked appropriately.
+ */
+ this->cframe->previous = &PyThreadState_GET()->root_cframe;
+}
+#endif
+
+const PythonState::OwnedFrame& PythonState::top_frame() const noexcept
+{
+ return this->_top_frame;
+}
+
+void PythonState::did_finish(PyThreadState* tstate) noexcept
+{
+#if GREENLET_PY311
+ // See https://github.com/gevent/gevent/issues/1924 and
+ // https://github.com/python-greenlet/greenlet/issues/328. In
+ // short, Python 3.11 allocates memory for frames as a sort of
+ // linked list that's kept as part of PyThreadState in the
+ // ``datastack_chunk`` member and friends. These are saved and
+ // restored as part of switching greenlets.
+ //
+ // When we initially switch to a greenlet, we set those to NULL.
+ // That causes the frame management code to treat this like a
+ // brand new thread and start a fresh list of chunks, beginning
+ // with a new "root" chunk. As we make calls in this greenlet,
+ // those chunks get added, and as calls return, they get popped.
+ // But the frame code (pystate.c) is careful to make sure that the
+ // root chunk never gets popped.
+ //
+ // Thus, when a greenlet exits for the last time, there will be at
+ // least a single root chunk that we must be responsible for
+ // deallocating.
+ //
+ // The complex part is that these chunks are allocated and freed
+ // using ``_PyObject_VirtualAlloc``/``Free``. Those aren't public
+ // functions, and they aren't exported for linking. It so happens
+ // that we know they are just thin wrappers around the Arena
+ // allocator, so we can use that directly to deallocate in a
+ // compatible way.
+ //
+ // CAUTION: Check this implementation detail on every major version.
+ //
+ // It might be nice to be able to do this in our destructor, but
+ // can we be sure that no one else is using that memory? Plus, as
+ // described below, our pointers may not even be valid anymore. As
+ // a special case, there is one time that we know we can do this,
+ // and that's from the destructor of the associated UserGreenlet
+ // (NOT main greenlet)
+ PyObjectArenaAllocator alloc;
+ _PyStackChunk* chunk = nullptr;
+ if (tstate) {
+ // We really did finish, we can never be switched to again.
+ chunk = tstate->datastack_chunk;
+ // Unfortunately, we can't do much sanity checking. Our
+ // this->datastack_chunk pointer is out of date (evaluation may
+ // have popped down through it already) so we can't verify that
+ // we deallocate it. I don't think we can even check datastack_top
+ // for the same reason.
+
+ PyObject_GetArenaAllocator(&alloc);
+ tstate->datastack_chunk = nullptr;
+ tstate->datastack_limit = nullptr;
+ tstate->datastack_top = nullptr;
+
+ }
+ else if (this->datastack_chunk) {
+ // The UserGreenlet (NOT the main greenlet!) is being deallocated. If we're
+ // still holding a stack chunk, it's garbage because we know
+ // we can never switch back to let cPython clean it up.
+ // Because the last time we got switched away from, and we
+ // haven't run since then, we know our chain is valid and can
+ // be dealloced.
+ chunk = this->datastack_chunk;
+ PyObject_GetArenaAllocator(&alloc);
+ }
+
+ if (alloc.free && chunk) {
+ // In case the arena mechanism has been torn down already.
+ while (chunk) {
+ _PyStackChunk *prev = chunk->previous;
+ chunk->previous = nullptr;
+ alloc.free(alloc.ctx, chunk, chunk->size);
+ chunk = prev;
+ }
+ }
+
+ this->datastack_chunk = nullptr;
+ this->datastack_limit = nullptr;
+ this->datastack_top = nullptr;
+#endif
+}
+
+
+}; // namespace greenlet
+
+#endif // GREENLET_PYTHON_STATE_CPP
diff --git a/venv/Lib/site-packages/greenlet/TStackState.cpp b/venv/Lib/site-packages/greenlet/TStackState.cpp
new file mode 100644
index 0000000..9aab596
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TStackState.cpp
@@ -0,0 +1,265 @@
+#ifndef GREENLET_STACK_STATE_CPP
+#define GREENLET_STACK_STATE_CPP
+
+#include "greenlet_greenlet.hpp"
+
+namespace greenlet {
+
+#ifdef GREENLET_USE_STDIO
+#include
+using std::cerr;
+using std::endl;
+
+std::ostream& operator<<(std::ostream& os, const StackState& s)
+{
+ os << "StackState(stack_start=" << (void*)s._stack_start
+ << ", stack_stop=" << (void*)s.stack_stop
+ << ", stack_copy=" << (void*)s.stack_copy
+ << ", stack_saved=" << s._stack_saved
+ << ", stack_prev=" << s.stack_prev
+ << ", addr=" << &s
+ << ")";
+ return os;
+}
+#endif
+
+StackState::StackState(void* mark, StackState& current)
+ : _stack_start(nullptr),
+ stack_stop((char*)mark),
+ stack_copy(nullptr),
+ _stack_saved(0),
+ /* Skip a dying greenlet */
+ stack_prev(current._stack_start
+ ? ¤t
+ : current.stack_prev)
+{
+}
+
+StackState::StackState()
+ : _stack_start(nullptr),
+ stack_stop(nullptr),
+ stack_copy(nullptr),
+ _stack_saved(0),
+ stack_prev(nullptr)
+{
+}
+
+StackState::StackState(const StackState& other)
+// can't use a delegating constructor because of
+// MSVC for Python 2.7
+ : _stack_start(nullptr),
+ stack_stop(nullptr),
+ stack_copy(nullptr),
+ _stack_saved(0),
+ stack_prev(nullptr)
+{
+ this->operator=(other);
+}
+
+StackState& StackState::operator=(const StackState& other)
+{
+ if (&other == this) {
+ return *this;
+ }
+ if (other._stack_saved) {
+ throw std::runtime_error("Refusing to steal memory.");
+ }
+
+ //If we have memory allocated, dispose of it
+ this->free_stack_copy();
+
+ this->_stack_start = other._stack_start;
+ this->stack_stop = other.stack_stop;
+ this->stack_copy = other.stack_copy;
+ this->_stack_saved = other._stack_saved;
+ this->stack_prev = other.stack_prev;
+ return *this;
+}
+
+inline void StackState::free_stack_copy() noexcept
+{
+ PyMem_Free(this->stack_copy);
+ this->stack_copy = nullptr;
+ this->_stack_saved = 0;
+}
+
+inline void StackState::copy_heap_to_stack(const StackState& current) noexcept
+{
+
+ /* Restore the heap copy back into the C stack */
+ if (this->_stack_saved != 0) {
+ memcpy(this->_stack_start, this->stack_copy, this->_stack_saved);
+ this->free_stack_copy();
+ }
+ StackState* owner = const_cast(¤t);
+ if (!owner->_stack_start) {
+ owner = owner->stack_prev; /* greenlet is dying, skip it */
+ }
+ while (owner && owner->stack_stop <= this->stack_stop) {
+ // cerr << "\tOwner: " << owner << endl;
+ owner = owner->stack_prev; /* find greenlet with more stack */
+ }
+ this->stack_prev = owner;
+ // cerr << "\tFinished with: " << *this << endl;
+}
+
+inline int StackState::copy_stack_to_heap_up_to(const char* const stop) noexcept
+{
+ /* Save more of g's stack into the heap -- at least up to 'stop'
+ g->stack_stop |________|
+ | |
+ | __ stop . . . . .
+ | | ==> . .
+ |________| _______
+ | | | |
+ | | | |
+ g->stack_start | | |_______| g->stack_copy
+ */
+ intptr_t sz1 = this->_stack_saved;
+ intptr_t sz2 = stop - this->_stack_start;
+ assert(this->_stack_start);
+ if (sz2 > sz1) {
+ char* c = (char*)PyMem_Realloc(this->stack_copy, sz2);
+ if (!c) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ memcpy(c + sz1, this->_stack_start + sz1, sz2 - sz1);
+ this->stack_copy = c;
+ this->_stack_saved = sz2;
+ }
+ return 0;
+}
+
+inline int StackState::copy_stack_to_heap(char* const stackref,
+ const StackState& current) noexcept
+{
+ /* must free all the C stack up to target_stop */
+ const char* const target_stop = this->stack_stop;
+
+ StackState* owner = const_cast(¤t);
+ assert(owner->_stack_saved == 0); // everything is present on the stack
+ if (!owner->_stack_start) {
+ owner = owner->stack_prev; /* not saved if dying */
+ }
+ else {
+ owner->_stack_start = stackref;
+ }
+
+ while (owner->stack_stop < target_stop) {
+ /* ts_current is entierely within the area to free */
+ if (owner->copy_stack_to_heap_up_to(owner->stack_stop)) {
+ return -1; /* XXX */
+ }
+ owner = owner->stack_prev;
+ }
+ if (owner != this) {
+ if (owner->copy_stack_to_heap_up_to(target_stop)) {
+ return -1; /* XXX */
+ }
+ }
+ return 0;
+}
+
+inline bool StackState::started() const noexcept
+{
+ return this->stack_stop != nullptr;
+}
+
+inline bool StackState::main() const noexcept
+{
+ return this->stack_stop == (char*)-1;
+}
+
+inline bool StackState::active() const noexcept
+{
+ return this->_stack_start != nullptr;
+}
+
+inline void StackState::set_active() noexcept
+{
+ assert(this->_stack_start == nullptr);
+ this->_stack_start = (char*)1;
+}
+
+inline void StackState::set_inactive() noexcept
+{
+ this->_stack_start = nullptr;
+ // XXX: What if we still have memory out there?
+ // That case is actually triggered by
+ // test_issue251_issue252_explicit_reference_not_collectable (greenlet.tests.test_leaks.TestLeaks)
+ // and
+ // test_issue251_issue252_need_to_collect_in_background
+ // (greenlet.tests.test_leaks.TestLeaks)
+ //
+ // Those objects never get deallocated, so the destructor never
+ // runs.
+ // It *seems* safe to clean up the memory here?
+ if (this->_stack_saved) {
+ this->free_stack_copy();
+ }
+}
+
+inline intptr_t StackState::stack_saved() const noexcept
+{
+ return this->_stack_saved;
+}
+
+inline char* StackState::stack_start() const noexcept
+{
+ return this->_stack_start;
+}
+
+
+inline StackState StackState::make_main() noexcept
+{
+ StackState s;
+ s._stack_start = (char*)1;
+ s.stack_stop = (char*)-1;
+ return s;
+}
+
+StackState::~StackState()
+{
+ if (this->_stack_saved != 0) {
+ this->free_stack_copy();
+ }
+}
+
+void StackState::copy_from_stack(void* vdest, const void* vsrc, size_t n) const
+{
+ char* dest = static_cast(vdest);
+ const char* src = static_cast(vsrc);
+ if (src + n <= this->_stack_start
+ || src >= this->_stack_start + this->_stack_saved
+ || this->_stack_saved == 0) {
+ // Nothing we're copying was spilled from the stack
+ memcpy(dest, src, n);
+ return;
+ }
+
+ if (src < this->_stack_start) {
+ // Copy the part before the saved stack.
+ // We know src + n > _stack_start due to the test above.
+ const size_t nbefore = this->_stack_start - src;
+ memcpy(dest, src, nbefore);
+ dest += nbefore;
+ src += nbefore;
+ n -= nbefore;
+ }
+ // We know src >= _stack_start after the before-copy, and
+ // src < _stack_start + _stack_saved due to the first if condition
+ size_t nspilled = std::min(n, this->_stack_start + this->_stack_saved - src);
+ memcpy(dest, this->stack_copy + (src - this->_stack_start), nspilled);
+ dest += nspilled;
+ src += nspilled;
+ n -= nspilled;
+ if (n > 0) {
+ // Copy the part after the saved stack
+ memcpy(dest, src, n);
+ }
+}
+
+}; // namespace greenlet
+
+#endif // GREENLET_STACK_STATE_CPP
diff --git a/venv/Lib/site-packages/greenlet/TThreadStateDestroy.cpp b/venv/Lib/site-packages/greenlet/TThreadStateDestroy.cpp
new file mode 100644
index 0000000..a149a1a
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TThreadStateDestroy.cpp
@@ -0,0 +1,195 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of the ThreadState destructors.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+#ifndef T_THREADSTATE_DESTROY
+#define T_THREADSTATE_DESTROY
+
+#include "greenlet_greenlet.hpp"
+#include "greenlet_thread_state.hpp"
+#include "greenlet_thread_support.hpp"
+#include "greenlet_cpython_add_pending.hpp"
+#include "TGreenletGlobals.cpp"
+
+namespace greenlet {
+
+struct ThreadState_DestroyWithGIL
+{
+ ThreadState_DestroyWithGIL(ThreadState* state)
+ {
+ if (state && state->has_main_greenlet()) {
+ DestroyWithGIL(state);
+ }
+ }
+
+ static int
+ DestroyWithGIL(ThreadState* state)
+ {
+ // Holding the GIL.
+ // Passed a non-shared pointer to the actual thread state.
+ // state -> main greenlet
+ assert(state->has_main_greenlet());
+ PyGreenlet* main(state->borrow_main_greenlet());
+ // When we need to do cross-thread operations, we check this.
+ // A NULL value means the thread died some time ago.
+ // We do this here, rather than in a Python dealloc function
+ // for the greenlet, in case there's still a reference out
+ // there.
+ static_cast(main->pimpl)->thread_state(nullptr);
+
+ delete state; // Deleting this runs the destructor, DECREFs the main greenlet.
+ return 0;
+ }
+};
+
+
+
+struct ThreadState_DestroyNoGIL
+{
+ // ensure this is actually defined.
+ static_assert(GREENLET_BROKEN_PY_ADD_PENDING == 1 || GREENLET_BROKEN_PY_ADD_PENDING == 0,
+ "GREENLET_BROKEN_PY_ADD_PENDING not defined correctly.");
+
+#if GREENLET_BROKEN_PY_ADD_PENDING
+ static int _push_pending_call(struct _pending_calls *pending,
+ int (*func)(void *), void *arg)
+ {
+ int i = pending->last;
+ int j = (i + 1) % NPENDINGCALLS;
+ if (j == pending->first) {
+ return -1; /* Queue full */
+ }
+ pending->calls[i].func = func;
+ pending->calls[i].arg = arg;
+ pending->last = j;
+ return 0;
+ }
+
+ static int AddPendingCall(int (*func)(void *), void *arg)
+ {
+ _PyRuntimeState *runtime = &_PyRuntime;
+ if (!runtime) {
+ // obviously impossible
+ return 0;
+ }
+ struct _pending_calls *pending = &runtime->ceval.pending;
+ if (!pending->lock) {
+ return 0;
+ }
+ int result = 0;
+ PyThread_acquire_lock(pending->lock, WAIT_LOCK);
+ if (!pending->finishing) {
+ result = _push_pending_call(pending, func, arg);
+ }
+ PyThread_release_lock(pending->lock);
+ SIGNAL_PENDING_CALLS(&runtime->ceval);
+ return result;
+ }
+#else
+ // Python < 3.8 or >= 3.9
+ static int AddPendingCall(int (*func)(void*), void* arg)
+ {
+ return Py_AddPendingCall(func, arg);
+ }
+#endif
+
+ ThreadState_DestroyNoGIL(ThreadState* state)
+ {
+ // We are *NOT* holding the GIL. Our thread is in the middle
+ // of its death throes and the Python thread state is already
+ // gone so we can't use most Python APIs. One that is safe is
+ // ``Py_AddPendingCall``, unless the interpreter itself has
+ // been torn down. There is a limited number of calls that can
+ // be queued: 32 (NPENDINGCALLS) in CPython 3.10, so we
+ // coalesce these calls using our own queue.
+ if (state && state->has_main_greenlet()) {
+ // mark the thread as dead ASAP.
+ // this is racy! If we try to throw or switch to a
+ // greenlet from this thread from some other thread before
+ // we clear the state pointer, it won't realize the state
+ // is dead which can crash the process.
+ PyGreenlet* p = state->borrow_main_greenlet();
+ assert(p->pimpl->thread_state() == state || p->pimpl->thread_state() == nullptr);
+ static_cast(p->pimpl)->thread_state(nullptr);
+ }
+
+ // NOTE: Because we're not holding the GIL here, some other
+ // Python thread could run and call ``os.fork()``, which would
+ // be bad if that happenend while we are holding the cleanup
+ // lock (it wouldn't function in the child process).
+ // Make a best effort to try to keep the duration we hold the
+ // lock short.
+ // TODO: On platforms that support it, use ``pthread_atfork`` to
+ // drop this lock.
+ LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
+
+ if (state && state->has_main_greenlet()) {
+ // Because we don't have the GIL, this is a race condition.
+ if (!PyInterpreterState_Head()) {
+ // We have to leak the thread state, if the
+ // interpreter has shut down when we're getting
+ // deallocated, we can't run the cleanup code that
+ // deleting it would imply.
+ return;
+ }
+
+ mod_globs->queue_to_destroy(state);
+ if (mod_globs->thread_states_to_destroy.size() == 1) {
+ // We added the first item to the queue. We need to schedule
+ // the cleanup.
+ int result = ThreadState_DestroyNoGIL::AddPendingCall(
+ ThreadState_DestroyNoGIL::DestroyQueueWithGIL,
+ NULL);
+ if (result < 0) {
+ // Hmm, what can we do here?
+ fprintf(stderr,
+ "greenlet: WARNING: failed in call to Py_AddPendingCall; "
+ "expect a memory leak.\n");
+ }
+ }
+ }
+ }
+
+ static int
+ DestroyQueueWithGIL(void* UNUSED(arg))
+ {
+ // We're holding the GIL here, so no Python code should be able to
+ // run to call ``os.fork()``.
+ while (1) {
+ ThreadState* to_destroy;
+ {
+ LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
+ if (mod_globs->thread_states_to_destroy.empty()) {
+ break;
+ }
+ to_destroy = mod_globs->take_next_to_destroy();
+ }
+ // Drop the lock while we do the actual deletion.
+ ThreadState_DestroyWithGIL::DestroyWithGIL(to_destroy);
+ }
+ return 0;
+ }
+
+};
+
+}; // namespace greenlet
+
+// The intent when GET_THREAD_STATE() is needed multiple times in a
+// function is to take a reference to its return value in a local
+// variable, to avoid the thread-local indirection. On some platforms
+// (macOS), accessing a thread-local involves a function call (plus an
+// initial function call in each function that uses a thread local);
+// in contrast, static volatile variables are at some pre-computed
+// offset.
+typedef greenlet::ThreadStateCreator ThreadStateCreator;
+static thread_local ThreadStateCreator g_thread_state_global;
+#define GET_THREAD_STATE() g_thread_state_global
+
+#endif //T_THREADSTATE_DESTROY
diff --git a/venv/Lib/site-packages/greenlet/TUserGreenlet.cpp b/venv/Lib/site-packages/greenlet/TUserGreenlet.cpp
new file mode 100644
index 0000000..495a794
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/TUserGreenlet.cpp
@@ -0,0 +1,667 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/**
+ * Implementation of greenlet::UserGreenlet.
+ *
+ * Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+
+#include "greenlet_internal.hpp"
+#include "greenlet_greenlet.hpp"
+#include "greenlet_thread_state.hpp"
+#include "TThreadStateDestroy.cpp"
+
+
+namespace greenlet {
+using greenlet::refs::BorrowedMainGreenlet;
+greenlet::PythonAllocator UserGreenlet::allocator;
+
+void* UserGreenlet::operator new(size_t UNUSED(count))
+{
+ return allocator.allocate(1);
+}
+
+
+void UserGreenlet::operator delete(void* ptr)
+{
+ return allocator.deallocate(static_cast(ptr),
+ 1);
+}
+
+
+UserGreenlet::UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent)
+ : Greenlet(p), _parent(the_parent)
+{
+ this->_self = p;
+}
+
+UserGreenlet::~UserGreenlet()
+{
+ // Python 3.11: If we don't clear out the raw frame datastack
+ // when deleting an unfinished greenlet,
+ // TestLeaks.test_untracked_memory_doesnt_increase_unfinished_thread_dealloc_in_main fails.
+ this->python_state.did_finish(nullptr);
+ this->tp_clear();
+}
+
+BorrowedGreenlet
+UserGreenlet::self() const noexcept
+{
+ return this->_self;
+}
+
+
+
+const BorrowedMainGreenlet
+UserGreenlet::main_greenlet() const
+{
+ return this->_main_greenlet;
+}
+
+
+BorrowedMainGreenlet
+UserGreenlet::find_main_greenlet_in_lineage() const
+{
+ if (this->started()) {
+ assert(this->_main_greenlet);
+ return BorrowedMainGreenlet(this->_main_greenlet);
+ }
+
+ if (!this->_parent) {
+ /* garbage collected greenlet in chain */
+ // XXX: WHAT?
+ return BorrowedMainGreenlet(nullptr);
+ }
+
+ return this->_parent->find_main_greenlet_in_lineage();
+}
+
+
+/**
+ * CAUTION: This will allocate memory and may trigger garbage
+ * collection and arbitrary Python code.
+ */
+OwnedObject
+UserGreenlet::throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state)
+{
+ /* The dying greenlet cannot be a parent of ts_current
+ because the 'parent' field chain would hold a
+ reference */
+ UserGreenlet::ParentIsCurrentGuard with_current_parent(this, current_thread_state);
+
+ // We don't care about the return value, only whether an
+ // exception happened. Whether or not an exception happens,
+ // we need to restore the parent in case the greenlet gets
+ // resurrected.
+ return Greenlet::throw_GreenletExit_during_dealloc(current_thread_state);
+}
+
+ThreadState*
+UserGreenlet::thread_state() const noexcept
+{
+ // TODO: maybe make this throw, if the thread state isn't there?
+ // if (!this->main_greenlet) {
+ // throw std::runtime_error("No thread state"); // TODO: Better exception
+ // }
+ if (!this->_main_greenlet) {
+ return nullptr;
+ }
+ return this->_main_greenlet->thread_state();
+}
+
+
+bool
+UserGreenlet::was_running_in_dead_thread() const noexcept
+{
+ return this->_main_greenlet && !this->thread_state();
+}
+
+OwnedObject
+UserGreenlet::g_switch()
+{
+ assert(this->args() || PyErr_Occurred());
+
+ try {
+ this->check_switch_allowed();
+ }
+ catch (const PyErrOccurred&) {
+ this->release_args();
+ throw;
+ }
+
+ // Switching greenlets used to attempt to clean out ones that need
+ // deleted *if* we detected a thread switch. Should it still do
+ // that?
+ // An issue is that if we delete a greenlet from another thread,
+ // it gets queued to this thread, and ``kill_greenlet()`` switches
+ // back into the greenlet
+
+ /* find the real target by ignoring dead greenlets,
+ and if necessary starting a greenlet. */
+ switchstack_result_t err;
+ Greenlet* target = this;
+ // TODO: probably cleaner to handle the case where we do
+ // switch to ourself separately from the other cases.
+ // This can probably even further be simplified if we keep
+ // track of the switching_state we're going for and just call
+ // into g_switch() if it's not ourself. The main problem with that
+ // is that we would be using more stack space.
+ bool target_was_me = true;
+ bool was_initial_stub = false;
+ while (target) {
+ if (target->active()) {
+ if (!target_was_me) {
+ target->args() <<= this->args();
+ assert(!this->args());
+ }
+ err = target->g_switchstack();
+ break;
+ }
+ if (!target->started()) {
+ // We never encounter a main greenlet that's not started.
+ assert(!target->main());
+ UserGreenlet* real_target = static_cast(target);
+ assert(real_target);
+ void* dummymarker;
+ was_initial_stub = true;
+ if (!target_was_me) {
+ target->args() <<= this->args();
+ assert(!this->args());
+ }
+ try {
+ // This can only throw back to us while we're
+ // still in this greenlet. Once the new greenlet
+ // is bootstrapped, it has its own exception state.
+ err = real_target->g_initialstub(&dummymarker);
+ }
+ catch (const PyErrOccurred&) {
+ this->release_args();
+ throw;
+ }
+ catch (const GreenletStartedWhileInPython&) {
+ // The greenlet was started sometime before this
+ // greenlet actually switched to it, i.e.,
+ // "concurrent" calls to switch() or throw().
+ // We need to retry the switch.
+ // Note that the current greenlet has been reset
+ // to this one (or we wouldn't be running!)
+ continue;
+ }
+ break;
+ }
+
+ target = target->parent();
+ target_was_me = false;
+ }
+ // The ``this`` pointer and all other stack or register based
+ // variables are invalid now, at least where things succeed
+ // above.
+ // But this one, probably not so much? It's not clear if it's
+ // safe to throw an exception at this point.
+
+ if (err.status < 0) {
+ // If we get here, either g_initialstub()
+ // failed, or g_switchstack() failed. Either one of those
+ // cases SHOULD leave us in the original greenlet with a valid
+ // stack.
+ return this->on_switchstack_or_initialstub_failure(target, err, target_was_me, was_initial_stub);
+ }
+
+ // err.the_new_current_greenlet would be the same as ``target``,
+ // if target wasn't probably corrupt.
+ return err.the_new_current_greenlet->g_switch_finish(err);
+}
+
+
+
+Greenlet::switchstack_result_t
+UserGreenlet::g_initialstub(void* mark)
+{
+ OwnedObject run;
+
+ // We need to grab a reference to the current switch arguments
+ // in case we're entered concurrently during the call to
+ // GetAttr() and have to try again.
+ // We'll restore them when we return in that case.
+ // Scope them tightly to avoid ref leaks.
+ {
+ SwitchingArgs args(this->args());
+
+ /* save exception in case getattr clears it */
+ PyErrPieces saved;
+
+ /*
+ self.run is the object to call in the new greenlet.
+ This could run arbitrary python code and switch greenlets!
+ */
+ run = this->_self.PyRequireAttr(mod_globs->str_run);
+ /* restore saved exception */
+ saved.PyErrRestore();
+
+
+ /* recheck that it's safe to switch in case greenlet reparented anywhere above */
+ this->check_switch_allowed();
+
+ /* by the time we got here another start could happen elsewhere,
+ * that means it should now be a regular switch.
+ * This can happen if the Python code is a subclass that implements
+ * __getattribute__ or __getattr__, or makes ``run`` a descriptor;
+ * all of those can run arbitrary code that switches back into
+ * this greenlet.
+ */
+ if (this->stack_state.started()) {
+ // the successful switch cleared these out, we need to
+ // restore our version. They will be copied on up to the
+ // next target.
+ assert(!this->args());
+ this->args() <<= args;
+ throw GreenletStartedWhileInPython();
+ }
+ }
+
+ // Sweet, if we got here, we have the go-ahead and will switch
+ // greenlets.
+ // Nothing we do from here on out should allow for a thread or
+ // greenlet switch: No arbitrary calls to Python, including
+ // decref'ing
+
+#if GREENLET_USE_CFRAME
+ /* OK, we need it, we're about to switch greenlets, save the state. */
+ /*
+ See green_new(). This is a stack-allocated variable used
+ while *self* is in PyObject_Call().
+ We want to defer copying the state info until we're sure
+ we need it and are in a stable place to do so.
+ */
+ _PyCFrame trace_info;
+
+ this->python_state.set_new_cframe(trace_info);
+#endif
+ /* start the greenlet */
+ ThreadState& thread_state = GET_THREAD_STATE().state();
+ this->stack_state = StackState(mark,
+ thread_state.borrow_current()->stack_state);
+ this->python_state.set_initial_state(PyThreadState_GET());
+ this->exception_state.clear();
+ this->_main_greenlet = thread_state.get_main_greenlet();
+
+ /* perform the initial switch */
+ switchstack_result_t err = this->g_switchstack();
+ /* returns twice!
+ The 1st time with ``err == 1``: we are in the new greenlet.
+ This one owns a greenlet that used to be current.
+ The 2nd time with ``err <= 0``: back in the caller's
+ greenlet; this happens if the child finishes or switches
+ explicitly to us. Either way, the ``err`` variable is
+ created twice at the same memory location, but possibly
+ having different ``origin`` values. Note that it's not
+ constructed for the second time until the switch actually happens.
+ */
+ if (err.status == 1) {
+ // In the new greenlet.
+
+ // This never returns! Calling inner_bootstrap steals
+ // the contents of our run object within this stack frame, so
+ // it is not valid to do anything with it.
+ try {
+ this->inner_bootstrap(err.origin_greenlet.relinquish_ownership(),
+ run.relinquish_ownership());
+ }
+ // Getting a C++ exception here isn't good. It's probably a
+ // bug in the underlying greenlet, meaning it's probably a
+ // C++ extension. We're going to abort anyway, but try to
+ // display some nice information *if* possible. Some obscure
+ // platforms don't properly support this (old 32-bit Arm, see see
+ // https://github.com/python-greenlet/greenlet/issues/385); that's not
+ // great, but should usually be OK because, as mentioned above, we're
+ // terminating anyway.
+ //
+ // The catching is tested by
+ // ``test_cpp.CPPTests.test_unhandled_exception_in_greenlet_aborts``.
+ //
+ // PyErrOccurred can theoretically be thrown by
+ // inner_bootstrap() -> g_switch_finish(), but that should
+ // never make it back to here. It is a std::exception and
+ // would be caught if it is.
+ catch (const std::exception& e) {
+ std::string base = "greenlet: Unhandled C++ exception: ";
+ base += e.what();
+ Py_FatalError(base.c_str());
+ }
+ catch (...) {
+ // Some compilers/runtimes use exceptions internally.
+ // It appears that GCC on Linux with libstdc++ throws an
+ // exception internally at process shutdown time to unwind
+ // stacks and clean up resources. Depending on exactly
+ // where we are when the process exits, that could result
+ // in an unknown exception getting here. If we
+ // Py_FatalError() or abort() here, we interfere with
+ // orderly process shutdown. Throwing the exception on up
+ // is the right thing to do.
+ //
+ // gevent's ``examples/dns_mass_resolve.py`` demonstrates this.
+#ifndef NDEBUG
+ fprintf(stderr,
+ "greenlet: inner_bootstrap threw unknown exception; "
+ "is the process terminating?\n");
+#endif
+ throw;
+ }
+ Py_FatalError("greenlet: inner_bootstrap returned with no exception.\n");
+ }
+
+
+ // In contrast, notice that we're keeping the origin greenlet
+ // around as an owned reference; we need it to call the trace
+ // function for the switch back into the parent. It was only
+ // captured at the time the switch actually happened, though,
+ // so we haven't been keeping an extra reference around this
+ // whole time.
+
+ /* back in the parent */
+ if (err.status < 0) {
+ /* start failed badly, restore greenlet state */
+ this->stack_state = StackState();
+ this->_main_greenlet.CLEAR();
+ // CAUTION: This may run arbitrary Python code.
+ run.CLEAR(); // inner_bootstrap didn't run, we own the reference.
+ }
+
+ // In the success case, the spawned code (inner_bootstrap) will
+ // take care of decrefing this, so we relinquish ownership so as
+ // to not double-decref.
+
+ run.relinquish_ownership();
+
+ return err;
+}
+
+
+void
+UserGreenlet::inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run)
+{
+ // The arguments here would be another great place for move.
+ // As it is, we take them as a reference so that when we clear
+ // them we clear what's on the stack above us. Do that NOW, and
+ // without using a C++ RAII object,
+ // so there's no way that exiting the parent frame can clear it,
+ // or we clear it unexpectedly. This arises in the context of the
+ // interpreter shutting down. See https://github.com/python-greenlet/greenlet/issues/325
+ //PyObject* run = _run.relinquish_ownership();
+
+ /* in the new greenlet */
+ assert(this->thread_state()->borrow_current() == this->_self);
+ // C++ exceptions cannot propagate to the parent greenlet from
+ // here. (TODO: Do we need a catch(...) clause, perhaps on the
+ // function itself? ALl we could do is terminate the program.)
+ // NOTE: On 32-bit Windows, the call chain is extremely
+ // important here in ways that are subtle, having to do with
+ // the depth of the SEH list. The call to restore it MUST NOT
+ // add a new SEH handler to the list, or we'll restore it to
+ // the wrong thing.
+ this->thread_state()->restore_exception_state();
+ /* stack variables from above are no good and also will not unwind! */
+ // EXCEPT: That can't be true, we access run, among others, here.
+
+ this->stack_state.set_active(); /* running */
+
+ // We're about to possibly run Python code again, which
+ // could switch back/away to/from us, so we need to grab the
+ // arguments locally.
+ SwitchingArgs args;
+ args <<= this->args();
+ assert(!this->args());
+
+ // XXX: We could clear this much earlier, right?
+ // Or would that introduce the possibility of running Python
+ // code when we don't want to?
+ // CAUTION: This may run arbitrary Python code.
+ this->_run_callable.CLEAR();
+
+
+ // The first switch we need to manually call the trace
+ // function here instead of in g_switch_finish, because we
+ // never return there.
+ if (OwnedObject tracefunc = this->thread_state()->get_tracefunc()) {
+ OwnedGreenlet trace_origin;
+ trace_origin = origin_greenlet;
+ try {
+ g_calltrace(tracefunc,
+ args ? mod_globs->event_switch : mod_globs->event_throw,
+ trace_origin,
+ this->_self);
+ }
+ catch (const PyErrOccurred&) {
+ /* Turn trace errors into switch throws */
+ args.CLEAR();
+ }
+ }
+
+ // We no longer need the origin, it was only here for
+ // tracing.
+ // We may never actually exit this stack frame so we need
+ // to explicitly clear it.
+ // This could run Python code and switch.
+ Py_CLEAR(origin_greenlet);
+
+ OwnedObject result;
+ if (!args) {
+ /* pending exception */
+ result = NULL;
+ }
+ else {
+ /* call g.run(*args, **kwargs) */
+ // This could result in further switches
+ try {
+ //result = run.PyCall(args.args(), args.kwargs());
+ // CAUTION: Just invoking this, before the function even
+ // runs, may cause memory allocations, which may trigger
+ // GC, which may run arbitrary Python code.
+ result = OwnedObject::consuming(PyObject_Call(run, args.args().borrow(), args.kwargs().borrow()));
+ }
+ catch (...) {
+ // Unhandled C++ exception!
+
+ // If we declare ourselves as noexcept, if we don't catch
+ // this here, most platforms will just abort() the
+ // process. But on 64-bit Windows with older versions of
+ // the C runtime, this can actually corrupt memory and
+ // just return. We see this when compiling with the
+ // Windows 7.0 SDK targeting Windows Server 2008, but not
+ // when using the Appveyor Visual Studio 2019 image. So
+ // this currently only affects Python 2.7 on Windows 64.
+ // That is, the tests pass and the runtime aborts
+ // everywhere else.
+ //
+ // However, if we catch it and try to continue with a
+ // Python error, then all Windows 64 bit platforms corrupt
+ // memory. So all we can do is manually abort, hopefully
+ // with a good error message. (Note that the above was
+ // tested WITHOUT the `/EHr` switch being used at compile
+ // time, so MSVC may have "optimized" out important
+ // checking. Using that switch, we may be in a better
+ // place in terms of memory corruption.) But sometimes it
+ // can't be caught here at all, which is confusing but not
+ // terribly surprising; so again, the G_NOEXCEPT_WIN32
+ // plus "/EHr".
+ //
+ // Hopefully the basic C stdlib is still functional enough
+ // for us to at least print an error.
+ //
+ // It gets more complicated than that, though, on some
+ // platforms, specifically at least Linux/gcc/libstdc++. They use
+ // an exception to unwind the stack when a background
+ // thread exits. (See comments about noexcept.) So this
+ // may not actually represent anything untoward. On those
+ // platforms we allow throws of this to propagate, or
+ // attempt to anyway.
+# if defined(WIN32) || defined(_WIN32)
+ Py_FatalError(
+ "greenlet: Unhandled C++ exception from a greenlet run function. "
+ "Because memory is likely corrupted, terminating process.");
+ std::abort();
+#else
+ throw;
+#endif
+ }
+ }
+ // These lines may run arbitrary code
+ args.CLEAR();
+ Py_CLEAR(run);
+
+ if (!result
+ && mod_globs->PyExc_GreenletExit.PyExceptionMatches()
+ && (this->args())) {
+ // This can happen, for example, if our only reference
+ // goes away after we switch back to the parent.
+ // See test_dealloc_switch_args_not_lost
+ PyErrPieces clear_error;
+ result <<= this->args();
+ result = single_result(result);
+ }
+ this->release_args();
+ this->python_state.did_finish(PyThreadState_GET());
+
+ result = g_handle_exit(result);
+ assert(this->thread_state()->borrow_current() == this->_self);
+
+ /* jump back to parent */
+ this->stack_state.set_inactive(); /* dead */
+
+
+ // TODO: Can we decref some things here? Release our main greenlet
+ // and maybe parent?
+ for (Greenlet* parent = this->_parent;
+ parent;
+ parent = parent->parent()) {
+ // We need to somewhere consume a reference to
+ // the result; in most cases we'll never have control
+ // back in this stack frame again. Calling
+ // green_switch actually adds another reference!
+ // This would probably be clearer with a specific API
+ // to hand results to the parent.
+ parent->args() <<= result;
+ assert(!result);
+ // The parent greenlet now owns the result; in the
+ // typical case we'll never get back here to assign to
+ // result and thus release the reference.
+ try {
+ result = parent->g_switch();
+ }
+ catch (const PyErrOccurred&) {
+ // Ignore, keep passing the error on up.
+ }
+
+ /* Return here means switch to parent failed,
+ * in which case we throw *current* exception
+ * to the next parent in chain.
+ */
+ assert(!result);
+ }
+ /* We ran out of parents, cannot continue */
+ PyErr_WriteUnraisable(this->self().borrow_o());
+ Py_FatalError("greenlet: ran out of parent greenlets while propagating exception; "
+ "cannot continue");
+ std::abort();
+}
+
+void
+UserGreenlet::run(const BorrowedObject nrun)
+{
+ if (this->started()) {
+ throw AttributeError(
+ "run cannot be set "
+ "after the start of the greenlet");
+ }
+ this->_run_callable = nrun;
+}
+
+const OwnedGreenlet
+UserGreenlet::parent() const
+{
+ return this->_parent;
+}
+
+void
+UserGreenlet::parent(const BorrowedObject raw_new_parent)
+{
+ if (!raw_new_parent) {
+ throw AttributeError("can't delete attribute");
+ }
+
+ BorrowedMainGreenlet main_greenlet_of_new_parent;
+ BorrowedGreenlet new_parent(raw_new_parent.borrow()); // could
+ // throw
+ // TypeError!
+ for (BorrowedGreenlet p = new_parent; p; p = p->parent()) {
+ if (p == this->_self) {
+ throw ValueError("cyclic parent chain");
+ }
+ main_greenlet_of_new_parent = p->main_greenlet();
+ }
+
+ if (!main_greenlet_of_new_parent) {
+ throw ValueError("parent must not be garbage collected");
+ }
+
+ if (this->started()
+ && this->_main_greenlet != main_greenlet_of_new_parent) {
+ throw ValueError("parent cannot be on a different thread");
+ }
+
+ this->_parent = new_parent;
+}
+
+void
+UserGreenlet::murder_in_place()
+{
+ this->_main_greenlet.CLEAR();
+ Greenlet::murder_in_place();
+}
+
+bool
+UserGreenlet::belongs_to_thread(const ThreadState* thread_state) const
+{
+ return Greenlet::belongs_to_thread(thread_state) && this->_main_greenlet == thread_state->borrow_main_greenlet();
+}
+
+
+int
+UserGreenlet::tp_traverse(visitproc visit, void* arg)
+{
+ Py_VISIT(this->_parent.borrow_o());
+ Py_VISIT(this->_main_greenlet.borrow_o());
+ Py_VISIT(this->_run_callable.borrow_o());
+
+ return Greenlet::tp_traverse(visit, arg);
+}
+
+int
+UserGreenlet::tp_clear()
+{
+ Greenlet::tp_clear();
+ this->_parent.CLEAR();
+ this->_main_greenlet.CLEAR();
+ this->_run_callable.CLEAR();
+ return 0;
+}
+
+UserGreenlet::ParentIsCurrentGuard::ParentIsCurrentGuard(UserGreenlet* p,
+ const ThreadState& thread_state)
+ : oldparent(p->_parent),
+ greenlet(p)
+{
+ p->_parent = thread_state.get_current();
+}
+
+UserGreenlet::ParentIsCurrentGuard::~ParentIsCurrentGuard()
+{
+ this->greenlet->_parent = oldparent;
+ oldparent.CLEAR();
+}
+
+}; //namespace greenlet
diff --git a/venv/Lib/site-packages/greenlet/__init__.py b/venv/Lib/site-packages/greenlet/__init__.py
new file mode 100644
index 0000000..298a19d
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/__init__.py
@@ -0,0 +1,71 @@
+# -*- coding: utf-8 -*-
+"""
+The root of the greenlet package.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+__all__ = [
+ '__version__',
+ '_C_API',
+
+ 'GreenletExit',
+ 'error',
+
+ 'getcurrent',
+ 'greenlet',
+
+ 'gettrace',
+ 'settrace',
+]
+
+# pylint:disable=no-name-in-module
+
+###
+# Metadata
+###
+__version__ = '3.0.3'
+from ._greenlet import _C_API # pylint:disable=no-name-in-module
+
+###
+# Exceptions
+###
+from ._greenlet import GreenletExit
+from ._greenlet import error
+
+###
+# greenlets
+###
+from ._greenlet import getcurrent
+from ._greenlet import greenlet
+
+###
+# tracing
+###
+try:
+ from ._greenlet import gettrace
+ from ._greenlet import settrace
+except ImportError:
+ # Tracing wasn't supported.
+ # XXX: The option to disable it was removed in 1.0,
+ # so this branch should be dead code.
+ pass
+
+###
+# Constants
+# These constants aren't documented and aren't recommended.
+# In 1.0, USE_GC and USE_TRACING are always true, and USE_CONTEXT_VARS
+# is the same as ``sys.version_info[:2] >= 3.7``
+###
+from ._greenlet import GREENLET_USE_CONTEXT_VARS # pylint:disable=unused-import
+from ._greenlet import GREENLET_USE_GC # pylint:disable=unused-import
+from ._greenlet import GREENLET_USE_TRACING # pylint:disable=unused-import
+
+# Controlling the use of the gc module. Provisional API for this greenlet
+# implementation in 2.0.
+from ._greenlet import CLOCKS_PER_SEC # pylint:disable=unused-import
+from ._greenlet import enable_optional_cleanup # pylint:disable=unused-import
+from ._greenlet import get_clocks_used_doing_optional_cleanup # pylint:disable=unused-import
+
+# Other APIS in the _greenlet module are for test support.
diff --git a/venv/Lib/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc b/venv/Lib/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000..d10394d
Binary files /dev/null and b/venv/Lib/site-packages/greenlet/__pycache__/__init__.cpython-312.pyc differ
diff --git a/venv/Lib/site-packages/greenlet/_greenlet.cp312-win_amd64.pyd b/venv/Lib/site-packages/greenlet/_greenlet.cp312-win_amd64.pyd
new file mode 100644
index 0000000..1f6178d
Binary files /dev/null and b/venv/Lib/site-packages/greenlet/_greenlet.cp312-win_amd64.pyd differ
diff --git a/venv/Lib/site-packages/greenlet/greenlet.cpp b/venv/Lib/site-packages/greenlet/greenlet.cpp
new file mode 100644
index 0000000..5a9818e
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet.cpp
@@ -0,0 +1,1494 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+/* Format with:
+ * clang-format -i --style=file src/greenlet/greenlet.c
+ *
+ *
+ * Fix missing braces with:
+ * clang-tidy src/greenlet/greenlet.c -fix -checks="readability-braces-around-statements"
+*/
+#include
+#include
+#include
+#include
+
+
+#define PY_SSIZE_T_CLEAN
+#include
+#include "structmember.h" // PyMemberDef
+
+#include "greenlet_internal.hpp"
+// Code after this point can assume access to things declared in stdint.h,
+// including the fixed-width types. This goes for the platform-specific switch functions
+// as well.
+#include "greenlet_refs.hpp"
+#include "greenlet_slp_switch.hpp"
+#include "greenlet_thread_state.hpp"
+#include "greenlet_thread_support.hpp"
+#include "greenlet_greenlet.hpp"
+
+#include "TGreenletGlobals.cpp"
+#include "TThreadStateDestroy.cpp"
+#include "TGreenlet.cpp"
+#include "TMainGreenlet.cpp"
+#include "TUserGreenlet.cpp"
+#include "TBrokenGreenlet.cpp"
+#include "TExceptionState.cpp"
+#include "TPythonState.cpp"
+#include "TStackState.cpp"
+
+
+using greenlet::LockGuard;
+using greenlet::LockInitError;
+using greenlet::PyErrOccurred;
+using greenlet::Require;
+
+using greenlet::g_handle_exit;
+using greenlet::single_result;
+
+using greenlet::Greenlet;
+using greenlet::UserGreenlet;
+using greenlet::MainGreenlet;
+using greenlet::BrokenGreenlet;
+using greenlet::ThreadState;
+using greenlet::PythonState;
+
+
+
+// ******* Implementation of things from included files
+template
+greenlet::refs::_BorrowedGreenlet& greenlet::refs::_BorrowedGreenlet::operator=(const greenlet::refs::BorrowedObject& other)
+{
+ this->_set_raw_pointer(static_cast(other));
+ return *this;
+}
+
+template
+inline greenlet::refs::_BorrowedGreenlet::operator Greenlet*() const noexcept
+{
+ if (!this->p) {
+ return nullptr;
+ }
+ return reinterpret_cast(this->p)->pimpl;
+}
+
+template
+greenlet::refs::_BorrowedGreenlet::_BorrowedGreenlet(const BorrowedObject& p)
+ : BorrowedReference(nullptr)
+{
+
+ this->_set_raw_pointer(p.borrow());
+}
+
+template
+inline greenlet::refs::_OwnedGreenlet::operator Greenlet*() const noexcept
+{
+ if (!this->p) {
+ return nullptr;
+ }
+ return reinterpret_cast(this->p)->pimpl;
+}
+
+
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wmissing-field-initializers"
+# pragma clang diagnostic ignored "-Wwritable-strings"
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+// warning: ISO C++ forbids converting a string constant to ‘char*’
+// (The python APIs aren't const correct and accept writable char*)
+# pragma GCC diagnostic ignored "-Wwrite-strings"
+#endif
+
+
+/***********************************************************
+
+A PyGreenlet is a range of C stack addresses that must be
+saved and restored in such a way that the full range of the
+stack contains valid data when we switch to it.
+
+Stack layout for a greenlet:
+
+ | ^^^ |
+ | older data |
+ | |
+ stack_stop . |_______________|
+ . | |
+ . | greenlet data |
+ . | in stack |
+ . * |_______________| . . _____________ stack_copy + stack_saved
+ . | | | |
+ . | data | |greenlet data|
+ . | unrelated | | saved |
+ . | to | | in heap |
+ stack_start . | this | . . |_____________| stack_copy
+ | greenlet |
+ | |
+ | newer data |
+ | vvv |
+
+
+Note that a greenlet's stack data is typically partly at its correct
+place in the stack, and partly saved away in the heap, but always in
+the above configuration: two blocks, the more recent one in the heap
+and the older one still in the stack (either block may be empty).
+
+Greenlets are chained: each points to the previous greenlet, which is
+the one that owns the data currently in the C stack above my
+stack_stop. The currently running greenlet is the first element of
+this chain. The main (initial) greenlet is the last one. Greenlets
+whose stack is entirely in the heap can be skipped from the chain.
+
+The chain is not related to execution order, but only to the order
+in which bits of C stack happen to belong to greenlets at a particular
+point in time.
+
+The main greenlet doesn't have a stack_stop: it is responsible for the
+complete rest of the C stack, and we don't know where it begins. We
+use (char*) -1, the largest possible address.
+
+States:
+ stack_stop == NULL && stack_start == NULL: did not start yet
+ stack_stop != NULL && stack_start == NULL: already finished
+ stack_stop != NULL && stack_start != NULL: active
+
+The running greenlet's stack_start is undefined but not NULL.
+
+ ***********************************************************/
+
+static PyGreenlet*
+green_create_main(ThreadState* state)
+{
+ PyGreenlet* gmain;
+
+ /* create the main greenlet for this thread */
+ gmain = (PyGreenlet*)PyType_GenericAlloc(&PyGreenlet_Type, 0);
+ if (gmain == NULL) {
+ Py_FatalError("green_create_main failed to alloc");
+ return NULL;
+ }
+ new MainGreenlet(gmain, state);
+
+ assert(Py_REFCNT(gmain) == 1);
+ return gmain;
+}
+
+
+
+/***********************************************************/
+
+/* Some functions must not be inlined:
+ * slp_restore_state, when inlined into slp_switch might cause
+ it to restore stack over its own local variables
+ * slp_save_state, when inlined would add its own local
+ variables to the saved stack, wasting space
+ * slp_switch, cannot be inlined for obvious reasons
+ * g_initialstub, when inlined would receive a pointer into its
+ own stack frame, leading to incomplete stack save/restore
+
+g_initialstub is a member function and declared virtual so that the
+compiler always calls it through a vtable.
+
+slp_save_state and slp_restore_state are also member functions. They
+are called from trampoline functions that themselves are declared as
+not eligible for inlining.
+*/
+
+extern "C" {
+static int GREENLET_NOINLINE(slp_save_state_trampoline)(char* stackref)
+{
+ return switching_thread_state->slp_save_state(stackref);
+}
+static void GREENLET_NOINLINE(slp_restore_state_trampoline)()
+{
+ switching_thread_state->slp_restore_state();
+}
+}
+
+
+/***********************************************************/
+
+static PyGreenlet*
+green_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds))
+{
+ PyGreenlet* o =
+ (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict);
+ if (o) {
+ new UserGreenlet(o, GET_THREAD_STATE().state().borrow_current());
+ assert(Py_REFCNT(o) == 1);
+ }
+ return o;
+}
+
+static PyGreenlet*
+green_unswitchable_new(PyTypeObject* type, PyObject* UNUSED(args), PyObject* UNUSED(kwds))
+{
+ PyGreenlet* o =
+ (PyGreenlet*)PyBaseObject_Type.tp_new(type, mod_globs->empty_tuple, mod_globs->empty_dict);
+ if (o) {
+ new BrokenGreenlet(o, GET_THREAD_STATE().state().borrow_current());
+ assert(Py_REFCNT(o) == 1);
+ }
+ return o;
+}
+
+static int
+green_setrun(BorrowedGreenlet self, BorrowedObject nrun, void* c);
+static int
+green_setparent(BorrowedGreenlet self, BorrowedObject nparent, void* c);
+
+static int
+green_init(BorrowedGreenlet self, BorrowedObject args, BorrowedObject kwargs)
+{
+ PyArgParseParam run;
+ PyArgParseParam nparent;
+ static const char* const kwlist[] = {
+ "run",
+ "parent",
+ NULL
+ };
+
+ // recall: The O specifier does NOT increase the reference count.
+ if (!PyArg_ParseTupleAndKeywords(
+ args, kwargs, "|OO:green", (char**)kwlist, &run, &nparent)) {
+ return -1;
+ }
+
+ if (run) {
+ if (green_setrun(self, run, NULL)) {
+ return -1;
+ }
+ }
+ if (nparent && !nparent.is_None()) {
+ return green_setparent(self, nparent, NULL);
+ }
+ return 0;
+}
+
+
+
+static int
+green_traverse(PyGreenlet* self, visitproc visit, void* arg)
+{
+ // We must only visit referenced objects, i.e. only objects
+ // Py_INCREF'ed by this greenlet (directly or indirectly):
+ //
+ // - stack_prev is not visited: holds previous stack pointer, but it's not
+ // referenced
+ // - frames are not visited as we don't strongly reference them;
+ // alive greenlets are not garbage collected
+ // anyway. This can be a problem, however, if this greenlet is
+ // never allowed to finish, and is referenced from the frame: we
+ // have an uncollectible cycle in that case. Note that the
+ // frame object itself is also frequently not even tracked by the GC
+ // starting with Python 3.7 (frames are allocated by the
+ // interpreter untracked, and only become tracked when their
+ // evaluation is finished if they have a refcount > 1). All of
+ // this is to say that we should probably strongly reference
+ // the frame object. Doing so, while always allowing GC on a
+ // greenlet, solves several leaks for us.
+
+ Py_VISIT(self->dict);
+ if (!self->pimpl) {
+ // Hmm. I have seen this at interpreter shutdown time,
+ // I think. That's very odd because this doesn't go away until
+ // we're ``green_dealloc()``, at which point we shouldn't be
+ // traversed anymore.
+ return 0;
+ }
+
+ return self->pimpl->tp_traverse(visit, arg);
+}
+
+static int
+green_is_gc(BorrowedGreenlet self)
+{
+ int result = 0;
+ /* Main greenlet can be garbage collected since it can only
+ become unreachable if the underlying thread exited.
+ Active greenlets --- including those that are suspended ---
+ cannot be garbage collected, however.
+ */
+ if (self->main() || !self->active()) {
+ result = 1;
+ }
+ // The main greenlet pointer will eventually go away after the thread dies.
+ if (self->was_running_in_dead_thread()) {
+ // Our thread is dead! We can never run again. Might as well
+ // GC us. Note that if a tuple containing only us and other
+ // immutable objects had been scanned before this, when we
+ // would have returned 0, the tuple will take itself out of GC
+ // tracking and never be investigated again. So that could
+ // result in both us and the tuple leaking due to an
+ // unreachable/uncollectible reference. The same goes for
+ // dictionaries.
+ //
+ // It's not a great idea to be changing our GC state on the
+ // fly.
+ result = 1;
+ }
+ return result;
+}
+
+
+static int
+green_clear(PyGreenlet* self)
+{
+ /* Greenlet is only cleared if it is about to be collected.
+ Since active greenlets are not garbage collectable, we can
+ be sure that, even if they are deallocated during clear,
+ nothing they reference is in unreachable or finalizers,
+ so even if it switches we are relatively safe. */
+ // XXX: Are we responsible for clearing weakrefs here?
+ Py_CLEAR(self->dict);
+ return self->pimpl->tp_clear();
+}
+
+/**
+ * Returns 0 on failure (the object was resurrected) or 1 on success.
+ **/
+static int
+_green_dealloc_kill_started_non_main_greenlet(BorrowedGreenlet self)
+{
+ /* Hacks hacks hacks copied from instance_dealloc() */
+ /* Temporarily resurrect the greenlet. */
+ assert(self.REFCNT() == 0);
+ Py_SET_REFCNT(self.borrow(), 1);
+ /* Save the current exception, if any. */
+ PyErrPieces saved_err;
+ try {
+ // BY THE TIME WE GET HERE, the state may actually be going
+ // away
+ // if we're shutting down the interpreter and freeing thread
+ // entries,
+ // this could result in freeing greenlets that were leaked. So
+ // we can't try to read the state.
+ self->deallocing_greenlet_in_thread(
+ self->thread_state()
+ ? static_cast(GET_THREAD_STATE())
+ : nullptr);
+ }
+ catch (const PyErrOccurred&) {
+ PyErr_WriteUnraisable(self.borrow_o());
+ /* XXX what else should we do? */
+ }
+ /* Check for no resurrection must be done while we keep
+ * our internal reference, otherwise PyFile_WriteObject
+ * causes recursion if using Py_INCREF/Py_DECREF
+ */
+ if (self.REFCNT() == 1 && self->active()) {
+ /* Not resurrected, but still not dead!
+ XXX what else should we do? we complain. */
+ PyObject* f = PySys_GetObject("stderr");
+ Py_INCREF(self.borrow_o()); /* leak! */
+ if (f != NULL) {
+ PyFile_WriteString("GreenletExit did not kill ", f);
+ PyFile_WriteObject(self.borrow_o(), f, 0);
+ PyFile_WriteString("\n", f);
+ }
+ }
+ /* Restore the saved exception. */
+ saved_err.PyErrRestore();
+ /* Undo the temporary resurrection; can't use DECREF here,
+ * it would cause a recursive call.
+ */
+ assert(self.REFCNT() > 0);
+
+ Py_ssize_t refcnt = self.REFCNT() - 1;
+ Py_SET_REFCNT(self.borrow_o(), refcnt);
+ if (refcnt != 0) {
+ /* Resurrected! */
+ _Py_NewReference(self.borrow_o());
+ Py_SET_REFCNT(self.borrow_o(), refcnt);
+ /* Better to use tp_finalizer slot (PEP 442)
+ * and call ``PyObject_CallFinalizerFromDealloc``,
+ * but that's only supported in Python 3.4+; see
+ * Modules/_io/iobase.c for an example.
+ *
+ * The following approach is copied from iobase.c in CPython 2.7.
+ * (along with much of this function in general). Here's their
+ * comment:
+ *
+ * When called from a heap type's dealloc, the type will be
+ * decref'ed on return (see e.g. subtype_dealloc in typeobject.c). */
+ if (PyType_HasFeature(self.TYPE(), Py_TPFLAGS_HEAPTYPE)) {
+ Py_INCREF(self.TYPE());
+ }
+
+ PyObject_GC_Track((PyObject*)self);
+
+ _Py_DEC_REFTOTAL;
+#ifdef COUNT_ALLOCS
+ --Py_TYPE(self)->tp_frees;
+ --Py_TYPE(self)->tp_allocs;
+#endif /* COUNT_ALLOCS */
+ return 0;
+ }
+ return 1;
+}
+
+
+static void
+green_dealloc(PyGreenlet* self)
+{
+ PyObject_GC_UnTrack(self);
+ BorrowedGreenlet me(self);
+ if (me->active()
+ && me->started()
+ && !me->main()) {
+ if (!_green_dealloc_kill_started_non_main_greenlet(me)) {
+ return;
+ }
+ }
+
+ if (self->weakreflist != NULL) {
+ PyObject_ClearWeakRefs((PyObject*)self);
+ }
+ Py_CLEAR(self->dict);
+
+ if (self->pimpl) {
+ // In case deleting this, which frees some memory,
+ // somehow winds up calling back into us. That's usually a
+ //bug in our code.
+ Greenlet* p = self->pimpl;
+ self->pimpl = nullptr;
+ delete p;
+ }
+ // and finally we're done. self is now invalid.
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+
+
+static OwnedObject
+throw_greenlet(BorrowedGreenlet self, PyErrPieces& err_pieces)
+{
+ PyObject* result = nullptr;
+ err_pieces.PyErrRestore();
+ assert(PyErr_Occurred());
+ if (self->started() && !self->active()) {
+ /* dead greenlet: turn GreenletExit into a regular return */
+ result = g_handle_exit(OwnedObject()).relinquish_ownership();
+ }
+ self->args() <<= result;
+
+ return single_result(self->g_switch());
+}
+
+
+
+PyDoc_STRVAR(
+ green_switch_doc,
+ "switch(*args, **kwargs)\n"
+ "\n"
+ "Switch execution to this greenlet.\n"
+ "\n"
+ "If this greenlet has never been run, then this greenlet\n"
+ "will be switched to using the body of ``self.run(*args, **kwargs)``.\n"
+ "\n"
+ "If the greenlet is active (has been run, but was switch()'ed\n"
+ "out before leaving its run function), then this greenlet will\n"
+ "be resumed and the return value to its switch call will be\n"
+ "None if no arguments are given, the given argument if one\n"
+ "argument is given, or the args tuple and keyword args dict if\n"
+ "multiple arguments are given.\n"
+ "\n"
+ "If the greenlet is dead, or is the current greenlet then this\n"
+ "function will simply return the arguments using the same rules as\n"
+ "above.\n");
+
+static PyObject*
+green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs)
+{
+ using greenlet::SwitchingArgs;
+ SwitchingArgs switch_args(OwnedObject::owning(args), OwnedObject::owning(kwargs));
+ self->pimpl->may_switch_away();
+ self->pimpl->args() <<= switch_args;
+
+ // If we're switching out of a greenlet, and that switch is the
+ // last thing the greenlet does, the greenlet ought to be able to
+ // go ahead and die at that point. Currently, someone else must
+ // manually switch back to the greenlet so that we "fall off the
+ // end" and can perform cleanup. You'd think we'd be able to
+ // figure out that this is happening using the frame's ``f_lasti``
+ // member, which is supposed to be an index into
+ // ``frame->f_code->co_code``, the bytecode string. However, in
+ // recent interpreters, ``f_lasti`` tends not to be updated thanks
+ // to things like the PREDICT() macros in ceval.c. So it doesn't
+ // really work to do that in many cases. For example, the Python
+ // code:
+ // def run():
+ // greenlet.getcurrent().parent.switch()
+ // produces bytecode of len 16, with the actual call to switch()
+ // being at index 10 (in Python 3.10). However, the reported
+ // ``f_lasti`` we actually see is...5! (Which happens to be the
+ // second byte of the CALL_METHOD op for ``getcurrent()``).
+
+ try {
+ //OwnedObject result = single_result(self->pimpl->g_switch());
+ OwnedObject result(single_result(self->pimpl->g_switch()));
+#ifndef NDEBUG
+ // Note that the current greenlet isn't necessarily self. If self
+ // finished, we went to one of its parents.
+ assert(!self->pimpl->args());
+
+ const BorrowedGreenlet& current = GET_THREAD_STATE().state().borrow_current();
+ // It's possible it's never been switched to.
+ assert(!current->args());
+#endif
+ PyObject* p = result.relinquish_ownership();
+
+ if (!p && !PyErr_Occurred()) {
+ // This shouldn't be happening anymore, so the asserts
+ // are there for debug builds. Non-debug builds
+ // crash "gracefully" in this case, although there is an
+ // argument to be made for killing the process in all
+ // cases --- for this to be the case, our switches
+ // probably nested in an incorrect way, so the state is
+ // suspicious. Nothing should be corrupt though, just
+ // confused at the Python level. Letting this propagate is
+ // probably good enough.
+ assert(p || PyErr_Occurred());
+ throw PyErrOccurred(
+ mod_globs->PyExc_GreenletError,
+ "Greenlet.switch() returned NULL without an exception set."
+ );
+ }
+ return p;
+ }
+ catch(const PyErrOccurred&) {
+ return nullptr;
+ }
+}
+
+PyDoc_STRVAR(
+ green_throw_doc,
+ "Switches execution to this greenlet, but immediately raises the\n"
+ "given exception in this greenlet. If no argument is provided, the "
+ "exception\n"
+ "defaults to `greenlet.GreenletExit`. The normal exception\n"
+ "propagation rules apply, as described for `switch`. Note that calling "
+ "this\n"
+ "method is almost equivalent to the following::\n"
+ "\n"
+ " def raiser():\n"
+ " raise typ, val, tb\n"
+ " g_raiser = greenlet(raiser, parent=g)\n"
+ " g_raiser.switch()\n"
+ "\n"
+ "except that this trick does not work for the\n"
+ "`greenlet.GreenletExit` exception, which would not propagate\n"
+ "from ``g_raiser`` to ``g``.\n");
+
+static PyObject*
+green_throw(PyGreenlet* self, PyObject* args)
+{
+ PyArgParseParam typ(mod_globs->PyExc_GreenletExit);
+ PyArgParseParam val;
+ PyArgParseParam tb;
+
+ if (!PyArg_ParseTuple(args, "|OOO:throw", &typ, &val, &tb)) {
+ return nullptr;
+ }
+
+ assert(typ.borrow() || val.borrow());
+
+ self->pimpl->may_switch_away();
+ try {
+ // Both normalizing the error and the actual throw_greenlet
+ // could throw PyErrOccurred.
+ PyErrPieces err_pieces(typ.borrow(), val.borrow(), tb.borrow());
+
+ return throw_greenlet(self, err_pieces).relinquish_ownership();
+ }
+ catch (const PyErrOccurred&) {
+ return nullptr;
+ }
+}
+
+static int
+green_bool(PyGreenlet* self)
+{
+ return self->pimpl->active();
+}
+
+/**
+ * CAUTION: Allocates memory, may run GC and arbitrary Python code.
+ */
+static PyObject*
+green_getdict(PyGreenlet* self, void* UNUSED(context))
+{
+ if (self->dict == NULL) {
+ self->dict = PyDict_New();
+ if (self->dict == NULL) {
+ return NULL;
+ }
+ }
+ Py_INCREF(self->dict);
+ return self->dict;
+}
+
+static int
+green_setdict(PyGreenlet* self, PyObject* val, void* UNUSED(context))
+{
+ PyObject* tmp;
+
+ if (val == NULL) {
+ PyErr_SetString(PyExc_TypeError, "__dict__ may not be deleted");
+ return -1;
+ }
+ if (!PyDict_Check(val)) {
+ PyErr_SetString(PyExc_TypeError, "__dict__ must be a dictionary");
+ return -1;
+ }
+ tmp = self->dict;
+ Py_INCREF(val);
+ self->dict = val;
+ Py_XDECREF(tmp);
+ return 0;
+}
+
+static bool
+_green_not_dead(BorrowedGreenlet self)
+{
+ // XXX: Where else should we do this?
+ // Probably on entry to most Python-facing functions?
+ if (self->was_running_in_dead_thread()) {
+ self->deactivate_and_free();
+ return false;
+ }
+ return self->active() || !self->started();
+}
+
+
+static PyObject*
+green_getdead(BorrowedGreenlet self, void* UNUSED(context))
+{
+ if (_green_not_dead(self)) {
+ Py_RETURN_FALSE;
+ }
+ else {
+ Py_RETURN_TRUE;
+ }
+}
+
+static PyObject*
+green_get_stack_saved(PyGreenlet* self, void* UNUSED(context))
+{
+ return PyLong_FromSsize_t(self->pimpl->stack_saved());
+}
+
+
+static PyObject*
+green_getrun(BorrowedGreenlet self, void* UNUSED(context))
+{
+ try {
+ OwnedObject result(self->run());
+ return result.relinquish_ownership();
+ }
+ catch(const PyErrOccurred&) {
+ return nullptr;
+ }
+}
+
+
+
+
+
+static int
+green_setrun(BorrowedGreenlet self, BorrowedObject nrun, void* UNUSED(context))
+{
+ try {
+ self->run(nrun);
+ return 0;
+ }
+ catch(const PyErrOccurred&) {
+ return -1;
+ }
+}
+
+static PyObject*
+green_getparent(BorrowedGreenlet self, void* UNUSED(context))
+{
+ return self->parent().acquire_or_None();
+}
+
+
+
+static int
+green_setparent(BorrowedGreenlet self, BorrowedObject nparent, void* UNUSED(context))
+{
+ try {
+ self->parent(nparent);
+ }
+ catch(const PyErrOccurred&) {
+ return -1;
+ }
+ return 0;
+}
+
+
+static PyObject*
+green_getcontext(const PyGreenlet* self, void* UNUSED(context))
+{
+ const Greenlet *const g = self->pimpl;
+ try {
+ OwnedObject result(g->context());
+ return result.relinquish_ownership();
+ }
+ catch(const PyErrOccurred&) {
+ return nullptr;
+ }
+}
+
+static int
+green_setcontext(BorrowedGreenlet self, PyObject* nctx, void* UNUSED(context))
+{
+ try {
+ self->context(nctx);
+ return 0;
+ }
+ catch(const PyErrOccurred&) {
+ return -1;
+ }
+}
+
+
+static PyObject*
+green_getframe(BorrowedGreenlet self, void* UNUSED(context))
+{
+ const PythonState::OwnedFrame& top_frame = self->top_frame();
+ return top_frame.acquire_or_None();
+}
+
+
+static PyObject*
+green_getstate(PyGreenlet* self)
+{
+ PyErr_Format(PyExc_TypeError,
+ "cannot serialize '%s' object",
+ Py_TYPE(self)->tp_name);
+ return nullptr;
+}
+
+static PyObject*
+green_repr(BorrowedGreenlet self)
+{
+ /*
+ Return a string like
+
+
+ The handling of greenlets across threads is not super good.
+ We mostly use the internal definitions of these terms, but they
+ generally should make sense to users as well.
+ */
+ PyObject* result;
+ int never_started = !self->started() && !self->active();
+
+ const char* const tp_name = Py_TYPE(self)->tp_name;
+
+ if (_green_not_dead(self)) {
+ /* XXX: The otid= is almost useless because you can't correlate it to
+ any thread identifier exposed to Python. We could use
+ PyThreadState_GET()->thread_id, but we'd need to save that in the
+ greenlet, or save the whole PyThreadState object itself.
+
+ As it stands, its only useful for identifying greenlets from the same thread.
+ */
+ const char* state_in_thread;
+ if (self->was_running_in_dead_thread()) {
+ // The thread it was running in is dead!
+ // This can happen, especially at interpreter shut down.
+ // It complicates debugging output because it may be
+ // impossible to access the current thread state at that
+ // time. Thus, don't access the current thread state.
+ state_in_thread = " (thread exited)";
+ }
+ else {
+ state_in_thread = GET_THREAD_STATE().state().is_current(self)
+ ? " current"
+ : (self->started() ? " suspended" : "");
+ }
+ result = PyUnicode_FromFormat(
+ "<%s object at %p (otid=%p)%s%s%s%s>",
+ tp_name,
+ self.borrow_o(),
+ self->thread_state(),
+ state_in_thread,
+ self->active() ? " active" : "",
+ never_started ? " pending" : " started",
+ self->main() ? " main" : ""
+ );
+ }
+ else {
+ result = PyUnicode_FromFormat(
+ "<%s object at %p (otid=%p) %sdead>",
+ tp_name,
+ self.borrow_o(),
+ self->thread_state(),
+ self->was_running_in_dead_thread()
+ ? "(thread exited) "
+ : ""
+ );
+ }
+
+ return result;
+}
+
+/*****************************************************************************
+ * C interface
+ *
+ * These are exported using the CObject API
+ */
+extern "C" {
+static PyGreenlet*
+PyGreenlet_GetCurrent(void)
+{
+ return GET_THREAD_STATE().state().get_current().relinquish_ownership();
+}
+
+static int
+PyGreenlet_SetParent(PyGreenlet* g, PyGreenlet* nparent)
+{
+ return green_setparent((PyGreenlet*)g, (PyObject*)nparent, NULL);
+}
+
+static PyGreenlet*
+PyGreenlet_New(PyObject* run, PyGreenlet* parent)
+{
+ using greenlet::refs::NewDictReference;
+ // In the past, we didn't use green_new and green_init, but that
+ // was a maintenance issue because we duplicated code. This way is
+ // much safer, but slightly slower. If that's a problem, we could
+ // refactor green_init to separate argument parsing from initialization.
+ OwnedGreenlet g = OwnedGreenlet::consuming(green_new(&PyGreenlet_Type, nullptr, nullptr));
+ if (!g) {
+ return NULL;
+ }
+
+ try {
+ NewDictReference kwargs;
+ if (run) {
+ kwargs.SetItem(mod_globs->str_run, run);
+ }
+ if (parent) {
+ kwargs.SetItem("parent", (PyObject*)parent);
+ }
+
+ Require(green_init(g, mod_globs->empty_tuple, kwargs));
+ }
+ catch (const PyErrOccurred&) {
+ return nullptr;
+ }
+
+ return g.relinquish_ownership();
+}
+
+static PyObject*
+PyGreenlet_Switch(PyGreenlet* self, PyObject* args, PyObject* kwargs)
+{
+ if (!PyGreenlet_Check(self)) {
+ PyErr_BadArgument();
+ return NULL;
+ }
+
+ if (args == NULL) {
+ args = mod_globs->empty_tuple;
+ }
+
+ if (kwargs == NULL || !PyDict_Check(kwargs)) {
+ kwargs = NULL;
+ }
+
+ return green_switch(self, args, kwargs);
+}
+
+static PyObject*
+PyGreenlet_Throw(PyGreenlet* self, PyObject* typ, PyObject* val, PyObject* tb)
+{
+ if (!PyGreenlet_Check(self)) {
+ PyErr_BadArgument();
+ return nullptr;
+ }
+ try {
+ PyErrPieces err_pieces(typ, val, tb);
+ return throw_greenlet(self, err_pieces).relinquish_ownership();
+ }
+ catch (const PyErrOccurred&) {
+ return nullptr;
+ }
+}
+
+static int
+Extern_PyGreenlet_MAIN(PyGreenlet* self)
+{
+ if (!PyGreenlet_Check(self)) {
+ PyErr_BadArgument();
+ return -1;
+ }
+ return self->pimpl->main();
+}
+
+static int
+Extern_PyGreenlet_ACTIVE(PyGreenlet* self)
+{
+ if (!PyGreenlet_Check(self)) {
+ PyErr_BadArgument();
+ return -1;
+ }
+ return self->pimpl->active();
+}
+
+static int
+Extern_PyGreenlet_STARTED(PyGreenlet* self)
+{
+ if (!PyGreenlet_Check(self)) {
+ PyErr_BadArgument();
+ return -1;
+ }
+ return self->pimpl->started();
+}
+
+static PyGreenlet*
+Extern_PyGreenlet_GET_PARENT(PyGreenlet* self)
+{
+ if (!PyGreenlet_Check(self)) {
+ PyErr_BadArgument();
+ return NULL;
+ }
+ // This can return NULL even if there is no exception
+ return self->pimpl->parent().acquire();
+}
+} // extern C.
+
+/** End C API ****************************************************************/
+
+static PyMethodDef green_methods[] = {
+ {"switch",
+ reinterpret_cast(green_switch),
+ METH_VARARGS | METH_KEYWORDS,
+ green_switch_doc},
+ {"throw", (PyCFunction)green_throw, METH_VARARGS, green_throw_doc},
+ {"__getstate__", (PyCFunction)green_getstate, METH_NOARGS, NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+static PyGetSetDef green_getsets[] = {
+ /* name, getter, setter, doc, context pointer */
+ {"__dict__", (getter)green_getdict, (setter)green_setdict, /*XXX*/ NULL},
+ {"run", (getter)green_getrun, (setter)green_setrun, /*XXX*/ NULL},
+ {"parent", (getter)green_getparent, (setter)green_setparent, /*XXX*/ NULL},
+ {"gr_frame", (getter)green_getframe, NULL, /*XXX*/ NULL},
+ {"gr_context",
+ (getter)green_getcontext,
+ (setter)green_setcontext,
+ /*XXX*/ NULL},
+ {"dead", (getter)green_getdead, NULL, /*XXX*/ NULL},
+ {"_stack_saved", (getter)green_get_stack_saved, NULL, /*XXX*/ NULL},
+ {NULL}
+};
+
+static PyMemberDef green_members[] = {
+ {NULL}
+};
+
+static PyNumberMethods green_as_number = {
+ NULL, /* nb_add */
+ NULL, /* nb_subtract */
+ NULL, /* nb_multiply */
+ NULL, /* nb_remainder */
+ NULL, /* nb_divmod */
+ NULL, /* nb_power */
+ NULL, /* nb_negative */
+ NULL, /* nb_positive */
+ NULL, /* nb_absolute */
+ (inquiry)green_bool, /* nb_bool */
+};
+
+
+PyTypeObject PyGreenlet_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "greenlet.greenlet", /* tp_name */
+ sizeof(PyGreenlet), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* methods */
+ (destructor)green_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ (reprfunc)green_repr, /* tp_repr */
+ &green_as_number, /* tp_as _number*/
+ 0, /* tp_as _sequence*/
+ 0, /* tp_as _mapping*/
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer*/
+ G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "greenlet(run=None, parent=None) -> greenlet\n\n"
+ "Creates a new greenlet object (without running it).\n\n"
+ " - *run* -- The callable to invoke.\n"
+ " - *parent* -- The parent greenlet. The default is the current "
+ "greenlet.", /* tp_doc */
+ (traverseproc)green_traverse, /* tp_traverse */
+ (inquiry)green_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ offsetof(PyGreenlet, weakreflist), /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ green_methods, /* tp_methods */
+ green_members, /* tp_members */
+ green_getsets, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ offsetof(PyGreenlet, dict), /* tp_dictoffset */
+ (initproc)green_init, /* tp_init */
+ PyType_GenericAlloc, /* tp_alloc */
+ (newfunc)green_new, /* tp_new */
+ PyObject_GC_Del, /* tp_free */
+ (inquiry)green_is_gc, /* tp_is_gc */
+};
+
+
+
+static PyObject*
+green_unswitchable_getforce(PyGreenlet* self, void* UNUSED(context))
+{
+ BrokenGreenlet* broken = dynamic_cast(self->pimpl);
+ return PyBool_FromLong(broken->_force_switch_error);
+}
+
+static int
+green_unswitchable_setforce(PyGreenlet* self, BorrowedObject nforce, void* UNUSED(context))
+{
+ if (!nforce) {
+ PyErr_SetString(
+ PyExc_AttributeError,
+ "Cannot delete force_switch_error"
+ );
+ return -1;
+ }
+ BrokenGreenlet* broken = dynamic_cast(self->pimpl);
+ int is_true = PyObject_IsTrue(nforce);
+ if (is_true == -1) {
+ return -1;
+ }
+ broken->_force_switch_error = is_true;
+ return 0;
+}
+
+static PyObject*
+green_unswitchable_getforceslp(PyGreenlet* self, void* UNUSED(context))
+{
+ BrokenGreenlet* broken = dynamic_cast(self->pimpl);
+ return PyBool_FromLong(broken->_force_slp_switch_error);
+}
+
+static int
+green_unswitchable_setforceslp(PyGreenlet* self, BorrowedObject nforce, void* UNUSED(context))
+{
+ if (!nforce) {
+ PyErr_SetString(
+ PyExc_AttributeError,
+ "Cannot delete force_slp_switch_error"
+ );
+ return -1;
+ }
+ BrokenGreenlet* broken = dynamic_cast(self->pimpl);
+ int is_true = PyObject_IsTrue(nforce);
+ if (is_true == -1) {
+ return -1;
+ }
+ broken->_force_slp_switch_error = is_true;
+ return 0;
+}
+
+static PyGetSetDef green_unswitchable_getsets[] = {
+ /* name, getter, setter, doc, context pointer */
+ {"force_switch_error",
+ (getter)green_unswitchable_getforce,
+ (setter)green_unswitchable_setforce,
+ /*XXX*/ NULL},
+ {"force_slp_switch_error",
+ (getter)green_unswitchable_getforceslp,
+ (setter)green_unswitchable_setforceslp,
+ /*XXX*/ NULL},
+
+ {NULL}
+};
+
+PyTypeObject PyGreenletUnswitchable_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "greenlet._greenlet.UnswitchableGreenlet",
+ 0, /* tp_basicsize */
+ 0, /* tp_itemsize */
+ /* methods */
+ (destructor)green_dealloc, /* tp_dealloc */
+ 0, /* tp_print */
+ 0, /* tp_getattr */
+ 0, /* tp_setattr */
+ 0, /* tp_compare */
+ 0, /* tp_repr */
+ 0, /* tp_as _number*/
+ 0, /* tp_as _sequence*/
+ 0, /* tp_as _mapping*/
+ 0, /* tp_hash */
+ 0, /* tp_call */
+ 0, /* tp_str */
+ 0, /* tp_getattro */
+ 0, /* tp_setattro */
+ 0, /* tp_as_buffer*/
+ G_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
+ "Undocumented internal class", /* tp_doc */
+ (traverseproc)green_traverse, /* tp_traverse */
+ (inquiry)green_clear, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ 0, /* tp_iter */
+ 0, /* tp_iternext */
+ 0, /* tp_methods */
+ 0, /* tp_members */
+ green_unswitchable_getsets, /* tp_getset */
+ &PyGreenlet_Type, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)green_init, /* tp_init */
+ PyType_GenericAlloc, /* tp_alloc */
+ (newfunc)green_unswitchable_new, /* tp_new */
+ PyObject_GC_Del, /* tp_free */
+ (inquiry)green_is_gc, /* tp_is_gc */
+};
+
+
+PyDoc_STRVAR(mod_getcurrent_doc,
+ "getcurrent() -> greenlet\n"
+ "\n"
+ "Returns the current greenlet (i.e. the one which called this "
+ "function).\n");
+
+static PyObject*
+mod_getcurrent(PyObject* UNUSED(module))
+{
+ return GET_THREAD_STATE().state().get_current().relinquish_ownership_o();
+}
+
+PyDoc_STRVAR(mod_settrace_doc,
+ "settrace(callback) -> object\n"
+ "\n"
+ "Sets a new tracing function and returns the previous one.\n");
+static PyObject*
+mod_settrace(PyObject* UNUSED(module), PyObject* args)
+{
+ PyArgParseParam tracefunc;
+ if (!PyArg_ParseTuple(args, "O", &tracefunc)) {
+ return NULL;
+ }
+ ThreadState& state = GET_THREAD_STATE();
+ OwnedObject previous = state.get_tracefunc();
+ if (!previous) {
+ previous = Py_None;
+ }
+
+ state.set_tracefunc(tracefunc);
+
+ return previous.relinquish_ownership();
+}
+
+PyDoc_STRVAR(mod_gettrace_doc,
+ "gettrace() -> object\n"
+ "\n"
+ "Returns the currently set tracing function, or None.\n");
+
+static PyObject*
+mod_gettrace(PyObject* UNUSED(module))
+{
+ OwnedObject tracefunc = GET_THREAD_STATE().state().get_tracefunc();
+ if (!tracefunc) {
+ tracefunc = Py_None;
+ }
+ return tracefunc.relinquish_ownership();
+}
+
+PyDoc_STRVAR(mod_set_thread_local_doc,
+ "set_thread_local(key, value) -> None\n"
+ "\n"
+ "Set a value in the current thread-local dictionary. Debbuging only.\n");
+
+static PyObject*
+mod_set_thread_local(PyObject* UNUSED(module), PyObject* args)
+{
+ PyArgParseParam key;
+ PyArgParseParam value;
+ PyObject* result = NULL;
+
+ if (PyArg_UnpackTuple(args, "set_thread_local", 2, 2, &key, &value)) {
+ if(PyDict_SetItem(
+ PyThreadState_GetDict(), // borrow
+ key,
+ value) == 0 ) {
+ // success
+ Py_INCREF(Py_None);
+ result = Py_None;
+ }
+ }
+ return result;
+}
+
+PyDoc_STRVAR(mod_get_pending_cleanup_count_doc,
+ "get_pending_cleanup_count() -> Integer\n"
+ "\n"
+ "Get the number of greenlet cleanup operations pending. Testing only.\n");
+
+
+static PyObject*
+mod_get_pending_cleanup_count(PyObject* UNUSED(module))
+{
+ LockGuard cleanup_lock(*mod_globs->thread_states_to_destroy_lock);
+ return PyLong_FromSize_t(mod_globs->thread_states_to_destroy.size());
+}
+
+PyDoc_STRVAR(mod_get_total_main_greenlets_doc,
+ "get_total_main_greenlets() -> Integer\n"
+ "\n"
+ "Quickly return the number of main greenlets that exist. Testing only.\n");
+
+static PyObject*
+mod_get_total_main_greenlets(PyObject* UNUSED(module))
+{
+ return PyLong_FromSize_t(G_TOTAL_MAIN_GREENLETS);
+}
+
+PyDoc_STRVAR(mod_get_clocks_used_doing_optional_cleanup_doc,
+ "get_clocks_used_doing_optional_cleanup() -> Integer\n"
+ "\n"
+ "Get the number of clock ticks the program has used doing optional "
+ "greenlet cleanup.\n"
+ "Beginning in greenlet 2.0, greenlet tries to find and dispose of greenlets\n"
+ "that leaked after a thread exited. This requires invoking Python's garbage collector,\n"
+ "which may have a performance cost proportional to the number of live objects.\n"
+ "This function returns the amount of processor time\n"
+ "greenlet has used to do this. In programs that run with very large amounts of live\n"
+ "objects, this metric can be used to decide whether the cost of doing this cleanup\n"
+ "is worth the memory leak being corrected. If not, you can disable the cleanup\n"
+ "using ``enable_optional_cleanup(False)``.\n"
+ "The units are arbitrary and can only be compared to themselves (similarly to ``time.clock()``);\n"
+ "for example, to see how it scales with your heap. You can attempt to convert them into seconds\n"
+ "by dividing by the value of CLOCKS_PER_SEC."
+ "If cleanup has been disabled, returns None."
+ "\n"
+ "This is an implementation specific, provisional API. It may be changed or removed\n"
+ "in the future.\n"
+ ".. versionadded:: 2.0"
+ );
+static PyObject*
+mod_get_clocks_used_doing_optional_cleanup(PyObject* UNUSED(module))
+{
+ std::clock_t& clocks = ThreadState::clocks_used_doing_gc();
+
+ if (clocks == std::clock_t(-1)) {
+ Py_RETURN_NONE;
+ }
+ // This might not actually work on some implementations; clock_t
+ // is an opaque type.
+ return PyLong_FromSsize_t(clocks);
+}
+
+PyDoc_STRVAR(mod_enable_optional_cleanup_doc,
+ "mod_enable_optional_cleanup(bool) -> None\n"
+ "\n"
+ "Enable or disable optional cleanup operations.\n"
+ "See ``get_clocks_used_doing_optional_cleanup()`` for details.\n"
+ );
+static PyObject*
+mod_enable_optional_cleanup(PyObject* UNUSED(module), PyObject* flag)
+{
+ int is_true = PyObject_IsTrue(flag);
+ if (is_true == -1) {
+ return nullptr;
+ }
+
+ std::clock_t& clocks = ThreadState::clocks_used_doing_gc();
+ if (is_true) {
+ // If we already have a value, we don't want to lose it.
+ if (clocks == std::clock_t(-1)) {
+ clocks = 0;
+ }
+ }
+ else {
+ clocks = std::clock_t(-1);
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(mod_get_tstate_trash_delete_nesting_doc,
+ "get_tstate_trash_delete_nesting() -> Integer\n"
+ "\n"
+ "Return the 'trash can' nesting level. Testing only.\n");
+static PyObject*
+mod_get_tstate_trash_delete_nesting(PyObject* UNUSED(module))
+{
+ PyThreadState* tstate = PyThreadState_GET();
+
+#if GREENLET_PY312
+ return PyLong_FromLong(tstate->trash.delete_nesting);
+#else
+ return PyLong_FromLong(tstate->trash_delete_nesting);
+#endif
+}
+
+static PyMethodDef GreenMethods[] = {
+ {"getcurrent",
+ (PyCFunction)mod_getcurrent,
+ METH_NOARGS,
+ mod_getcurrent_doc},
+ {"settrace", (PyCFunction)mod_settrace, METH_VARARGS, mod_settrace_doc},
+ {"gettrace", (PyCFunction)mod_gettrace, METH_NOARGS, mod_gettrace_doc},
+ {"set_thread_local", (PyCFunction)mod_set_thread_local, METH_VARARGS, mod_set_thread_local_doc},
+ {"get_pending_cleanup_count", (PyCFunction)mod_get_pending_cleanup_count, METH_NOARGS, mod_get_pending_cleanup_count_doc},
+ {"get_total_main_greenlets", (PyCFunction)mod_get_total_main_greenlets, METH_NOARGS, mod_get_total_main_greenlets_doc},
+ {"get_clocks_used_doing_optional_cleanup", (PyCFunction)mod_get_clocks_used_doing_optional_cleanup, METH_NOARGS, mod_get_clocks_used_doing_optional_cleanup_doc},
+ {"enable_optional_cleanup", (PyCFunction)mod_enable_optional_cleanup, METH_O, mod_enable_optional_cleanup_doc},
+ {"get_tstate_trash_delete_nesting", (PyCFunction)mod_get_tstate_trash_delete_nesting, METH_NOARGS, mod_get_tstate_trash_delete_nesting_doc},
+ {NULL, NULL} /* Sentinel */
+};
+
+static const char* const copy_on_greentype[] = {
+ "getcurrent",
+ "error",
+ "GreenletExit",
+ "settrace",
+ "gettrace",
+ NULL
+};
+
+static struct PyModuleDef greenlet_module_def = {
+ PyModuleDef_HEAD_INIT,
+ "greenlet._greenlet",
+ NULL,
+ -1,
+ GreenMethods,
+};
+
+
+
+static PyObject*
+greenlet_internal_mod_init() noexcept
+{
+ static void* _PyGreenlet_API[PyGreenlet_API_pointers];
+
+ try {
+ CreatedModule m(greenlet_module_def);
+
+ Require(PyType_Ready(&PyGreenlet_Type));
+ Require(PyType_Ready(&PyGreenletUnswitchable_Type));
+
+ mod_globs = new greenlet::GreenletGlobals;
+ ThreadState::init();
+
+ m.PyAddObject("greenlet", PyGreenlet_Type);
+ m.PyAddObject("UnswitchableGreenlet", PyGreenletUnswitchable_Type);
+ m.PyAddObject("error", mod_globs->PyExc_GreenletError);
+ m.PyAddObject("GreenletExit", mod_globs->PyExc_GreenletExit);
+
+ m.PyAddObject("GREENLET_USE_GC", 1);
+ m.PyAddObject("GREENLET_USE_TRACING", 1);
+ m.PyAddObject("GREENLET_USE_CONTEXT_VARS", 1L);
+ m.PyAddObject("GREENLET_USE_STANDARD_THREADING", 1L);
+
+ OwnedObject clocks_per_sec = OwnedObject::consuming(PyLong_FromSsize_t(CLOCKS_PER_SEC));
+ m.PyAddObject("CLOCKS_PER_SEC", clocks_per_sec);
+
+ /* also publish module-level data as attributes of the greentype. */
+ // XXX: This is weird, and enables a strange pattern of
+ // confusing the class greenlet with the module greenlet; with
+ // the exception of (possibly) ``getcurrent()``, this
+ // shouldn't be encouraged so don't add new items here.
+ for (const char* const* p = copy_on_greentype; *p; p++) {
+ OwnedObject o = m.PyRequireAttr(*p);
+ PyDict_SetItemString(PyGreenlet_Type.tp_dict, *p, o.borrow());
+ }
+
+ /*
+ * Expose C API
+ */
+
+ /* types */
+ _PyGreenlet_API[PyGreenlet_Type_NUM] = (void*)&PyGreenlet_Type;
+
+ /* exceptions */
+ _PyGreenlet_API[PyExc_GreenletError_NUM] = (void*)mod_globs->PyExc_GreenletError;
+ _PyGreenlet_API[PyExc_GreenletExit_NUM] = (void*)mod_globs->PyExc_GreenletExit;
+
+ /* methods */
+ _PyGreenlet_API[PyGreenlet_New_NUM] = (void*)PyGreenlet_New;
+ _PyGreenlet_API[PyGreenlet_GetCurrent_NUM] = (void*)PyGreenlet_GetCurrent;
+ _PyGreenlet_API[PyGreenlet_Throw_NUM] = (void*)PyGreenlet_Throw;
+ _PyGreenlet_API[PyGreenlet_Switch_NUM] = (void*)PyGreenlet_Switch;
+ _PyGreenlet_API[PyGreenlet_SetParent_NUM] = (void*)PyGreenlet_SetParent;
+
+ /* Previously macros, but now need to be functions externally. */
+ _PyGreenlet_API[PyGreenlet_MAIN_NUM] = (void*)Extern_PyGreenlet_MAIN;
+ _PyGreenlet_API[PyGreenlet_STARTED_NUM] = (void*)Extern_PyGreenlet_STARTED;
+ _PyGreenlet_API[PyGreenlet_ACTIVE_NUM] = (void*)Extern_PyGreenlet_ACTIVE;
+ _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM] = (void*)Extern_PyGreenlet_GET_PARENT;
+
+ /* XXX: Note that our module name is ``greenlet._greenlet``, but for
+ backwards compatibility with existing C code, we need the _C_API to
+ be directly in greenlet.
+ */
+ const NewReference c_api_object(Require(
+ PyCapsule_New(
+ (void*)_PyGreenlet_API,
+ "greenlet._C_API",
+ NULL)));
+ m.PyAddObject("_C_API", c_api_object);
+ assert(c_api_object.REFCNT() == 2);
+
+ // cerr << "Sizes:"
+ // << "\n\tGreenlet : " << sizeof(Greenlet)
+ // << "\n\tUserGreenlet : " << sizeof(UserGreenlet)
+ // << "\n\tMainGreenlet : " << sizeof(MainGreenlet)
+ // << "\n\tExceptionState : " << sizeof(greenlet::ExceptionState)
+ // << "\n\tPythonState : " << sizeof(greenlet::PythonState)
+ // << "\n\tStackState : " << sizeof(greenlet::StackState)
+ // << "\n\tSwitchingArgs : " << sizeof(greenlet::SwitchingArgs)
+ // << "\n\tOwnedObject : " << sizeof(greenlet::refs::OwnedObject)
+ // << "\n\tBorrowedObject : " << sizeof(greenlet::refs::BorrowedObject)
+ // << "\n\tPyGreenlet : " << sizeof(PyGreenlet)
+ // << endl;
+
+ return m.borrow(); // But really it's the main reference.
+ }
+ catch (const LockInitError& e) {
+ PyErr_SetString(PyExc_MemoryError, e.what());
+ return NULL;
+ }
+ catch (const PyErrOccurred&) {
+ return NULL;
+ }
+
+}
+
+extern "C" {
+
+PyMODINIT_FUNC
+PyInit__greenlet(void)
+{
+ return greenlet_internal_mod_init();
+}
+
+}; // extern C
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#elif defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
diff --git a/venv/Lib/site-packages/greenlet/greenlet.h b/venv/Lib/site-packages/greenlet/greenlet.h
new file mode 100644
index 0000000..d02a16e
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet.h
@@ -0,0 +1,164 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+
+/* Greenlet object interface */
+
+#ifndef Py_GREENLETOBJECT_H
+#define Py_GREENLETOBJECT_H
+
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is deprecated and undocumented. It does not change. */
+#define GREENLET_VERSION "1.0.0"
+
+#ifndef GREENLET_MODULE
+#define implementation_ptr_t void*
+#endif
+
+typedef struct _greenlet {
+ PyObject_HEAD
+ PyObject* weakreflist;
+ PyObject* dict;
+ implementation_ptr_t pimpl;
+} PyGreenlet;
+
+#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
+
+
+/* C API functions */
+
+/* Total number of symbols that are exported */
+#define PyGreenlet_API_pointers 12
+
+#define PyGreenlet_Type_NUM 0
+#define PyExc_GreenletError_NUM 1
+#define PyExc_GreenletExit_NUM 2
+
+#define PyGreenlet_New_NUM 3
+#define PyGreenlet_GetCurrent_NUM 4
+#define PyGreenlet_Throw_NUM 5
+#define PyGreenlet_Switch_NUM 6
+#define PyGreenlet_SetParent_NUM 7
+
+#define PyGreenlet_MAIN_NUM 8
+#define PyGreenlet_STARTED_NUM 9
+#define PyGreenlet_ACTIVE_NUM 10
+#define PyGreenlet_GET_PARENT_NUM 11
+
+#ifndef GREENLET_MODULE
+/* This section is used by modules that uses the greenlet C API */
+static void** _PyGreenlet_API = NULL;
+
+# define PyGreenlet_Type \
+ (*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
+
+# define PyExc_GreenletError \
+ ((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
+
+# define PyExc_GreenletExit \
+ ((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
+
+/*
+ * PyGreenlet_New(PyObject *args)
+ *
+ * greenlet.greenlet(run, parent=None)
+ */
+# define PyGreenlet_New \
+ (*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
+ _PyGreenlet_API[PyGreenlet_New_NUM])
+
+/*
+ * PyGreenlet_GetCurrent(void)
+ *
+ * greenlet.getcurrent()
+ */
+# define PyGreenlet_GetCurrent \
+ (*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
+
+/*
+ * PyGreenlet_Throw(
+ * PyGreenlet *greenlet,
+ * PyObject *typ,
+ * PyObject *val,
+ * PyObject *tb)
+ *
+ * g.throw(...)
+ */
+# define PyGreenlet_Throw \
+ (*(PyObject * (*)(PyGreenlet * self, \
+ PyObject * typ, \
+ PyObject * val, \
+ PyObject * tb)) \
+ _PyGreenlet_API[PyGreenlet_Throw_NUM])
+
+/*
+ * PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
+ *
+ * g.switch(*args, **kwargs)
+ */
+# define PyGreenlet_Switch \
+ (*(PyObject * \
+ (*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
+ _PyGreenlet_API[PyGreenlet_Switch_NUM])
+
+/*
+ * PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
+ *
+ * g.parent = new_parent
+ */
+# define PyGreenlet_SetParent \
+ (*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
+ _PyGreenlet_API[PyGreenlet_SetParent_NUM])
+
+/*
+ * PyGreenlet_GetParent(PyObject* greenlet)
+ *
+ * return greenlet.parent;
+ *
+ * This could return NULL even if there is no exception active.
+ * If it does not return NULL, you are responsible for decrementing the
+ * reference count.
+ */
+# define PyGreenlet_GetParent \
+ (*(PyGreenlet* (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
+
+/*
+ * deprecated, undocumented alias.
+ */
+# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
+
+# define PyGreenlet_MAIN \
+ (*(int (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_MAIN_NUM])
+
+# define PyGreenlet_STARTED \
+ (*(int (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_STARTED_NUM])
+
+# define PyGreenlet_ACTIVE \
+ (*(int (*)(PyGreenlet*)) \
+ _PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
+
+
+
+
+/* Macro that imports greenlet and initializes C API */
+/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
+ keep the older definition to be sure older code that might have a copy of
+ the header still works. */
+# define PyGreenlet_Import() \
+ { \
+ _PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
+ }
+
+#endif /* GREENLET_MODULE */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py_GREENLETOBJECT_H */
diff --git a/venv/Lib/site-packages/greenlet/greenlet_allocator.hpp b/venv/Lib/site-packages/greenlet/greenlet_allocator.hpp
new file mode 100644
index 0000000..b452f54
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_allocator.hpp
@@ -0,0 +1,63 @@
+#ifndef GREENLET_ALLOCATOR_HPP
+#define GREENLET_ALLOCATOR_HPP
+
+#define PY_SSIZE_T_CLEAN
+#include
+#include
+#include "greenlet_compiler_compat.hpp"
+
+
+namespace greenlet
+{
+ // This allocator is stateless; all instances are identical.
+ // It can *ONLY* be used when we're sure we're holding the GIL
+ // (Python's allocators require the GIL).
+ template
+ struct PythonAllocator : public std::allocator {
+
+ PythonAllocator(const PythonAllocator& UNUSED(other))
+ : std::allocator()
+ {
+ }
+
+ PythonAllocator(const std::allocator other)
+ : std::allocator(other)
+ {}
+
+ template
+ PythonAllocator(const std::allocator& other)
+ : std::allocator(other)
+ {
+ }
+
+ PythonAllocator() : std::allocator() {}
+
+ T* allocate(size_t number_objects, const void* UNUSED(hint)=0)
+ {
+ void* p;
+ if (number_objects == 1)
+ p = PyObject_Malloc(sizeof(T));
+ else
+ p = PyMem_Malloc(sizeof(T) * number_objects);
+ return static_cast(p);
+ }
+
+ void deallocate(T* t, size_t n)
+ {
+ void* p = t;
+ if (n == 1) {
+ PyObject_Free(p);
+ }
+ else
+ PyMem_Free(p);
+ }
+ // This member is deprecated in C++17 and removed in C++20
+ template< class U >
+ struct rebind {
+ typedef PythonAllocator other;
+ };
+
+ };
+}
+
+#endif
diff --git a/venv/Lib/site-packages/greenlet/greenlet_compiler_compat.hpp b/venv/Lib/site-packages/greenlet/greenlet_compiler_compat.hpp
new file mode 100644
index 0000000..ee5bbdd
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_compiler_compat.hpp
@@ -0,0 +1,95 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+#ifndef GREENLET_COMPILER_COMPAT_HPP
+#define GREENLET_COMPILER_COMPAT_HPP
+
+/**
+ * Definitions to aid with compatibility with different compilers.
+ *
+ * .. caution:: Use extreme care with noexcept.
+ * Some compilers and runtimes, specifically gcc/libgcc/libstdc++ on
+ * Linux, implement stack unwinding by throwing an uncatchable
+ * exception, one that specifically does not appear to be an active
+ * exception to the rest of the runtime. If this happens while we're in a noexcept function,
+ * we have violated our dynamic exception contract, and so the runtime
+ * will call std::terminate(), which kills the process with the
+ * unhelpful message "terminate called without an active exception".
+ *
+ * This has happened in this scenario: A background thread is running
+ * a greenlet that has made a native call and released the GIL.
+ * Meanwhile, the main thread finishes and starts shutting down the
+ * interpreter. When the background thread is scheduled again and
+ * attempts to obtain the GIL, it notices that the interpreter is
+ * exiting and calls ``pthread_exit()``. This in turn starts to unwind
+ * the stack by throwing that exception. But we had the ``PyCall``
+ * functions annotated as noexcept, so the runtime terminated us.
+ *
+ * #2 0x00007fab26fec2b7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6
+ * #3 0x00007fab26febb3c in __gxx_personality_v0 () from /lib/x86_64-linux-gnu/libstdc++.so.6
+ * #4 0x00007fab26f34de6 in ?? () from /lib/x86_64-linux-gnu/libgcc_s.so.1
+ * #6 0x00007fab276a34c6 in __GI___pthread_unwind at ./nptl/unwind.c:130
+ * #7 0x00007fab2769bd3a in __do_cancel () at ../sysdeps/nptl/pthreadP.h:280
+ * #8 __GI___pthread_exit (value=value@entry=0x0) at ./nptl/pthread_exit.c:36
+ * #9 0x000000000052e567 in PyThread_exit_thread () at ../Python/thread_pthread.h:370
+ * #10 0x00000000004d60b5 in take_gil at ../Python/ceval_gil.h:224
+ * #11 0x00000000004d65f9 in PyEval_RestoreThread at ../Python/ceval.c:467
+ * #12 0x000000000060cce3 in setipaddr at ../Modules/socketmodule.c:1203
+ * #13 0x00000000006101cd in socket_gethostbyname
+ */
+
+#include
+
+# if defined(__clang__)
+# define G_FP_TMPL_STATIC static
+# else
+// GCC has no problem allowing static function pointers, but emits
+// tons of warnings about "whose type uses the anonymous namespace [-Wsubobject-linkage]"
+# define G_FP_TMPL_STATIC
+# endif
+
+# define G_NO_COPIES_OF_CLS(Cls) private: \
+ Cls(const Cls& other) = delete; \
+ Cls& operator=(const Cls& other) = delete
+
+# define G_NO_ASSIGNMENT_OF_CLS(Cls) private: \
+ Cls& operator=(const Cls& other) = delete
+
+# define G_NO_COPY_CONSTRUCTOR_OF_CLS(Cls) private: \
+ Cls(const Cls& other) = delete;
+
+
+// CAUTION: MSVC is stupidly picky:
+//
+// "The compiler ignores, without warning, any __declspec keywords
+// placed after * or & and in front of the variable identifier in a
+// declaration."
+// (https://docs.microsoft.com/en-us/cpp/cpp/declspec?view=msvc-160)
+//
+// So pointer return types must be handled differently (because of the
+// trailing *), or you get inscrutable compiler warnings like "error
+// C2059: syntax error: ''"
+//
+// In C++ 11, there is a standard syntax for attributes, and
+// GCC defines an attribute to use with this: [[gnu:noinline]].
+// In the future, this is expected to become standard.
+
+#if defined(__GNUC__) || defined(__clang__)
+/* We used to check for GCC 4+ or 3.4+, but those compilers are
+ laughably out of date. Just assume they support it. */
+# define GREENLET_NOINLINE(name) __attribute__((noinline)) name
+# define GREENLET_NOINLINE_P(rtype, name) rtype __attribute__((noinline)) name
+# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
+#elif defined(_MSC_VER)
+/* We used to check for && (_MSC_VER >= 1300) but that's also out of date. */
+# define GREENLET_NOINLINE(name) __declspec(noinline) name
+# define GREENLET_NOINLINE_P(rtype, name) __declspec(noinline) rtype name
+# define UNUSED(x) UNUSED_ ## x
+#endif
+
+#if defined(_MSC_VER)
+# define G_NOEXCEPT_WIN32 noexcept
+#else
+# define G_NOEXCEPT_WIN32
+#endif
+
+
+#endif
diff --git a/venv/Lib/site-packages/greenlet/greenlet_cpython_add_pending.hpp b/venv/Lib/site-packages/greenlet/greenlet_cpython_add_pending.hpp
new file mode 100644
index 0000000..0d28efd
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_cpython_add_pending.hpp
@@ -0,0 +1,172 @@
+#ifndef GREENLET_CPYTHON_ADD_PENDING_HPP
+#define GREENLET_CPYTHON_ADD_PENDING_HPP
+
+#if (PY_VERSION_HEX >= 0x30800A0 && PY_VERSION_HEX < 0x3090000) && !(defined(_WIN32) || defined(WIN32))
+// XXX: From Python 3.8a3 [1] up until Python 3.9a6 [2][3],
+// ``Py_AddPendingCall`` would try to produce a Python exception if
+// the interpreter was in the beginning of shutting down when this
+// function is called. However, ``Py_AddPendingCall`` doesn't require
+// the GIL, and we are absolutely not holding it when we make that
+// call. That means that trying to create the Python exception is
+// using the C API in an undefined state; here the C API detects this
+// and aborts the process with an error ("Fatal Python error: Python
+// memory allocator called without holding the GIL": Add ->
+// PyErr_SetString -> PyUnicode_New -> PyObject_Malloc). This arises
+// (obviously) in multi-threaded programs and happens if one thread is
+// exiting and cleaning up its thread-local data while the other
+// thread is trying to shut down the interpreter. A crash on shutdown
+// is still a crash and could result in data loss (e.g., daemon
+// threads are still running, pending signal handlers may be present,
+// buffers may not be flushed, there may be __del__ that need run,
+// etc), so we have to work around it.
+//
+// Of course, we can (and do) check for whether the interpreter is
+// shutting down before calling ``Py_AddPendingCall``, but that's a
+// race condition since we don't hold the GIL, and so we may not
+// actually get the right answer. Plus, ``Py_FinalizeEx`` actually
+// calls ``_Py_FinishPendingCalls`` (which sets the pending->finishing
+// flag, which is used to gate creating the exceptioen) *before*
+// publishing any other data that would let us detect the shutdown
+// (such as runtime->finalizing). So that point is moot.
+//
+// Our solution for those versions is to inline the same code, without
+// the problematic bit that sets the exception. Unfortunately, all of
+// the structure definitions are private/opaque, *and* we can't
+// actually count on being able to include their definitions from
+// ``internal/pycore_*``, because on some platforms those header files
+// are incomplete (i.e., on macOS with macports 3.8, the includes are
+// fine, but on Ubuntu jammy with 3.8 from ppa:deadsnakes or GitHub
+// Actions 3.8 (I think it's Ubuntu 18.04), they con't be used; at
+// least, I couldn't get them to work). So we need to define the
+// structures and _PyRuntime data member ourself. Yet more
+// unfortunately, _PyRuntime won't link on Windows, so we can only do
+// this on other platforms.
+//
+// [1] https://github.com/python/cpython/commit/842a2f07f2f08a935ef470bfdaeef40f87490cfc
+// [2] https://github.com/python/cpython/commit/cfc3c2f8b34d3864717ab584c5b6c260014ba55a
+// [3] https://github.com/python/cpython/issues/81308
+# define GREENLET_BROKEN_PY_ADD_PENDING 1
+
+// When defining these structures, the important thing is to get
+// binary compatibility, i.e., structure layout. For that, we only
+// need to define fields up to the ones we use; after that they're
+// irrelevant UNLESS the structure is included in another structure
+// *before* the structure we're interested in --- in that case, it
+// must be complete. Ellipsis indicate elided trailing members.
+// Pointer types are changed to void* to keep from having to define
+// more structures.
+
+// From "internal/pycore_atomic.h"
+
+// There are several different definitions of this, including the
+// plain ``int`` version, a ``volatile int`` and an ``_Atomic int``
+// I don't think any of those change the size/layout.
+typedef struct _Py_atomic_int {
+ volatile int _value;
+} _Py_atomic_int;
+
+// This needs too much infrastructure, so we just do a regular store.
+#define _Py_atomic_store_relaxed(ATOMIC_VAL, NEW_VAL) \
+ (ATOMIC_VAL)->_value = NEW_VAL
+
+
+
+// From "internal/pycore_pymem.h"
+#define NUM_GENERATIONS 3
+
+
+struct gc_generation {
+ PyGC_Head head; // We already have this defined.
+ int threshold;
+ int count;
+};
+struct gc_generation_stats {
+ Py_ssize_t collections;
+ Py_ssize_t collected;
+ Py_ssize_t uncollectable;
+};
+
+struct _gc_runtime_state {
+ void *trash_delete_later;
+ int trash_delete_nesting;
+ int enabled;
+ int debug;
+ struct gc_generation generations[NUM_GENERATIONS];
+ void *generation0;
+ struct gc_generation permanent_generation;
+ struct gc_generation_stats generation_stats[NUM_GENERATIONS];
+ int collecting;
+ void *garbage;
+ void *callbacks;
+ Py_ssize_t long_lived_total;
+ Py_ssize_t long_lived_pending;
+};
+
+// From "internal/pycore_pystate.h"
+struct _pending_calls {
+ int finishing;
+ PyThread_type_lock lock;
+ _Py_atomic_int calls_to_do;
+ int async_exc;
+#define NPENDINGCALLS 32
+ struct {
+ int (*func)(void *);
+ void *arg;
+ } calls[NPENDINGCALLS];
+ int first;
+ int last;
+};
+
+struct _ceval_runtime_state {
+ int recursion_limit;
+ int tracing_possible;
+ _Py_atomic_int eval_breaker;
+ _Py_atomic_int gil_drop_request;
+ struct _pending_calls pending;
+ // ...
+};
+
+typedef struct pyruntimestate {
+ int preinitializing;
+ int preinitialized;
+ int core_initialized;
+ int initialized;
+ void *finalizing;
+
+ struct pyinterpreters {
+ PyThread_type_lock mutex;
+ void *head;
+ void *main;
+ int64_t next_id;
+ } interpreters;
+ // XXX Remove this field once we have a tp_* slot.
+ struct _xidregistry {
+ PyThread_type_lock mutex;
+ void *head;
+ } xidregistry;
+
+ unsigned long main_thread;
+
+#define NEXITFUNCS 32
+ void (*exitfuncs[NEXITFUNCS])(void);
+ int nexitfuncs;
+
+ struct _gc_runtime_state gc;
+ struct _ceval_runtime_state ceval;
+ // ...
+} _PyRuntimeState;
+
+#define SIGNAL_PENDING_CALLS(ceval) \
+ do { \
+ _Py_atomic_store_relaxed(&(ceval)->pending.calls_to_do, 1); \
+ _Py_atomic_store_relaxed(&(ceval)->eval_breaker, 1); \
+ } while (0)
+
+extern _PyRuntimeState _PyRuntime;
+
+#else
+# define GREENLET_BROKEN_PY_ADD_PENDING 0
+#endif
+
+
+#endif
diff --git a/venv/Lib/site-packages/greenlet/greenlet_cpython_compat.hpp b/venv/Lib/site-packages/greenlet/greenlet_cpython_compat.hpp
new file mode 100644
index 0000000..cdc1617
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_cpython_compat.hpp
@@ -0,0 +1,127 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+#ifndef GREENLET_CPYTHON_COMPAT_H
+#define GREENLET_CPYTHON_COMPAT_H
+
+/**
+ * Helpers for compatibility with multiple versions of CPython.
+ */
+
+#define PY_SSIZE_T_CLEAN
+#include "Python.h"
+
+
+#if PY_VERSION_HEX >= 0x30A00B1
+# define GREENLET_PY310 1
+/*
+Python 3.10 beta 1 changed tstate->use_tracing to a nested cframe member.
+See https://github.com/python/cpython/pull/25276
+We have to save and restore this as well.
+*/
+# define GREENLET_USE_CFRAME 1
+#else
+# define GREENLET_USE_CFRAME 0
+# define GREENLET_PY310 0
+#endif
+
+
+
+#if PY_VERSION_HEX >= 0x30B00A4
+/*
+Greenlet won't compile on anything older than Python 3.11 alpha 4 (see
+https://bugs.python.org/issue46090). Summary of breaking internal changes:
+- Python 3.11 alpha 1 changed how frame objects are represented internally.
+ - https://github.com/python/cpython/pull/30122
+- Python 3.11 alpha 3 changed how recursion limits are stored.
+ - https://github.com/python/cpython/pull/29524
+- Python 3.11 alpha 4 changed how exception state is stored. It also includes a
+ change to help greenlet save and restore the interpreter frame "data stack".
+ - https://github.com/python/cpython/pull/30122
+ - https://github.com/python/cpython/pull/30234
+*/
+# define GREENLET_PY311 1
+#else
+# define GREENLET_PY311 0
+#endif
+
+
+#if PY_VERSION_HEX >= 0x30C0000
+# define GREENLET_PY312 1
+#else
+# define GREENLET_PY312 0
+#endif
+
+#ifndef Py_SET_REFCNT
+/* Py_REFCNT and Py_SIZE macros are converted to functions
+https://bugs.python.org/issue39573 */
+# define Py_SET_REFCNT(obj, refcnt) Py_REFCNT(obj) = (refcnt)
+#endif
+
+#ifndef _Py_DEC_REFTOTAL
+/* _Py_DEC_REFTOTAL macro has been removed from Python 3.9 by:
+ https://github.com/python/cpython/commit/49932fec62c616ec88da52642339d83ae719e924
+
+ The symbol we use to replace it was removed by at least 3.12.
+*/
+# ifdef Py_REF_DEBUG
+# if GREENLET_PY312
+# define _Py_DEC_REFTOTAL
+# else
+# define _Py_DEC_REFTOTAL _Py_RefTotal--
+# endif
+# else
+# define _Py_DEC_REFTOTAL
+# endif
+#endif
+// Define these flags like Cython does if we're on an old version.
+#ifndef Py_TPFLAGS_CHECKTYPES
+ #define Py_TPFLAGS_CHECKTYPES 0
+#endif
+#ifndef Py_TPFLAGS_HAVE_INDEX
+ #define Py_TPFLAGS_HAVE_INDEX 0
+#endif
+#ifndef Py_TPFLAGS_HAVE_NEWBUFFER
+ #define Py_TPFLAGS_HAVE_NEWBUFFER 0
+#endif
+
+#ifndef Py_TPFLAGS_HAVE_VERSION_TAG
+ #define Py_TPFLAGS_HAVE_VERSION_TAG 0
+#endif
+
+#define G_TPFLAGS_DEFAULT Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_VERSION_TAG | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_HAVE_NEWBUFFER | Py_TPFLAGS_HAVE_GC
+
+
+#if PY_VERSION_HEX < 0x03090000
+// The official version only became available in 3.9
+# define PyObject_GC_IsTracked(o) _PyObject_GC_IS_TRACKED(o)
+#endif
+
+
+// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2
+#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
+static inline void PyThreadState_EnterTracing(PyThreadState *tstate)
+{
+ tstate->tracing++;
+#if PY_VERSION_HEX >= 0x030A00A1
+ tstate->cframe->use_tracing = 0;
+#else
+ tstate->use_tracing = 0;
+#endif
+}
+#endif
+
+// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2
+#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION)
+static inline void PyThreadState_LeaveTracing(PyThreadState *tstate)
+{
+ tstate->tracing--;
+ int use_tracing = (tstate->c_tracefunc != NULL
+ || tstate->c_profilefunc != NULL);
+#if PY_VERSION_HEX >= 0x030A00A1
+ tstate->cframe->use_tracing = use_tracing;
+#else
+ tstate->use_tracing = use_tracing;
+#endif
+}
+#endif
+
+#endif /* GREENLET_CPYTHON_COMPAT_H */
diff --git a/venv/Lib/site-packages/greenlet/greenlet_exceptions.hpp b/venv/Lib/site-packages/greenlet/greenlet_exceptions.hpp
new file mode 100644
index 0000000..3807018
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_exceptions.hpp
@@ -0,0 +1,150 @@
+#ifndef GREENLET_EXCEPTIONS_HPP
+#define GREENLET_EXCEPTIONS_HPP
+
+#define PY_SSIZE_T_CLEAN
+#include
+#include
+#include
+
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunused-function"
+#endif
+
+namespace greenlet {
+
+ class PyErrOccurred : public std::runtime_error
+ {
+ public:
+
+ // CAUTION: In debug builds, may run arbitrary Python code.
+ static const PyErrOccurred
+ from_current()
+ {
+ assert(PyErr_Occurred());
+#ifndef NDEBUG
+ // This is not exception safe, and
+ // not necessarily safe in general (what if it switches?)
+ // But we only do this in debug mode, where we are in
+ // tight control of what exceptions are getting raised and
+ // can prevent those issues.
+
+ // You can't call PyObject_Str with a pending exception.
+ PyObject* typ;
+ PyObject* val;
+ PyObject* tb;
+
+ PyErr_Fetch(&typ, &val, &tb);
+ PyObject* typs = PyObject_Str(typ);
+ PyObject* vals = PyObject_Str(val ? val : typ);
+ const char* typ_msg = PyUnicode_AsUTF8(typs);
+ const char* val_msg = PyUnicode_AsUTF8(vals);
+ PyErr_Restore(typ, val, tb);
+
+ std::string msg(typ_msg);
+ msg += ": ";
+ msg += val_msg;
+ PyErrOccurred ex(msg);
+ Py_XDECREF(typs);
+ Py_XDECREF(vals);
+
+ return ex;
+#else
+ return PyErrOccurred();
+#endif
+ }
+
+ PyErrOccurred() : std::runtime_error("")
+ {
+ assert(PyErr_Occurred());
+ }
+
+ PyErrOccurred(const std::string& msg) : std::runtime_error(msg)
+ {
+ assert(PyErr_Occurred());
+ }
+
+ PyErrOccurred(PyObject* exc_kind, const char* const msg)
+ : std::runtime_error(msg)
+ {
+ PyErr_SetString(exc_kind, msg);
+ }
+
+ PyErrOccurred(PyObject* exc_kind, const std::string msg)
+ : std::runtime_error(msg)
+ {
+ // This copies the c_str, so we don't have any lifetime
+ // issues to worry about.
+ PyErr_SetString(exc_kind, msg.c_str());
+ }
+ };
+
+ class TypeError : public PyErrOccurred
+ {
+ public:
+ TypeError(const char* const what)
+ : PyErrOccurred(PyExc_TypeError, what)
+ {
+ }
+ TypeError(const std::string what)
+ : PyErrOccurred(PyExc_TypeError, what)
+ {
+ }
+ };
+
+ class ValueError : public PyErrOccurred
+ {
+ public:
+ ValueError(const char* const what)
+ : PyErrOccurred(PyExc_ValueError, what)
+ {
+ }
+ };
+
+ class AttributeError : public PyErrOccurred
+ {
+ public:
+ AttributeError(const char* const what)
+ : PyErrOccurred(PyExc_AttributeError, what)
+ {
+ }
+ };
+
+ /**
+ * Calls `Py_FatalError` when constructed, so you can't actually
+ * throw this. It just makes static analysis easier.
+ */
+ class PyFatalError : public std::runtime_error
+ {
+ public:
+ PyFatalError(const char* const msg)
+ : std::runtime_error(msg)
+ {
+ Py_FatalError(msg);
+ }
+ };
+
+ static inline PyObject*
+ Require(PyObject* p, const std::string& msg="")
+ {
+ if (!p) {
+ throw PyErrOccurred(msg);
+ }
+ return p;
+ };
+
+ static inline void
+ Require(const int retval)
+ {
+ if (retval < 0) {
+ throw PyErrOccurred();
+ }
+ };
+
+
+};
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+#endif
diff --git a/venv/Lib/site-packages/greenlet/greenlet_greenlet.hpp b/venv/Lib/site-packages/greenlet/greenlet_greenlet.hpp
new file mode 100644
index 0000000..d52ce1f
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_greenlet.hpp
@@ -0,0 +1,805 @@
+#ifndef GREENLET_GREENLET_HPP
+#define GREENLET_GREENLET_HPP
+/*
+ * Declarations of the core data structures.
+*/
+
+#define PY_SSIZE_T_CLEAN
+#include
+
+#include "greenlet_compiler_compat.hpp"
+#include "greenlet_refs.hpp"
+#include "greenlet_cpython_compat.hpp"
+#include "greenlet_allocator.hpp"
+
+using greenlet::refs::OwnedObject;
+using greenlet::refs::OwnedGreenlet;
+using greenlet::refs::OwnedMainGreenlet;
+using greenlet::refs::BorrowedGreenlet;
+
+#if PY_VERSION_HEX < 0x30B00A6
+# define _PyCFrame CFrame
+# define _PyInterpreterFrame _interpreter_frame
+#endif
+
+#if GREENLET_PY312
+# include "internal/pycore_frame.h"
+#endif
+
+// XXX: TODO: Work to remove all virtual functions
+// for speed of calling and size of objects (no vtable).
+// One pattern is the Curiously Recurring Template
+namespace greenlet
+{
+ class ExceptionState
+ {
+ private:
+ G_NO_COPIES_OF_CLS(ExceptionState);
+
+ // Even though these are borrowed objects, we actually own
+ // them, when they're not null.
+ // XXX: Express that in the API.
+ private:
+ _PyErr_StackItem* exc_info;
+ _PyErr_StackItem exc_state;
+ public:
+ ExceptionState();
+ void operator<<(const PyThreadState *const tstate) noexcept;
+ void operator>>(PyThreadState* tstate) noexcept;
+ void clear() noexcept;
+
+ int tp_traverse(visitproc visit, void* arg) noexcept;
+ void tp_clear() noexcept;
+ };
+
+ template
+ void operator<<(const PyThreadState *const tstate, T& exc);
+
+ class PythonStateContext
+ {
+ protected:
+ greenlet::refs::OwnedContext _context;
+ public:
+ inline const greenlet::refs::OwnedContext& context() const
+ {
+ return this->_context;
+ }
+ inline greenlet::refs::OwnedContext& context()
+ {
+ return this->_context;
+ }
+
+ inline void tp_clear()
+ {
+ this->_context.CLEAR();
+ }
+
+ template
+ inline static PyObject* context(T* tstate)
+ {
+ return tstate->context;
+ }
+
+ template
+ inline static void context(T* tstate, PyObject* new_context)
+ {
+ tstate->context = new_context;
+ tstate->context_ver++;
+ }
+ };
+ class SwitchingArgs;
+ class PythonState : public PythonStateContext
+ {
+ public:
+ typedef greenlet::refs::OwnedReference OwnedFrame;
+ private:
+ G_NO_COPIES_OF_CLS(PythonState);
+ // We own this if we're suspended (although currently we don't
+ // tp_traverse into it; that's a TODO). If we're running, it's
+ // empty. If we get deallocated and *still* have a frame, it
+ // won't be reachable from the place that normally decref's
+ // it, so we need to do it (hence owning it).
+ OwnedFrame _top_frame;
+#if GREENLET_USE_CFRAME
+ _PyCFrame* cframe;
+ int use_tracing;
+#endif
+#if GREENLET_PY312
+ int py_recursion_depth;
+ int c_recursion_depth;
+#else
+ int recursion_depth;
+#endif
+ int trash_delete_nesting;
+#if GREENLET_PY311
+ _PyInterpreterFrame* current_frame;
+ _PyStackChunk* datastack_chunk;
+ PyObject** datastack_top;
+ PyObject** datastack_limit;
+#endif
+ // The PyInterpreterFrame list on 3.12+ contains some entries that are
+ // on the C stack, which can't be directly accessed while a greenlet is
+ // suspended. In order to keep greenlet gr_frame introspection working,
+ // we adjust stack switching to rewrite the interpreter frame list
+ // to skip these C-stack frames; we call this "exposing" the greenlet's
+ // frames because it makes them valid to work with in Python. Then when
+ // the greenlet is resumed we need to remember to reverse the operation
+ // we did. The C-stack frames are "entry frames" which are a low-level
+ // interpreter detail; they're not needed for introspection, but do
+ // need to be present for the eval loop to work.
+ void unexpose_frames();
+
+ public:
+
+ PythonState();
+ // You can use this for testing whether we have a frame
+ // or not. It returns const so they can't modify it.
+ const OwnedFrame& top_frame() const noexcept;
+
+ inline void operator<<(const PyThreadState *const tstate) noexcept;
+ inline void operator>>(PyThreadState* tstate) noexcept;
+ void clear() noexcept;
+
+ int tp_traverse(visitproc visit, void* arg, bool visit_top_frame) noexcept;
+ void tp_clear(bool own_top_frame) noexcept;
+ void set_initial_state(const PyThreadState* const tstate) noexcept;
+#if GREENLET_USE_CFRAME
+ void set_new_cframe(_PyCFrame& frame) noexcept;
+#endif
+
+ inline void may_switch_away() noexcept;
+ inline void will_switch_from(PyThreadState *const origin_tstate) noexcept;
+ void did_finish(PyThreadState* tstate) noexcept;
+ };
+
+ class StackState
+ {
+ // By having only plain C (POD) members, no virtual functions
+ // or bases, we get a trivial assignment operator generated
+ // for us. However, that's not safe since we do manage memory.
+ // So we declare an assignment operator that only works if we
+ // don't have any memory allocated. (We don't use
+ // std::shared_ptr for reference counting just to keep this
+ // object small)
+ private:
+ char* _stack_start;
+ char* stack_stop;
+ char* stack_copy;
+ intptr_t _stack_saved;
+ StackState* stack_prev;
+ inline int copy_stack_to_heap_up_to(const char* const stop) noexcept;
+ inline void free_stack_copy() noexcept;
+
+ public:
+ /**
+ * Creates a started, but inactive, state, using *current*
+ * as the previous.
+ */
+ StackState(void* mark, StackState& current);
+ /**
+ * Creates an inactive, unstarted, state.
+ */
+ StackState();
+ ~StackState();
+ StackState(const StackState& other);
+ StackState& operator=(const StackState& other);
+ inline void copy_heap_to_stack(const StackState& current) noexcept;
+ inline int copy_stack_to_heap(char* const stackref, const StackState& current) noexcept;
+ inline bool started() const noexcept;
+ inline bool main() const noexcept;
+ inline bool active() const noexcept;
+ inline void set_active() noexcept;
+ inline void set_inactive() noexcept;
+ inline intptr_t stack_saved() const noexcept;
+ inline char* stack_start() const noexcept;
+ static inline StackState make_main() noexcept;
+#ifdef GREENLET_USE_STDIO
+ friend std::ostream& operator<<(std::ostream& os, const StackState& s);
+#endif
+
+ // Fill in [dest, dest + n) with the values that would be at
+ // [src, src + n) while this greenlet is running. This is like memcpy
+ // except that if the greenlet is suspended it accounts for the portion
+ // of the greenlet's stack that was spilled to the heap. `src` may
+ // be on this greenlet's stack, or on the heap, but not on a different
+ // greenlet's stack.
+ void copy_from_stack(void* dest, const void* src, size_t n) const;
+ };
+#ifdef GREENLET_USE_STDIO
+ std::ostream& operator<<(std::ostream& os, const StackState& s);
+#endif
+
+ class SwitchingArgs
+ {
+ private:
+ G_NO_ASSIGNMENT_OF_CLS(SwitchingArgs);
+ // If args and kwargs are both false (NULL), this is a *throw*, not a
+ // switch. PyErr_... must have been called already.
+ OwnedObject _args;
+ OwnedObject _kwargs;
+ public:
+
+ SwitchingArgs()
+ {}
+
+ SwitchingArgs(const OwnedObject& args, const OwnedObject& kwargs)
+ : _args(args),
+ _kwargs(kwargs)
+ {}
+
+ SwitchingArgs(const SwitchingArgs& other)
+ : _args(other._args),
+ _kwargs(other._kwargs)
+ {}
+
+ const OwnedObject& args()
+ {
+ return this->_args;
+ }
+
+ const OwnedObject& kwargs()
+ {
+ return this->_kwargs;
+ }
+
+ /**
+ * Moves ownership from the argument to this object.
+ */
+ SwitchingArgs& operator<<=(SwitchingArgs& other)
+ {
+ if (this != &other) {
+ this->_args = other._args;
+ this->_kwargs = other._kwargs;
+ other.CLEAR();
+ }
+ return *this;
+ }
+
+ /**
+ * Acquires ownership of the argument (consumes the reference).
+ */
+ SwitchingArgs& operator<<=(PyObject* args)
+ {
+ this->_args = OwnedObject::consuming(args);
+ this->_kwargs.CLEAR();
+ return *this;
+ }
+
+ /**
+ * Acquires ownership of the argument.
+ *
+ * Sets the args to be the given value; clears the kwargs.
+ */
+ SwitchingArgs& operator<<=(OwnedObject& args)
+ {
+ assert(&args != &this->_args);
+ this->_args = args;
+ this->_kwargs.CLEAR();
+ args.CLEAR();
+
+ return *this;
+ }
+
+ explicit operator bool() const noexcept
+ {
+ return this->_args || this->_kwargs;
+ }
+
+ inline void CLEAR()
+ {
+ this->_args.CLEAR();
+ this->_kwargs.CLEAR();
+ }
+
+ const std::string as_str() const noexcept
+ {
+ return PyUnicode_AsUTF8(
+ OwnedObject::consuming(
+ PyUnicode_FromFormat(
+ "SwitchingArgs(args=%R, kwargs=%R)",
+ this->_args.borrow(),
+ this->_kwargs.borrow()
+ )
+ ).borrow()
+ );
+ }
+ };
+
+ class ThreadState;
+
+ class UserGreenlet;
+ class MainGreenlet;
+
+ class Greenlet
+ {
+ private:
+ G_NO_COPIES_OF_CLS(Greenlet);
+ private:
+ // XXX: Work to remove these.
+ friend class ThreadState;
+ friend class UserGreenlet;
+ friend class MainGreenlet;
+ protected:
+ ExceptionState exception_state;
+ SwitchingArgs switch_args;
+ StackState stack_state;
+ PythonState python_state;
+ Greenlet(PyGreenlet* p, const StackState& initial_state);
+ public:
+ Greenlet(PyGreenlet* p);
+ virtual ~Greenlet();
+
+ const OwnedObject context() const;
+
+ // You MUST call this _very_ early in the switching process to
+ // prepare anything that may need prepared. This might perform
+ // garbage collections or otherwise run arbitrary Python code.
+ //
+ // One specific use of it is for Python 3.11+, preventing
+ // running arbitrary code at unsafe times. See
+ // PythonState::may_switch_away().
+ inline void may_switch_away()
+ {
+ this->python_state.may_switch_away();
+ }
+
+ inline void context(refs::BorrowedObject new_context);
+
+ inline SwitchingArgs& args()
+ {
+ return this->switch_args;
+ }
+
+ virtual const refs::BorrowedMainGreenlet main_greenlet() const = 0;
+
+ inline intptr_t stack_saved() const noexcept
+ {
+ return this->stack_state.stack_saved();
+ }
+
+ // This is used by the macro SLP_SAVE_STATE to compute the
+ // difference in stack sizes. It might be nice to handle the
+ // computation ourself, but the type of the result
+ // varies by platform, so doing it in the macro is the
+ // simplest way.
+ inline const char* stack_start() const noexcept
+ {
+ return this->stack_state.stack_start();
+ }
+
+ virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state);
+ virtual OwnedObject g_switch() = 0;
+ /**
+ * Force the greenlet to appear dead. Used when it's not
+ * possible to throw an exception into a greenlet anymore.
+ *
+ * This losses access to the thread state and the main greenlet.
+ */
+ virtual void murder_in_place();
+
+ /**
+ * Called when somebody notices we were running in a dead
+ * thread to allow cleaning up resources (because we can't
+ * raise GreenletExit into it anymore).
+ * This is very similar to ``murder_in_place()``, except that
+ * it DOES NOT lose the main greenlet or thread state.
+ */
+ inline void deactivate_and_free();
+
+
+ // Called when some thread wants to deallocate a greenlet
+ // object.
+ // The thread may or may not be the same thread the greenlet
+ // was running in.
+ // The thread state will be null if the thread the greenlet
+ // was running in was known to have exited.
+ void deallocing_greenlet_in_thread(const ThreadState* current_state);
+
+ // Must be called on 3.12+ before exposing a suspended greenlet's
+ // frames to user code. This rewrites the linked list of interpreter
+ // frames to skip the ones that are being stored on the C stack (which
+ // can't be safely accessed while the greenlet is suspended because
+ // that stack space might be hosting a different greenlet), and
+ // sets PythonState::frames_were_exposed so we remember to restore
+ // the original list before resuming the greenlet. The C-stack frames
+ // are a low-level interpreter implementation detail; while they're
+ // important to the bytecode eval loop, they're superfluous for
+ // introspection purposes.
+ void expose_frames();
+
+
+ // TODO: Figure out how to make these non-public.
+ inline void slp_restore_state() noexcept;
+ inline int slp_save_state(char *const stackref) noexcept;
+
+ inline bool is_currently_running_in_some_thread() const;
+ virtual bool belongs_to_thread(const ThreadState* state) const;
+
+ inline bool started() const
+ {
+ return this->stack_state.started();
+ }
+ inline bool active() const
+ {
+ return this->stack_state.active();
+ }
+ inline bool main() const
+ {
+ return this->stack_state.main();
+ }
+ virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const = 0;
+
+ virtual const OwnedGreenlet parent() const = 0;
+ virtual void parent(const refs::BorrowedObject new_parent) = 0;
+
+ inline const PythonState::OwnedFrame& top_frame()
+ {
+ return this->python_state.top_frame();
+ }
+
+ virtual const OwnedObject& run() const = 0;
+ virtual void run(const refs::BorrowedObject nrun) = 0;
+
+
+ virtual int tp_traverse(visitproc visit, void* arg);
+ virtual int tp_clear();
+
+
+ // Return the thread state that the greenlet is running in, or
+ // null if the greenlet is not running or the thread is known
+ // to have exited.
+ virtual ThreadState* thread_state() const noexcept = 0;
+
+ // Return true if the greenlet is known to have been running
+ // (active) in a thread that has now exited.
+ virtual bool was_running_in_dead_thread() const noexcept = 0;
+
+ // Return a borrowed greenlet that is the Python object
+ // this object represents.
+ virtual BorrowedGreenlet self() const noexcept = 0;
+
+ // For testing. If this returns true, we should pretend that
+ // slp_switch() failed.
+ virtual bool force_slp_switch_error() const noexcept;
+
+ protected:
+ inline void release_args();
+
+ // The functions that must not be inlined are declared virtual.
+ // We also mark them as protected, not private, so that the
+ // compiler is forced to call them through a function pointer.
+ // (A sufficiently smart compiler could directly call a private
+ // virtual function since it can never be overridden in a
+ // subclass).
+
+ // Also TODO: Switch away from integer error codes and to enums,
+ // or throw exceptions when possible.
+ struct switchstack_result_t
+ {
+ int status;
+ Greenlet* the_new_current_greenlet;
+ OwnedGreenlet origin_greenlet;
+
+ switchstack_result_t()
+ : status(0),
+ the_new_current_greenlet(nullptr)
+ {}
+
+ switchstack_result_t(int err)
+ : status(err),
+ the_new_current_greenlet(nullptr)
+ {}
+
+ switchstack_result_t(int err, Greenlet* state, OwnedGreenlet& origin)
+ : status(err),
+ the_new_current_greenlet(state),
+ origin_greenlet(origin)
+ {
+ }
+
+ switchstack_result_t(int err, Greenlet* state, const BorrowedGreenlet& origin)
+ : status(err),
+ the_new_current_greenlet(state),
+ origin_greenlet(origin)
+ {
+ }
+
+ switchstack_result_t(const switchstack_result_t& other)
+ : status(other.status),
+ the_new_current_greenlet(other.the_new_current_greenlet),
+ origin_greenlet(other.origin_greenlet)
+ {}
+
+ switchstack_result_t& operator=(const switchstack_result_t& other)
+ {
+ this->status = other.status;
+ this->the_new_current_greenlet = other.the_new_current_greenlet;
+ this->origin_greenlet = other.origin_greenlet;
+ return *this;
+ }
+ };
+
+ OwnedObject on_switchstack_or_initialstub_failure(
+ Greenlet* target,
+ const switchstack_result_t& err,
+ const bool target_was_me=false,
+ const bool was_initial_stub=false);
+
+ // Returns the previous greenlet we just switched away from.
+ virtual OwnedGreenlet g_switchstack_success() noexcept;
+
+
+ // Check the preconditions for switching to this greenlet; if they
+ // aren't met, throws PyErrOccurred. Most callers will want to
+ // catch this and clear the arguments
+ inline void check_switch_allowed() const;
+ class GreenletStartedWhileInPython : public std::runtime_error
+ {
+ public:
+ GreenletStartedWhileInPython() : std::runtime_error("")
+ {}
+ };
+
+ protected:
+
+
+ /**
+ Perform a stack switch into this greenlet.
+
+ This temporarily sets the global variable
+ ``switching_thread_state`` to this greenlet; as soon as the
+ call to ``slp_switch`` completes, this is reset to NULL.
+ Consequently, this depends on the GIL.
+
+ TODO: Adopt the stackman model and pass ``slp_switch`` a
+ callback function and context pointer; this eliminates the
+ need for global variables altogether.
+
+ Because the stack switch happens in this function, this
+ function can't use its own stack (local) variables, set
+ before the switch, and then accessed after the switch.
+
+ Further, you con't even access ``g_thread_state_global``
+ before and after the switch from the global variable.
+ Because it is thread local some compilers cache it in a
+ register/on the stack, notably new versions of MSVC; this
+ breaks with strange crashes sometime later, because writing
+ to anything in ``g_thread_state_global`` after the switch
+ is actually writing to random memory. For this reason, we
+ call a non-inlined function to finish the operation. (XXX:
+ The ``/GT`` MSVC compiler argument probably fixes that.)
+
+ It is very important that stack switch is 'atomic', i.e. no
+ calls into other Python code allowed (except very few that
+ are safe), because global variables are very fragile. (This
+ should no longer be the case with thread-local variables.)
+
+ */
+ // Made virtual to facilitate subclassing UserGreenlet for testing.
+ virtual switchstack_result_t g_switchstack(void);
+
+class TracingGuard
+{
+private:
+ PyThreadState* tstate;
+public:
+ TracingGuard()
+ : tstate(PyThreadState_GET())
+ {
+ PyThreadState_EnterTracing(this->tstate);
+ }
+
+ ~TracingGuard()
+ {
+ PyThreadState_LeaveTracing(this->tstate);
+ this->tstate = nullptr;
+ }
+
+ inline void CallTraceFunction(const OwnedObject& tracefunc,
+ const greenlet::refs::ImmortalEventName& event,
+ const BorrowedGreenlet& origin,
+ const BorrowedGreenlet& target)
+ {
+ // TODO: This calls tracefunc(event, (origin, target)). Add a shortcut
+ // function for that that's specialized to avoid the Py_BuildValue
+ // string parsing, or start with just using "ON" format with PyTuple_Pack(2,
+ // origin, target). That seems like what the N format is meant
+ // for.
+ // XXX: Why does event not automatically cast back to a PyObject?
+ // It tries to call the "deleted constructor ImmortalEventName
+ // const" instead.
+ assert(tracefunc);
+ assert(event);
+ assert(origin);
+ assert(target);
+ greenlet::refs::NewReference retval(
+ PyObject_CallFunction(
+ tracefunc.borrow(),
+ "O(OO)",
+ event.borrow(),
+ origin.borrow(),
+ target.borrow()
+ ));
+ if (!retval) {
+ throw PyErrOccurred::from_current();
+ }
+ }
+};
+
+ static void
+ g_calltrace(const OwnedObject& tracefunc,
+ const greenlet::refs::ImmortalEventName& event,
+ const greenlet::refs::BorrowedGreenlet& origin,
+ const BorrowedGreenlet& target);
+ private:
+ OwnedObject g_switch_finish(const switchstack_result_t& err);
+
+ };
+
+ class UserGreenlet : public Greenlet
+ {
+ private:
+ static greenlet::PythonAllocator allocator;
+ BorrowedGreenlet _self;
+ OwnedMainGreenlet _main_greenlet;
+ OwnedObject _run_callable;
+ OwnedGreenlet _parent;
+ public:
+ static void* operator new(size_t UNUSED(count));
+ static void operator delete(void* ptr);
+
+ UserGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent);
+ virtual ~UserGreenlet();
+
+ virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const;
+ virtual bool was_running_in_dead_thread() const noexcept;
+ virtual ThreadState* thread_state() const noexcept;
+ virtual OwnedObject g_switch();
+ virtual const OwnedObject& run() const
+ {
+ if (this->started() || !this->_run_callable) {
+ throw AttributeError("run");
+ }
+ return this->_run_callable;
+ }
+ virtual void run(const refs::BorrowedObject nrun);
+
+ virtual const OwnedGreenlet parent() const;
+ virtual void parent(const refs::BorrowedObject new_parent);
+
+ virtual const refs::BorrowedMainGreenlet main_greenlet() const;
+
+ virtual BorrowedGreenlet self() const noexcept;
+ virtual void murder_in_place();
+ virtual bool belongs_to_thread(const ThreadState* state) const;
+ virtual int tp_traverse(visitproc visit, void* arg);
+ virtual int tp_clear();
+ class ParentIsCurrentGuard
+ {
+ private:
+ OwnedGreenlet oldparent;
+ UserGreenlet* greenlet;
+ G_NO_COPIES_OF_CLS(ParentIsCurrentGuard);
+ public:
+ ParentIsCurrentGuard(UserGreenlet* p, const ThreadState& thread_state);
+ ~ParentIsCurrentGuard();
+ };
+ virtual OwnedObject throw_GreenletExit_during_dealloc(const ThreadState& current_thread_state);
+ protected:
+ virtual switchstack_result_t g_initialstub(void* mark);
+ private:
+ // This function isn't meant to return.
+ // This accepts raw pointers and the ownership of them at the
+ // same time. The caller should use ``inner_bootstrap(origin.relinquish_ownership())``.
+ void inner_bootstrap(PyGreenlet* origin_greenlet, PyObject* run);
+ };
+
+ class BrokenGreenlet : public UserGreenlet
+ {
+ private:
+ static greenlet::PythonAllocator allocator;
+ public:
+ bool _force_switch_error = false;
+ bool _force_slp_switch_error = false;
+
+ static void* operator new(size_t UNUSED(count));
+ static void operator delete(void* ptr);
+ BrokenGreenlet(PyGreenlet* p, BorrowedGreenlet the_parent)
+ : UserGreenlet(p, the_parent)
+ {}
+ virtual ~BrokenGreenlet()
+ {}
+
+ virtual switchstack_result_t g_switchstack(void);
+ virtual bool force_slp_switch_error() const noexcept;
+
+ };
+
+ class MainGreenlet : public Greenlet
+ {
+ private:
+ static greenlet::PythonAllocator allocator;
+ refs::BorrowedMainGreenlet _self;
+ ThreadState* _thread_state;
+ G_NO_COPIES_OF_CLS(MainGreenlet);
+ public:
+ static void* operator new(size_t UNUSED(count));
+ static void operator delete(void* ptr);
+
+ MainGreenlet(refs::BorrowedMainGreenlet::PyType*, ThreadState*);
+ virtual ~MainGreenlet();
+
+
+ virtual const OwnedObject& run() const;
+ virtual void run(const refs::BorrowedObject nrun);
+
+ virtual const OwnedGreenlet parent() const;
+ virtual void parent(const refs::BorrowedObject new_parent);
+
+ virtual const refs::BorrowedMainGreenlet main_greenlet() const;
+
+ virtual refs::BorrowedMainGreenlet find_main_greenlet_in_lineage() const;
+ virtual bool was_running_in_dead_thread() const noexcept;
+ virtual ThreadState* thread_state() const noexcept;
+ void thread_state(ThreadState*) noexcept;
+ virtual OwnedObject g_switch();
+ virtual BorrowedGreenlet self() const noexcept;
+ virtual int tp_traverse(visitproc visit, void* arg);
+ };
+
+ // Instantiate one on the stack to save the GC state,
+ // and then disable GC. When it goes out of scope, GC will be
+ // restored to its original state. Sadly, these APIs are only
+ // available on 3.10+; luckily, we only need them on 3.11+.
+#if GREENLET_PY310
+ class GCDisabledGuard
+ {
+ private:
+ int was_enabled = 0;
+ public:
+ GCDisabledGuard()
+ : was_enabled(PyGC_IsEnabled())
+ {
+ PyGC_Disable();
+ }
+
+ ~GCDisabledGuard()
+ {
+ if (this->was_enabled) {
+ PyGC_Enable();
+ }
+ }
+ };
+#endif
+
+ OwnedObject& operator<<=(OwnedObject& lhs, greenlet::SwitchingArgs& rhs) noexcept;
+
+ //TODO: Greenlet::g_switch() should call this automatically on its
+ //return value. As it is, the module code is calling it.
+ static inline OwnedObject
+ single_result(const OwnedObject& results)
+ {
+ if (results
+ && PyTuple_Check(results.borrow())
+ && PyTuple_GET_SIZE(results.borrow()) == 1) {
+ PyObject* result = PyTuple_GET_ITEM(results.borrow(), 0);
+ assert(result);
+ return OwnedObject::owning(result);
+ }
+ return results;
+ }
+
+
+ static OwnedObject
+ g_handle_exit(const OwnedObject& greenlet_result);
+
+
+ template
+ void operator<<(const PyThreadState *const lhs, T& rhs)
+ {
+ rhs.operator<<(lhs);
+ }
+
+} // namespace greenlet ;
+
+#endif
diff --git a/venv/Lib/site-packages/greenlet/greenlet_internal.hpp b/venv/Lib/site-packages/greenlet/greenlet_internal.hpp
new file mode 100644
index 0000000..c8e3849
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_internal.hpp
@@ -0,0 +1,106 @@
+/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
+#ifndef GREENLET_INTERNAL_H
+#define GREENLET_INTERNAL_H
+#ifdef __clang__
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wunused-function"
+# pragma clang diagnostic ignored "-Wmissing-field-initializers"
+# pragma clang diagnostic ignored "-Wunused-variable"
+#endif
+
+/**
+ * Implementation helpers.
+ *
+ * C++ templates and inline functions should go here.
+ */
+#define PY_SSIZE_T_CLEAN
+#include "greenlet_compiler_compat.hpp"
+#include "greenlet_cpython_compat.hpp"
+#include "greenlet_exceptions.hpp"
+#include "greenlet_greenlet.hpp"
+#include "greenlet_allocator.hpp"
+
+#include
+#include
+
+#define GREENLET_MODULE
+struct _greenlet;
+typedef struct _greenlet PyGreenlet;
+namespace greenlet {
+
+ class ThreadState;
+
+};
+
+
+#define implementation_ptr_t greenlet::Greenlet*
+
+
+#include "greenlet.h"
+
+G_FP_TMPL_STATIC inline void
+greenlet::refs::MainGreenletExactChecker(void *p)
+{
+ if (!p) {
+ return;
+ }
+ // We control the class of the main greenlet exactly.
+ if (Py_TYPE(p) != &PyGreenlet_Type) {
+ std::string err("MainGreenlet: Expected exactly a greenlet, not a ");
+ err += Py_TYPE(p)->tp_name;
+ throw greenlet::TypeError(err);
+ }
+
+ // Greenlets from dead threads no longer respond to main() with a
+ // true value; so in that case we need to perform an additional
+ // check.
+ Greenlet* g = ((PyGreenlet*)p)->pimpl;
+ if (g->main()) {
+ return;
+ }
+ if (!dynamic_cast(g)) {
+ std::string err("MainGreenlet: Expected exactly a main greenlet, not a ");
+ err += Py_TYPE(p)->tp_name;
+ throw greenlet::TypeError(err);
+ }
+}
+
+
+
+template
+inline greenlet::Greenlet* greenlet::refs::_OwnedGreenlet::operator->() const noexcept
+{
+ return reinterpret_cast(this->p)->pimpl;
+}
+
+template
+inline greenlet::Greenlet* greenlet::refs::_BorrowedGreenlet::operator->() const noexcept
+{
+ return reinterpret_cast(this->p)->pimpl;
+}
+
+#include
+#include
+
+
+extern PyTypeObject PyGreenlet_Type;
+
+
+
+/**
+ * Forward declarations needed in multiple files.
+ */
+static PyGreenlet* green_create_main(greenlet::ThreadState*);
+static PyObject* green_switch(PyGreenlet* self, PyObject* args, PyObject* kwargs);
+static int green_is_gc(BorrowedGreenlet self);
+
+#ifdef __clang__
+# pragma clang diagnostic pop
+#endif
+
+
+#endif
+
+// Local Variables:
+// flycheck-clang-include-path: ("../../include" "/opt/local/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10")
+// End:
diff --git a/venv/Lib/site-packages/greenlet/greenlet_refs.hpp b/venv/Lib/site-packages/greenlet/greenlet_refs.hpp
new file mode 100644
index 0000000..72ee68b
--- /dev/null
+++ b/venv/Lib/site-packages/greenlet/greenlet_refs.hpp
@@ -0,0 +1,1100 @@
+#ifndef GREENLET_REFS_HPP
+#define GREENLET_REFS_HPP
+
+#define PY_SSIZE_T_CLEAN
+#include
+//#include "greenlet_internal.hpp"
+#include "greenlet_compiler_compat.hpp"
+#include "greenlet_cpython_compat.hpp"
+#include "greenlet_exceptions.hpp"
+
+struct _greenlet;
+struct _PyMainGreenlet;
+
+typedef struct _greenlet PyGreenlet;
+extern PyTypeObject PyGreenlet_Type;
+
+
+#ifdef GREENLET_USE_STDIO
+#include
+using std::cerr;
+using std::endl;
+#endif
+
+namespace greenlet
+{
+ class Greenlet;
+
+ namespace refs
+ {
+ // Type checkers throw a TypeError if the argument is not
+ // null, and isn't of the required Python type.
+ // (We can't use most of the defined type checkers
+ // like PyList_Check, etc, directly, because they are
+ // implemented as macros.)
+ typedef void (*TypeChecker)(void*);
+
+ G_FP_TMPL_STATIC inline void
+ NoOpChecker(void*)
+ {
+ return;
+ }
+
+ G_FP_TMPL_STATIC inline void
+ GreenletChecker(void *p)
+ {
+ if (!p) {
+ return;
+ }
+
+ PyTypeObject* typ = Py_TYPE(p);
+ // fast, common path. (PyObject_TypeCheck is a macro or
+ // static inline function, and it also does a
+ // direct comparison of the type pointers, but its fast
+ // path only handles one type)
+ if (typ == &PyGreenlet_Type) {
+ return;
+ }
+
+ if (!PyObject_TypeCheck(p, &PyGreenlet_Type)) {
+ std::string err("GreenletChecker: Expected any type of greenlet, not ");
+ err += Py_TYPE(p)->tp_name;
+ throw TypeError(err);
+ }
+ }
+
+ G_FP_TMPL_STATIC inline void
+ MainGreenletExactChecker(void *p);
+
+ template
+ class PyObjectPointer;
+
+ template
+ class OwnedReference;
+
+
+ template
+ class BorrowedReference;
+
+ typedef BorrowedReference BorrowedObject;
+ typedef OwnedReference OwnedObject;
+
+ class ImmortalObject;
+ class ImmortalString;
+
+ template
+ class _OwnedGreenlet;
+
+ typedef _OwnedGreenlet OwnedGreenlet;
+ typedef _OwnedGreenlet OwnedMainGreenlet;
+
+ template
+ class _BorrowedGreenlet;
+
+ typedef _BorrowedGreenlet BorrowedGreenlet;
+
+ G_FP_TMPL_STATIC inline void
+ ContextExactChecker(void *p)
+ {
+ if (!p) {
+ return;
+ }
+ if (!PyContext_CheckExact(p)) {
+ throw TypeError(
+ "greenlet context must be a contextvars.Context or None"
+ );
+ }
+ }
+
+ typedef OwnedReference OwnedContext;
+ }
+}
+
+namespace greenlet {
+
+
+ namespace refs {
+ // A set of classes to make reference counting rules in python
+ // code explicit.
+ //
+ // Rules of use:
+ // (1) Functions returning a new reference that the caller of the
+ // function is expected to dispose of should return a
+ // ``OwnedObject`` object. This object automatically releases its
+ // reference when it goes out of scope. It works like a ``std::shared_ptr``
+ // and can be copied or used as a function parameter (but don't do
+ // that). Note that constructing a ``OwnedObject`` from a
+ // PyObject* steals the reference.
+ // (2) Parameters to functions should be either a
+ // ``OwnedObject&``, or, more generally, a ``PyObjectPointer&``.
+ // If the function needs to create its own new reference, it can
+ // do so by copying to a local ``OwnedObject``.
+ // (3) Functions returning an existing pointer that is NOT
+ // incref'd, and which the caller MUST NOT decref,
+ // should return a ``BorrowedObject``.
+
+ //
+ // For a class with a single pointer member, whose constructor
+ // does nothing but copy a pointer parameter into the member, and
+ // which can then be converted back to the pointer type, compilers
+ // generate code that's the same as just passing the pointer.
+ // That is, func(BorrowedObject x) called like ``PyObject* p =
+ // ...; f(p)`` has 0 overhead. Similarly, they "unpack" to the
+ // pointer type with 0 overhead.
+ //
+ // If there are no virtual functions, no complex inheritance (maybe?) and
+ // no destructor, these can be directly used as parameters in
+ // Python callbacks like tp_init: the layout is the same as a
+ // single pointer. Only subclasses with trivial constructors that
+ // do nothing but set the single pointer member are safe to use
+ // that way.
+
+
+ // This is the base class for things that can be done with a
+ // PyObject pointer. It assumes nothing about memory management.
+ // NOTE: Nothing is virtual, so subclasses shouldn't add new
+ // storage fields or try to override these methods.
+ template
+ class PyObjectPointer
+ {
+ public:
+ typedef T PyType;
+ protected:
+ T* p;
+ public:
+ explicit PyObjectPointer(T* it=nullptr) : p(it)
+ {
+ TC(p);
+ }
+
+ // We don't allow automatic casting to PyObject* at this
+ // level, because then we could be passed to Py_DECREF/INCREF,
+ // but we want nothing to do with memory management. If you
+ // know better, then you can use the get() method, like on a
+ // std::shared_ptr. Except we name it borrow() to clarify that
+ // if this is a reference-tracked object, the pointer you get
+ // back will go away when the object does.
+ // TODO: This should probably not exist here, but be moved
+ // down to relevant sub-types.
+
+ inline T* borrow() const noexcept
+ {
+ return this->p;
+ }
+
+ PyObject* borrow_o() const noexcept
+ {
+ return reinterpret_cast(this->p);
+ }
+
+ inline T* operator->() const noexcept
+ {
+ return this->p;
+ }
+
+ bool is_None() const noexcept
+ {
+ return this->p == Py_None;
+ }
+
+ inline PyObject* acquire_or_None() const noexcept
+ {
+ PyObject* result = this->p ? reinterpret_cast(this->p) : Py_None;
+ Py_INCREF(result);
+ return result;
+ }
+
+ explicit operator bool() const noexcept
+ {
+ return p != nullptr;
+ }
+
+ inline Py_ssize_t REFCNT() const noexcept
+ {
+ return p ? Py_REFCNT(p) : -42;
+ }
+
+ inline PyTypeObject* TYPE() const noexcept
+ {
+ return p ? Py_TYPE(p) : nullptr;
+ }
+
+ inline OwnedObject PyStr() const noexcept;
+ inline const std::string as_str() const noexcept;
+ inline OwnedObject PyGetAttr(const ImmortalObject& name) const noexcept;
+ inline OwnedObject PyRequireAttr(const char* const name) const;
+ inline OwnedObject PyRequireAttr(const ImmortalString& name) const;
+ inline OwnedObject PyCall(const BorrowedObject& arg) const;
+ inline OwnedObject PyCall(PyGreenlet* arg) const ;
+ inline OwnedObject PyCall(PyObject* arg) const ;
+ // PyObject_Call(this, args, kwargs);
+ inline OwnedObject PyCall(const BorrowedObject args,
+ const BorrowedObject kwargs) const;
+ inline OwnedObject PyCall(const OwnedObject& args,
+ const OwnedObject& kwargs) const;
+
+ protected:
+ void _set_raw_pointer(void* t)
+ {
+ TC(t);
+ p = reinterpret_cast(t);
+ }
+ void* _get_raw_pointer() const
+ {
+ return p;
+ }
+ };
+
+#ifdef GREENLET_USE_STDIO
+ template
+ std::ostream& operator<<(std::ostream& os, const PyObjectPointer& s)
+ {
+ const std::type_info& t = typeid(s);
+ os << t.name()
+ << "(addr=" << s.borrow()
+ << ", refcnt=" << s.REFCNT()
+ << ", value=" << s.as_str()
+ << ")";
+
+ return os;
+ }
+#endif
+
+ template
+ inline bool operator==(const PyObjectPointer& lhs, const void* const rhs) noexcept
+ {
+ return lhs.borrow_o() == rhs;
+ }
+
+ template
+ inline bool operator==(const PyObjectPointer& lhs, const PyObjectPointer& rhs) noexcept
+ {
+ return lhs.borrow_o() == rhs.borrow_o();
+ }
+
+ template
+ inline bool operator!=(const PyObjectPointer& lhs,
+ const PyObjectPointer& rhs) noexcept
+ {
+ return lhs.borrow_o() != rhs.borrow_o();
+ }
+
+ template
+ class OwnedReference : public PyObjectPointer
+ {
+ private:
+ friend class OwnedList;
+
+ protected:
+ explicit OwnedReference(T* it) : PyObjectPointer(it)
+ {
+ }
+
+ public:
+
+ // Constructors
+
+ static OwnedReference consuming(PyObject* p)
+ {
+ return OwnedReference(reinterpret_cast(p));
+ }
+
+ static OwnedReference owning(T* p)
+ {
+ OwnedReference result(p);
+ Py_XINCREF(result.p);
+ return result;
+ }
+
+ OwnedReference() : PyObjectPointer(nullptr)
+ {}
+
+ explicit OwnedReference(const PyObjectPointer<>& other)
+ : PyObjectPointer(nullptr)
+ {
+ T* op = other.borrow();
+ TC(op);
+ this->p = other.borrow();
+ Py_XINCREF(this->p);
+ }
+
+ // It would be good to make use of the C++11 distinction
+ // between move and copy operations, e.g., constructing from a
+ // pointer should be a move operation.
+ // In the common case of ``OwnedObject x = Py_SomeFunction()``,
+ // the call to the copy constructor will be elided completely.
+ OwnedReference(const OwnedReference& other)
+ : PyObjectPointer(other.p)
+ {
+ Py_XINCREF(this->p);
+ }
+
+ static OwnedReference None()
+ {
+ Py_INCREF(Py_None);
+ return OwnedReference(Py_None);
+ }
+
+ // We can assign from exactly our type without any extra checking
+ OwnedReference& operator=(const OwnedReference& other)
+ {
+ Py_XINCREF(other.p);
+ const T* tmp = this->p;
+ this->p = other.p;
+ Py_XDECREF(tmp);
+ return *this;
+ }
+
+ OwnedReference& operator=(const BorrowedReference other)
+ {
+ return this->operator=(other.borrow());
+ }
+
+ OwnedReference& operator=(T* const other)
+ {
+ TC(other);
+ Py_XINCREF(other);
+ T* tmp = this->p;
+ this->p = other;
+ Py_XDECREF(tmp);
+ return *this;
+ }
+
+ // We can assign from an arbitrary reference type
+ // if it passes our check.
+ template
+ OwnedReference& operator=(const OwnedReference& other)
+ {
+ X* op = other.borrow();
+ TC(op);
+ return this->operator=(reinterpret_cast(op));
+ }
+
+ inline void steal(T* other)
+ {
+ assert(this->p == nullptr);
+ TC(other);
+ this->p = other;
+ }
+
+ T* relinquish_ownership()
+ {
+ T* result = this->p;
+ this->p = nullptr;
+ return result;
+ }
+
+ T* acquire() const
+ {
+ // Return a new reference.
+ // TODO: This may go away when we have reference objects
+ // throughout the code.
+ Py_XINCREF(this->p);
+ return this->p;
+ }
+
+ // Nothing else declares a destructor, we're the leaf, so we
+ // should be able to get away without virtual.
+ ~OwnedReference()
+ {
+ Py_CLEAR(this->p);
+ }
+
+ void CLEAR()
+ {
+ Py_CLEAR(this->p);
+ assert(this->p == nullptr);
+ }
+ };
+
+ static inline
+ void operator<<=(PyObject*& target, OwnedObject& o)
+ {
+ target = o.relinquish_ownership();
+ }
+
+ class NewReference : public OwnedObject
+ {
+ private:
+ G_NO_COPIES_OF_CLS(NewReference);
+ public:
+ // Consumes the reference. Only use this
+ // for API return values.
+ NewReference(PyObject* it) : OwnedObject(it)
+ {
+ }
+ };
+
+ class NewDictReference : public NewReference
+ {
+ private:
+ G_NO_COPIES_OF_CLS(NewDictReference);
+ public:
+ NewDictReference() : NewReference(PyDict_New())
+ {
+ if (!this->p) {
+ throw PyErrOccurred();
+ }
+ }
+
+ void SetItem(const char* const key, PyObject* value)
+ {
+ Require(PyDict_SetItemString(this->p, key, value));
+ }
+
+ void SetItem(const PyObjectPointer<>& key, PyObject* value)
+ {
+ Require(PyDict_SetItem(this->p, key.borrow_o(), value));
+ }
+ };
+
+ template
+ class _OwnedGreenlet: public OwnedReference
+ {
+ private:
+ protected:
+ _OwnedGreenlet(T* it) : OwnedReference(it)
+ {}
+
+ public:
+ _OwnedGreenlet() : OwnedReference()
+ {}
+
+ _OwnedGreenlet(const _OwnedGreenlet& other) : OwnedReference(other)
+ {
+ }
+ _OwnedGreenlet(OwnedMainGreenlet& other) :
+ OwnedReference(reinterpret_cast(other.acquire()))
+ {
+ }
+ _OwnedGreenlet(const BorrowedGreenlet& other);
+ // Steals a reference.
+ static _OwnedGreenlet consuming(PyGreenlet* it)
+ {
+ return _OwnedGreenlet(reinterpret_cast(it));
+ }
+
+ inline _OwnedGreenlet& operator=(const OwnedGreenlet& other)
+ {
+ return this->operator=(other.borrow());
+ }
+
+ inline _OwnedGreenlet& operator=(const BorrowedGreenlet& other);
+
+ _OwnedGreenlet& operator=(const OwnedMainGreenlet& other)
+ {
+ PyGreenlet* owned = other.acquire();
+ Py_XDECREF(this->p);
+ this->p = reinterpret_cast(owned);
+ return *this;
+ }
+
+ _OwnedGreenlet& operator=(T* const other)
+ {
+ OwnedReference::operator=(other);
+ return *this;
+ }
+
+ T* relinquish_ownership()
+ {
+ T* result = this->p;
+ this->p = nullptr;
+ return result;
+ }
+
+ PyObject* relinquish_ownership_o()
+ {
+ return reinterpret_cast(relinquish_ownership());
+ }
+
+ inline Greenlet* operator->() const noexcept;
+ inline operator Greenlet*() const noexcept;
+ };
+
+ template
+ class BorrowedReference : public PyObjectPointer
+ {
+ public:
+ // Allow implicit creation from PyObject* pointers as we
+ // transition to using these classes. Also allow automatic
+ // conversion to PyObject* for passing to C API calls and even
+ // for Py_INCREF/DECREF, because we ourselves do no memory management.
+ BorrowedReference(T* it) : PyObjectPointer(it)
+ {}
+
+ BorrowedReference(const PyObjectPointer& ref) : PyObjectPointer(ref.borrow())
+ {}
+
+ BorrowedReference() : PyObjectPointer(nullptr)
+ {}
+
+ operator T*() const
+ {
+ return this->p;
+ }
+ };
+
+ typedef BorrowedReference BorrowedObject;
+ //typedef BorrowedReference BorrowedGreenlet;
+
+ template
+ class _BorrowedGreenlet : public BorrowedReference
+ {
+ public:
+ _BorrowedGreenlet() :
+ BorrowedReference(nullptr)
+ {}
+
+ _BorrowedGreenlet(T* it) :
+ BorrowedReference(it)
+ {}
+
+ _BorrowedGreenlet(const BorrowedObject& it);
+
+ _BorrowedGreenlet(const OwnedGreenlet& it) :
+ BorrowedReference(it.borrow())
+ {}
+
+ _BorrowedGreenlet& operator=(const BorrowedObject& other);
+
+ // We get one of these for PyGreenlet, but one for PyObject
+ // is handy as well
+ operator PyObject*() const
+ {
+ return reinterpret_cast(this->p);
+ }
+ inline Greenlet* operator->() const noexcept;
+ inline operator Greenlet*() const noexcept;
+ };
+
+ typedef _BorrowedGreenlet BorrowedGreenlet;
+
+ template
+ _OwnedGreenlet::_OwnedGreenlet(const BorrowedGreenlet& other)
+ : OwnedReference(reinterpret_cast(other.borrow()))
+ {
+ Py_XINCREF(this->p);
+ }
+
+
+ class BorrowedMainGreenlet
+ : public _BorrowedGreenlet
+ {
+ public:
+ BorrowedMainGreenlet(const OwnedMainGreenlet& it) :
+ _BorrowedGreenlet(it.borrow())
+ {}
+ BorrowedMainGreenlet(PyGreenlet* it=nullptr)
+ : _BorrowedGreenlet(it)
+ {}
+ };
+
+ template
+ _OwnedGreenlet& _OwnedGreenlet::operator=(const BorrowedGreenlet& other)
+ {
+ return this->operator=(other.borrow());
+ }
+
+
+ class ImmortalObject : public PyObjectPointer<>
+ {
+ private:
+ G_NO_ASSIGNMENT_OF_CLS(ImmortalObject);
+ public:
+ explicit ImmortalObject(PyObject* it) : PyObjectPointer<>(it)
+ {
+ }
+
+ ImmortalObject(const ImmortalObject& other)
+ : PyObjectPointer<>(other.p)
+ {
+
+ }
+
+ /**
+ * Become the new owner of the object. Does not change the
+ * reference count.
+ */
+ ImmortalObject& operator=(PyObject* it)
+ {
+ assert(this->p == nullptr);
+ this->p = it;
+ return *this;
+ }
+
+ static ImmortalObject consuming(PyObject* it)
+ {
+ return ImmortalObject(it);
+ }
+
+ inline operator PyObject*() const
+ {
+ return this->p;
+ }
+ };
+
+ class ImmortalString : public ImmortalObject
+ {
+ private:
+ G_NO_COPIES_OF_CLS(ImmortalString);
+ const char* str;
+ public:
+ ImmortalString(const char* const str) :
+ ImmortalObject(str ? Require(PyUnicode_InternFromString(str)) : nullptr)
+ {
+ this->str = str;
+ }
+
+ inline ImmortalString& operator=(const char* const str)
+ {
+ if (!this->p) {
+ this->p = Require(PyUnicode_InternFromString(str));
+ this->str = str;
+ }
+ else {
+ assert(this->str == str);
+ }
+ return *this;
+ }
+
+ inline operator std::string() const
+ {
+ return this->str;
+ }
+
+ };
+
+ class ImmortalEventName : public ImmortalString
+ {
+ private:
+ G_NO_COPIES_OF_CLS(ImmortalEventName);
+ public:
+ ImmortalEventName(const char* const str) : ImmortalString(str)
+ {}
+ };
+
+ class ImmortalException : public ImmortalObject
+ {
+ private:
+ G_NO_COPIES_OF_CLS(ImmortalException);
+ public:
+ ImmortalException(const char* const name, PyObject* base=nullptr) :
+ ImmortalObject(name
+ // Python 2.7 isn't const correct
+ ? Require(PyErr_NewException((char*)name, base, nullptr))
+ : nullptr)
+ {}
+
+ inline bool PyExceptionMatches() const
+ {
+ return PyErr_ExceptionMatches(this->p) > 0;
+ }
+
+ };
+
+ template
+ inline OwnedObject PyObjectPointer::PyStr() const noexcept
+ {
+ if (!this->p) {
+ return OwnedObject();
+ }
+ return OwnedObject::consuming(PyObject_Str(reinterpret_cast(this->p)));
+ }
+
+ template
+ inline const std::string PyObjectPointer::as_str() const noexcept
+ {
+ // NOTE: This is not Python exception safe.
+ if (this->p) {
+ // The Python APIs return a cached char* value that's only valid
+ // as long as the original object stays around, and we're
+ // about to (probably) toss it. Hence the copy to std::string.
+ OwnedObject py_str = this->PyStr();
+ if (!py_str) {
+ return "(nil)";
+ }
+ return PyUnicode_AsUTF8(py_str.borrow());
+ }
+ return "(nil)";
+ }
+
+ template
+ inline OwnedObject PyObjectPointer::PyGetAttr(const ImmortalObject& name) const noexcept
+ {
+ assert(this->p);
+ return OwnedObject::consuming(PyObject_GetAttr(reinterpret_cast(this->p), name));
+ }
+
+ template
+ inline OwnedObject PyObjectPointer::PyRequireAttr(const char* const name) const
+ {
+ assert(this->p);
+ return OwnedObject::consuming(Require(PyObject_GetAttrString(this->p, name), name));
+ }
+
+ template
+ inline OwnedObject PyObjectPointer::PyRequireAttr(const ImmortalString& name) const
+ {
+ assert(this->p);
+ return OwnedObject::consuming(Require(
+ PyObject_GetAttr(
+ reinterpret_cast(this->p),
+ name
+ ),
+ name
+ ));
+ }
+
+ template
+ inline OwnedObject PyObjectPointer::PyCall(const BorrowedObject& arg) const
+ {
+ return this->PyCall(arg.borrow());
+ }
+
+ template
+ inline OwnedObject PyObjectPointer::PyCall(PyGreenlet* arg) const
+ {
+ return this->PyCall(reinterpret_cast