Browse Source

add new configuration file for archivial data

Federica 1 year ago
parent
commit
071db4349b
100 changed files with 21258 additions and 100 deletions
  1. 4 0
      parsers/CSV_to_RDF_generico.py
  2. 43 0
      parsers/configuration_files/configuration-aspo.json
  3. 1 1
      parsers/configuration_files/configuration.json
  4. 0 1
      samples/CSV/.~lock.AR20AUT_Ospedale_2.csv#
  5. 4 4
      samples/CSV/OSPEDALE-onomastica-persone-singole.csv
  6. 48 94
      samples/RDF/OSPEDALE-onomastica-persone-singole.ttl
  7. 247 0
      venv/bin/Activate.ps1
  8. 69 0
      venv/bin/activate
  9. 26 0
      venv/bin/activate.csh
  10. 66 0
      venv/bin/activate.fish
  11. 8 0
      venv/bin/flask
  12. 8 0
      venv/bin/pip
  13. 8 0
      venv/bin/pip3
  14. 8 0
      venv/bin/pip3.10
  15. 1 0
      venv/bin/python
  16. 1 0
      venv/bin/python3
  17. 1 0
      venv/bin/python3.10
  18. 1 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/INSTALLER
  19. 28 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/LICENSE.rst
  20. 123 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/METADATA
  21. 54 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/RECORD
  22. 0 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/REQUESTED
  23. 5 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/WHEEL
  24. 2 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/entry_points.txt
  25. 1 0
      venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/top_level.txt
  26. 1 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/INSTALLER
  27. 28 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/LICENSE.rst
  28. 113 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/METADATA
  29. 58 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/RECORD
  30. 5 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/WHEEL
  31. 2 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/entry_points.txt
  32. 1 0
      venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/top_level.txt
  33. 1 0
      venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/INSTALLER
  34. 28 0
      venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/LICENSE.rst
  35. 101 0
      venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/METADATA
  36. 14 0
      venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/RECORD
  37. 5 0
      venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/WHEEL
  38. 1 0
      venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/top_level.txt
  39. 1 0
      venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/INSTALLER
  40. 28 0
      venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/LICENSE.rst
  41. 126 0
      venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/METADATA
  42. 98 0
      venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/RECORD
  43. 5 0
      venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/WHEEL
  44. 1 0
      venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/top_level.txt
  45. 128 0
      venv/lib/python3.10/site-packages/_distutils_hack/__init__.py
  46. 1 0
      venv/lib/python3.10/site-packages/_distutils_hack/override.py
  47. 1 0
      venv/lib/python3.10/site-packages/click-8.1.3.dist-info/INSTALLER
  48. 28 0
      venv/lib/python3.10/site-packages/click-8.1.3.dist-info/LICENSE.rst
  49. 111 0
      venv/lib/python3.10/site-packages/click-8.1.3.dist-info/METADATA
  50. 39 0
      venv/lib/python3.10/site-packages/click-8.1.3.dist-info/RECORD
  51. 5 0
      venv/lib/python3.10/site-packages/click-8.1.3.dist-info/WHEEL
  52. 1 0
      venv/lib/python3.10/site-packages/click-8.1.3.dist-info/top_level.txt
  53. 73 0
      venv/lib/python3.10/site-packages/click/__init__.py
  54. 626 0
      venv/lib/python3.10/site-packages/click/_compat.py
  55. 717 0
      venv/lib/python3.10/site-packages/click/_termui_impl.py
  56. 49 0
      venv/lib/python3.10/site-packages/click/_textwrap.py
  57. 279 0
      venv/lib/python3.10/site-packages/click/_winconsole.py
  58. 2998 0
      venv/lib/python3.10/site-packages/click/core.py
  59. 497 0
      venv/lib/python3.10/site-packages/click/decorators.py
  60. 287 0
      venv/lib/python3.10/site-packages/click/exceptions.py
  61. 301 0
      venv/lib/python3.10/site-packages/click/formatting.py
  62. 68 0
      venv/lib/python3.10/site-packages/click/globals.py
  63. 529 0
      venv/lib/python3.10/site-packages/click/parser.py
  64. 0 0
      venv/lib/python3.10/site-packages/click/py.typed
  65. 580 0
      venv/lib/python3.10/site-packages/click/shell_completion.py
  66. 787 0
      venv/lib/python3.10/site-packages/click/termui.py
  67. 479 0
      venv/lib/python3.10/site-packages/click/testing.py
  68. 1073 0
      venv/lib/python3.10/site-packages/click/types.py
  69. 580 0
      venv/lib/python3.10/site-packages/click/utils.py
  70. 1 0
      venv/lib/python3.10/site-packages/distutils-precedence.pth
  71. 71 0
      venv/lib/python3.10/site-packages/flask/__init__.py
  72. 3 0
      venv/lib/python3.10/site-packages/flask/__main__.py
  73. 2548 0
      venv/lib/python3.10/site-packages/flask/app.py
  74. 706 0
      venv/lib/python3.10/site-packages/flask/blueprints.py
  75. 1051 0
      venv/lib/python3.10/site-packages/flask/cli.py
  76. 337 0
      venv/lib/python3.10/site-packages/flask/config.py
  77. 438 0
      venv/lib/python3.10/site-packages/flask/ctx.py
  78. 158 0
      venv/lib/python3.10/site-packages/flask/debughelpers.py
  79. 107 0
      venv/lib/python3.10/site-packages/flask/globals.py
  80. 705 0
      venv/lib/python3.10/site-packages/flask/helpers.py
  81. 342 0
      venv/lib/python3.10/site-packages/flask/json/__init__.py
  82. 310 0
      venv/lib/python3.10/site-packages/flask/json/provider.py
  83. 312 0
      venv/lib/python3.10/site-packages/flask/json/tag.py
  84. 74 0
      venv/lib/python3.10/site-packages/flask/logging.py
  85. 0 0
      venv/lib/python3.10/site-packages/flask/py.typed
  86. 898 0
      venv/lib/python3.10/site-packages/flask/scaffold.py
  87. 419 0
      venv/lib/python3.10/site-packages/flask/sessions.py
  88. 56 0
      venv/lib/python3.10/site-packages/flask/signals.py
  89. 212 0
      venv/lib/python3.10/site-packages/flask/templating.py
  90. 286 0
      venv/lib/python3.10/site-packages/flask/testing.py
  91. 80 0
      venv/lib/python3.10/site-packages/flask/typing.py
  92. 188 0
      venv/lib/python3.10/site-packages/flask/views.py
  93. 171 0
      venv/lib/python3.10/site-packages/flask/wrappers.py
  94. 1 0
      venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/INSTALLER
  95. 28 0
      venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/LICENSE.rst
  96. 97 0
      venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/METADATA
  97. 23 0
      venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/RECORD
  98. 5 0
      venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/WHEEL
  99. 1 0
      venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/top_level.txt
  100. 19 0
      venv/lib/python3.10/site-packages/itsdangerous/__init__.py

+ 4 - 0
parsers/CSV_to_RDF_generico.py

@@ -16,6 +16,7 @@ class RDFcoords:
 
 # Repositories
 museoCoords = RDFcoords('<https://palazzopretorio.prato.it/it/le-opere/alcuni-capolavori/>', 'mpp:')
+aspoCoords = RDFcoords('<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/>', 'aspo:')
 autCoords = RDFcoords('<https://palazzopretorio.prato.it/it/opere/autori/>', 'aut:')
 foafCoords = RDFcoords('<http://xmlns.com/foaf/0.1/>', 'foaf:')
 
@@ -24,6 +25,7 @@ aatCoords = RDFcoords('<http://vocab.getty.edu/aat/>', 'aat:')
 nsCoords = RDFcoords('<http://www.w3.org/1999/02/22-rdf-syntax-ns#>', 'rdf:')
 schemaCoords = RDFcoords('<http://www.schema.org/>', 'schema:')
 rdfsCoords = RDFcoords('<http://www.w3.org/2000/01/rdf-schema#>', 'rdfs:')
+owlCoords = RDFcoords('<http://www.w3.org/2002/07/owl#>', 'owl:')
 
 
 # Basic utilities to format triples / shortened triples in TTL format
@@ -58,6 +60,8 @@ def writeTTLHeader(output):
     output.write('@prefix ' + schemaCoords.prefix + ' ' + schemaCoords.uri + closeLine)
     output.write('@prefix ' + nsCoords.prefix + ' ' + nsCoords.uri + closeLine)
     output.write('@prefix ' + rdfsCoords.prefix + ' ' + rdfsCoords.uri + closeLine)
+    output.write('@prefix ' + owlCoords.prefix + ' ' + owlCoords.uri + closeLine)
+    output.write('@prefix ' + aspoCoords.prefix + ' ' + aspoCoords.uri + closeLine)
     output.write('\n')
 
 max_entries = None

+ 43 - 0
parsers/configuration_files/configuration-aspo.json

@@ -0,0 +1,43 @@
+[
+    {
+        "subject": {
+          "value": "aspo:#csv:recordId#",
+          "ref": "MAIN"
+        },
+        "comment": "Commento opzionale",
+        "content": [
+            {
+              "predicate": "rdf:type",
+              "object": "crm:E21_Person"
+            },
+            {
+              "predicate": "rdf:type",
+              "object": "foaf:person"
+            },
+            {
+              "predicate": "foaf:name",
+              "object": "\"#csv:nameEntry@normal#\""
+            },
+            {
+              "predicate": "foaf:givenName",
+              "object": "\"#csv:nome proprio#\""
+            },
+            {
+              "predicate": "foaf:familyName",
+              "object": "\"#csv:nome di famiglia#\""
+            },
+            {
+              "predicate": "schema:alternateName",
+              "object": "\"#csv:Alias#\""
+            },
+            {
+              "predicate": "foaf:gender",
+              "object": "\"#csv:genere#\""
+            },
+            {
+              "predicate": "rdfs:label",
+              "object": "\"#csv:nameEntry@normal#\""
+            }
+        ]
+    }
+]

+ 1 - 1
parsers/configuration_files/configuration.json

@@ -4,7 +4,7 @@
           "value": "aut:#csv:URL#",
           "ref": "MAIN"
         },
-        "comment": "OPZIONALE - ci si può scrivere icché si vòle.",
+        "comment": "Commento opzionale",
         "content": [
             {
               "predicate": "rdf:type",

+ 0 - 1
samples/CSV/.~lock.AR20AUT_Ospedale_2.csv#

@@ -1 +0,0 @@
-,kora,koras,22.09.2022 09:06,file:///home/kora/.config/libreoffice/4;

+ 4 - 4
samples/CSV/OSPEDALE-onomastica-persone-singole.csv

@@ -1,11 +1,11 @@
 recordId,ID collegato,entityType,nameEntry@normal,nameEntry@prime,genere,nome proprio,nome di famiglia,patronimico/matronimico,avo 1,avo 2,recordID relazione,nome relazione,provenienza,Variante,Alias,Qualifica,occupation_1,place occupation  1 PROVINCIA,place occupation 1 COMUNE,place occupation 1 MICROTOPONIMO,place occupation 1 ENTE,occupation_2,place occupation 2 PROVINCIA,place occupation 2 COMUNE,place occupation 2 MICROTOPONIMO,place occupation 2 ENTE,occupation_3,place occupation 3 PROVINCIA,place occupation 3 COMUNE,place occupation 3 MICROTOPONIMO,place occupation 3 ENTE,biogHist p,nameEntry@ulterior,nameEntry@ord,place PROVINCIA,place COMUNE,place MICROTOPONIMO
-IT-ASPO-AU00002-0001329,,person,[...] Bice,,F,Bice,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
-IT-ASPO-AU00002-0001325,,person,[...] Giovanni,,M,Giovanni,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
-IT-ASPO-AU00002-0001140,,person,[...] Giovanni di Andrea,,M,Giovanni,,di Andrea,,,,,,,,,vice camarlingo,PRATO,PRATO,,COMUNE,,,,,,,,,,,Vice camarlingo del Comune di Prato.,,Vechi (?),,,
 IT-ASPO-AU00002-0001230,,person,Accolti Angelo di Graziasanta da Arezzo,,M,Angelo ,Accolti ,di Graziasanta ,,,,,da Arezzo,,,,,,,,,,,,,,,,,,,,,,,,
 IT-ASPO-AU00002-0001275,,person,Agostino di Andrea di Fioco,,M,Agostino ,,di Andrea ,di Fioco,,,,,,,,,,,,,,,,,,,,,,,,,,,,
 IT-ASPO-AU00002-0000001,,person,Aiazzi Antonio,,M,Antonio,Aiazzi ,,,,,,,,,,,,,,,,,,,,,,,,,,,Aiazzi,,,
 IT-ASPO-AU00002-0000002,,person,Alamanno di ser Betto,,M,Alamanno ,,di ser Betto,,,,,,,,,notaio,PRATO,PRATO,,,,,,,,,,,,,Notaio pubblico di Prato.,,,,,
 IT-ASPO-AU00002-0000003,,person,Albani Giovanni Francesco (papa Clemente XI),,M,Giovanni Francesco ,Albani,,,,,,,,Clemente XI,papa,,,,,,,,,,,,,,,,1700-1721,,Albani,,,
 IT-ASPO-AU00002-0000004,,person,Alberto di Teri,,M,Alberto ,,di Teri,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
-IT-ASPO-AU00002-0000005,,person,Albizzi Rinaldo,,M,Rinaldo,Albizzi ,,,,,,,,,,,,,,,,,,,,,,,,,,,Albizzi,,,
+IT-ASPO-AU00002-0000005,,person,Albizzi Rinaldo,,M,Rinaldo,Albizzi ,,,,,,,,,,,,,,,,,,,,,,,,,,,Albizzi,,,
+IT-ASPO-AU00002-0001329,,person,[...] Bice,,F,Bice,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+IT-ASPO-AU00002-0001325,,person,[...] Giovanni,,M,Giovanni,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+IT-ASPO-AU00002-0001140,,person,[...] Giovanni di Andrea,,M,Giovanni,,di Andrea,,,,,,,,,vice camarlingo,PRATO,PRATO,,COMUNE,,,,,,,,,,,Vice camarlingo del Comune di Prato.,,Vechi (?),,,

+ 48 - 94
samples/RDF/OSPEDALE-onomastica-persone-singole.ttl

@@ -1,147 +1,101 @@
-@prefix aspo: <http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/> .
+@prefix mpp: <https://palazzopretorio.prato.it/it/le-opere/alcuni-capolavori/> .
 @prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix aut: <https://palazzopretorio.prato.it/it/opere/autori/> .
 @prefix crm: <http://www.cidoc-crm.org/cidoc-crm/> .
-@prefix person: <http://www.w3.org/ns/person#> .
-@prefix schema: <http://schema.org/> .
+@prefix aat: <http://vocab.getty.edu/aat/> .
+@prefix schema: <http://www.schema.org/> .
 @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
 @prefix owl: <http://www.w3.org/2002/07/owl#> .
-
-aspo:IT-ASPO-AU00002-0001329 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0001329 rdf:type person:Person .
-aspo:IT-ASPO-AU00002-0001329 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0001329 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0001329_E42 .
-aspo:IT-ASPO-AU00002-0001329_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0001329_E42 rdfs:label "IT-ASPO-AU00002-0001329" .
-aspo:IT-ASPO-AU00002-0001329 foaf:name "[...] Bice" .
-aspo:IT-ASPO-AU00002-0001329 rdfs:label "[...] Bice" .
-aspo:IT-ASPO-AU00002-0001329 foaf:givenName "Bice" .
-aspo:IT-ASPO-AU00002-0001329 foaf:gender "F" .
-
-aspo:IT-ASPO-AU00002-0001325 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0001325 rdf:type person:Person .
-aspo:IT-ASPO-AU00002-0001325 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0001325 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0001325_E42 .
-aspo:IT-ASPO-AU00002-0001325_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0001325_E42 rdfs:label "IT-ASPO-AU00002-0001325" .
-aspo:IT-ASPO-AU00002-0001325 foaf:name "[...] Giovanni" .
-aspo:IT-ASPO-AU00002-0001325 rdfs:label "[...] Giovanni" .
-aspo:IT-ASPO-AU00002-0001325 foaf:givenName "Giovanni" .
-aspo:IT-ASPO-AU00002-0001325 foaf:gender "M" .
-
-aspo:IT-ASPO-AU00002-0001140 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0001140 rdf:type person:Person .
-aspo:IT-ASPO-AU00002-0001140 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0001140 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0001140_E42 .
-aspo:IT-ASPO-AU00002-0001140_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0001140_E42 rdfs:label "IT-ASPO-AU00002-0001140" .
-aspo:IT-ASPO-AU00002-0001140 foaf:name "[...] Giovanni di Andrea" .
-aspo:IT-ASPO-AU00002-0001140 rdfs:label "[...] Giovanni di Andrea" .
-aspo:IT-ASPO-AU00002-0001140 foaf:givenName "Giovanni" .
-aspo:IT-ASPO-AU00002-0001140 foaf:gender "M" .
-aspo:IT-ASPO-AU00002-0001140 person:patronymicName "di Andrea" .
-aspo:IT-ASPO-AU00002-0001140 crm:P3_has_note "Vice camarlingo del Comune di Prato." .
+@prefix aspo: <http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/> .
 
 aspo:IT-ASPO-AU00002-0001230 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0001230 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0001230 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0001230 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0001230_E42 .
-aspo:IT-ASPO-AU00002-0001230_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0001230_E42 rdfs:label "IT-ASPO-AU00002-0001230" .
 aspo:IT-ASPO-AU00002-0001230 foaf:name "Accolti Angelo di Graziasanta da Arezzo" .
-aspo:IT-ASPO-AU00002-0001230 rdfs:label "Accolti Angelo di Graziasanta da Arezzo" .
 aspo:IT-ASPO-AU00002-0001230 foaf:givenName "Angelo " .
 aspo:IT-ASPO-AU00002-0001230 foaf:familyName "Accolti " .
+aspo:IT-ASPO-AU00002-0001230 schema:alternateName "" .
 aspo:IT-ASPO-AU00002-0001230 foaf:gender "M" .
-aspo:IT-ASPO-AU00002-0001230 person:patronymicName "di Graziasanta " .
-aspo:IT-ASPO-AU00002-0001230 crm:P74_has_current_or_former_residence <http://www.archiviodistato.prato.it/Arezzo> .
-<http://www.archiviodistato.prato.it/Arezzo> rdf:type crm:E53_Place .
-<http://www.archiviodistato.prato.it/Arezzo> rdfs:label "da Arezzo" .
-<http://www.archiviodistato.prato.it/Arezzo> crm:P2_has_type "Provenienza" .
+aspo:IT-ASPO-AU00002-0001230 rdfs:label "Accolti Angelo di Graziasanta da Arezzo" .
 
 aspo:IT-ASPO-AU00002-0001275 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0001275 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0001275 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0001275 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0001275_E42 .
-aspo:IT-ASPO-AU00002-0001275_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0001275_E42 rdfs:label "IT-ASPO-AU00002-0001275" .
 aspo:IT-ASPO-AU00002-0001275 foaf:name "Agostino di Andrea di Fioco" .
-aspo:IT-ASPO-AU00002-0001275 rdfs:label "Agostino di Andrea di Fioco" .
 aspo:IT-ASPO-AU00002-0001275 foaf:givenName "Agostino " .
+aspo:IT-ASPO-AU00002-0001275 foaf:familyName "" .
+aspo:IT-ASPO-AU00002-0001275 schema:alternateName "" .
 aspo:IT-ASPO-AU00002-0001275 foaf:gender "M" .
-aspo:IT-ASPO-AU00002-0001275 person:patronymicName "di Andrea " .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_Fioco_AVO1_IT-ASPO-AU00002-0001275> rdf:type crm:E13_Attribute_Assignment .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_Fioco_AVO1_IT-ASPO-AU00002-0001275> crm:P141_assigned aspo:IT-ASPO-AU00002-0001275 .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_Fioco_AVO1_IT-ASPO-AU00002-0001275> rdfs:label "Relazione: di Fioco avo di secondo grado di IT-ASPO-AU00002-0001275" .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_fioco> crm:P141_assigned <http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_Fioco_AVO1_IT-ASPO-AU00002-0001275> .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_fioco> rdfs:label "di Fioco" .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_fioco> rdf:type crm:E21_Person .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_fioco> rdf:type person:Person .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_fioco> rdf:type foaf:person .
-<http://www.archiviodistato.prato.it/accedi-e-consulta/aspoMV001/scheda/di_Fioco_AVO1_IT-ASPO-AU00002-0001275> crm:P42_assigned <http://www.archiviodistato.prato.it/avo_secondo_grado> .
-<http://www.archiviodistato.prato.it/avo_secondo_grado> rdfs:label "Avo di secondo grado" .
+aspo:IT-ASPO-AU00002-0001275 rdfs:label "Agostino di Andrea di Fioco" .
 
 aspo:IT-ASPO-AU00002-0000001 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0000001 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0000001 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0000001 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0000001_E42 .
-aspo:IT-ASPO-AU00002-0000001_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0000001_E42 rdfs:label "IT-ASPO-AU00002-0000001" .
 aspo:IT-ASPO-AU00002-0000001 foaf:name "Aiazzi Antonio" .
-aspo:IT-ASPO-AU00002-0000001 rdfs:label "Aiazzi Antonio" .
 aspo:IT-ASPO-AU00002-0000001 foaf:givenName "Antonio" .
 aspo:IT-ASPO-AU00002-0000001 foaf:familyName "Aiazzi " .
+aspo:IT-ASPO-AU00002-0000001 schema:alternateName "" .
 aspo:IT-ASPO-AU00002-0000001 foaf:gender "M" .
+aspo:IT-ASPO-AU00002-0000001 rdfs:label "Aiazzi Antonio" .
 
 aspo:IT-ASPO-AU00002-0000002 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0000002 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0000002 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0000002 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0000002_E42 .
-aspo:IT-ASPO-AU00002-0000002_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0000002_E42 rdfs:label "IT-ASPO-AU00002-0000002" .
 aspo:IT-ASPO-AU00002-0000002 foaf:name "Alamanno di ser Betto" .
-aspo:IT-ASPO-AU00002-0000002 rdfs:label "Alamanno di ser Betto" .
 aspo:IT-ASPO-AU00002-0000002 foaf:givenName "Alamanno " .
+aspo:IT-ASPO-AU00002-0000002 foaf:familyName "" .
+aspo:IT-ASPO-AU00002-0000002 schema:alternateName "" .
 aspo:IT-ASPO-AU00002-0000002 foaf:gender "M" .
-aspo:IT-ASPO-AU00002-0000002 person:patronymicName "di ser Betto" .
-aspo:IT-ASPO-AU00002-0000002 crm:P3_has_note "Notaio pubblico di Prato." .
+aspo:IT-ASPO-AU00002-0000002 rdfs:label "Alamanno di ser Betto" .
 
 aspo:IT-ASPO-AU00002-0000003 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0000003 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0000003 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0000003 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0000003_E42 .
-aspo:IT-ASPO-AU00002-0000003_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0000003_E42 rdfs:label "IT-ASPO-AU00002-0000003" .
 aspo:IT-ASPO-AU00002-0000003 foaf:name "Albani Giovanni Francesco (papa Clemente XI)" .
-aspo:IT-ASPO-AU00002-0000003 rdfs:label "Albani Giovanni Francesco (papa Clemente XI)" .
 aspo:IT-ASPO-AU00002-0000003 foaf:givenName "Giovanni Francesco " .
 aspo:IT-ASPO-AU00002-0000003 foaf:familyName "Albani" .
 aspo:IT-ASPO-AU00002-0000003 schema:alternateName "Clemente XI" .
 aspo:IT-ASPO-AU00002-0000003 foaf:gender "M" .
-aspo:IT-ASPO-AU00002-0000003 schema:honorificPrefix "papa" .
-aspo:IT-ASPO-AU00002-0000003 crm:P3_has_note "1700-1721" .
+aspo:IT-ASPO-AU00002-0000003 rdfs:label "Albani Giovanni Francesco (papa Clemente XI)" .
 
 aspo:IT-ASPO-AU00002-0000004 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0000004 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0000004 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0000004 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0000004_E42 .
-aspo:IT-ASPO-AU00002-0000004_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0000004_E42 rdfs:label "IT-ASPO-AU00002-0000004" .
 aspo:IT-ASPO-AU00002-0000004 foaf:name "Alberto di Teri" .
-aspo:IT-ASPO-AU00002-0000004 rdfs:label "Alberto di Teri" .
 aspo:IT-ASPO-AU00002-0000004 foaf:givenName "Alberto " .
+aspo:IT-ASPO-AU00002-0000004 foaf:familyName "" .
+aspo:IT-ASPO-AU00002-0000004 schema:alternateName "" .
 aspo:IT-ASPO-AU00002-0000004 foaf:gender "M" .
-aspo:IT-ASPO-AU00002-0000004 person:patronymicName "di Teri" .
+aspo:IT-ASPO-AU00002-0000004 rdfs:label "Alberto di Teri" .
 
 aspo:IT-ASPO-AU00002-0000005 rdf:type crm:E21_Person .
-aspo:IT-ASPO-AU00002-0000005 rdf:type person:Person .
 aspo:IT-ASPO-AU00002-0000005 rdf:type foaf:person .
-aspo:IT-ASPO-AU00002-0000005 crm:P1_is_identified_by aspo:IT-ASPO-AU00002-0000005_E42 .
-aspo:IT-ASPO-AU00002-0000005_E42 rdf:type crm:E42_Identifier .
-aspo:IT-ASPO-AU00002-0000005_E42 rdfs:label "IT-ASPO-AU00002-0000005" .
 aspo:IT-ASPO-AU00002-0000005 foaf:name "Albizzi Rinaldo" .
-aspo:IT-ASPO-AU00002-0000005 rdfs:label "Albizzi Rinaldo" .
 aspo:IT-ASPO-AU00002-0000005 foaf:givenName "Rinaldo" .
 aspo:IT-ASPO-AU00002-0000005 foaf:familyName "Albizzi " .
+aspo:IT-ASPO-AU00002-0000005 schema:alternateName "" .
 aspo:IT-ASPO-AU00002-0000005 foaf:gender "M" .
+aspo:IT-ASPO-AU00002-0000005 rdfs:label "Albizzi Rinaldo" .
+
+aspo:IT-ASPO-AU00002-0001329 rdf:type crm:E21_Person .
+aspo:IT-ASPO-AU00002-0001329 rdf:type foaf:person .
+aspo:IT-ASPO-AU00002-0001329 foaf:name "[...] Bice" .
+aspo:IT-ASPO-AU00002-0001329 foaf:givenName "Bice" .
+aspo:IT-ASPO-AU00002-0001329 foaf:familyName "" .
+aspo:IT-ASPO-AU00002-0001329 schema:alternateName "" .
+aspo:IT-ASPO-AU00002-0001329 foaf:gender "F" .
+aspo:IT-ASPO-AU00002-0001329 rdfs:label "[...] Bice" .
+
+aspo:IT-ASPO-AU00002-0001325 rdf:type crm:E21_Person .
+aspo:IT-ASPO-AU00002-0001325 rdf:type foaf:person .
+aspo:IT-ASPO-AU00002-0001325 foaf:name "[...] Giovanni" .
+aspo:IT-ASPO-AU00002-0001325 foaf:givenName "Giovanni" .
+aspo:IT-ASPO-AU00002-0001325 foaf:familyName "" .
+aspo:IT-ASPO-AU00002-0001325 schema:alternateName "" .
+aspo:IT-ASPO-AU00002-0001325 foaf:gender "M" .
+aspo:IT-ASPO-AU00002-0001325 rdfs:label "[...] Giovanni" .
+
+aspo:IT-ASPO-AU00002-0001140 rdf:type crm:E21_Person .
+aspo:IT-ASPO-AU00002-0001140 rdf:type foaf:person .
+aspo:IT-ASPO-AU00002-0001140 foaf:name "[...] Giovanni di Andrea" .
+aspo:IT-ASPO-AU00002-0001140 foaf:givenName "Giovanni" .
+aspo:IT-ASPO-AU00002-0001140 foaf:familyName "" .
+aspo:IT-ASPO-AU00002-0001140 schema:alternateName "" .
+aspo:IT-ASPO-AU00002-0001140 foaf:gender "M" .
+aspo:IT-ASPO-AU00002-0001140 rdfs:label "[...] Giovanni di Andrea" .
 

+ 247 - 0
venv/bin/Activate.ps1

@@ -0,0 +1,247 @@
+<#
+.Synopsis
+Activate a Python virtual environment for the current PowerShell session.
+
+.Description
+Pushes the python executable for a virtual environment to the front of the
+$Env:PATH environment variable and sets the prompt to signify that you are
+in a Python virtual environment. Makes use of the command line switches as
+well as the `pyvenv.cfg` file values present in the virtual environment.
+
+.Parameter VenvDir
+Path to the directory that contains the virtual environment to activate. The
+default value for this is the parent of the directory that the Activate.ps1
+script is located within.
+
+.Parameter Prompt
+The prompt prefix to display when this virtual environment is activated. By
+default, this prompt is the name of the virtual environment folder (VenvDir)
+surrounded by parentheses and followed by a single space (ie. '(.venv) ').
+
+.Example
+Activate.ps1
+Activates the Python virtual environment that contains the Activate.ps1 script.
+
+.Example
+Activate.ps1 -Verbose
+Activates the Python virtual environment that contains the Activate.ps1 script,
+and shows extra information about the activation as it executes.
+
+.Example
+Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
+Activates the Python virtual environment located in the specified location.
+
+.Example
+Activate.ps1 -Prompt "MyPython"
+Activates the Python virtual environment that contains the Activate.ps1 script,
+and prefixes the current prompt with the specified string (surrounded in
+parentheses) while the virtual environment is active.
+
+.Notes
+On Windows, it may be required to enable this Activate.ps1 script by setting the
+execution policy for the user. You can do this by issuing the following PowerShell
+command:
+
+PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+For more information on Execution Policies: 
+https://go.microsoft.com/fwlink/?LinkID=135170
+
+#>
+Param(
+    [Parameter(Mandatory = $false)]
+    [String]
+    $VenvDir,
+    [Parameter(Mandatory = $false)]
+    [String]
+    $Prompt
+)
+
+<# Function declarations --------------------------------------------------- #>
+
+<#
+.Synopsis
+Remove all shell session elements added by the Activate script, including the
+addition of the virtual environment's Python executable from the beginning of
+the PATH variable.
+
+.Parameter NonDestructive
+If present, do not remove this function from the global namespace for the
+session.
+
+#>
+function global:deactivate ([switch]$NonDestructive) {
+    # Revert to original values
+
+    # The prior prompt:
+    if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
+        Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
+        Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
+    }
+
+    # The prior PYTHONHOME:
+    if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
+        Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
+        Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
+    }
+
+    # The prior PATH:
+    if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
+        Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
+        Remove-Item -Path Env:_OLD_VIRTUAL_PATH
+    }
+
+    # Just remove the VIRTUAL_ENV altogether:
+    if (Test-Path -Path Env:VIRTUAL_ENV) {
+        Remove-Item -Path env:VIRTUAL_ENV
+    }
+
+    # Just remove VIRTUAL_ENV_PROMPT altogether.
+    if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
+        Remove-Item -Path env:VIRTUAL_ENV_PROMPT
+    }
+
+    # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
+    if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
+        Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
+    }
+
+    # Leave deactivate function in the global namespace if requested:
+    if (-not $NonDestructive) {
+        Remove-Item -Path function:deactivate
+    }
+}
+
+<#
+.Description
+Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
+given folder, and returns them in a map.
+
+For each line in the pyvenv.cfg file, if that line can be parsed into exactly
+two strings separated by `=` (with any amount of whitespace surrounding the =)
+then it is considered a `key = value` line. The left hand string is the key,
+the right hand is the value.
+
+If the value starts with a `'` or a `"` then the first and last character is
+stripped from the value before being captured.
+
+.Parameter ConfigDir
+Path to the directory that contains the `pyvenv.cfg` file.
+#>
+function Get-PyVenvConfig(
+    [String]
+    $ConfigDir
+) {
+    Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
+
+    # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
+    $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
+
+    # An empty map will be returned if no config file is found.
+    $pyvenvConfig = @{ }
+
+    if ($pyvenvConfigPath) {
+
+        Write-Verbose "File exists, parse `key = value` lines"
+        $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
+
+        $pyvenvConfigContent | ForEach-Object {
+            $keyval = $PSItem -split "\s*=\s*", 2
+            if ($keyval[0] -and $keyval[1]) {
+                $val = $keyval[1]
+
+                # Remove extraneous quotations around a string value.
+                if ("'""".Contains($val.Substring(0, 1))) {
+                    $val = $val.Substring(1, $val.Length - 2)
+                }
+
+                $pyvenvConfig[$keyval[0]] = $val
+                Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
+            }
+        }
+    }
+    return $pyvenvConfig
+}
+
+
+<# Begin Activate script --------------------------------------------------- #>
+
+# Determine the containing directory of this script
+$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
+$VenvExecDir = Get-Item -Path $VenvExecPath
+
+Write-Verbose "Activation script is located in path: '$VenvExecPath'"
+Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
+Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
+
+# Set values required in priority: CmdLine, ConfigFile, Default
+# First, get the location of the virtual environment, it might not be
+# VenvExecDir if specified on the command line.
+if ($VenvDir) {
+    Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
+}
+else {
+    Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
+    $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
+    Write-Verbose "VenvDir=$VenvDir"
+}
+
+# Next, read the `pyvenv.cfg` file to determine any required value such
+# as `prompt`.
+$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
+
+# Next, set the prompt from the command line, or the config file, or
+# just use the name of the virtual environment folder.
+if ($Prompt) {
+    Write-Verbose "Prompt specified as argument, using '$Prompt'"
+}
+else {
+    Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
+    if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
+        Write-Verbose "  Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
+        $Prompt = $pyvenvCfg['prompt'];
+    }
+    else {
+        Write-Verbose "  Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
+        Write-Verbose "  Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
+        $Prompt = Split-Path -Path $venvDir -Leaf
+    }
+}
+
+Write-Verbose "Prompt = '$Prompt'"
+Write-Verbose "VenvDir='$VenvDir'"
+
+# Deactivate any currently active virtual environment, but leave the
+# deactivate function in place.
+deactivate -nondestructive
+
+# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
+# that there is an activated venv.
+$env:VIRTUAL_ENV = $VenvDir
+
+if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
+
+    Write-Verbose "Setting prompt to '$Prompt'"
+
+    # Set the prompt to include the env name
+    # Make sure _OLD_VIRTUAL_PROMPT is global
+    function global:_OLD_VIRTUAL_PROMPT { "" }
+    Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
+    New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
+
+    function global:prompt {
+        Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
+        _OLD_VIRTUAL_PROMPT
+    }
+    $env:VIRTUAL_ENV_PROMPT = $Prompt
+}
+
+# Clear PYTHONHOME
+if (Test-Path -Path Env:PYTHONHOME) {
+    Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
+    Remove-Item -Path Env:PYTHONHOME
+}
+
+# Add the venv to the PATH
+Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
+$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

+ 69 - 0
venv/bin/activate

@@ -0,0 +1,69 @@
+# This file must be used with "source bin/activate" *from bash*
+# you cannot run it directly
+
+deactivate () {
+    # reset old environment variables
+    if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
+        PATH="${_OLD_VIRTUAL_PATH:-}"
+        export PATH
+        unset _OLD_VIRTUAL_PATH
+    fi
+    if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
+        PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
+        export PYTHONHOME
+        unset _OLD_VIRTUAL_PYTHONHOME
+    fi
+
+    # This should detect bash and zsh, which have a hash command that must
+    # be called to get it to forget past commands.  Without forgetting
+    # past commands the $PATH changes we made may not be respected
+    if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+        hash -r 2> /dev/null
+    fi
+
+    if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
+        PS1="${_OLD_VIRTUAL_PS1:-}"
+        export PS1
+        unset _OLD_VIRTUAL_PS1
+    fi
+
+    unset VIRTUAL_ENV
+    unset VIRTUAL_ENV_PROMPT
+    if [ ! "${1:-}" = "nondestructive" ] ; then
+    # Self destruct!
+        unset -f deactivate
+    fi
+}
+
+# unset irrelevant variables
+deactivate nondestructive
+
+VIRTUAL_ENV="/Users/federicaspinelli/Semantization_Interface/venv"
+export VIRTUAL_ENV
+
+_OLD_VIRTUAL_PATH="$PATH"
+PATH="$VIRTUAL_ENV/bin:$PATH"
+export PATH
+
+# unset PYTHONHOME if set
+# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
+# could use `if (set -u; : $PYTHONHOME) ;` in bash
+if [ -n "${PYTHONHOME:-}" ] ; then
+    _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
+    unset PYTHONHOME
+fi
+
+if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
+    _OLD_VIRTUAL_PS1="${PS1:-}"
+    PS1="(venv) ${PS1:-}"
+    export PS1
+    VIRTUAL_ENV_PROMPT="(venv) "
+    export VIRTUAL_ENV_PROMPT
+fi
+
+# This should detect bash and zsh, which have a hash command that must
+# be called to get it to forget past commands.  Without forgetting
+# past commands the $PATH changes we made may not be respected
+if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
+    hash -r 2> /dev/null
+fi

+ 26 - 0
venv/bin/activate.csh

@@ -0,0 +1,26 @@
+# This file must be used with "source bin/activate.csh" *from csh*.
+# You cannot run it directly.
+# Created by Davide Di Blasi <davidedb@gmail.com>.
+# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
+
+alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+setenv VIRTUAL_ENV "/Users/federicaspinelli/Semantization_Interface/venv"
+
+set _OLD_VIRTUAL_PATH="$PATH"
+setenv PATH "$VIRTUAL_ENV/bin:$PATH"
+
+
+set _OLD_VIRTUAL_PROMPT="$prompt"
+
+if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
+    set prompt = "(venv) $prompt"
+    setenv VIRTUAL_ENV_PROMPT "(venv) "
+endif
+
+alias pydoc python -m pydoc
+
+rehash

+ 66 - 0
venv/bin/activate.fish

@@ -0,0 +1,66 @@
+# This file must be used with "source <venv>/bin/activate.fish" *from fish*
+# (https://fishshell.com/); you cannot run it directly.
+
+function deactivate  -d "Exit virtual environment and return to normal shell environment"
+    # reset old environment variables
+    if test -n "$_OLD_VIRTUAL_PATH"
+        set -gx PATH $_OLD_VIRTUAL_PATH
+        set -e _OLD_VIRTUAL_PATH
+    end
+    if test -n "$_OLD_VIRTUAL_PYTHONHOME"
+        set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
+        set -e _OLD_VIRTUAL_PYTHONHOME
+    end
+
+    if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
+        functions -e fish_prompt
+        set -e _OLD_FISH_PROMPT_OVERRIDE
+        functions -c _old_fish_prompt fish_prompt
+        functions -e _old_fish_prompt
+    end
+
+    set -e VIRTUAL_ENV
+    set -e VIRTUAL_ENV_PROMPT
+    if test "$argv[1]" != "nondestructive"
+        # Self-destruct!
+        functions -e deactivate
+    end
+end
+
+# Unset irrelevant variables.
+deactivate nondestructive
+
+set -gx VIRTUAL_ENV "/Users/federicaspinelli/Semantization_Interface/venv"
+
+set -gx _OLD_VIRTUAL_PATH $PATH
+set -gx PATH "$VIRTUAL_ENV/bin" $PATH
+
+# Unset PYTHONHOME if set.
+if set -q PYTHONHOME
+    set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
+    set -e PYTHONHOME
+end
+
+if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
+    # fish uses a function instead of an env var to generate the prompt.
+
+    # Save the current fish_prompt function as the function _old_fish_prompt.
+    functions -c fish_prompt _old_fish_prompt
+
+    # With the original prompt function renamed, we can override with our own.
+    function fish_prompt
+        # Save the return status of the last command.
+        set -l old_status $status
+
+        # Output the venv prompt; color taken from the blue of the Python logo.
+        printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal)
+
+        # Restore the return status of the previous command.
+        echo "exit $old_status" | .
+        # Output the original/"old" prompt.
+        _old_fish_prompt
+    end
+
+    set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
+    set -gx VIRTUAL_ENV_PROMPT "(venv) "
+end

+ 8 - 0
venv/bin/flask

@@ -0,0 +1,8 @@
+#!/Users/federicaspinelli/Semantization_Interface/venv/bin/python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from flask.cli import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/pip

@@ -0,0 +1,8 @@
+#!/Users/federicaspinelli/Semantization_Interface/venv/bin/python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/pip3

@@ -0,0 +1,8 @@
+#!/Users/federicaspinelli/Semantization_Interface/venv/bin/python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 8 - 0
venv/bin/pip3.10

@@ -0,0 +1,8 @@
+#!/Users/federicaspinelli/Semantization_Interface/venv/bin/python3
+# -*- coding: utf-8 -*-
+import re
+import sys
+from pip._internal.cli.main import main
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(main())

+ 1 - 0
venv/bin/python

@@ -0,0 +1 @@
+python3

+ 1 - 0
venv/bin/python3

@@ -0,0 +1 @@
+/Library/Frameworks/Python.framework/Versions/3.10/bin/python3

+ 1 - 0
venv/bin/python3.10

@@ -0,0 +1 @@
+python3

+ 1 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 28 - 0
venv/lib/python3.10/site-packages/Flask-2.2.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.

+ 123 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/METADATA

@@ -0,0 +1,123 @@
+Metadata-Version: 2.1
+Name: Flask
+Version: 2.2.2
+Summary: A simple framework for building complex web applications.
+Home-page: https://palletsprojects.com/p/flask
+Author: Armin Ronacher
+Author-email: armin.ronacher@active-4.com
+Maintainer: Pallets
+Maintainer-email: contact@palletsprojects.com
+License: BSD-3-Clause
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Documentation, https://flask.palletsprojects.com/
+Project-URL: Changes, https://flask.palletsprojects.com/changes/
+Project-URL: Source Code, https://github.com/pallets/flask/
+Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
+Project-URL: Twitter, https://twitter.com/PalletsTeam
+Project-URL: Chat, https://discord.gg/pallets
+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-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE.rst
+Requires-Dist: Werkzeug (>=2.2.2)
+Requires-Dist: Jinja2 (>=3.0)
+Requires-Dist: itsdangerous (>=2.0)
+Requires-Dist: click (>=8.0)
+Requires-Dist: importlib-metadata (>=3.6.0) ; python_version < "3.10"
+Provides-Extra: async
+Requires-Dist: asgiref (>=3.2) ; extra == 'async'
+Provides-Extra: dotenv
+Requires-Dist: python-dotenv ; 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/
+-   Website: https://palletsprojects.com/p/flask/
+-   Twitter: https://twitter.com/PalletsTeam
+-   Chat: https://discord.gg/pallets

+ 54 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/RECORD

@@ -0,0 +1,54 @@
+../../../bin/flask,sha256=hCiQS0XiVWwQ6WfTi5CM0OK9EVon9-Wtf7P9cCFC8mM,256
+Flask-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+Flask-2.2.2.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
+Flask-2.2.2.dist-info/METADATA,sha256=UXiwRLD1johd_tGlYOlOKXkJFIG82ehR3bxqh4XWFwA,3889
+Flask-2.2.2.dist-info/RECORD,,
+Flask-2.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+Flask-2.2.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
+Flask-2.2.2.dist-info/entry_points.txt,sha256=s3MqQpduU25y4dq3ftBYD6bMVdVnbMpZP-sUNw0zw0k,41
+Flask-2.2.2.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
+flask/__init__.py,sha256=Y4mEWqAMxj_Oxq9eYv3tWyN-0nU9yVKBGK_t6BxqvvM,2890
+flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
+flask/__pycache__/__init__.cpython-310.pyc,,
+flask/__pycache__/__main__.cpython-310.pyc,,
+flask/__pycache__/app.cpython-310.pyc,,
+flask/__pycache__/blueprints.cpython-310.pyc,,
+flask/__pycache__/cli.cpython-310.pyc,,
+flask/__pycache__/config.cpython-310.pyc,,
+flask/__pycache__/ctx.cpython-310.pyc,,
+flask/__pycache__/debughelpers.cpython-310.pyc,,
+flask/__pycache__/globals.cpython-310.pyc,,
+flask/__pycache__/helpers.cpython-310.pyc,,
+flask/__pycache__/logging.cpython-310.pyc,,
+flask/__pycache__/scaffold.cpython-310.pyc,,
+flask/__pycache__/sessions.cpython-310.pyc,,
+flask/__pycache__/signals.cpython-310.pyc,,
+flask/__pycache__/templating.cpython-310.pyc,,
+flask/__pycache__/testing.cpython-310.pyc,,
+flask/__pycache__/typing.cpython-310.pyc,,
+flask/__pycache__/views.cpython-310.pyc,,
+flask/__pycache__/wrappers.cpython-310.pyc,,
+flask/app.py,sha256=VfBcGmEVveMcSajkUmDXCEOvAd-2mIBJ355KicvQ4gE,99025
+flask/blueprints.py,sha256=Jbrt-2jlLiFklC3De9EWBioPtDjHYYbXlTDK9Z7L2nk,26936
+flask/cli.py,sha256=foLlD8NiIXcxpxMmRQvvlZPbVM8pxOaJG3sa58c9dAA,33486
+flask/config.py,sha256=IWqHecH4poDxNEUg4U_ZA1CTlL5BKZDX3ofG4UGYyi0,12584
+flask/ctx.py,sha256=ZOGEWuFjsCIk3vm-C9pLME0e4saeBkeGpr2tTSvemSM,14851
+flask/debughelpers.py,sha256=_RvAL3TW5lqMJeCVWtTU6rSDJC7jnRaBL6OEkVmooyU,5511
+flask/globals.py,sha256=1DLZMi8Su-S1gf8zEiR3JPi6VXYIrYqm8C9__Ly66ss,3187
+flask/helpers.py,sha256=ELq27745jihrdyAP9qY8KENlCVDdnWRWTIn35L9a-UU,25334
+flask/json/__init__.py,sha256=TOwldHT3_kFaXHlORKi9yCWt7dbPNB0ovdHHQWlSRzY,11175
+flask/json/__pycache__/__init__.cpython-310.pyc,,
+flask/json/__pycache__/provider.cpython-310.pyc,,
+flask/json/__pycache__/tag.cpython-310.pyc,,
+flask/json/provider.py,sha256=jXCNypf11PN4ngQjEt6LnSdCWQ1yHIAkNLHlXQlCB-A,10674
+flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857
+flask/logging.py,sha256=WYng0bLTRS_CJrocGcCLJpibHf1lygHE_pg-KoUIQ4w,2293
+flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+flask/scaffold.py,sha256=tiQRK-vMY5nucoN6pewXF87GaxrltsCGOgTVsT6wm7s,33443
+flask/sessions.py,sha256=66oGlE-v9iac-eb54tFN3ILAjJ1FeeuHHWw98UVaoxc,15847
+flask/signals.py,sha256=H7QwDciK-dtBxinjKpexpglP0E6k0MJILiFWTItfmqU,2136
+flask/templating.py,sha256=1P4OzvSnA2fsJTYgQT3G4owVKsuOz8XddCiR6jMHGJ0,7419
+flask/testing.py,sha256=p51f9P7jDc_IDSiZug7jypnfVcxsQrMg3B2tnjlpEFw,10596
+flask/typing.py,sha256=KgxegTF9v9WvuongeF8LooIvpZPauzGrq9ZXf3gBlYc,2969
+flask/views.py,sha256=bveWilivkPP-4HB9w_fOusBz6sHNIl0QTqKUFMCltzE,6738
+flask/wrappers.py,sha256=Wa-bhjNdPPveSHS1dpzD_r-ayZxIYFF1DoWncKOafrk,5695

+ 0 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/REQUESTED


+ 5 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 2 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/entry_points.txt

@@ -0,0 +1,2 @@
+[console_scripts]
+flask = flask.cli:main

+ 1 - 0
venv/lib/python3.10/site-packages/Flask-2.2.2.dist-info/top_level.txt

@@ -0,0 +1 @@
+flask

+ 1 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 28 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.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.

+ 113 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/METADATA

@@ -0,0 +1,113 @@
+Metadata-Version: 2.1
+Name: Jinja2
+Version: 3.1.2
+Summary: A very fast and expressive template engine.
+Home-page: https://palletsprojects.com/p/jinja/
+Author: Armin Ronacher
+Author-email: armin.ronacher@active-4.com
+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: Twitter, https://twitter.com/PalletsTeam
+Project-URL: Chat, https://discord.gg/pallets
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: 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 %}
+      <ul>
+      {% for user in users %}
+        <li><a href="{{ user.url }}">{{ user.username }}</a></li>
+      {% endfor %}
+      </ul>
+    {% 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/
+-   Website: https://palletsprojects.com/p/jinja/
+-   Twitter: https://twitter.com/PalletsTeam
+-   Chat: https://discord.gg/pallets
+
+

+ 58 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/RECORD

@@ -0,0 +1,58 @@
+Jinja2-3.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+Jinja2-3.1.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
+Jinja2-3.1.2.dist-info/METADATA,sha256=PZ6v2SIidMNixR7MRUX9f7ZWsPwtXanknqiZUmRbh4U,3539
+Jinja2-3.1.2.dist-info/RECORD,,
+Jinja2-3.1.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
+Jinja2-3.1.2.dist-info/entry_points.txt,sha256=zRd62fbqIyfUpsRtU7EVIFyiu1tPwfgO7EvPErnxgTE,59
+Jinja2-3.1.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
+jinja2/__init__.py,sha256=8vGduD8ytwgD6GDSqpYc2m3aU-T7PKOAddvVXgGr_Fs,1927
+jinja2/__pycache__/__init__.cpython-310.pyc,,
+jinja2/__pycache__/_identifier.cpython-310.pyc,,
+jinja2/__pycache__/async_utils.cpython-310.pyc,,
+jinja2/__pycache__/bccache.cpython-310.pyc,,
+jinja2/__pycache__/compiler.cpython-310.pyc,,
+jinja2/__pycache__/constants.cpython-310.pyc,,
+jinja2/__pycache__/debug.cpython-310.pyc,,
+jinja2/__pycache__/defaults.cpython-310.pyc,,
+jinja2/__pycache__/environment.cpython-310.pyc,,
+jinja2/__pycache__/exceptions.cpython-310.pyc,,
+jinja2/__pycache__/ext.cpython-310.pyc,,
+jinja2/__pycache__/filters.cpython-310.pyc,,
+jinja2/__pycache__/idtracking.cpython-310.pyc,,
+jinja2/__pycache__/lexer.cpython-310.pyc,,
+jinja2/__pycache__/loaders.cpython-310.pyc,,
+jinja2/__pycache__/meta.cpython-310.pyc,,
+jinja2/__pycache__/nativetypes.cpython-310.pyc,,
+jinja2/__pycache__/nodes.cpython-310.pyc,,
+jinja2/__pycache__/optimizer.cpython-310.pyc,,
+jinja2/__pycache__/parser.cpython-310.pyc,,
+jinja2/__pycache__/runtime.cpython-310.pyc,,
+jinja2/__pycache__/sandbox.cpython-310.pyc,,
+jinja2/__pycache__/tests.cpython-310.pyc,,
+jinja2/__pycache__/utils.cpython-310.pyc,,
+jinja2/__pycache__/visitor.cpython-310.pyc,,
+jinja2/_identifier.py,sha256=_zYctNKzRqlk_murTNlzrju1FFJL7Va_Ijqqd7ii2lU,1958
+jinja2/async_utils.py,sha256=dHlbTeaxFPtAOQEYOGYh_PHcDT0rsDaUJAFDl_0XtTg,2472
+jinja2/bccache.py,sha256=mhz5xtLxCcHRAa56azOhphIAe19u1we0ojifNMClDio,14061
+jinja2/compiler.py,sha256=Gs-N8ThJ7OWK4-reKoO8Wh1ZXz95MVphBKNVf75qBr8,72172
+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=6uHIcc7ZblqOMdx_uYNKqRnnwAF0_nzbyeMP9FFtuh4,61349
+jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071
+jinja2/ext.py,sha256=ivr3P7LKbddiXDVez20EflcO3q2aHQwz9P_PgWGHVqE,31502
+jinja2/filters.py,sha256=9js1V-h2RlyW90IhLiBGLM2U-k6SCy2F4BUUMgB3K9Q,53509
+jinja2/idtracking.py,sha256=GfNmadir4oDALVxzn3DL9YInhJDr69ebXeA2ygfuCGA,10704
+jinja2/lexer.py,sha256=DW2nX9zk-6MWp65YR2bqqj0xqCvLtD-u9NWT8AnFRxQ,29726
+jinja2/loaders.py,sha256=BfptfvTVpClUd-leMkHczdyPNYFzp_n7PKOJ98iyHOg,23207
+jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396
+jinja2/nativetypes.py,sha256=DXgORDPRmVWgy034H0xL8eF7qYoK3DrMxs-935d0Fzk,4226
+jinja2/nodes.py,sha256=i34GPRAZexXMT6bwuf5SEyvdmS-bRCy9KMjwN5O6pjk,34550
+jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650
+jinja2/parser.py,sha256=nHd-DFHbiygvfaPtm9rcQXJChZG7DPsWfiEsqfwKerY,39595
+jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+jinja2/runtime.py,sha256=5CmD5BjbEJxSiDNTFBeKCaq8qU4aYD2v6q2EluyExms,33476
+jinja2/sandbox.py,sha256=Y0xZeXQnH6EX5VjaV2YixESxoepnRbW_3UeQosaBU3M,14584
+jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905
+jinja2/utils.py,sha256=u9jXESxGn8ATZNVolwmkjUVu4SA-tLgV0W7PcSfPfdQ,23965
+jinja2/visitor.py,sha256=MH14C6yq24G_KVtWzjwaI7Wg14PCJIYlWW1kpkxYak0,3568

+ 5 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 2 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/entry_points.txt

@@ -0,0 +1,2 @@
+[babel.extractors]
+jinja2 = jinja2.ext:babel_extract[i18n]

+ 1 - 0
venv/lib/python3.10/site-packages/Jinja2-3.1.2.dist-info/top_level.txt

@@ -0,0 +1 @@
+jinja2

+ 1 - 0
venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 28 - 0
venv/lib/python3.10/site-packages/MarkupSafe-2.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.

+ 101 - 0
venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/METADATA

@@ -0,0 +1,101 @@
+Metadata-Version: 2.1
+Name: MarkupSafe
+Version: 2.1.1
+Summary: Safely add untrusted strings to HTML/XML markup.
+Home-page: https://palletsprojects.com/p/markupsafe/
+Author: Armin Ronacher
+Author-email: armin.ronacher@active-4.com
+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: Twitter, https://twitter.com/PalletsTeam
+Project-URL: Chat, https://discord.gg/pallets
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Web Environment
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: 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("<script>alert(document.cookie);</script>")
+    Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
+
+    >>> # wrap in Markup to mark text "safe" and prevent escaping
+    >>> Markup("<strong>Hello</strong>")
+    Markup('<strong>hello</strong>')
+
+    >>> escape(Markup("<strong>Hello</strong>"))
+    Markup('<strong>hello</strong>')
+
+    >>> # Markup is a str subclass
+    >>> # methods and operators escape their arguments
+    >>> template = Markup("Hello <em>{name}</em>")
+    >>> template.format(name='"World"')
+    Markup('Hello <em>&#34;World&#34;</em>')
+
+
+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/
+-   Website: https://palletsprojects.com/p/markupsafe/
+-   Twitter: https://twitter.com/PalletsTeam
+-   Chat: https://discord.gg/pallets
+
+

+ 14 - 0
venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/RECORD

@@ -0,0 +1,14 @@
+MarkupSafe-2.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+MarkupSafe-2.1.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
+MarkupSafe-2.1.1.dist-info/METADATA,sha256=DC93VszmzjLQcrVChRUjtW4XbUwjTdbaplpgdlbFdbs,3242
+MarkupSafe-2.1.1.dist-info/RECORD,,
+MarkupSafe-2.1.1.dist-info/WHEEL,sha256=QN3MNOFrRIeE9ccPTe5mH4Q_A2DBDdD7_qUigvuJeQU,111
+MarkupSafe-2.1.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
+markupsafe/__init__.py,sha256=xfaUQkKNRTdYWe6HnnJ2HjguFmS-C_0H6g8-Q9VAfkQ,9284
+markupsafe/__pycache__/__init__.cpython-310.pyc,,
+markupsafe/__pycache__/_native.cpython-310.pyc,,
+markupsafe/_native.py,sha256=GR86Qvo_GcgKmKreA1WmYN9ud17OFwkww8E-fiW-57s,1713
+markupsafe/_speedups.c,sha256=X2XvQVtIdcK4Usz70BvkzoOfjTCmQlDkkjYSn-swE0g,7083
+markupsafe/_speedups.cpython-310-darwin.so,sha256=RrUcL36CQAvdE7Xf4OcvU3CVuQ4qkgDwylh52HXTyHM,35136
+markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229
+markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

+ 5 - 0
venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.0)
+Root-Is-Purelib: false
+Tag: cp310-cp310-macosx_10_9_x86_64
+

+ 1 - 0
venv/lib/python3.10/site-packages/MarkupSafe-2.1.1.dist-info/top_level.txt

@@ -0,0 +1 @@
+markupsafe

+ 1 - 0
venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 28 - 0
venv/lib/python3.10/site-packages/Werkzeug-2.2.2.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.

+ 126 - 0
venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/METADATA

@@ -0,0 +1,126 @@
+Metadata-Version: 2.1
+Name: Werkzeug
+Version: 2.2.2
+Summary: The comprehensive WSGI web application library.
+Home-page: https://palletsprojects.com/p/werkzeug/
+Author: Armin Ronacher
+Author-email: armin.ronacher@active-4.com
+Maintainer: Pallets
+Maintainer-email: contact@palletsprojects.com
+License: BSD-3-Clause
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Documentation, https://werkzeug.palletsprojects.com/
+Project-URL: Changes, https://werkzeug.palletsprojects.com/changes/
+Project-URL: Source Code, https://github.com/pallets/werkzeug/
+Project-URL: Issue Tracker, https://github.com/pallets/werkzeug/issues/
+Project-URL: Twitter, https://twitter.com/PalletsTeam
+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 :: Internet :: WWW/HTTP :: WSGI
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
+Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
+Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
+License-File: LICENSE.rst
+Requires-Dist: MarkupSafe (>=2.1.1)
+Provides-Extra: watchdog
+Requires-Dist: watchdog ; extra == 'watchdog'
+
+Werkzeug
+========
+
+*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff")
+
+Werkzeug is a comprehensive `WSGI`_ web application library. It began as
+a simple collection of various utilities for WSGI applications and has
+become one of the most advanced WSGI utility libraries.
+
+It includes:
+
+-   An interactive debugger that allows inspecting stack traces and
+    source code in the browser with an interactive interpreter for any
+    frame in the stack.
+-   A full-featured request object with objects to interact with
+    headers, query args, form data, files, and cookies.
+-   A response object that can wrap other WSGI applications and handle
+    streaming data.
+-   A routing system for matching URLs to endpoints and generating URLs
+    for endpoints, with an extensible system for capturing variables
+    from URLs.
+-   HTTP utilities to handle entity tags, cache control, dates, user
+    agents, cookies, files, and more.
+-   A threaded WSGI server for use while developing applications
+    locally.
+-   A test client for simulating HTTP requests during testing without
+    requiring running a server.
+
+Werkzeug doesn't enforce any dependencies. It is up to the developer to
+choose a template engine, database adapter, and even how to handle
+requests. It can be used to build all sorts of end user applications
+such as blogs, wikis, or bulletin boards.
+
+`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
+providing more structure and patterns for defining powerful
+applications.
+
+.. _WSGI: https://wsgi.readthedocs.io/en/latest/
+.. _Flask: https://www.palletsprojects.com/p/flask/
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+    pip install -U Werkzeug
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+A Simple Example
+----------------
+
+.. code-block:: python
+
+    from werkzeug.wrappers import Request, Response
+
+    @Request.application
+    def application(request):
+        return Response('Hello, World!')
+
+    if __name__ == '__main__':
+        from werkzeug.serving import run_simple
+        run_simple('localhost', 4000, application)
+
+
+Donate
+------
+
+The Pallets organization develops and supports Werkzeug 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://werkzeug.palletsprojects.com/
+-   Changes: https://werkzeug.palletsprojects.com/changes/
+-   PyPI Releases: https://pypi.org/project/Werkzeug/
+-   Source Code: https://github.com/pallets/werkzeug/
+-   Issue Tracker: https://github.com/pallets/werkzeug/issues/
+-   Website: https://palletsprojects.com/p/werkzeug/
+-   Twitter: https://twitter.com/PalletsTeam
+-   Chat: https://discord.gg/pallets

+ 98 - 0
venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/RECORD

@@ -0,0 +1,98 @@
+Werkzeug-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+Werkzeug-2.2.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
+Werkzeug-2.2.2.dist-info/METADATA,sha256=hz42ndovEQQy3rwXKZDwR7LA4UNthKegxf_7xIQrjsM,4416
+Werkzeug-2.2.2.dist-info/RECORD,,
+Werkzeug-2.2.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
+Werkzeug-2.2.2.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9
+werkzeug/__init__.py,sha256=UP218Ddd2NYm1dUhTlhvGRQytzAx1Ms1A716UKiPOYk,188
+werkzeug/__pycache__/__init__.cpython-310.pyc,,
+werkzeug/__pycache__/_internal.cpython-310.pyc,,
+werkzeug/__pycache__/_reloader.cpython-310.pyc,,
+werkzeug/__pycache__/datastructures.cpython-310.pyc,,
+werkzeug/__pycache__/exceptions.cpython-310.pyc,,
+werkzeug/__pycache__/formparser.cpython-310.pyc,,
+werkzeug/__pycache__/http.cpython-310.pyc,,
+werkzeug/__pycache__/local.cpython-310.pyc,,
+werkzeug/__pycache__/security.cpython-310.pyc,,
+werkzeug/__pycache__/serving.cpython-310.pyc,,
+werkzeug/__pycache__/test.cpython-310.pyc,,
+werkzeug/__pycache__/testapp.cpython-310.pyc,,
+werkzeug/__pycache__/urls.cpython-310.pyc,,
+werkzeug/__pycache__/user_agent.cpython-310.pyc,,
+werkzeug/__pycache__/utils.cpython-310.pyc,,
+werkzeug/__pycache__/wsgi.cpython-310.pyc,,
+werkzeug/_internal.py,sha256=g8PHJz2z39I3x0vwTvTKbXIg0eUQqGF9UtUzDMWT0Qw,16222
+werkzeug/_reloader.py,sha256=lYStlIDduTxBOB8BSozy_44HQ7YT5fup-x3uuac1-2o,14331
+werkzeug/datastructures.py,sha256=T1SRE_KRuNz9Q7P-Ck4PyKPyil1NOx9zDuNMLgrN1Z0,97083
+werkzeug/datastructures.pyi,sha256=HRzDLc7A6qnwluhNqn6AT76CsLZIkAbVVqxn0AbfV-s,34506
+werkzeug/debug/__init__.py,sha256=Gpq6OpS6mHwHk0mJkHc2fWvvjo6ccJVS9QJwJgoeb9I,18893
+werkzeug/debug/__pycache__/__init__.cpython-310.pyc,,
+werkzeug/debug/__pycache__/console.cpython-310.pyc,,
+werkzeug/debug/__pycache__/repr.cpython-310.pyc,,
+werkzeug/debug/__pycache__/tbtools.cpython-310.pyc,,
+werkzeug/debug/console.py,sha256=dechqiCtHfs0AQZWZofUC1S97tCuvwDgT0gdha5KwWM,6208
+werkzeug/debug/repr.py,sha256=FFczy4yhVfEQjW99HuZtUce-ebtJWMjp9GnfasXa0KA,9488
+werkzeug/debug/shared/ICON_LICENSE.md,sha256=DhA6Y1gUl5Jwfg0NFN9Rj4VWITt8tUx0IvdGf0ux9-s,222
+werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
+werkzeug/debug/shared/debugger.js,sha256=tg42SZs1SVmYWZ-_Fj5ELK5-FLHnGNQrei0K2By8Bw8,10521
+werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
+werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
+werkzeug/debug/shared/style.css,sha256=-xSxzUEZGw_IqlDR5iZxitNl8LQUjBM-_Y4UAvXVH8g,6078
+werkzeug/debug/tbtools.py,sha256=Fsmlk6Ao3CcXm9iX7i_8MhCp2SQZ8uHm8Cf5wacnlW4,13293
+werkzeug/exceptions.py,sha256=5MFy6RyaU4nokoYzdDafloY51QUDIGVNKeK_FORUFS0,26543
+werkzeug/formparser.py,sha256=rLEu_ZwVpvqshZg6E4Qiv36QsmzmCytTijBeGX3dDGk,16056
+werkzeug/http.py,sha256=i_LrIU9KsOz27zfkwKIK6eifFuFMKgSuW15k57HbMiE,42162
+werkzeug/local.py,sha256=1IRMV9MFrauLaZeliF0Md1n7ZOcOKLbS03bnQ8Gz5WY,22326
+werkzeug/middleware/__init__.py,sha256=qfqgdT5npwG9ses3-FXQJf3aB95JYP1zchetH_T3PUw,500
+werkzeug/middleware/__pycache__/__init__.cpython-310.pyc,,
+werkzeug/middleware/__pycache__/dispatcher.cpython-310.pyc,,
+werkzeug/middleware/__pycache__/http_proxy.cpython-310.pyc,,
+werkzeug/middleware/__pycache__/lint.cpython-310.pyc,,
+werkzeug/middleware/__pycache__/profiler.cpython-310.pyc,,
+werkzeug/middleware/__pycache__/proxy_fix.cpython-310.pyc,,
+werkzeug/middleware/__pycache__/shared_data.cpython-310.pyc,,
+werkzeug/middleware/dispatcher.py,sha256=Fh_w-KyWnTSYF-Lfv5dimQ7THSS7afPAZMmvc4zF1gg,2580
+werkzeug/middleware/http_proxy.py,sha256=HE8VyhS7CR-E1O6_9b68huv8FLgGGR1DLYqkS3Xcp3Q,7558
+werkzeug/middleware/lint.py,sha256=Sr6gV4royDs6ezkqv5trRAyKMDQ60KaEq3-tQ3opUvw,13968
+werkzeug/middleware/profiler.py,sha256=QkXk7cqnaPnF8wQu-5SyPCIOT3_kdABUBorQOghVNOA,4899
+werkzeug/middleware/proxy_fix.py,sha256=l7LC_LDu0Yd4SvUxS5SFigAJMzcIOGm6LNKl9IXJBSU,6974
+werkzeug/middleware/shared_data.py,sha256=fXjrEkuqxUVLG1DLrOdQLc96QQdjftCBZ1oM5oK89h4,9528
+werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+werkzeug/routing/__init__.py,sha256=HpvahY7WwkLdV4Cq3Bsc3GrqNon4u6t8-vhbb9E5o00,4819
+werkzeug/routing/__pycache__/__init__.cpython-310.pyc,,
+werkzeug/routing/__pycache__/converters.cpython-310.pyc,,
+werkzeug/routing/__pycache__/exceptions.cpython-310.pyc,,
+werkzeug/routing/__pycache__/map.cpython-310.pyc,,
+werkzeug/routing/__pycache__/matcher.cpython-310.pyc,,
+werkzeug/routing/__pycache__/rules.cpython-310.pyc,,
+werkzeug/routing/converters.py,sha256=05bkekg64vLC6mqqK4ddBh589WH9yBsjtW8IJhdUBvw,6968
+werkzeug/routing/exceptions.py,sha256=RklUDL9ajOv2fTcRNj4pb18Bs4Y-GKk4rIeTSfsqkks,4737
+werkzeug/routing/map.py,sha256=XN4ZjzEF1SfMxtdov89SDE-1_U78KVnnobTfnHzqbmE,36757
+werkzeug/routing/matcher.py,sha256=U8xZTB3e5f3TgbkxdDyVuyxK4w72l1lo_b3tdG2zNrc,7122
+werkzeug/routing/rules.py,sha256=v27RaR5H3sIPRdJ_pdEfOBMN6EivFVpmFzJk7aizdyw,31072
+werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+werkzeug/sansio/__pycache__/__init__.cpython-310.pyc,,
+werkzeug/sansio/__pycache__/http.cpython-310.pyc,,
+werkzeug/sansio/__pycache__/multipart.cpython-310.pyc,,
+werkzeug/sansio/__pycache__/request.cpython-310.pyc,,
+werkzeug/sansio/__pycache__/response.cpython-310.pyc,,
+werkzeug/sansio/__pycache__/utils.cpython-310.pyc,,
+werkzeug/sansio/http.py,sha256=9eORg44CDxpmV9i_U_pZ_NR8gdc9UXFCdE7EAP1v-c0,5162
+werkzeug/sansio/multipart.py,sha256=Uyrg2U6s2oft8LXOyuTvFCWTLOEr7INVW8zFTXNwZ7A,9756
+werkzeug/sansio/request.py,sha256=SiGcx2cz-l81TlCCrKrT2fePqC64hN8fSg5Ig6J6vRs,20175
+werkzeug/sansio/response.py,sha256=UTl-teQDDjovrZMkjj3ZQsHw-JtiFak5JfKEk1_vBYU,26026
+werkzeug/sansio/utils.py,sha256=EjbqdHdT-JZWgjUQaaWSgBUIRprXUkrsMQQqJlJHpVU,4847
+werkzeug/security.py,sha256=vrBofh4WZZoUo1eAdJ6F1DrzVRlYauGS2CUDYpbQKj8,4658
+werkzeug/serving.py,sha256=18pfjrHw8b5UCgPPo1ZEoxlYZZ5UREl4jQ9f8LGWMAo,38458
+werkzeug/test.py,sha256=t7T5G-HciIlv1ZXtlydFVpow0VrXnJ_Y3yyEB7T0_Ww,48125
+werkzeug/testapp.py,sha256=RJhT_2JweNiMKe304N3bF1zaIeMpRx-CIMERdeydfTY,9404
+werkzeug/urls.py,sha256=Q9Si-eVh7yxk3rwkzrwGRm146FXVXgg9lBP3k0HUfVM,36600
+werkzeug/user_agent.py,sha256=WclZhpvgLurMF45hsioSbS75H1Zb4iMQGKN3_yZ2oKo,1420
+werkzeug/utils.py,sha256=OYdB2cZPYYgq3C0EVKMIv05BrYzzr9xdefW0H00_IVo,24936
+werkzeug/wrappers/__init__.py,sha256=kGyK7rOud3qCxll_jFyW15YarJhj1xtdf3ocx9ZheB8,120
+werkzeug/wrappers/__pycache__/__init__.cpython-310.pyc,,
+werkzeug/wrappers/__pycache__/request.cpython-310.pyc,,
+werkzeug/wrappers/__pycache__/response.cpython-310.pyc,,
+werkzeug/wrappers/request.py,sha256=UQ559KkGS0Po6HTBgvKMlk1_AsNw5zstzm8o_dRrfdQ,23415
+werkzeug/wrappers/response.py,sha256=c2HUXrrt5Sf8-XEB1fUXxm6jp7Lu80KR0A_tbQFvw1Q,34750
+werkzeug/wsgi.py,sha256=sgkFCzhl23hlSmbvjxbI-hVEjSlPuEBGTDAHmXFcAts,34732

+ 5 - 0
venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 1 - 0
venv/lib/python3.10/site-packages/Werkzeug-2.2.2.dist-info/top_level.txt

@@ -0,0 +1 @@
+werkzeug

+ 128 - 0
venv/lib/python3.10/site-packages/_distutils_hack/__init__.py

@@ -0,0 +1,128 @@
+import sys
+import os
+import re
+import importlib
+import warnings
+
+
+is_pypy = '__pypy__' in sys.builtin_module_names
+
+
+warnings.filterwarnings('ignore',
+                        r'.+ distutils\b.+ deprecated',
+                        DeprecationWarning)
+
+
+def warn_distutils_present():
+    if 'distutils' not in sys.modules:
+        return
+    if is_pypy and sys.version_info < (3, 7):
+        # PyPy for 3.6 unconditionally imports distutils, so bypass the warning
+        # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
+        return
+    warnings.warn(
+        "Distutils was imported before Setuptools, but importing Setuptools "
+        "also replaces the `distutils` module in `sys.modules`. This may lead "
+        "to undesirable behaviors or errors. To avoid these issues, avoid "
+        "using distutils directly, ensure that setuptools is installed in the "
+        "traditional way (e.g. not an editable install), and/or make sure "
+        "that setuptools is always imported before distutils.")
+
+
+def clear_distutils():
+    if 'distutils' not in sys.modules:
+        return
+    warnings.warn("Setuptools is replacing distutils.")
+    mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
+    for name in mods:
+        del sys.modules[name]
+
+
+def enabled():
+    """
+    Allow selection of distutils by environment variable.
+    """
+    which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
+    return which == 'local'
+
+
+def ensure_local_distutils():
+    clear_distutils()
+    distutils = importlib.import_module('setuptools._distutils')
+    distutils.__name__ = 'distutils'
+    sys.modules['distutils'] = distutils
+
+    # sanity check that submodules load as expected
+    core = importlib.import_module('distutils.core')
+    assert '_distutils' in core.__file__, core.__file__
+
+
+def do_override():
+    """
+    Ensure that the local copy of distutils is preferred over stdlib.
+
+    See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
+    for more motivation.
+    """
+    if enabled():
+        warn_distutils_present()
+        ensure_local_distutils()
+
+
+class DistutilsMetaFinder:
+    def find_spec(self, fullname, path, target=None):
+        if path is not None:
+            return
+
+        method_name = 'spec_for_{fullname}'.format(**locals())
+        method = getattr(self, method_name, lambda: None)
+        return method()
+
+    def spec_for_distutils(self):
+        import importlib.abc
+        import importlib.util
+
+        class DistutilsLoader(importlib.abc.Loader):
+
+            def create_module(self, spec):
+                return importlib.import_module('setuptools._distutils')
+
+            def exec_module(self, module):
+                pass
+
+        return importlib.util.spec_from_loader('distutils', DistutilsLoader())
+
+    def spec_for_pip(self):
+        """
+        Ensure stdlib distutils when running under pip.
+        See pypa/pip#8761 for rationale.
+        """
+        if self.pip_imported_during_build():
+            return
+        clear_distutils()
+        self.spec_for_distutils = lambda: None
+
+    @staticmethod
+    def pip_imported_during_build():
+        """
+        Detect if pip is being imported in a build script. Ref #2355.
+        """
+        import traceback
+        return any(
+            frame.f_globals['__file__'].endswith('setup.py')
+            for frame, line in traceback.walk_stack(None)
+        )
+
+
+DISTUTILS_FINDER = DistutilsMetaFinder()
+
+
+def add_shim():
+    sys.meta_path.insert(0, DISTUTILS_FINDER)
+
+
+def remove_shim():
+    try:
+        sys.meta_path.remove(DISTUTILS_FINDER)
+    except ValueError:
+        pass

+ 1 - 0
venv/lib/python3.10/site-packages/_distutils_hack/override.py

@@ -0,0 +1 @@
+__import__('_distutils_hack').do_override()

+ 1 - 0
venv/lib/python3.10/site-packages/click-8.1.3.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 28 - 0
venv/lib/python3.10/site-packages/click-8.1.3.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.

+ 111 - 0
venv/lib/python3.10/site-packages/click-8.1.3.dist-info/METADATA

@@ -0,0 +1,111 @@
+Metadata-Version: 2.1
+Name: click
+Version: 8.1.3
+Summary: Composable command line interface toolkit
+Home-page: https://palletsprojects.com/p/click/
+Author: Armin Ronacher
+Author-email: armin.ronacher@active-4.com
+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: Twitter, https://twitter.com/PalletsTeam
+Project-URL: Chat, https://discord.gg/pallets
+Platform: UNKNOWN
+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
+-   Website: https://palletsprojects.com/p/click
+-   Twitter: https://twitter.com/PalletsTeam
+-   Chat: https://discord.gg/pallets
+
+

+ 39 - 0
venv/lib/python3.10/site-packages/click-8.1.3.dist-info/RECORD

@@ -0,0 +1,39 @@
+click-8.1.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+click-8.1.3.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
+click-8.1.3.dist-info/METADATA,sha256=tFJIX5lOjx7c5LjZbdTPFVDJSgyv9F74XY0XCPp_gnc,3247
+click-8.1.3.dist-info/RECORD,,
+click-8.1.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
+click-8.1.3.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
+click/__init__.py,sha256=rQBLutqg-z6m8nOzivIfigDn_emijB_dKv9BZ2FNi5s,3138
+click/__pycache__/__init__.cpython-310.pyc,,
+click/__pycache__/_compat.cpython-310.pyc,,
+click/__pycache__/_termui_impl.cpython-310.pyc,,
+click/__pycache__/_textwrap.cpython-310.pyc,,
+click/__pycache__/_winconsole.cpython-310.pyc,,
+click/__pycache__/core.cpython-310.pyc,,
+click/__pycache__/decorators.cpython-310.pyc,,
+click/__pycache__/exceptions.cpython-310.pyc,,
+click/__pycache__/formatting.cpython-310.pyc,,
+click/__pycache__/globals.cpython-310.pyc,,
+click/__pycache__/parser.cpython-310.pyc,,
+click/__pycache__/shell_completion.cpython-310.pyc,,
+click/__pycache__/termui.cpython-310.pyc,,
+click/__pycache__/testing.cpython-310.pyc,,
+click/__pycache__/types.cpython-310.pyc,,
+click/__pycache__/utils.cpython-310.pyc,,
+click/_compat.py,sha256=JIHLYs7Jzz4KT9t-ds4o4jBzLjnwCiJQKqur-5iwCKI,18810
+click/_termui_impl.py,sha256=qK6Cfy4mRFxvxE8dya8RBhLpSC8HjF-lvBc6aNrPdwg,23451
+click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353
+click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860
+click/core.py,sha256=mz87bYEKzIoNYEa56BFAiOJnvt1Y0L-i7wD4_ZecieE,112782
+click/decorators.py,sha256=yo3zvzgUm5q7h5CXjyV6q3h_PJAiUaem178zXwdWUFI,16350
+click/exceptions.py,sha256=7gDaLGuFZBeCNwY9ERMsF2-Z3R9Fvq09Zc6IZSKjseo,9167
+click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706
+click/globals.py,sha256=TP-qM88STzc7f127h35TD_v920FgfOD2EwzqA0oE8XU,1961
+click/parser.py,sha256=cAEt1uQR8gq3-S9ysqbVU-fdAZNvilxw4ReJ_T1OQMk,19044
+click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+click/shell_completion.py,sha256=qOp_BeC9esEOSZKyu5G7RIxEUaLsXUX-mTb7hB1r4QY,18018
+click/termui.py,sha256=ACBQVOvFCTSqtD5VREeCAdRtlHd-Imla-Lte4wSfMjA,28355
+click/testing.py,sha256=ptpMYgRY7dVfE3UDgkgwayu9ePw98sQI3D7zZXiCpj4,16063
+click/types.py,sha256=rEb1aZSQKq3ciCMmjpG2Uva9vk498XRL7ThrcK2GRss,35805
+click/utils.py,sha256=33D6E7poH_nrKB-xr-UyDEXnxOcCiQqxuRLtrqeVv6o,18682

+ 5 - 0
venv/lib/python3.10/site-packages/click-8.1.3.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 1 - 0
venv/lib/python3.10/site-packages/click-8.1.3.dist-info/top_level.txt

@@ -0,0 +1 @@
+click

+ 73 - 0
venv/lib/python3.10/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.3"

+ 626 - 0
venv/lib/python3.10/site-packages/click/_compat.py

@@ -0,0 +1,626 @@
+import codecs
+import io
+import os
+import re
+import sys
+import typing as t
+from weakref import WeakKeyDictionary
+
+CYGWIN = sys.platform.startswith("cygwin")
+MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version)
+# Determine local App Engine environment, per Google's own suggestion
+APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get(
+    "SERVER_SOFTWARE", ""
+)
+WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2
+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 get_filesystem_encoding() -> str:
+    return sys.getfilesystemencoding() or sys.getdefaultencoding()
+
+
+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) -> 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, 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, 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.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.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,
+    encoding: t.Optional[str],
+    errors: t.Optional[str],
+    is_binary: t.Callable[[t.IO, bool], bool],
+    find_binary: t.Callable[[t.IO], 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,
+    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,
+    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, int],
+    mode: str,
+    encoding: t.Optional[str],
+    errors: t.Optional[str],
+) -> t.IO:
+    """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: str,
+    mode: str = "r",
+    encoding: t.Optional[str] = None,
+    errors: t.Optional[str] = "strict",
+    atomic: bool = False,
+) -> t.Tuple[t.IO, bool]:
+    binary = "b" in mode
+
+    # 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, af), True
+
+
+class _AtomicFile:
+    def __init__(self, f: t.IO, 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, exc_value, tb):  # type: ignore
+        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) -> bool:
+    while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
+        stream = stream._stream
+
+    return stream.__class__.__module__.startswith("ipykernel.")
+
+
+def should_strip_ansi(
+    stream: t.Optional[t.IO] = 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(
+        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 get_filesystem_encoding()
+
+    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) -> bool:
+    try:
+        return stream.isatty()
+    except Exception:
+        return False
+
+
+def _make_cached_stream_func(
+    src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO]
+) -> t.Callable[[], t.TextIO]:
+    cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
+
+    def func() -> t.TextIO:
+        stream = src_func()
+        try:
+            rv = cache.get(stream)
+        except Exception:
+            rv = None
+        if rv is not None:
+            return rv
+        rv = wrapper_func()
+        try:
+            cache[stream] = rv
+        except Exception:
+            pass
+        return rv
+
+    return func
+
+
+_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
+_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
+_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
+
+
+binary_streams: 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,
+}

+ 717 - 0
venv/lib/python3.10/site-packages/click/_termui_impl.py

@@ -0,0 +1,717 @@
+"""
+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 ._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 = label or ""
+        if file is None:
+            file = _default_text_stdout()
+        self.file = file
+        self.color = color
+        self.update_min_steps = update_min_steps
+        self._completed_intervals = 0
+        self.width = width
+        self.autowidth = 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 = iter(iterable)
+        self.length = length
+        self.pos = 0
+        self.avg: t.List[float] = []
+        self.start = self.last_eta = time.time()
+        self.eta_known = False
+        self.finished = False
+        self.max_width: t.Optional[int] = None
+        self.entered = False
+        self.current_item: t.Optional[V] = None
+        self.is_hidden = not isatty(self.file)
+        self._last_line: t.Optional[str] = None
+
+    def __enter__(self) -> "ProgressBar":
+        self.entered = True
+        self.render_progress()
+        return self
+
+    def __exit__(self, exc_type, exc_value, tb):  # type: ignore
+        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()
+    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

+ 49 - 0
venv/lib/python3.10/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)

+ 279 - 0
venv/lib/python3.10/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"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
+
+
+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)

+ 2998 - 0
venv/lib/python3.10/site-packages/click/core.py

@@ -0,0 +1,2998 @@
+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 partial
+from functools import update_wrapper
+from gettext import gettext as _
+from gettext import ngettext
+from itertools import repeat
+
+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.Dict[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.Dict[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, exc_value, tb):  # type: ignore
+        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)
+
+    def invoke(
+        __self,  # noqa: B902
+        __callback: t.Union["Command", t.Callable[..., t.Any]],
+        *args: t.Any,
+        **kwargs: t.Any,
+    ) -> t.Any:
+        """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 = 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.Dict[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.Dict[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 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 invokable 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
+                             ``"_<prog_name>_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):
+                echo(file=sys.stderr)
+                raise Abort() from None
+            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.Dict[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``.
+        """
+        if complete_var is None:
+            complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").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.Dict[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."""
+        text = self.help if self.help is not None else ""
+
+        if self.deprecated:
+            text = _("(Deprecated) {text}").format(text=text)
+
+        if text:
+            text = inspect.cleandoc(text).partition("\f")[0]
+            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.
+    """
+
+    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)  # type: ignore
+                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 ``commmands`` 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.Dict[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.Dict[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
+
+        if self.command_class and kwargs.get("cls") is None:
+            kwargs["cls"] = self.command_class
+
+        func: t.Optional[t.Callable] = None
+
+        if args and callable(args[0]):
+            assert (
+                len(args) == 1 and not kwargs
+            ), "Use 'command(**kwargs)(callable)' to provide arguments."
+            (func,) = args
+            args = ()
+
+        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] = 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.
+    """
+
+    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 later 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, self.opts, self.secondary_opts = self._parse_decls(
+            param_decls or (), expose_value
+        )
+        self.type = 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:
+            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:
+            convert: t.Callable[[t.Any], t.Any] = partial(
+                self.type, param=self, ctx=ctx
+            )
+        elif self.nargs == -1:
+
+            def convert(value: t.Any) -> t.Tuple:
+                return tuple(self.type(x, self, ctx) for x in check_iter(value))
+
+        else:  # nargs > 1
+
+            def convert(value: t.Any) -> t.Tuple:
+                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.
+
+    .. 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
+
+        if is_flag and default_is_missing and not self.required:
+            self.default: t.Union[t.Any, t.Callable[[], t.Any]] = False
+
+        if flag_value is None:
+            flag_value = not self.default
+
+        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 = 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'.")
+
+            if self.multiple and self.is_flag:
+                raise TypeError("'multiple' is not valid with 'is_flag', use 'count'.")
+
+    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 param.flag_value  # type: ignore
+
+            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 parameter constructor.
+    """
+
+    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)

+ 497 - 0
venv/lib/python3.10/site-packages/click/decorators.py

@@ -0,0 +1,497 @@
+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
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+FC = t.TypeVar("FC", bound=t.Union[t.Callable[..., t.Any], Command])
+
+
+def pass_context(f: F) -> F:
+    """Marks a callback as wanting to receive the current context
+    object as first argument.
+    """
+
+    def new_func(*args, **kwargs):  # type: ignore
+        return f(get_current_context(), *args, **kwargs)
+
+    return update_wrapper(t.cast(F, new_func), f)
+
+
+def pass_obj(f: F) -> F:
+    """Similar to :func:`pass_context`, but only pass the object on the
+    context onwards (:attr:`Context.obj`).  This is useful if that object
+    represents the state of a nested system.
+    """
+
+    def new_func(*args, **kwargs):  # type: ignore
+        return f(get_current_context().obj, *args, **kwargs)
+
+    return update_wrapper(t.cast(F, new_func), f)
+
+
+def make_pass_decorator(
+    object_type: t.Type, ensure: bool = False
+) -> "t.Callable[[F], F]":
+    """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: F) -> F:
+        def new_func(*args, **kwargs):  # type: ignore
+            ctx = get_current_context()
+
+            if ensure:
+                obj = ctx.ensure_object(object_type)
+            else:
+                obj = ctx.find_object(object_type)
+
+            if obj is None:
+                raise RuntimeError(
+                    "Managed to invoke callback without a context"
+                    f" object of type {object_type.__name__!r}"
+                    " existing."
+                )
+
+            return ctx.invoke(f, obj, *args, **kwargs)
+
+        return update_wrapper(t.cast(F, new_func), f)
+
+    return decorator
+
+
+def pass_meta_key(
+    key: str, *, doc_description: t.Optional[str] = None
+) -> "t.Callable[[F], F]":
+    """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: F) -> F:
+        def new_func(*args, **kwargs):  # type: ignore
+            ctx = get_current_context()
+            obj = ctx.meta[key]
+            return ctx.invoke(f, obj, *args, **kwargs)
+
+        return update_wrapper(t.cast(F, 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
+
+
+CmdType = t.TypeVar("CmdType", bound=Command)
+
+
+@t.overload
+def command(
+    __func: t.Callable[..., t.Any],
+) -> Command:
+    ...
+
+
+@t.overload
+def command(
+    name: t.Optional[str] = None,
+    **attrs: t.Any,
+) -> t.Callable[..., Command]:
+    ...
+
+
+@t.overload
+def command(
+    name: t.Optional[str] = None,
+    cls: t.Type[CmdType] = ...,
+    **attrs: t.Any,
+) -> t.Callable[..., CmdType]:
+    ...
+
+
+def command(
+    name: t.Union[str, t.Callable[..., t.Any], None] = None,
+    cls: t.Optional[t.Type[Command]] = None,
+    **attrs: t.Any,
+) -> t.Union[Command, t.Callable[..., Command]]:
+    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[..., 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 = Command
+
+    def decorator(f: t.Callable[..., t.Any]) -> Command:
+        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__
+
+        cmd = cls(  # type: ignore[misc]
+            name=name or f.__name__.lower().replace("_", "-"),  # type: ignore[arg-type]
+            callback=f,
+            params=params,
+            **attrs,
+        )
+        cmd.__doc__ = f.__doc__
+        return cmd
+
+    if func is not None:
+        return decorator(func)
+
+    return decorator
+
+
+@t.overload
+def group(
+    __func: t.Callable[..., t.Any],
+) -> Group:
+    ...
+
+
+@t.overload
+def group(
+    name: t.Optional[str] = None,
+    **attrs: t.Any,
+) -> t.Callable[[F], Group]:
+    ...
+
+
+def group(
+    name: t.Union[str, t.Callable[..., t.Any], None] = None, **attrs: t.Any
+) -> t.Union[Group, t.Callable[[F], Group]]:
+    """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 attrs.get("cls") is None:
+        attrs["cls"] = Group
+
+    if callable(name):
+        grp: t.Callable[[F], Group] = t.cast(Group, command(**attrs))
+        return grp(name)
+
+    return t.cast(Group, command(name, **attrs))
+
+
+def _param_memo(f: FC, 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, **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.
+
+    :param cls: the argument class to instantiate.  This defaults to
+                :class:`Argument`.
+    """
+
+    def decorator(f: FC) -> FC:
+        ArgumentClass = attrs.pop("cls", None) or Argument
+        _param_memo(f, ArgumentClass(param_decls, **attrs))
+        return f
+
+    return decorator
+
+
+def option(*param_decls: str, **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.
+
+    :param cls: the option class to instantiate.  This defaults to
+                :class:`Option`.
+    """
+
+    def decorator(f: FC) -> FC:
+        # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
+        option_attrs = attrs.copy()
+        OptionClass = option_attrs.pop("cls", None) or Option
+        _param_memo(f, OptionClass(param_decls, **option_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(
+            t.cast(str, 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)

+ 287 - 0
venv/lib/python3.10/site-packages/click/exceptions.py

@@ -0,0 +1,287 @@
+import os
+import typing as t
+from gettext import gettext as _
+from gettext import ngettext
+
+from ._compat import get_text_stderr
+from .utils import echo
+
+if t.TYPE_CHECKING:
+    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] = 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 = self.ctx.command if self.ctx else None
+
+    def show(self, file: t.Optional[t.IO] = 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 = os.fsdecode(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 = code

+ 301 - 0
venv/lib/python3.10/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

+ 68 - 0
venv/lib/python3.10/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

+ 529 - 0
venv/lib/python3.10/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 = 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: str, 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 = True
+        #: This tells the parser how to deal with unknown options.  By
+        #: default it will error out (which is sensible), but there is a
+        #: second mode where it will ignore it and continue processing
+        #: after shifting all the unknown options into the resulting args.
+        self.ignore_unknown_options = False
+
+        if ctx is not None:
+            self.allow_interspersed_args = ctx.allow_interspersed_args
+            self.ignore_unknown_options = ctx.ignore_unknown_options
+
+        self._short_opt: 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 re-combinate the string of the
+        # remaining options and re-attach the prefix, then report that
+        # to the state as new larg.  This way there is basic combinatorics
+        # that can be achieved while still ignoring unknown arguments.
+        if self.ignore_unknown_options and unknown_options:
+            state.largs.append(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)

+ 0 - 0
venv/lib/python3.10/site-packages/click/py.typed


+ 580 - 0
venv/lib/python3.10/site-packages/click/shell_completion.py

@@ -0,0 +1,580 @@
+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.Dict[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 = value
+        self.type = type
+        self.help = 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
+}
+
+compdef %(complete_func)s %(prog_name)s;
+"""
+
+_SOURCE_FISH = """\
+function %(complete_func)s;
+    set -l response;
+
+    for value in (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \
+COMP_CWORD=(commandline -t) %(prog_name)s);
+        set response $response $value;
+    end;
+
+    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.Dict[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("-", "_"), 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
+
+    def _check_version(self) -> 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":
+                raise RuntimeError(
+                    _(
+                        "Shell completion is not supported for Bash"
+                        " versions older than 4.4."
+                    )
+                )
+        else:
+            raise RuntimeError(
+                _("Couldn't detect Bash version, shell completion is not supported.")
+            )
+
+    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}"
+
+
+_available_shells: t.Dict[str, t.Type[ShellComplete]] = {
+    "bash": BashComplete,
+    "fish": FishComplete,
+    "zsh": ZshComplete,
+}
+
+
+def add_completion_class(
+    cls: t.Type[ShellComplete], name: t.Optional[str] = None
+) -> None:
+    """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
+
+
+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
+    value = ctx.params[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.Dict[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:
+                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

+ 787 - 0
venv/lib/python3.10/site-packages/click/termui.py

@@ -0,0 +1,787 @@
+import inspect
+import io
+import itertools
+import os
+import sys
+import typing as t
+from gettext import gettext as _
+
+from ._compat import isatty
+from ._compat import strip_ansi
+from ._compat import WIN
+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  # type: ignore
+
+    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
+    if WIN:
+        os.system("cls")
+    else:
+        sys.stdout.write("\033[2J\033[1;1H")
+
+
+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)

+ 479 - 0
venv/lib/python3.10/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]], charset: str
+) -> t.BinaryIO:
+    # Is already an input stream.
+    if hasattr(input, "read"):
+        rv = _find_binary_reader(t.cast(t.IO, 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(t.cast(bytes, 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 = 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]] = 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="<stdin>", 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="<stdout>", 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="<stderr>",
+                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] = 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]] = 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]] = 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)  # type: ignore[type-var]
+        os.chdir(dt)
+
+        try:
+            yield t.cast(str, dt)
+        finally:
+            os.chdir(cwd)
+
+            if temp_dir is None:
+                try:
+                    shutil.rmtree(dt)
+                except OSError:  # noqa: B014
+                    pass

+ 1073 - 0
venv/lib/python3.10/site-packages/click/types.py

@@ -0,0 +1,1073 @@
+import os
+import stat
+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 get_filesystem_encoding
+from ._compat import open_stream
+from .exceptions import BadParameter
+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 = 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 = get_filesystem_encoding()
+                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 = 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]
+
+    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 = 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.Any) -> bool:
+        if self.lazy is not None:
+            return self.lazy
+        if value == "-":
+            return False
+        elif "w" in self.mode:
+            return True
+        return False
+
+    def convert(
+        self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+    ) -> t.Any:
+        try:
+            if hasattr(value, "read") or hasattr(value, "write"):
+                return value
+
+            lazy = self.resolve_lazy_flag(value)
+
+            if lazy:
+                f: t.IO = t.cast(
+                    t.IO,
+                    LazyFile(
+                        value, self.mode, self.encoding, self.errors, atomic=self.atomic
+                    ),
+                )
+
+                if ctx is not None:
+                    ctx.call_on_close(f.close_intelligently)  # type: ignore
+
+                return f
+
+            f, should_close = open_stream(
+                value, self.mode, self.encoding, self.errors, atomic=self.atomic
+            )
+
+            # If a context is provided, we automatically close the file
+            # at the end of the context execution (or flush out).  If a
+            # context does not exist, it's the caller's responsibility to
+            # properly close the file.  This for instance happens when the
+            # type is used with prompts.
+            if ctx is not None:
+                if should_close:
+                    ctx.call_on_close(safecall(f.close))
+                else:
+                    ctx.call_on_close(safecall(f.flush))
+
+            return f
+        except OSError as e:  # noqa: B014
+            self.fail(f"'{os.fsdecode(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")]
+
+
+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 ``type=pathlib.Path``.
+
+    .. versionchanged:: 6.0
+        Added the ``allow_dash`` parameter.
+    """
+
+    envvar_list_splitter = 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] = 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 = _("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, rv: t.Any) -> t.Any:
+        if self.type is not None and not isinstance(rv, self.type):
+            if self.type is str:
+                rv = os.fsdecode(rv)
+            elif self.type is bytes:
+                rv = os.fsencode(rv)
+            else:
+                rv = self.type(rv)
+
+        return rv
+
+    def convert(
+        self, value: t.Any, param: t.Optional["Parameter"], ctx: t.Optional["Context"]
+    ) -> t.Any:
+        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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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=os.fsdecode(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, ParamType]]) -> None:
+        self.types = [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()

+ 580 - 0
venv/lib/python3.10/site-packages/click/utils.py

@@ -0,0 +1,580 @@
+import os
+import re
+import sys
+import typing as t
+from functools import update_wrapper
+from types import ModuleType
+
+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 get_filesystem_encoding
+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
+
+F = t.TypeVar("F", bound=t.Callable[..., t.Any])
+
+
+def _posixify(name: str) -> str:
+    return "-".join(name.split()).lower()
+
+
+def safecall(func: F) -> F:
+    """Wraps a function so that it swallows exceptions."""
+
+    def wrapper(*args, **kwargs):  # type: ignore
+        try:
+            return func(*args, **kwargs)
+        except Exception:
+            pass
+
+    return update_wrapper(t.cast(F, wrapper), func)
+
+
+def make_str(value: t.Any) -> str:
+    """Converts a value into a valid string."""
+    if isinstance(value, bytes):
+        try:
+            return value.decode(get_filesystem_encoding())
+        except UnicodeError:
+            return value.decode("utf-8", "replace")
+    return 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: str,
+        mode: str = "r",
+        encoding: t.Optional[str] = None,
+        errors: t.Optional[str] = "strict",
+        atomic: bool = False,
+    ):
+        self.name = filename
+        self.mode = mode
+        self.encoding = encoding
+        self.errors = errors
+        self.atomic = atomic
+        self._f: t.Optional[t.IO]
+
+        if filename == "-":
+            self._f, self.should_close = open_stream(filename, mode, encoding, errors)
+        else:
+            if "r" in mode:
+                # Open and close the file in case we're opening it for
+                # reading so that we can catch at least some errors in
+                # some cases early.
+                open(filename, mode).close()
+            self._f = None
+            self.should_close = True
+
+    def __getattr__(self, name: str) -> t.Any:
+        return getattr(self.open(), name)
+
+    def __repr__(self) -> str:
+        if self._f is not None:
+            return repr(self._f)
+        return f"<unopened file '{self.name}' {self.mode}>"
+
+    def open(self) -> t.IO:
+        """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, exc_value, tb):  # type: ignore
+        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) -> None:
+        self._file = file
+
+    def __getattr__(self, name: str) -> t.Any:
+        return getattr(self._file, name)
+
+    def __enter__(self) -> "KeepOpenFile":
+        return self
+
+    def __exit__(self, exc_type, exc_value, tb):  # type: ignore
+        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()
+
+    # 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:
+    """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, 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, KeepOpenFile(f))
+
+    return f
+
+
+def format_filename(
+    filename: t.Union[str, bytes, os.PathLike], shorten: bool = False
+) -> str:
+    """Formats a filename for user display.  The main purpose of this
+    function is to ensure that the filename can be displayed at all.  This
+    will decode the filename to unicode if necessary in a way that it will
+    not fail.  Optionally, it can shorten the filename to not include the
+    full path to the filename.
+
+    :param filename: formats a filename for UI display.  This will also convert
+                     the filename into unicode without failing.
+    :param shorten: this optionally shortens the filename to strip of the
+                    path that leads up to it.
+    """
+    if shorten:
+        filename = os.path.basename(filename)
+
+    return os.fsdecode(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\<user>\AppData\Roaming\Foo Bar``
+    Windows (not roaming):
+      ``C:\Users\<user>\AppData\Local\Foo Bar``
+
+    .. versionadded:: 2.0
+
+    :param app_name: the application name.  This should be properly capitalized
+                     and can contain whitespace.
+    :param roaming: controls if the folder should be roaming or not on Windows.
+                    Has no affect otherwise.
+    :param force_posix: if this is set to `True` then on any POSIX system the
+                        folder will be stored in the home folder with a leading
+                        dot instead of the XDG config home or darwin's
+                        application support folder.
+    """
+    if WIN:
+        key = "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) -> 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.
+    if getattr(_main, "__package__", None) is 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

+ 1 - 0
venv/lib/python3.10/site-packages/distutils-precedence.pth

@@ -0,0 +1 @@
+import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim(); 

+ 71 - 0
venv/lib/python3.10/site-packages/flask/__init__.py

@@ -0,0 +1,71 @@
+from markupsafe import escape
+from markupsafe import Markup
+
+from . import json as json
+from .app import Flask as Flask
+from .app import Request as Request
+from .app import Response as Response
+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 signals_available as signals_available
+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
+
+__version__ = "2.2.2"
+
+
+def __getattr__(name):
+    if name == "_app_ctx_stack":
+        import warnings
+        from .globals import __app_ctx_stack
+
+        warnings.warn(
+            "'_app_ctx_stack' is deprecated and will be removed in Flask 2.3.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return __app_ctx_stack
+
+    if name == "_request_ctx_stack":
+        import warnings
+        from .globals import __request_ctx_stack
+
+        warnings.warn(
+            "'_request_ctx_stack' is deprecated and will be removed in Flask 2.3.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return __request_ctx_stack
+
+    raise AttributeError(name)

+ 3 - 0
venv/lib/python3.10/site-packages/flask/__main__.py

@@ -0,0 +1,3 @@
+from .cli import main
+
+main()

+ 2548 - 0
venv/lib/python3.10/site-packages/flask/app.py

@@ -0,0 +1,2548 @@
+import functools
+import inspect
+import json
+import logging
+import os
+import sys
+import typing as t
+import weakref
+from collections.abc import Iterator as _abc_Iterator
+from datetime import timedelta
+from itertools import chain
+from threading import Lock
+from types import TracebackType
+
+import click
+from werkzeug.datastructures import Headers
+from werkzeug.datastructures import ImmutableDict
+from werkzeug.exceptions import Aborter
+from werkzeug.exceptions import BadRequest
+from werkzeug.exceptions import BadRequestKeyError
+from werkzeug.exceptions import HTTPException
+from werkzeug.exceptions import InternalServerError
+from werkzeug.routing import BuildError
+from werkzeug.routing import Map
+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.urls import url_quote
+from werkzeug.utils import redirect as _wz_redirect
+from werkzeug.wrappers import Response as BaseResponse
+
+from . import cli
+from . import typing as ft
+from .config import Config
+from .config import ConfigAttribute
+from .ctx import _AppCtxGlobals
+from .ctx import AppContext
+from .ctx import RequestContext
+from .globals import _cv_app
+from .globals import _cv_request
+from .globals import g
+from .globals import request
+from .globals import request_ctx
+from .globals import session
+from .helpers import _split_blueprint_path
+from .helpers import get_debug_flag
+from .helpers import get_flashed_messages
+from .helpers import get_load_dotenv
+from .helpers import locked_cached_property
+from .json.provider import DefaultJSONProvider
+from .json.provider import JSONProvider
+from .logging import create_logger
+from .scaffold import _endpoint_from_view_func
+from .scaffold import _sentinel
+from .scaffold import find_package
+from .scaffold import Scaffold
+from .scaffold import setupmethod
+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 DispatchingJinjaLoader
+from .templating import Environment
+from .wrappers import Request
+from .wrappers import Response
+
+if t.TYPE_CHECKING:  # pragma: no cover
+    import typing_extensions as te
+    from .blueprints import Blueprint
+    from .testing import FlaskClient
+    from .testing import FlaskCliRunner
+
+T_before_first_request = t.TypeVar(
+    "T_before_first_request", bound=ft.BeforeFirstRequestCallable
+)
+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)
+
+if sys.version_info >= (3, 8):
+    iscoroutinefunction = inspect.iscoroutinefunction
+else:
+
+    def iscoroutinefunction(func: t.Any) -> bool:
+        while inspect.ismethod(func):
+            func = func.__func__
+
+        while isinstance(func, functools.partial):
+            func = func.func
+
+        return inspect.iscoroutinefunction(func)
+
+
+def _make_timedelta(value: t.Union[timedelta, int, None]) -> t.Optional[timedelta]:
+    if value is None or isinstance(value, timedelta):
+        return value
+
+    return timedelta(seconds=value)
+
+
+class Flask(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 that is used for request objects.  See :class:`~flask.Request`
+    #: for more information.
+    request_class = Request
+
+    #: The class that is used for response objects.  See
+    #: :class:`~flask.Response` for more information.
+    response_class = Response
+
+    #: 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("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("SECRET_KEY")
+
+    @property
+    def session_cookie_name(self) -> str:
+        """The name of the cookie set by the session interface.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3. Use ``app.config["SESSION_COOKIE_NAME"]``
+            instead.
+        """
+        import warnings
+
+        warnings.warn(
+            "'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use"
+            " 'SESSION_COOKIE_NAME' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.config["SESSION_COOKIE_NAME"]
+
+    @session_cookie_name.setter
+    def session_cookie_name(self, value: str) -> None:
+        import warnings
+
+        warnings.warn(
+            "'session_cookie_name' is deprecated and will be removed in Flask 2.3. Use"
+            " 'SESSION_COOKIE_NAME' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.config["SESSION_COOKIE_NAME"] = value
+
+    #: 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(
+        "PERMANENT_SESSION_LIFETIME", get_converter=_make_timedelta
+    )
+
+    @property
+    def send_file_max_age_default(self) -> t.Optional[timedelta]:
+        """The default value for ``max_age`` for :func:`~flask.send_file`. The default
+        is ``None``, which tells the browser to use conditional requests instead of a
+        timed cache.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3. Use
+            ``app.config["SEND_FILE_MAX_AGE_DEFAULT"]`` instead.
+
+        .. versionchanged:: 2.0
+            Defaults to ``None`` instead of 12 hours.
+        """
+        import warnings
+
+        warnings.warn(
+            "'send_file_max_age_default' is deprecated and will be removed in Flask"
+            " 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return _make_timedelta(self.config["SEND_FILE_MAX_AGE_DEFAULT"])
+
+    @send_file_max_age_default.setter
+    def send_file_max_age_default(self, value: t.Union[int, timedelta, None]) -> None:
+        import warnings
+
+        warnings.warn(
+            "'send_file_max_age_default' is deprecated and will be removed in Flask"
+            " 2.3. Use 'SEND_FILE_MAX_AGE_DEFAULT' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.config["SEND_FILE_MAX_AGE_DEFAULT"] = _make_timedelta(value)
+
+    @property
+    def use_x_sendfile(self) -> bool:
+        """Enable this to use the ``X-Sendfile`` feature, assuming the server supports
+        it, from :func:`~flask.send_file`.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3. Use ``app.config["USE_X_SENDFILE"]`` instead.
+        """
+        import warnings
+
+        warnings.warn(
+            "'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use"
+            " 'USE_X_SENDFILE' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.config["USE_X_SENDFILE"]
+
+    @use_x_sendfile.setter
+    def use_x_sendfile(self, value: bool) -> None:
+        import warnings
+
+        warnings.warn(
+            "'use_x_sendfile' is deprecated and will be removed in Flask 2.3. Use"
+            " 'USE_X_SENDFILE' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.config["USE_X_SENDFILE"] = value
+
+    _json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
+    _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
+
+    @property  # type: ignore[override]
+    def json_encoder(self) -> t.Type[json.JSONEncoder]:  # type: ignore[override]
+        """The JSON encoder class to use. Defaults to
+        :class:`~flask.json.JSONEncoder`.
+
+        .. deprecated:: 2.2
+             Will be removed in Flask 2.3. Customize
+             :attr:`json_provider_class` instead.
+
+        .. versionadded:: 0.10
+        """
+        import warnings
+
+        warnings.warn(
+            "'app.json_encoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if self._json_encoder is None:
+            from . import json
+
+            return json.JSONEncoder
+
+        return self._json_encoder
+
+    @json_encoder.setter
+    def json_encoder(self, value: t.Type[json.JSONEncoder]) -> None:
+        import warnings
+
+        warnings.warn(
+            "'app.json_encoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self._json_encoder = value
+
+    @property  # type: ignore[override]
+    def json_decoder(self) -> t.Type[json.JSONDecoder]:  # type: ignore[override]
+        """The JSON decoder class to use. Defaults to
+        :class:`~flask.json.JSONDecoder`.
+
+        .. deprecated:: 2.2
+             Will be removed in Flask 2.3. Customize
+             :attr:`json_provider_class` instead.
+
+        .. versionadded:: 0.10
+        """
+        import warnings
+
+        warnings.warn(
+            "'app.json_decoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if self._json_decoder is None:
+            from . import json
+
+            return json.JSONDecoder
+
+        return self._json_decoder
+
+    @json_decoder.setter
+    def json_decoder(self, value: t.Type[json.JSONDecoder]) -> None:
+        import warnings
+
+        warnings.warn(
+            "'app.json_decoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self._json_decoder = value
+
+    json_provider_class: t.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 = {}
+
+    #: Default configuration parameters.
+    default_config = ImmutableDict(
+        {
+            "ENV": None,
+            "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",
+            "JSON_AS_ASCII": None,
+            "JSON_SORT_KEYS": None,
+            "JSONIFY_PRETTYPRINT_REGULAR": None,
+            "JSONIFY_MIMETYPE": None,
+            "TEMPLATES_AUTO_RELOAD": None,
+            "MAX_COOKIE_SIZE": 4093,
+        }
+    )
+
+    #: 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: t.Optional[t.Type["FlaskClient"]] = 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: t.Optional[t.Type["FlaskCliRunner"]] = None
+
+    #: 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: t.Optional[str] = None,
+        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
+        static_host: t.Optional[str] = None,
+        host_matching: bool = False,
+        subdomain_matching: bool = False,
+        template_folder: t.Optional[str] = "templates",
+        instance_path: t.Optional[str] = None,
+        instance_relative_config: bool = False,
+        root_path: t.Optional[str] = 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: t.List[
+            t.Callable[[Exception, str, t.Dict[str, t.Any]], str]
+        ] = []
+
+        #: A list of functions that will be called at the beginning of the
+        #: first request to this instance. To register a function, use the
+        #: :meth:`before_first_request` decorator.
+        #:
+        #: .. deprecated:: 2.2
+        #:     Will be removed in Flask 2.3. Run setup code when
+        #:     creating the application instead.
+        #:
+        #: .. versionadded:: 0.8
+        self.before_first_request_funcs: t.List[ft.BeforeFirstRequestCallable] = []
+
+        #: 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: t.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: t.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: t.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 = {}
+
+        #: 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()
+
+        self.url_map.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
+        self._before_request_lock = Lock()
+
+        # 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}/<path:filename>",
+                endpoint="static",
+                host=static_host,
+                view_func=lambda **kw: self_ref().send_static_file(**kw),  # type: ignore # noqa: B950
+            )
+
+        # 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."
+            )
+
+    @locked_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 = 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
+
+    @property
+    def propagate_exceptions(self) -> bool:
+        """Returns the value of the ``PROPAGATE_EXCEPTIONS`` configuration
+        value in case it's set, otherwise a sensible default is returned.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3.
+
+        .. versionadded:: 0.7
+        """
+        import warnings
+
+        warnings.warn(
+            "'propagate_exceptions' is deprecated and will be removed in Flask 2.3.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        rv = self.config["PROPAGATE_EXCEPTIONS"]
+        if rv is not None:
+            return rv
+        return self.testing or self.debug
+
+    @locked_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)
+
+    @locked_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()
+
+    @property
+    def got_first_request(self) -> bool:
+        """This attribute is set to ``True`` if the application started
+        handling the first request.
+
+        .. versionadded:: 0.8
+        """
+        return self._got_first_request
+
+    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["ENV"] = os.environ.get("FLASK_ENV") or "production"
+        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 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)
+
+    @property
+    def templates_auto_reload(self) -> bool:
+        """Reload templates when they are changed. Used by
+        :meth:`create_jinja_environment`. It is enabled by default in debug mode.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3. Use ``app.config["TEMPLATES_AUTO_RELOAD"]``
+            instead.
+
+        .. versionadded:: 1.0
+            This property was added but the underlying config and behavior
+            already existed.
+        """
+        import warnings
+
+        warnings.warn(
+            "'templates_auto_reload' is deprecated and will be removed in Flask 2.3."
+            " Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        rv = self.config["TEMPLATES_AUTO_RELOAD"]
+        return rv if rv is not None else self.debug
+
+    @templates_auto_reload.setter
+    def templates_auto_reload(self, value: bool) -> None:
+        import warnings
+
+        warnings.warn(
+            "'templates_auto_reload' is deprecated and will be removed in Flask 2.3."
+            " Use 'TEMPLATES_AUTO_RELOAD' in 'app.config' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.config["TEMPLATES_AUTO_RELOAD"] = value
+
+    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_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`.
+
+        .. versionadded:: 0.5
+        """
+        if filename is None:
+            return True
+        return filename.endswith((".html", ".htm", ".xml", ".xhtml"))
+
+    def update_template_context(self, context: dict) -> 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[t.Optional[str]] = (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(func())
+
+        context.update(orig_ctx)
+
+    def make_shell_context(self) -> dict:
+        """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
+
+    @property
+    def env(self) -> str:
+        """What environment the app is running in. This maps to the :data:`ENV` config
+        key.
+
+        **Do not enable development when deploying in production.**
+
+        Default: ``'production'``
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3.
+        """
+        import warnings
+
+        warnings.warn(
+            "'app.env' is deprecated and will be removed in Flask 2.3."
+            " Use 'app.debug' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.config["ENV"]
+
+    @env.setter
+    def env(self, value: str) -> None:
+        import warnings
+
+        warnings.warn(
+            "'app.env' is deprecated and will be removed in Flask 2.3."
+            " Use 'app.debug' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.config["ENV"] = value
+
+    @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"]
+
+    @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
+
+    def run(
+        self,
+        host: t.Optional[str] = None,
+        port: t.Optional[int] = None,
+        debug: t.Optional[bool] = 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, let env vars override previous values
+            if "FLASK_ENV" in os.environ:
+                print(
+                    "'FLASK_ENV' is deprecated and will not be used in"
+                    " Flask 2.3. Use 'FLASK_DEBUG' instead.",
+                    file=sys.stderr,
+                )
+                self.config["ENV"] = os.environ.get("FLASK_ENV") or "production"
+                self.debug = get_debug_flag()
+            elif "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  # type: ignore
+        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  # type: ignore
+
+        return cls(self, **kwargs)  # type: ignore
+
+    @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: t.Optional[str] = None,
+        view_func: t.Optional[ft.RouteCallable] = None,
+        provide_automatic_options: t.Optional[bool] = 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 = self.url_rule_class(rule, methods=methods, **options)
+        rule.provide_automatic_options = provide_automatic_options  # type: ignore
+
+        self.url_map.add(rule)
+        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: t.Optional[str] = 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: t.Optional[str] = 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: t.Optional[str] = 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: t.Optional[str] = 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: t.Optional[str] = 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: t.Optional[str] = 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 before_first_request(self, f: T_before_first_request) -> T_before_first_request:
+        """Registers a function to be run before the first request to this
+        instance of the application.
+
+        The function will be called without any arguments and its return
+        value is ignored.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3. Run setup code when creating
+            the application instead.
+
+        .. versionadded:: 0.8
+        """
+        import warnings
+
+        warnings.warn(
+            "'before_first_request' is deprecated and will be removed"
+            " in Flask 2.3. Run setup code while creating the"
+            " application instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.before_first_request_funcs.append(f)
+        return 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) -> t.Optional[ft.ErrorHandlerCallable]:
+        """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 = (*request.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 handle_http_exception(
+        self, e: HTTPException
+    ) -> t.Union[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)
+        if handler is None:
+            return e
+        return self.ensure_sync(handler)(e)
+
+    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 handle_user_exception(
+        self, e: Exception
+    ) -> t.Union[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)
+
+        if handler is None:
+            raise
+
+        return self.ensure_sync(handler)(e)
+
+    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 :attr:`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, 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: t.Union[InternalServerError, ft.ResponseReturnValue]
+        server_error = InternalServerError(original_exception=e)
+        handler = self._find_error_handler(server_error)
+
+        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: t.Union[
+            t.Tuple[type, BaseException, TracebackType], t.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 raise_routing_exception(self, request: Request) -> "te.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
+
+        from .debughelpers import FormDataRoutingRedirect
+
+        raise FormDataRoutingRedirect(request)
+
+    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: t.Dict[str, t.Any] = req.view_args  # type: ignore[assignment]
+        return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
+
+    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
+        """
+        # Run before_first_request functions if this is the thread's first request.
+        # Inlined to avoid a method call on subsequent requests.
+        # This is deprecated, will be removed in Flask 2.3.
+        if not self._got_first_request:
+            with self._before_request_lock:
+                if not self._got_first_request:
+                    for func in self.before_first_request_funcs:
+                        self.ensure_sync(func)()
+
+                    self._got_first_request = True
+
+        try:
+            request_started.send(self)
+            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: t.Union[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, 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 should_ignore_error(self, error: t.Optional[BaseException]) -> 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 ensure_sync(self, func: t.Callable) -> t.Callable:
+        """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.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: t.Optional[str] = None,
+        _method: t.Optional[str] = None,
+        _scheme: t.Optional[str] = None,
+        _external: t.Optional[bool] = 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() <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:
+            rv = f"{rv}#{url_quote(_anchor)}"
+
+        return rv
+
+    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)
+
+    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, _abc_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, request.environ  # type: ignore[arg-type]
+                    )
+                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 create_url_adapter(
+        self, request: t.Optional[Request]
+    ) -> t.Optional[MapAdapter]:
+        """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 inject_url_defaults(self, endpoint: str, values: dict) -> 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[t.Optional[str]] = (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: t.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
+
+    def preprocess_request(self) -> t.Optional[ft.ResponseReturnValue]:
+        """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
+
+        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: t.Optional[BaseException] = _sentinel  # type: ignore
+    ) -> 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() <flask.ctx.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, exc=exc)
+
+    def do_teardown_appcontext(
+        self, exc: t.Optional[BaseException] = _sentinel  # type: ignore
+    ) -> 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() <flask.ctx.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, 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() <flask.ctx.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: dict) -> 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 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: dict, start_response: t.Callable) -> t.Any:
+        """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: t.Optional[BaseException] = 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: dict, start_response: t.Callable) -> t.Any:
+        """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)

+ 706 - 0
venv/lib/python3.10/site-packages/flask/blueprints.py

@@ -0,0 +1,706 @@
+import json
+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 Flask
+
+DeferredSetupFunction = t.Callable[["BlueprintSetupState"], t.Callable]
+T_after_request = t.TypeVar("T_after_request", bound=ft.AfterRequestCallable)
+T_before_first_request = t.TypeVar(
+    "T_before_first_request", bound=ft.BeforeFirstRequestCallable
+)
+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: "Flask",
+        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: t.Optional[str] = None,
+        view_func: t.Optional[t.Callable] = 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
+
+    _json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
+    _json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
+
+    @property  # type: ignore[override]
+    def json_encoder(  # type: ignore[override]
+        self,
+    ) -> t.Union[t.Type[json.JSONEncoder], None]:
+        """Blueprint-local JSON encoder class to use. Set to ``None`` to use the app's.
+
+        .. deprecated:: 2.2
+             Will be removed in Flask 2.3. Customize
+             :attr:`json_provider_class` instead.
+
+        .. versionadded:: 0.10
+        """
+        import warnings
+
+        warnings.warn(
+            "'bp.json_encoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self._json_encoder
+
+    @json_encoder.setter
+    def json_encoder(self, value: t.Union[t.Type[json.JSONEncoder], None]) -> None:
+        import warnings
+
+        warnings.warn(
+            "'bp.json_encoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self._json_encoder = value
+
+    @property  # type: ignore[override]
+    def json_decoder(  # type: ignore[override]
+        self,
+    ) -> t.Union[t.Type[json.JSONDecoder], None]:
+        """Blueprint-local JSON decoder class to use. Set to ``None`` to use the app's.
+
+        .. deprecated:: 2.2
+             Will be removed in Flask 2.3. Customize
+             :attr:`json_provider_class` instead.
+
+        .. versionadded:: 0.10
+        """
+        import warnings
+
+        warnings.warn(
+            "'bp.json_decoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self._json_decoder
+
+    @json_decoder.setter
+    def json_decoder(self, value: t.Union[t.Type[json.JSONDecoder], None]) -> None:
+        import warnings
+
+        warnings.warn(
+            "'bp.json_decoder' is deprecated and will be removed in Flask 2.3."
+            " Customize 'app.json_provider_class' or 'app.json' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self._json_decoder = value
+
+    def __init__(
+        self,
+        name: str,
+        import_name: str,
+        static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
+        static_url_path: t.Optional[str] = None,
+        template_folder: t.Optional[str] = None,
+        url_prefix: t.Optional[str] = None,
+        subdomain: t.Optional[str] = None,
+        url_defaults: t.Optional[dict] = None,
+        root_path: t.Optional[str] = None,
+        cli_group: t.Optional[str] = _sentinel,  # type: ignore
+    ):
+        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 "." 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: t.List[DeferredSetupFunction] = []
+
+        if url_defaults is None:
+            url_defaults = {}
+
+        self.url_values_defaults = url_defaults
+        self.cli_group = cli_group
+        self._blueprints: t.List[t.Tuple["Blueprint", dict]] = []
+
+    def _check_setup_finished(self, f_name: str) -> None:
+        if self._got_registered_once:
+            import warnings
+
+            warnings.warn(
+                f"The setup method '{f_name}' can no longer be called on"
+                f" the blueprint '{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.\n"
+                "This warning will become an exception in Flask 2.3.",
+                UserWarning,
+                stacklevel=3,
+            )
+
+    @setupmethod
+    def record(self, func: t.Callable) -> 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: t.Callable) -> 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: "Flask", options: dict, 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: "Flask", options: dict) -> 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.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``.
+
+        .. versionchanged:: 2.0.1
+            Registering the same blueprint with the same name multiple
+            times is deprecated and will become an error in Flask 2.1.
+        """
+        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}/<path:filename>",
+                view_func=self.send_static_file,
+                endpoint="static",
+            )
+
+        # Merge blueprint data into parent.
+        if first_bp_registration or first_name_registration:
+
+            def extend(bp_dict, parent_dict):
+                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)
+
+        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")
+
+            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)
+
+    @setupmethod
+    def add_url_rule(
+        self,
+        rule: str,
+        endpoint: t.Optional[str] = None,
+        view_func: t.Optional[ft.RouteCallable] = None,
+        provide_automatic_options: t.Optional[bool] = None,
+        **options: t.Any,
+    ) -> None:
+        """Like :meth:`Flask.add_url_rule` but for a blueprint.  The endpoint for
+        the :func:`url_for` function is prefixed with the name of the blueprint.
+        """
+        if endpoint 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: t.Optional[str] = None
+    ) -> t.Callable[[T_template_filter], T_template_filter]:
+        """Register a custom template filter, available application wide.  Like
+        :meth:`Flask.template_filter` but for a blueprint.
+
+        :param name: the optional name of the filter, otherwise the
+                     function name will be used.
+        """
+
+        def decorator(f: 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: t.Optional[str] = None
+    ) -> None:
+        """Register a custom template filter, available application wide.  Like
+        :meth:`Flask.add_template_filter` but for a blueprint.  Works exactly
+        like the :meth:`app_template_filter` decorator.
+
+        :param name: the optional name of the filter, otherwise the
+                     function name will be used.
+        """
+
+        def register_template(state: BlueprintSetupState) -> None:
+            state.app.jinja_env.filters[name or f.__name__] = f
+
+        self.record_once(register_template)
+
+    @setupmethod
+    def app_template_test(
+        self, name: t.Optional[str] = None
+    ) -> t.Callable[[T_template_test], T_template_test]:
+        """Register a custom template test, available application wide.  Like
+        :meth:`Flask.template_test` but for a blueprint.
+
+        .. versionadded:: 0.10
+
+        :param name: the optional name of the test, otherwise the
+                     function name will be used.
+        """
+
+        def decorator(f: 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: t.Optional[str] = None
+    ) -> None:
+        """Register a custom template test, available application wide.  Like
+        :meth:`Flask.add_template_test` but for a blueprint.  Works exactly
+        like the :meth:`app_template_test` decorator.
+
+        .. versionadded:: 0.10
+
+        :param name: the optional name of the test, otherwise the
+                     function name will be used.
+        """
+
+        def register_template(state: BlueprintSetupState) -> None:
+            state.app.jinja_env.tests[name or f.__name__] = f
+
+        self.record_once(register_template)
+
+    @setupmethod
+    def app_template_global(
+        self, name: t.Optional[str] = None
+    ) -> t.Callable[[T_template_global], T_template_global]:
+        """Register a custom template global, available application wide.  Like
+        :meth:`Flask.template_global` but for a blueprint.
+
+        .. versionadded:: 0.10
+
+        :param name: the optional name of the global, otherwise the
+                     function name will be used.
+        """
+
+        def decorator(f: 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: t.Optional[str] = None
+    ) -> None:
+        """Register a custom template global, available application wide.  Like
+        :meth:`Flask.add_template_global` but for a blueprint.  Works exactly
+        like the :meth:`app_template_global` decorator.
+
+        .. versionadded:: 0.10
+
+        :param name: the optional name of the global, otherwise the
+                     function name will be used.
+        """
+
+        def register_template(state: 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:`Flask.before_request`.  Such a function is executed
+        before each request, even if outside of a blueprint.
+        """
+        self.record_once(
+            lambda s: s.app.before_request_funcs.setdefault(None, []).append(f)
+        )
+        return f
+
+    @setupmethod
+    def before_app_first_request(
+        self, f: T_before_first_request
+    ) -> T_before_first_request:
+        """Like :meth:`Flask.before_first_request`.  Such a function is
+        executed before the first request to the application.
+
+        .. deprecated:: 2.2
+            Will be removed in Flask 2.3. Run setup code when creating
+            the application instead.
+        """
+        import warnings
+
+        warnings.warn(
+            "'before_app_first_request' is deprecated and will be"
+            " removed in Flask 2.3. Use 'record_once' instead to run"
+            " setup code when registering the blueprint.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.record_once(lambda s: s.app.before_first_request_funcs.append(f))
+        return f
+
+    @setupmethod
+    def after_app_request(self, f: T_after_request) -> T_after_request:
+        """Like :meth:`Flask.after_request` but for a blueprint.  Such a function
+        is executed after each request, even if outside of the blueprint.
+        """
+        self.record_once(
+            lambda s: s.app.after_request_funcs.setdefault(None, []).append(f)
+        )
+        return f
+
+    @setupmethod
+    def teardown_app_request(self, f: T_teardown) -> T_teardown:
+        """Like :meth:`Flask.teardown_request` but for a blueprint.  Such a
+        function is executed when tearing down each request, even if outside of
+        the blueprint.
+        """
+        self.record_once(
+            lambda s: s.app.teardown_request_funcs.setdefault(None, []).append(f)
+        )
+        return f
+
+    @setupmethod
+    def app_context_processor(
+        self, f: T_template_context_processor
+    ) -> T_template_context_processor:
+        """Like :meth:`Flask.context_processor` but for a blueprint.  Such a
+        function is executed each request, even if outside of the blueprint.
+        """
+        self.record_once(
+            lambda s: s.app.template_context_processors.setdefault(None, []).append(f)
+        )
+        return f
+
+    @setupmethod
+    def app_errorhandler(
+        self, code: t.Union[t.Type[Exception], int]
+    ) -> t.Callable[[T_error_handler], T_error_handler]:
+        """Like :meth:`Flask.errorhandler` but for a blueprint.  This
+        handler is used for all requests, even if outside of the blueprint.
+        """
+
+        def decorator(f: T_error_handler) -> T_error_handler:
+            self.record_once(lambda s: s.app.errorhandler(code)(f))
+            return f
+
+        return decorator
+
+    @setupmethod
+    def app_url_value_preprocessor(
+        self, f: T_url_value_preprocessor
+    ) -> T_url_value_preprocessor:
+        """Same as :meth:`url_value_preprocessor` but application wide."""
+        self.record_once(
+            lambda s: s.app.url_value_preprocessors.setdefault(None, []).append(f)
+        )
+        return f
+
+    @setupmethod
+    def app_url_defaults(self, f: T_url_defaults) -> T_url_defaults:
+        """Same as :meth:`url_defaults` but application wide."""
+        self.record_once(
+            lambda s: s.app.url_default_functions.setdefault(None, []).append(f)
+        )
+        return f

+ 1051 - 0
venv/lib/python3.10/site-packages/flask/cli.py

@@ -0,0 +1,1051 @@
+from __future__ import annotations
+
+import ast
+import inspect
+import os
+import platform
+import re
+import sys
+import traceback
+import typing as t
+from functools import update_wrapper
+from operator import attrgetter
+
+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:
+    from .app import Flask
+
+
+class NoAppException(click.UsageError):
+    """Raised if an application cannot be found or loaded."""
+
+
+def find_best_app(module):
+    """Given a module instance this tries to find the best possible
+    application in the module or raises an exception.
+    """
+    from . import Flask
+
+    # Search for the most common names first.
+    for attr_name in ("app", "application"):
+        app = getattr(module, attr_name, None)
+
+        if isinstance(app, Flask):
+            return app
+
+    # Otherwise find the only object that is a Flask instance.
+    matches = [v for 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):
+    """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, app_name):
+    """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}
+        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):
+    """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])
+
+
+def locate_app(module_name, app_name, raise_if_not_found=True):
+    try:
+        __import__(module_name)
+    except ImportError:
+        # Reraise the ImportError if it occurred within the imported module.
+        # Determine this by checking whether the trace has a depth > 1.
+        if sys.exc_info()[2].tb_next:
+            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
+
+    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, param, value):
+    if not value or ctx.resilient_parsing:
+        return
+
+    import werkzeug
+    from . import __version__
+
+    click.echo(
+        f"Python {platform.python_version()}\n"
+        f"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: t.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 = self.create_app()
+        else:
+            if self.app_import_path:
+                path, name = (
+                    re.split(r":(?![\\/])", self.app_import_path, 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:
+                        break
+
+        if not app:
+            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)
+
+
+def with_appcontext(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, *args, **kwargs):
+        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)
+
+
+class AppGroup(click.Group):
+    """This works similar to a regular click :class:`~click.Group` but it
+    changes the behavior of the :meth:`command` decorator so that it
+    automatically wraps the functions in :func:`with_appcontext`.
+
+    Not to be confused with :class:`FlaskGroup`.
+    """
+
+    def command(self, *args, **kwargs):
+        """This works exactly like the method of the same name on a regular
+        :class:`click.Group` but it wraps callbacks in :func:`with_appcontext`
+        unless it's disabled by passing ``with_appcontext=False``.
+        """
+        wrap_for_ctx = kwargs.pop("with_appcontext", True)
+
+        def decorator(f):
+            if wrap_for_ctx:
+                f = with_appcontext(f)
+            return click.Group.command(self, *args, **kwargs)(f)
+
+        return decorator
+
+    def group(self, *args, **kwargs):
+        """This works exactly like the method of the same name on a regular
+        :class:`click.Group` but it defaults the group class to
+        :class:`AppGroup`.
+        """
+        kwargs.setdefault("cls", AppGroup)
+        return click.Group.group(self, *args, **kwargs)
+
+
+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):
+        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, name):
+        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:
+            ctx.with_resource(app.app_context())
+
+        return app.cli.get_command(ctx, name)
+
+    def list_commands(self, ctx):
+        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, other):
+    """Take ``other`` and remove the length of ``path`` from it. Then join it
+    to ``path``. If it is the original value, ``path`` is an ancestor of
+    ``other``."""
+    return os.path.join(path, other[len(path) :].lstrip(os.sep)) == other
+
+
+def load_dotenv(path: str | os.PathLike | 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, app_import_path):
+    """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):
+        self.path_type = click.Path(exists=True, dir_okay=False, resolve_path=True)
+
+    def convert(self, value, param, ctx):
+        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, param, value):
+    """The ``--key`` option must be specified when ``--cert`` is a file.
+    Modifies the ``cert`` param to be a ``(cert, key)`` pair if needed.
+    """
+    cert = ctx.params.get("cert")
+    is_adhoc = cert == "adhoc"
+
+    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, param, ctx):
+        items = self.split_envvar_value(value)
+        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,
+    host,
+    port,
+    reload,
+    debugger,
+    with_threads,
+    cert,
+    extra_files,
+    exclude_patterns,
+):
+    """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 = 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, start_response):
+                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,
+    )
+
+
+@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 = {}
+
+    # 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", "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"))
+
+    if sort in ("endpoint", "rule"):
+        rules = sorted(rules, key=attrgetter(sort))
+    elif sort == "methods":
+        rules = sorted(rules, key=lambda rule: sorted(rule.methods))  # type: ignore
+
+    rule_methods = [
+        ", ".join(sorted(rule.methods - ignored_methods))  # type: ignore
+        for rule in rules
+    ]
+
+    headers = ("Endpoint", "Methods", "Rule")
+    widths = (
+        max(len(rule.endpoint) for rule in rules),
+        max(len(methods) for methods in rule_methods),
+        max(len(rule.rule) for rule in rules),
+    )
+    widths = [max(len(h), w) for h, w in zip(headers, widths)]
+    row = "{{0:<{0}}}  {{1:<{1}}}  {{2:<{2}}}".format(*widths)
+
+    click.echo(row.format(*headers).strip())
+    click.echo(row.format(*("-" * width for width in widths)))
+
+    for rule, methods in zip(rules, rule_methods):
+        click.echo(row.format(rule.endpoint, methods, rule.rule).rstrip())
+
+
+cli = FlaskGroup(
+    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()

+ 337 - 0
venv/lib/python3.10/site-packages/flask/config.py

@@ -0,0 +1,337 @@
+import errno
+import json
+import os
+import types
+import typing as t
+
+from werkzeug.utils import import_string
+
+
+class ConfigAttribute:
+    """Makes an attribute forward to the config"""
+
+    def __init__(self, name: str, get_converter: t.Optional[t.Callable] = None) -> None:
+        self.__name__ = name
+        self.get_converter = get_converter
+
+    def __get__(self, obj: t.Any, owner: t.Any = None) -> t.Any:
+        if obj is None:
+            return self
+        rv = obj.config[self.__name__]
+        if self.get_converter is not None:
+            rv = self.get_converter(rv)
+        return rv
+
+    def __set__(self, obj: t.Any, value: t.Any) -> None:
+        obj.config[self.__name__] = value
+
+
+class Config(dict):
+    """Works exactly like a dict but provides ways to fill it from files
+    or special dictionaries.  There are two common patterns to populate the
+    config.
+
+    Either you can fill the config from a config file::
+
+        app.config.from_pyfile('yourconfig.cfg')
+
+    Or alternatively you can define the configuration options in the
+    module that calls :meth:`from_object` or provide an import path to
+    a module that should be loaded.  It is also possible to tell it to
+    use the same module and with that provide the configuration values
+    just before the call::
+
+        DEBUG = True
+        SECRET_KEY = 'development key'
+        app.config.from_object(__name__)
+
+    In both cases (loading from any Python file or loading from modules),
+    only uppercase keys are added to the config.  This makes it possible to use
+    lowercase values in the config file for temporary values that are not added
+    to the config or to define the config keys in the same file that implements
+    the application.
+
+    Probably the most interesting way to load configurations is from an
+    environment variable pointing to a file::
+
+        app.config.from_envvar('YOURAPPLICATION_SETTINGS')
+
+    In this case before launching the application you have to set this
+    environment variable to the file you want to use.  On Linux and OS X
+    use the export statement::
+
+        export YOURAPPLICATION_SETTINGS='/path/to/config/file'
+
+    On windows use `set` instead.
+
+    :param root_path: path to which files are read relative from.  When the
+                      config object is created by the application, this is
+                      the application's :attr:`~flask.Flask.root_path`.
+    :param defaults: an optional dictionary of default values
+    """
+
+    def __init__(self, root_path: str, defaults: t.Optional[dict] = 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, 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: t.Union[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,
+        load: t.Callable[[t.IO[t.Any]], t.Mapping],
+        silent: bool = False,
+    ) -> 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 toml
+            app.config.from_file("config.toml", load=toml.load)
+
+        :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.
+        :return: ``True`` if the file was loaded successfully.
+
+        .. versionadded:: 2.0
+        """
+        filename = os.path.join(self.root_path, filename)
+
+        try:
+            with open(filename) 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.Optional[t.Mapping[str, t.Any]] = 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: t.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
+    ) -> t.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)}>"

+ 438 - 0
venv/lib/python3.10/site-packages/flask/ctx.py

@@ -0,0 +1,438 @@
+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 .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.Optional[t.Any] = 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"<flask.g of '{ctx.app.name}'>"
+        return object.__repr__(self)
+
+
+def after_this_request(f: ft.AfterRequestCallable) -> ft.AfterRequestCallable:
+    """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
+
+
+def copy_current_request_context(f: t.Callable) -> t.Callable:
+    """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, **kwargs):
+        with ctx:
+            return ctx.app.ensure_sync(f)(*args, **kwargs)
+
+    return update_wrapper(wrapper, f)
+
+
+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: t.List[contextvars.Token] = []
+
+    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)
+
+    def pop(self, exc: t.Optional[BaseException] = _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)
+
+    def __enter__(self) -> "AppContext":
+        self.push()
+        return self
+
+    def __exit__(
+        self,
+        exc_type: t.Optional[type],
+        exc_value: t.Optional[BaseException],
+        tb: t.Optional[TracebackType],
+    ) -> 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: dict,
+        request: t.Optional["Request"] = None,
+        session: t.Optional["SessionMixin"] = None,
+    ) -> None:
+        self.app = app
+        if request is None:
+            request = app.request_class(environ)
+            request.json_module = app.json  # type: ignore[misc]
+        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: t.Optional[t.List[t.Tuple[str, str]]] = None
+        self.session: t.Optional["SessionMixin"] = 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: t.List[ft.AfterRequestCallable] = []
+
+        self._cv_tokens: t.List[t.Tuple[contextvars.Token, t.Optional[AppContext]]] = []
+
+    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: t.Optional[BaseException] = _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: t.Optional[type],
+        exc_value: t.Optional[BaseException],
+        tb: t.Optional[TracebackType],
+    ) -> 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}>"
+        )

+ 158 - 0
venv/lib/python3.10/site-packages/flask/debughelpers.py

@@ -0,0 +1,158 @@
+import typing as t
+
+from .app import Flask
+from .blueprints import Blueprint
+from .globals import request_ctx
+
+
+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, key):
+        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):
+        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):
+        exc = request.routing_exception
+        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):
+    """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):
+        def __getitem__(self, key):
+            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) -> t.Generator:
+    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: Flask, template, attempts) -> 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, Flask):
+            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 '<string>'!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))

+ 107 - 0
venv/lib/python3.10/site-packages/flask/globals.py

@@ -0,0 +1,107 @@
+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
+
+
+class _FakeStack:
+    def __init__(self, name: str, cv: ContextVar[t.Any]) -> None:
+        self.name = name
+        self.cv = cv
+
+    def _warn(self):
+        import warnings
+
+        warnings.warn(
+            f"'_{self.name}_ctx_stack' is deprecated and will be"
+            " removed in Flask 2.3. Use 'g' to store data, or"
+            f" '{self.name}_ctx' to access the current context.",
+            DeprecationWarning,
+            stacklevel=3,
+        )
+
+    def push(self, obj: t.Any) -> None:
+        self._warn()
+        self.cv.set(obj)
+
+    def pop(self) -> t.Any:
+        self._warn()
+        ctx = self.cv.get(None)
+        self.cv.set(None)
+        return ctx
+
+    @property
+    def top(self) -> t.Optional[t.Any]:
+        self._warn()
+        return self.cv.get(None)
+
+
+_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_stack = _FakeStack("app", _cv_app)
+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_stack = _FakeStack("request", _cv_request)
+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
+)
+
+
+def __getattr__(name: str) -> t.Any:
+    if name == "_app_ctx_stack":
+        import warnings
+
+        warnings.warn(
+            "'_app_ctx_stack' is deprecated and will be remoevd in Flask 2.3.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return __app_ctx_stack
+
+    if name == "_request_ctx_stack":
+        import warnings
+
+        warnings.warn(
+            "'_request_ctx_stack' is deprecated and will be remoevd in Flask 2.3.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return __request_ctx_stack
+
+    raise AttributeError(name)

+ 705 - 0
venv/lib/python3.10/site-packages/flask/helpers.py

@@ -0,0 +1,705 @@
+import os
+import pkgutil
+import socket
+import sys
+import typing as t
+from datetime import datetime
+from functools import lru_cache
+from functools import update_wrapper
+from threading import RLock
+
+import werkzeug.utils
+from werkzeug.exceptions import abort as _wz_abort
+from werkzeug.utils import redirect as _wz_redirect
+
+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 werkzeug.wrappers import Response as BaseResponse
+    from .wrappers import Response
+    import typing_extensions as te
+
+
+def get_env() -> str:
+    """Get the environment the app is running in, indicated by the
+    :envvar:`FLASK_ENV` environment variable. The default is
+    ``'production'``.
+
+    .. deprecated:: 2.2
+        Will be removed in Flask 2.3.
+    """
+    import warnings
+
+    warnings.warn(
+        "'FLASK_ENV' and 'get_env' are deprecated and will be removed"
+        " in Flask 2.3. Use 'FLASK_DEBUG' instead.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    return os.environ.get("FLASK_ENV") or "production"
+
+
+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")
+
+    if not val:
+        env = os.environ.get("FLASK_ENV")
+
+        if env is not None:
+            print(
+                "'FLASK_ENV' is deprecated and will not be used in"
+                " Flask 2.3. Use 'FLASK_DEBUG' instead.",
+                file=sys.stderr,
+            )
+            return env == "development"
+
+        return False
+
+    return 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.Union[
+        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
+    except TypeError:
+
+        def decorator(*args: t.Any, **kwargs: t.Any) -> t.Any:
+            gen = generator_or_function(*args, **kwargs)  # type: ignore
+            return stream_with_context(gen)
+
+        return update_wrapper(decorator, generator_or_function)  # type: ignore
+
+    def generator() -> t.Generator:
+        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()  # type: ignore
+
+    # 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
+
+
+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)  # type: ignore
+
+
+def url_for(
+    endpoint: str,
+    *,
+    _anchor: t.Optional[str] = None,
+    _method: t.Optional[str] = None,
+    _scheme: t.Optional[str] = None,
+    _external: t.Optional[bool] = 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() <flask.Flask.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: t.Optional[t.Type["BaseResponse"]] = 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(  # type: ignore[misc]
+    code: t.Union[int, "BaseResponse"], *args: t.Any, **kwargs: t.Any
+) -> "te.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
+    message_flashed.send(
+        current_app._get_current_object(),  # type: ignore
+        message=message,
+        category=category,
+    )
+
+
+def get_flashed_messages(
+    with_categories: bool = False, category_filter: t.Iterable[str] = ()
+) -> t.Union[t.List[str], t.List[t.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) -> t.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: t.Union[os.PathLike, str, t.BinaryIO],
+    mimetype: t.Optional[str] = None,
+    as_attachment: bool = False,
+    download_name: t.Optional[str] = None,
+    conditional: bool = True,
+    etag: t.Union[bool, str] = True,
+    last_modified: t.Optional[t.Union[datetime, int, float]] = None,
+    max_age: t.Optional[
+        t.Union[int, t.Callable[[t.Optional[str]], t.Optional[int]]]
+    ] = 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
+        deprecated 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: t.Union[os.PathLike, str],
+    path: t.Union[os.PathLike, str],
+    **kwargs: t.Any,
+) -> "Response":
+    """Send a file from within a directory using :func:`send_file`.
+
+    .. code-block:: python
+
+        @app.route("/uploads/<path:name>")
+        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.
+    loader = pkgutil.get_loader(import_name)
+
+    # 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 or import_name == "__main__":
+        return os.getcwd()
+
+    if hasattr(loader, "get_filename"):
+        filepath = loader.get_filename(import_name)  # type: ignore
+    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))
+
+
+class locked_cached_property(werkzeug.utils.cached_property):
+    """A :func:`property` that is only evaluated once. Like
+    :class:`werkzeug.utils.cached_property` except access uses a lock
+    for thread safety.
+
+    .. versionchanged:: 2.0
+        Inherits from Werkzeug's ``cached_property`` (and ``property``).
+    """
+
+    def __init__(
+        self,
+        fget: t.Callable[[t.Any], t.Any],
+        name: t.Optional[str] = None,
+        doc: t.Optional[str] = None,
+    ) -> None:
+        super().__init__(fget, name=name, doc=doc)
+        self.lock = RLock()
+
+    def __get__(self, obj: object, type: type = None) -> t.Any:  # type: ignore
+        if obj is None:
+            return self
+
+        with self.lock:
+            return super().__get__(obj, type=type)
+
+    def __set__(self, obj: object, value: t.Any) -> None:
+        with self.lock:
+            super().__set__(obj, value)
+
+    def __delete__(self, obj: object) -> None:
+        with self.lock:
+            super().__delete__(obj)
+
+
+def is_ip(value: str) -> bool:
+    """Determine if the given string is an IP address.
+
+    :param value: value to check
+    :type value: str
+
+    :return: True if string is an IP address
+    :rtype: bool
+    """
+    for family in (socket.AF_INET, socket.AF_INET6):
+        try:
+            socket.inet_pton(family, value)
+        except OSError:
+            pass
+        else:
+            return True
+
+    return False
+
+
+@lru_cache(maxsize=None)
+def _split_blueprint_path(name: str) -> t.List[str]:
+    out: t.List[str] = [name]
+
+    if "." in name:
+        out.extend(_split_blueprint_path(name.rpartition(".")[0]))
+
+    return out

+ 342 - 0
venv/lib/python3.10/site-packages/flask/json/__init__.py

@@ -0,0 +1,342 @@
+from __future__ import annotations
+
+import json as _json
+import typing as t
+
+from jinja2.utils import htmlsafe_json_dumps as _jinja_htmlsafe_dumps
+
+from ..globals import current_app
+from .provider import _default
+
+if t.TYPE_CHECKING:  # pragma: no cover
+    from ..app import Flask
+    from ..wrappers import Response
+
+
+class JSONEncoder(_json.JSONEncoder):
+    """The default JSON encoder. Handles extra types compared to the
+    built-in :class:`json.JSONEncoder`.
+
+    -   :class:`datetime.datetime` and :class:`datetime.date` are
+        serialized to :rfc:`822` strings. This is the same as the HTTP
+        date format.
+    -   :class:`decimal.Decimal` is serialized to a string.
+    -   :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.
+
+    Assign a subclass of this to :attr:`flask.Flask.json_encoder` or
+    :attr:`flask.Blueprint.json_encoder` to override the default.
+
+    .. deprecated:: 2.2
+        Will be removed in Flask 2.3. Use ``app.json`` instead.
+    """
+
+    def __init__(self, **kwargs) -> None:
+        import warnings
+
+        warnings.warn(
+            "'JSONEncoder' is deprecated and will be removed in"
+            " Flask 2.3. Use 'Flask.json' to provide an alternate"
+            " JSON implementation instead.",
+            DeprecationWarning,
+            stacklevel=3,
+        )
+        super().__init__(**kwargs)
+
+    def default(self, o: t.Any) -> t.Any:
+        """Convert ``o`` to a JSON serializable type. See
+        :meth:`json.JSONEncoder.default`. Python does not support
+        overriding how basic types like ``str`` or ``list`` are
+        serialized, they are handled before this method.
+        """
+        return _default(o)
+
+
+class JSONDecoder(_json.JSONDecoder):
+    """The default JSON decoder.
+
+    This does not change any behavior from the built-in
+    :class:`json.JSONDecoder`.
+
+    Assign a subclass of this to :attr:`flask.Flask.json_decoder` or
+    :attr:`flask.Blueprint.json_decoder` to override the default.
+
+    .. deprecated:: 2.2
+        Will be removed in Flask 2.3. Use ``app.json`` instead.
+    """
+
+    def __init__(self, **kwargs) -> None:
+        import warnings
+
+        warnings.warn(
+            "'JSONDecoder' is deprecated and will be removed in"
+            " Flask 2.3. Use 'Flask.json' to provide an alternate"
+            " JSON implementation instead.",
+            DeprecationWarning,
+            stacklevel=3,
+        )
+        super().__init__(**kwargs)
+
+
+def dumps(obj: t.Any, *, app: Flask | None = None, **kwargs: t.Any) -> str:
+    """Serialize data as JSON.
+
+    If :data:`~flask.current_app` is available, it will use its
+    :meth:`app.json.dumps() <flask.json.provider.JSONProvider.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.2
+        Calls ``current_app.json.dumps``, allowing an app to override
+        the behavior.
+
+    .. versionchanged:: 2.2
+        The ``app`` parameter will be removed in Flask 2.3.
+
+    .. 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 app is not None:
+        import warnings
+
+        warnings.warn(
+            "The 'app' parameter is deprecated and will be removed in"
+            " Flask 2.3. Call 'app.json.dumps' directly instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+    else:
+        app = current_app
+
+    if app:
+        return app.json.dumps(obj, **kwargs)
+
+    kwargs.setdefault("default", _default)
+    return _json.dumps(obj, **kwargs)
+
+
+def dump(
+    obj: t.Any, fp: t.IO[str], *, app: Flask | None = None, **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() <flask.json.provider.JSONProvider.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.2
+        Calls ``current_app.json.dump``, allowing an app to override
+        the behavior.
+
+    .. versionchanged:: 2.2
+        The ``app`` parameter will be removed in Flask 2.3.
+
+    .. versionchanged:: 2.0
+        Writing to a binary file, and the ``encoding`` argument, will be
+        removed in Flask 2.1.
+    """
+    if app is not None:
+        import warnings
+
+        warnings.warn(
+            "The 'app' parameter is deprecated and will be removed in"
+            " Flask 2.3. Call 'app.json.dump' directly instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+    else:
+        app = current_app
+
+    if app:
+        app.json.dump(obj, fp, **kwargs)
+    else:
+        kwargs.setdefault("default", _default)
+        _json.dump(obj, fp, **kwargs)
+
+
+def loads(s: str | bytes, *, app: Flask | None = None, **kwargs: t.Any) -> t.Any:
+    """Deserialize data as JSON.
+
+    If :data:`~flask.current_app` is available, it will use its
+    :meth:`app.json.loads() <flask.json.provider.JSONProvider.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.2
+        Calls ``current_app.json.loads``, 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 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 app is not None:
+        import warnings
+
+        warnings.warn(
+            "The 'app' parameter is deprecated and will be removed in"
+            " Flask 2.3. Call 'app.json.loads' directly instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+    else:
+        app = current_app
+
+    if app:
+        return app.json.loads(s, **kwargs)
+
+    return _json.loads(s, **kwargs)
+
+
+def load(fp: t.IO[t.AnyStr], *, app: Flask | None = None, **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() <flask.json.provider.JSONProvider.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.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 app is not None:
+        import warnings
+
+        warnings.warn(
+            "The 'app' parameter is deprecated and will be removed in"
+            " Flask 2.3. Call 'app.json.load' directly instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+    else:
+        app = current_app
+
+    if app:
+        return app.json.load(fp, **kwargs)
+
+    return _json.load(fp, **kwargs)
+
+
+def htmlsafe_dumps(obj: t.Any, **kwargs: t.Any) -> str:
+    """Serialize an object to a string of JSON with :func:`dumps`, then
+    replace HTML-unsafe characters with Unicode escapes and mark the
+    result safe with :class:`~markupsafe.Markup`.
+
+    This is available in templates as the ``|tojson`` filter.
+
+    The returned string is safe to render in HTML documents and
+    ``<script>`` tags. The exception is in HTML attributes that are
+    double quoted; either use single quotes or the ``|forceescape``
+    filter.
+
+    .. deprecated:: 2.2
+        Will be removed in Flask 2.3. This is built-in to Jinja now.
+
+    .. versionchanged:: 2.0
+        Uses :func:`jinja2.utils.htmlsafe_json_dumps`. The returned
+        value is marked safe by wrapping in :class:`~markupsafe.Markup`.
+
+    .. versionchanged:: 0.10
+        Single quotes are escaped, making this safe to use in HTML,
+        ``<script>`` tags, and single-quoted attributes without further
+        escaping.
+    """
+    import warnings
+
+    warnings.warn(
+        "'htmlsafe_dumps' is deprecated and will be removed in Flask"
+        " 2.3. Use 'jinja2.utils.htmlsafe_json_dumps' instead.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    return _jinja_htmlsafe_dumps(obj, dumps=dumps, **kwargs)
+
+
+def htmlsafe_dump(obj: t.Any, fp: t.IO[str], **kwargs: t.Any) -> None:
+    """Serialize an object to JSON written to a file object, replacing
+    HTML-unsafe characters with Unicode escapes. See
+    :func:`htmlsafe_dumps` and :func:`dumps`.
+
+    .. deprecated:: 2.2
+        Will be removed in Flask 2.3.
+    """
+    import warnings
+
+    warnings.warn(
+        "'htmlsafe_dump' is deprecated and will be removed in Flask"
+        " 2.3. Use 'jinja2.utils.htmlsafe_json_dumps' instead.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    fp.write(htmlsafe_dumps(obj, **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() <flask.json.provider.JSONProvider.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)

+ 310 - 0
venv/lib/python3.10/site-packages/flask/json/provider.py

@@ -0,0 +1,310 @@
+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
+
+from ..globals import request
+
+if t.TYPE_CHECKING:  # pragma: no cover
+    from ..app import Flask
+    from ..wrappers import Response
+
+
+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 <flask.Flask.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: Flask) -> None:
+        self._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: t.Tuple[t.Any, ...], kwargs: t.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`.
+        """
+        cls = self._app._json_encoder
+        bp = self._app.blueprints.get(request.blueprint) if request else None
+
+        if bp is not None and bp._json_encoder is not None:
+            cls = bp._json_encoder
+
+        if cls is not None:
+            import warnings
+
+            warnings.warn(
+                "Setting 'json_encoder' on the app or a blueprint is"
+                " deprecated and will be removed in Flask 2.3."
+                " Customize 'app.json' instead.",
+                DeprecationWarning,
+            )
+            kwargs.setdefault("cls", cls)
+
+            if "default" not in cls.__dict__:
+                kwargs.setdefault("default", self.default)
+        else:
+            kwargs.setdefault("default", self.default)
+
+        ensure_ascii = self._app.config["JSON_AS_ASCII"]
+        sort_keys = self._app.config["JSON_SORT_KEYS"]
+
+        if ensure_ascii is not None:
+            import warnings
+
+            warnings.warn(
+                "The 'JSON_AS_ASCII' config key is deprecated and will"
+                " be removed in Flask 2.3. Set 'app.json.ensure_ascii'"
+                " instead.",
+                DeprecationWarning,
+            )
+        else:
+            ensure_ascii = self.ensure_ascii
+
+        if sort_keys is not None:
+            import warnings
+
+            warnings.warn(
+                "The 'JSON_SORT_KEYS' config key is deprecated and will"
+                " be removed in Flask 2.3. Set 'app.json.sort_keys'"
+                " instead.",
+                DeprecationWarning,
+            )
+        else:
+            sort_keys = self.sort_keys
+
+        kwargs.setdefault("ensure_ascii", ensure_ascii)
+        kwargs.setdefault("sort_keys", 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`.
+        """
+        cls = self._app._json_decoder
+        bp = self._app.blueprints.get(request.blueprint) if request else None
+
+        if bp is not None and bp._json_decoder is not None:
+            cls = bp._json_decoder
+
+        if cls is not None:
+            import warnings
+
+            warnings.warn(
+                "Setting 'json_decoder' on the app or a blueprint is"
+                " deprecated and will be removed in Flask 2.3."
+                " Customize 'app.json' instead.",
+                DeprecationWarning,
+            )
+            kwargs.setdefault("cls", cls)
+
+        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: t.Dict[str, t.Any] = {}
+        pretty = self._app.config["JSONIFY_PRETTYPRINT_REGULAR"]
+        mimetype = self._app.config["JSONIFY_MIMETYPE"]
+
+        if pretty is not None:
+            import warnings
+
+            warnings.warn(
+                "The 'JSONIFY_PRETTYPRINT_REGULAR' config key is"
+                " deprecated and will be removed in Flask 2.3. Set"
+                " 'app.json.compact' instead.",
+                DeprecationWarning,
+            )
+            compact: bool | None = not pretty
+        else:
+            compact = self.compact
+
+        if (compact is None and self._app.debug) or compact is False:
+            dump_args.setdefault("indent", 2)
+        else:
+            dump_args.setdefault("separators", (",", ":"))
+
+        if mimetype is not None:
+            import warnings
+
+            warnings.warn(
+                "The 'JSONIFY_MIMETYPE' config key is deprecated and"
+                " will be removed in Flask 2.3. Set 'app.json.mimetype'"
+                " instead.",
+                DeprecationWarning,
+            )
+        else:
+            mimetype = self.mimetype
+
+        return self._app.response_class(
+            f"{self.dumps(obj, **dump_args)}\n", mimetype=mimetype
+        )

+ 312 - 0
venv/lib/python3.10/site-packages/flask/json/tag.py

@@ -0,0 +1,312 @@
+"""
+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)
+"""
+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 ``None``, this tag is
+    #: only used as an intermediate step during tagging.
+    key: t.Optional[str] = None
+
+    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) -> 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: t.Dict[str, JSONTag] = {}
+        self.order: t.List[JSONTag] = []
+
+        for cls in self.default_tags:
+            self.register(cls)
+
+    def register(
+        self,
+        tag_class: t.Type[JSONTag],
+        force: bool = False,
+        index: t.Optional[int] = 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 is not None:
+            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.Dict[str, 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: t.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 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 loads(value, object_hook=self.untag)

+ 74 - 0
venv/lib/python3.10/site-packages/flask/logging.py

@@ -0,0 +1,74 @@
+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 .app import Flask
+
+
+@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``.
+    """
+    return request.environ["wsgi.errors"] if request else 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: "Flask") -> 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 <flask.Flask.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

+ 0 - 0
venv/lib/python3.10/site-packages/flask/py.typed


+ 898 - 0
venv/lib/python3.10/site-packages/flask/scaffold.py

@@ -0,0 +1,898 @@
+import importlib.util
+import json
+import os
+import pathlib
+import pkgutil
+import sys
+import typing as t
+from collections import defaultdict
+from datetime import timedelta
+from functools import update_wrapper
+
+from jinja2 import FileSystemLoader
+from werkzeug.exceptions import default_exceptions
+from werkzeug.exceptions import HTTPException
+
+from . import typing as ft
+from .cli import AppGroup
+from .globals import current_app
+from .helpers import get_root_path
+from .helpers import locked_cached_property
+from .helpers import send_from_directory
+from .templating import _default_template_ctx_processor
+
+if t.TYPE_CHECKING:  # pragma: no cover
+    from .wrappers import Response
+
+# 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_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, *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: t.Optional[str] = None
+    _static_url_path: t.Optional[str] = None
+
+    #: JSON encoder class used by :func:`flask.json.dumps`. If a
+    #: blueprint sets this, it will be used instead of the app's value.
+    #:
+    #: .. deprecated:: 2.2
+    #:      Will be removed in Flask 2.3.
+    json_encoder: t.Union[t.Type[json.JSONEncoder], None] = None
+
+    #: JSON decoder class used by :func:`flask.json.loads`. If a
+    #: blueprint sets this, it will be used instead of the app's value.
+    #:
+    #: .. deprecated:: 2.2
+    #:      Will be removed in Flask 2.3.
+    json_decoder: t.Union[t.Type[json.JSONDecoder], None] = None
+
+    def __init__(
+        self,
+        import_name: str,
+        static_folder: t.Optional[t.Union[str, os.PathLike]] = None,
+        static_url_path: t.Optional[str] = None,
+        template_folder: t.Optional[str] = None,
+        root_path: t.Optional[str] = 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 = 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: t.Dict[str, t.Callable] = {}
+
+        #: 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: t.Dict[
+            ft.AppOrBlueprintKey,
+            t.Dict[t.Optional[int], t.Dict[t.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: t.Dict[
+            ft.AppOrBlueprintKey, t.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: t.Dict[
+            ft.AppOrBlueprintKey, t.List[ft.AfterRequestCallable]
+        ] = 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: t.Dict[
+            ft.AppOrBlueprintKey, t.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: t.Dict[
+            ft.AppOrBlueprintKey, t.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: t.Dict[
+            ft.AppOrBlueprintKey,
+            t.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: t.Dict[
+            ft.AppOrBlueprintKey, t.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) -> t.Optional[str]:
+        """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: t.Optional[t.Union[str, os.PathLike]]) -> 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) -> t.Optional[str]:
+        """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: t.Optional[str]) -> None:
+        if value is not None:
+            value = value.rstrip("/")
+
+        self._static_url_path = value
+
+    def get_send_file_max_age(self, filename: t.Optional[str]) -> t.Optional[int]:
+        """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.
+
+        .. 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
+
+    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.
+
+        .. 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
+        )
+
+    @locked_cached_property
+    def jinja_loader(self) -> t.Optional[FileSystemLoader]:
+        """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 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".
+        """
+        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 _method_route(
+        self,
+        method: str,
+        rule: str,
+        options: dict,
+    ) -> 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: t.Optional[str] = None,
+        view_func: t.Optional[ft.RouteCallable] = None,
+        provide_automatic_options: t.Optional[bool] = 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.
+        """
+        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.
+        """
+        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.
+        """
+        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."""
+        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.
+        """
+        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.
+        """
+        self.url_default_functions[None].append(f)
+        return f
+
+    @setupmethod
+    def errorhandler(
+        self, code_or_exception: t.Union[t.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
+
+        .. 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: t.Union[t.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: t.Union[t.Type[Exception], int]
+    ) -> t.Tuple[t.Type[Exception], t.Optional[int]]:
+        """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: t.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: t.Callable) -> 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 _matching_loader_thinks_module_is_package(loader, mod_name):
+    """Attempt to figure out if the given name is a package or a module.
+
+    :param: loader: The loader that handled the name.
+    :param mod_name: The name of the package or module.
+    """
+    # Use loader.is_package if it's available.
+    if hasattr(loader, "is_package"):
+        return loader.is_package(mod_name)
+
+    cls = type(loader)
+
+    # NamespaceLoader doesn't implement is_package, but all names it
+    # loads must be packages.
+    if cls.__module__ == "_frozen_importlib" and cls.__name__ == "NamespaceLoader":
+        return True
+
+    # Otherwise we need to fail with an error that explains what went
+    # wrong.
+    raise AttributeError(
+        f"'{cls.__name__}.is_package()' must be implemented for PEP 302"
+        f" import hooks."
+    )
+
+
+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):
+    """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")
+    # 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`
+    except (ImportError, ValueError):
+        pass  # handled below
+    else:
+        # namespace package
+        if root_spec.origin in {"namespace", None}:
+            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_locations = (
+                    location
+                    for location in root_spec.submodule_search_locations
+                    if _path_is_relative_to(package_path, location)
+                )
+            else:
+                # Pick the first path.
+                search_locations = iter(root_spec.submodule_search_locations)
+            return os.path.dirname(next(search_locations))
+        # a package (with __init__.py)
+        elif root_spec.submodule_search_locations:
+            return os.path.dirname(os.path.dirname(root_spec.origin))
+        # just a normal module
+        else:
+            return os.path.dirname(root_spec.origin)
+
+    # we were unable to find the `package_path` using PEP 451 loaders
+    loader = pkgutil.get_loader(root_mod_name)
+
+    if loader is None or root_mod_name == "__main__":
+        # import name is not found, or interactive/main module
+        return os.getcwd()
+
+    if hasattr(loader, "get_filename"):
+        filename = loader.get_filename(root_mod_name)
+    elif hasattr(loader, "archive"):
+        # zipimporter's loader.archive points to the .egg or .zip file.
+        filename = loader.archive
+    else:
+        # At least one loader is missing both get_filename and archive:
+        # Google App Engine's HardenedModulesHook, use __file__.
+        filename = importlib.import_module(root_mod_name).__file__
+
+    package_path = os.path.abspath(os.path.dirname(filename))
+
+    # If the imported name is a package, filename is currently pointing
+    # to the root of the package, need to get the current directory.
+    if _matching_loader_thinks_module_is_package(loader, root_mod_name):
+        package_path = os.path.dirname(package_path)
+
+    return package_path
+
+
+def find_package(import_name: 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

+ 419 - 0
venv/lib/python3.10/site-packages/flask/sessions.py

@@ -0,0 +1,419 @@
+import hashlib
+import typing as t
+import warnings
+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 .helpers import is_ip
+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, Response
+
+
+class SessionMixin(MutableMapping):
+    """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
+
+
+class SecureCookieSession(CallbackDict, SessionMixin):
+    """Base class for sessions based on signed cookies.
+
+    This session backend will set the :attr:`modified` and
+    :attr:`accessed` attributes. It cannot reliably track whether a
+    session is new (vs. empty), so :attr:`new` remains hard coded to
+    ``False``.
+    """
+
+    #: When data is changed, this is set to ``True``. Only the session
+    #: dictionary itself is tracked; if the session contains mutable
+    #: data (for example a nested dict) then this must be set to
+    #: ``True`` manually when modifying that data. The session cookie
+    #: will only be written to the response if this is ``True``.
+    modified = False
+
+    #: When data is read or written, this is set to ``True``. Used by
+    # :class:`.SecureCookieSessionInterface` to add a ``Vary: Cookie``
+    #: header, which allows caching proxies to cache different pages for
+    #: different users.
+    accessed = False
+
+    def __init__(self, initial: t.Any = None) -> None:
+        def on_update(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) -> "te.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"]
+
+    def get_cookie_domain(self, app: "Flask") -> t.Optional[str]:
+        """Returns the domain that should be set for the session cookie.
+
+        Uses ``SESSION_COOKIE_DOMAIN`` if it is configured, otherwise
+        falls back to detecting the domain based on ``SERVER_NAME``.
+
+        Once detected (or if not set at all), ``SESSION_COOKIE_DOMAIN`` is
+        updated to avoid re-running the logic.
+        """
+
+        rv = app.config["SESSION_COOKIE_DOMAIN"]
+
+        # set explicitly, or cached from SERVER_NAME detection
+        # if False, return None
+        if rv is not None:
+            return rv if rv else None
+
+        rv = app.config["SERVER_NAME"]
+
+        # server name not set, cache False to return none next time
+        if not rv:
+            app.config["SESSION_COOKIE_DOMAIN"] = False
+            return None
+
+        # chop off the port which is usually not supported by browsers
+        # remove any leading '.' since we'll add that later
+        rv = rv.rsplit(":", 1)[0].lstrip(".")
+
+        if "." not in rv:
+            # Chrome doesn't allow names without a '.'. This should only
+            # come up with localhost. Hack around this by not setting
+            # the name, and show a warning.
+            warnings.warn(
+                f"{rv!r} is not a valid cookie domain, it must contain"
+                " a '.'. Add an entry to your hosts file, for example"
+                f" '{rv}.localdomain', and use that instead."
+            )
+            app.config["SESSION_COOKIE_DOMAIN"] = False
+            return None
+
+        ip = is_ip(rv)
+
+        if ip:
+            warnings.warn(
+                "The session cookie domain is an IP address. This may not work"
+                " as intended in some browsers. Add an entry to your hosts"
+                ' file, for example "localhost.localdomain", and use that'
+                " instead."
+            )
+
+        # if this is not an ip and app is mounted at the root, allow subdomain
+        # matching by adding a '.' prefix
+        if self.get_cookie_path(app) == "/" and not ip:
+            rv = f".{rv}"
+
+        app.config["SESSION_COOKIE_DOMAIN"] = rv
+        return rv
+
+    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"]
+
+    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"]
+
+    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"]
+
+    def get_cookie_samesite(self, app: "Flask") -> str:
+        """Return ``'Strict'`` or ``'Lax'`` if the cookie should use the
+        ``SameSite`` attribute. This currently just returns the value of
+        the :data:`SESSION_COOKIE_SAMESITE` setting.
+        """
+        return app.config["SESSION_COOKIE_SAMESITE"]
+
+    def get_expiration_time(
+        self, app: "Flask", session: SessionMixin
+    ) -> t.Optional[datetime]:
+        """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"
+    ) -> t.Optional[SessionMixin]:
+        """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"
+    ) -> t.Optional[URLSafeTimedSerializer]:
+        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"
+    ) -> t.Optional[SecureCookieSession]:
+        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)
+
+        # 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,
+                )
+
+            return
+
+        # Add a "Vary: Cookie" header if the session was accessed at all.
+        if session.accessed:
+            response.vary.add("Cookie")
+
+        if not self.should_set_cookie(app, session):
+            return
+
+        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,
+        )

+ 56 - 0
venv/lib/python3.10/site-packages/flask/signals.py

@@ -0,0 +1,56 @@
+import typing as t
+
+try:
+    from blinker import Namespace
+
+    signals_available = True
+except ImportError:
+    signals_available = False
+
+    class Namespace:  # type: ignore
+        def signal(self, name: str, doc: t.Optional[str] = None) -> "_FakeSignal":
+            return _FakeSignal(name, doc)
+
+    class _FakeSignal:
+        """If blinker is unavailable, create a fake class with the same
+        interface that allows sending of signals but will fail with an
+        error on anything else.  Instead of doing anything on send, it
+        will just ignore the arguments and do nothing instead.
+        """
+
+        def __init__(self, name: str, doc: t.Optional[str] = None) -> None:
+            self.name = name
+            self.__doc__ = doc
+
+        def send(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
+            pass
+
+        def _fail(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
+            raise RuntimeError(
+                "Signalling support is unavailable because the blinker"
+                " library is not installed."
+            ) from None
+
+        connect = connect_via = connected_to = temporarily_connected_to = _fail
+        disconnect = _fail
+        has_receivers_for = receivers_for = _fail
+        del _fail
+
+
+# The namespace for code signals.  If you are not Flask code, do
+# not put signals in here.  Create your own namespace instead.
+_signals = Namespace()
+
+
+# Core signals.  For usage examples grep the source code or consult
+# the API documentation in docs/api.rst as well as docs/signals.rst
+template_rendered = _signals.signal("template-rendered")
+before_render_template = _signals.signal("before-render-template")
+request_started = _signals.signal("request-started")
+request_finished = _signals.signal("request-finished")
+request_tearing_down = _signals.signal("request-tearing-down")
+got_request_exception = _signals.signal("got-request-exception")
+appcontext_tearing_down = _signals.signal("appcontext-tearing-down")
+appcontext_pushed = _signals.signal("appcontext-pushed")
+appcontext_popped = _signals.signal("appcontext-popped")
+message_flashed = _signals.signal("message-flashed")

+ 212 - 0
venv/lib/python3.10/site-packages/flask/templating.py

@@ -0,0 +1,212 @@
+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 .scaffold import Scaffold
+
+
+def _default_template_ctx_processor() -> t.Dict[str, t.Any]:
+    """Default template context processor.  Injects `request`,
+    `session` and `g`.
+    """
+    appctx = _cv_app.get(None)
+    reqctx = _cv_request.get(None)
+    rv: t.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: "Flask", **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: "Flask") -> None:
+        self.app = app
+
+    def get_source(  # type: ignore
+        self, environment: Environment, template: str
+    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
+        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: Environment, template: str
+    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
+        attempts = []
+        rv: t.Optional[t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]]
+        trv: t.Optional[
+            t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
+        ] = 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: Environment, template: str
+    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
+        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.Generator[t.Tuple["Scaffold", BaseLoader], None, None]:
+        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) -> t.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: t.Dict[str, t.Any]) -> str:
+    app.update_template_context(context)
+    before_render_template.send(app, template=template, context=context)
+    rv = template.render(context)
+    template_rendered.send(app, template=template, context=context)
+    return rv
+
+
+def render_template(
+    template_name_or_list: t.Union[str, Template, t.List[t.Union[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: t.Dict[str, t.Any]
+) -> t.Iterator[str]:
+    app.update_template_context(context)
+    before_render_template.send(app, template=template, context=context)
+
+    def generate() -> t.Iterator[str]:
+        yield from template.generate(context)
+        template_rendered.send(app, 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: t.Union[str, Template, t.List[t.Union[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)

+ 286 - 0
venv/lib/python3.10/site-packages/flask/testing.py

@@ -0,0 +1,286 @@
+import typing as t
+from contextlib import contextmanager
+from contextlib import ExitStack
+from copy import copy
+from types import TracebackType
+
+import werkzeug.test
+from click.testing import CliRunner
+from werkzeug.test import Client
+from werkzeug.urls import url_parse
+from werkzeug.wrappers import Request as BaseRequest
+
+from .cli import ScriptInfo
+from .globals import _cv_request
+from .sessions import SessionMixin
+
+if t.TYPE_CHECKING:  # pragma: no cover
+    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: t.Optional[str] = None,
+        subdomain: t.Optional[str] = None,
+        url_scheme: t.Optional[str] = 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 = url_parse(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)
+
+
+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: t.List[t.ContextManager[t.Any]] = []
+        self._context_stack = ExitStack()
+        self.environ_base = {
+            "REMOTE_ADDR": "127.0.0.1",
+            "HTTP_USER_AGENT": f"werkzeug/{werkzeug.__version__}",
+        }
+
+    @contextmanager
+    def session_transaction(
+        self, *args: t.Any, **kwargs: t.Any
+    ) -> t.Generator[SessionMixin, None, None]:
+        """When used in combination with a ``with`` statement this opens a
+        session transaction.  This can be used to modify the session that
+        the test client uses.  Once the ``with`` block is left the session is
+        stored back.
+
+        ::
+
+            with client.session_transaction() as session:
+                session['value'] = 42
+
+        Internally this is implemented by going through a temporary test
+        request context and since session handling could depend on
+        request variables this function accepts the same arguments as
+        :meth:`~flask.Flask.test_request_context` which are directly
+        passed through.
+        """
+        if self.cookie_jar is None:
+            raise RuntimeError(
+                "Session transactions only make sense with cookies enabled."
+            )
+        app = self.application
+        environ_overrides = kwargs.setdefault("environ_overrides", {})
+        self.cookie_jar.inject_wsgi(environ_overrides)
+        outer_reqctx = _cv_request.get(None)
+        with app.test_request_context(*args, **kwargs) as c:
+            session_interface = app.session_interface
+            sess = session_interface.open_session(app, c.request)
+            if sess is None:
+                raise RuntimeError(
+                    "Session backend did not open a session. Check the configuration"
+                )
+
+            # Since we have to open a new request context for the session
+            # handling we want to make sure that we hide out own context
+            # from the caller.  By pushing the original request context
+            # (or None) on top of this and popping it we get exactly that
+            # behavior.  It's important to not use the push and pop
+            # methods of the actual request context object since that would
+            # mean that cleanup handlers are called
+            token = _cv_request.set(outer_reqctx)  # type: ignore[arg-type]
+            try:
+                yield sess
+            finally:
+                _cv_request.reset(token)
+
+            resp = app.response_class()
+            if not session_interface.is_null_session(sess):
+                session_interface.save_session(app, sess, resp)
+            headers = resp.get_wsgi_headers(c.request.environ)
+            self.cookie_jar.extract_wsgi(c.request.environ, headers)
+
+    def _copy_environ(self, other):
+        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, kwargs):
+        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 {})
+                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[misc]
+
+        # 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: t.Optional[type],
+        exc_value: t.Optional[BaseException],
+        tb: t.Optional[TracebackType],
+    ) -> 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 <click.testing.CliRunner.invoke>` for
+        full method documentation. See :ref:`testing-cli` for examples.
+
+        If the ``obj`` argument is not given, passes an instance of
+        :class:`~flask.cli.ScriptInfo` that knows how to load the Flask
+        app being tested.
+
+        :param cli: Command object to invoke. Default is the app's
+            :attr:`~flask.app.Flask.cli` group.
+        :param args: List of strings to invoke the command with.
+
+        :return: a :class:`~click.testing.Result` object.
+        """
+        if cli is None:
+            cli = self.app.cli  # type: ignore
+
+        if "obj" not in kwargs:
+            kwargs["obj"] = ScriptInfo(create_app=lambda: self.app)
+
+        return super().invoke(cli, args, **kwargs)

+ 80 - 0
venv/lib/python3.10/site-packages/flask/typing.py

@@ -0,0 +1,80 @@
+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.wrappers 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.Callable[[], t.Dict[str, t.Any]]
+TemplateFilterCallable = t.Callable[..., t.Any]
+TemplateGlobalCallable = t.Callable[..., t.Any]
+TemplateTestCallable = t.Callable[..., bool]
+URLDefaultCallable = t.Callable[[str, dict], None]
+URLValuePreprocessorCallable = t.Callable[[t.Optional[str], t.Optional[dict]], 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.Callable[[t.Any], ResponseReturnValue]
+
+RouteCallable = t.Union[
+    t.Callable[..., ResponseReturnValue],
+    t.Callable[..., t.Awaitable[ResponseReturnValue]],
+]

+ 188 - 0
venv/lib/python3.10/site-packages/flask/views.py

@@ -0,0 +1,188 @@
+import typing as t
+
+from . import typing as ft
+from .globals import current_app
+from .globals import request
+
+
+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/<name>", 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.Optional[t.Collection[str]]] = 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[t.Optional[bool]] = 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[t.List[t.Callable]] = []
+
+    #: 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.
+
+        The 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)
+
+        else:
+            self = cls(*class_args, **class_kwargs)
+
+            def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
+                return current_app.ensure_sync(self.dispatch_request)(**kwargs)
+
+        if cls.decorators:
+            view.__name__ = name
+            view.__module__ = cls.__module__
+            for decorator in cls.decorators:
+                view = decorator(view)
+
+        # We attach the view class to the view function for two reasons:
+        # first of all it allows us to easily figure out what class-based
+        # view this thing came from, secondly it's also used for instantiating
+        # the view class so you can actually replace it with something else
+        # for testing purposes and debugging.
+        view.view_class = cls  # 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)

+ 171 - 0
venv/lib/python3.10/site-packages/flask/wrappers.py

@@ -0,0 +1,171 @@
+import typing as t
+
+from werkzeug.exceptions import BadRequest
+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 = 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: t.Optional["Rule"] = None
+
+    #: A dict of view arguments that matched the request.  If an exception
+    #: happened when matching, this will be ``None``.
+    view_args: t.Optional[t.Dict[str, t.Any]] = 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: t.Optional[Exception] = None
+
+    @property
+    def max_content_length(self) -> t.Optional[int]:  # type: ignore
+        """Read-only view of the ``MAX_CONTENT_LENGTH`` config key."""
+        if current_app:
+            return current_app.config["MAX_CONTENT_LENGTH"]
+        else:
+            return None
+
+    @property
+    def endpoint(self) -> t.Optional[str]:
+        """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) -> t.Optional[str]:
+        """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) -> t.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: t.Optional[ValueError]) -> 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 = "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"]
+
+        # return Werkzeug's default when not in an app context
+        return super().max_cookie_size

+ 1 - 0
venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/INSTALLER

@@ -0,0 +1 @@
+pip

+ 28 - 0
venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/LICENSE.rst

@@ -0,0 +1,28 @@
+Copyright 2011 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.

+ 97 - 0
venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/METADATA

@@ -0,0 +1,97 @@
+Metadata-Version: 2.1
+Name: itsdangerous
+Version: 2.1.2
+Summary: Safely pass data to untrusted environments and back.
+Home-page: https://palletsprojects.com/p/itsdangerous/
+Author: Armin Ronacher
+Author-email: armin.ronacher@active-4.com
+Maintainer: Pallets
+Maintainer-email: contact@palletsprojects.com
+License: BSD-3-Clause
+Project-URL: Donate, https://palletsprojects.com/donate
+Project-URL: Documentation, https://itsdangerous.palletsprojects.com/
+Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/
+Project-URL: Source Code, https://github.com/pallets/itsdangerous/
+Project-URL: Issue Tracker, https://github.com/pallets/itsdangerous/issues/
+Project-URL: Twitter, https://twitter.com/PalletsTeam
+Project-URL: Chat, https://discord.gg/pallets
+Platform: UNKNOWN
+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
+
+ItsDangerous
+============
+
+... so better sign this
+
+Various helpers to pass data to untrusted environments and to get it
+back safe and sound. Data is cryptographically signed to ensure that a
+token has not been tampered with.
+
+It's possible to customize how data is serialized. Data is compressed as
+needed. A timestamp can be added and verified automatically while
+loading a token.
+
+
+Installing
+----------
+
+Install and update using `pip`_:
+
+.. code-block:: text
+
+    pip install -U itsdangerous
+
+.. _pip: https://pip.pypa.io/en/stable/getting-started/
+
+
+A Simple Example
+----------------
+
+Here's how you could generate a token for transmitting a user's id and
+name between web requests.
+
+.. code-block:: python
+
+    from itsdangerous import URLSafeSerializer
+    auth_s = URLSafeSerializer("secret key", "auth")
+    token = auth_s.dumps({"id": 5, "name": "itsdangerous"})
+
+    print(token)
+    # eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg
+
+    data = auth_s.loads(token)
+    print(data["name"])
+    # itsdangerous
+
+
+Donate
+------
+
+The Pallets organization develops and supports ItsDangerous 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://itsdangerous.palletsprojects.com/
+-   Changes: https://itsdangerous.palletsprojects.com/changes/
+-   PyPI Releases: https://pypi.org/project/ItsDangerous/
+-   Source Code: https://github.com/pallets/itsdangerous/
+-   Issue Tracker: https://github.com/pallets/itsdangerous/issues/
+-   Website: https://palletsprojects.com/p/itsdangerous/
+-   Twitter: https://twitter.com/PalletsTeam
+-   Chat: https://discord.gg/pallets
+
+

+ 23 - 0
venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/RECORD

@@ -0,0 +1,23 @@
+itsdangerous-2.1.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+itsdangerous-2.1.2.dist-info/LICENSE.rst,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475
+itsdangerous-2.1.2.dist-info/METADATA,sha256=ThrHIJQ_6XlfbDMCAVe_hawT7IXiIxnTBIDrwxxtucQ,2928
+itsdangerous-2.1.2.dist-info/RECORD,,
+itsdangerous-2.1.2.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
+itsdangerous-2.1.2.dist-info/top_level.txt,sha256=gKN1OKLk81i7fbWWildJA88EQ9NhnGMSvZqhfz9ICjk,13
+itsdangerous/__init__.py,sha256=n4mkyjlIVn23pgsgCIw0MJKPdcHIetyeRpe5Fwsn8qg,876
+itsdangerous/__pycache__/__init__.cpython-310.pyc,,
+itsdangerous/__pycache__/_json.cpython-310.pyc,,
+itsdangerous/__pycache__/encoding.cpython-310.pyc,,
+itsdangerous/__pycache__/exc.cpython-310.pyc,,
+itsdangerous/__pycache__/serializer.cpython-310.pyc,,
+itsdangerous/__pycache__/signer.cpython-310.pyc,,
+itsdangerous/__pycache__/timed.cpython-310.pyc,,
+itsdangerous/__pycache__/url_safe.cpython-310.pyc,,
+itsdangerous/_json.py,sha256=wIhs_7-_XZolmyr-JvKNiy_LgAcfevYR0qhCVdlIhg8,450
+itsdangerous/encoding.py,sha256=pgh86snHC76dPLNCnPlrjR5SaYL_M8H-gWRiiLNbhCU,1419
+itsdangerous/exc.py,sha256=VFxmP2lMoSJFqxNMzWonqs35ROII4-fvCBfG0v1Tkbs,3206
+itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+itsdangerous/serializer.py,sha256=zgZ1-U705jHDpt62x_pmLJdryEKDNAbt5UkJtnkcCSw,11144
+itsdangerous/signer.py,sha256=QUH0iX0in-OTptMAXKU5zWMwmOCXn1fsDsubXiGdFN4,9367
+itsdangerous/timed.py,sha256=5CBWLds4Nm8-3bFVC8RxNzFjx6PSwjch8wuZ5cwcHFI,8174
+itsdangerous/url_safe.py,sha256=5bC4jSKOjWNRkWrFseifWVXUnHnPgwOLROjiOwb-eeo,2402

+ 5 - 0
venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.37.1)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 1 - 0
venv/lib/python3.10/site-packages/itsdangerous-2.1.2.dist-info/top_level.txt

@@ -0,0 +1 @@
+itsdangerous

+ 19 - 0
venv/lib/python3.10/site-packages/itsdangerous/__init__.py

@@ -0,0 +1,19 @@
+from .encoding import base64_decode as base64_decode
+from .encoding import base64_encode as base64_encode
+from .encoding import want_bytes as want_bytes
+from .exc import BadData as BadData
+from .exc import BadHeader as BadHeader
+from .exc import BadPayload as BadPayload
+from .exc import BadSignature as BadSignature
+from .exc import BadTimeSignature as BadTimeSignature
+from .exc import SignatureExpired as SignatureExpired
+from .serializer import Serializer as Serializer
+from .signer import HMACAlgorithm as HMACAlgorithm
+from .signer import NoneAlgorithm as NoneAlgorithm
+from .signer import Signer as Signer
+from .timed import TimedSerializer as TimedSerializer
+from .timed import TimestampSigner as TimestampSigner
+from .url_safe import URLSafeSerializer as URLSafeSerializer
+from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer
+
+__version__ = "2.1.2"

Some files were not shown because too many files changed in this diff