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