diff --git a/server/venv/Lib/site-packages/_mysql_connector.cp313-win_amd64.pyd b/server/venv/Lib/site-packages/_mysql_connector.cp313-win_amd64.pyd deleted file mode 100644 index 311f718..0000000 Binary files a/server/venv/Lib/site-packages/_mysql_connector.cp313-win_amd64.pyd and /dev/null differ diff --git a/server/venv/Lib/site-packages/_yaml/__init__.py b/server/venv/Lib/site-packages/_yaml/__init__.py deleted file mode 100644 index 7baa8c4..0000000 --- a/server/venv/Lib/site-packages/_yaml/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# This is a stub package designed to roughly emulate the _yaml -# extension module, which previously existed as a standalone module -# and has been moved into the `yaml` package namespace. -# It does not perfectly mimic its old counterpart, but should get -# close enough for anyone who's relying on it even when they shouldn't. -import yaml - -# in some circumstances, the yaml module we imoprted may be from a different version, so we need -# to tread carefully when poking at it here (it may not have the attributes we expect) -if not getattr(yaml, '__with_libyaml__', False): - from sys import version_info - - exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError - raise exc("No module named '_yaml'") -else: - from yaml._yaml import * - import warnings - warnings.warn( - 'The _yaml extension module is now located at yaml._yaml' - ' and its location is subject to change. To use the' - ' LibYAML-based parser and emitter, import from `yaml`:' - ' `from yaml import CLoader as Loader, CDumper as Dumper`.', - DeprecationWarning - ) - del warnings - # Don't `del yaml` here because yaml is actually an existing - # namespace member of _yaml. - -__name__ = '_yaml' -# If the module is top-level (i.e. not a part of any specific package) -# then the attribute should be set to ''. -# https://docs.python.org/3.8/library/types.html -__package__ = '' diff --git a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER b/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/METADATA b/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/METADATA deleted file mode 100644 index 9bf7a9e..0000000 --- a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/METADATA +++ /dev/null @@ -1,145 +0,0 @@ -Metadata-Version: 2.4 -Name: annotated-doc -Version: 0.0.4 -Summary: Document parameters, class attributes, return types, and variables inline, with Annotated. -Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= -License-Expression: MIT -License-File: LICENSE -Classifier: Intended Audience :: Information Technology -Classifier: Intended Audience :: System Administrators -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python -Classifier: Topic :: Internet -Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Software Development -Classifier: Typing :: Typed -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 -Project-URL: Homepage, https://github.com/fastapi/annotated-doc -Project-URL: Documentation, https://github.com/fastapi/annotated-doc -Project-URL: Repository, https://github.com/fastapi/annotated-doc -Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues -Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md -Requires-Python: >=3.8 -Description-Content-Type: text/markdown - -# Annotated Doc - -Document parameters, class attributes, return types, and variables inline, with `Annotated`. - - - Test - - - Coverage - - - Package version - - - Supported Python versions - - -## Installation - -```bash -pip install annotated-doc -``` - -Or with `uv`: - -```Python -uv add annotated-doc -``` - -## Usage - -Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable. - -For example, to document a parameter `name` in a function `hi` you could do: - -```Python -from typing import Annotated - -from annotated_doc import Doc - -def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None: - print(f"Hi, {name}!") -``` - -You can also use it to document class attributes: - -```Python -from typing import Annotated - -from annotated_doc import Doc - -class User: - name: Annotated[str, Doc("The user's name")] - age: Annotated[int, Doc("The user's age")] -``` - -The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`. - -## Who Uses This - -`annotated-doc` was made for: - -* [FastAPI](https://fastapi.tiangolo.com/) -* [Typer](https://typer.tiangolo.com/) -* [SQLModel](https://sqlmodel.tiangolo.com/) -* [Asyncer](https://asyncer.tiangolo.com/) - -`annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/). - -## Reasons not to use `annotated-doc` - -You are already comfortable with one of the existing docstring formats, like: - -* Sphinx -* numpydoc -* Google -* Keras - -Your team is already comfortable using them. - -You prefer having the documentation about parameters all together in a docstring, separated from the code defining them. - -You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use. - -## Reasons to use `annotated-doc` - -* No micro-syntax to learn for newcomers, it’s **just Python** syntax. -* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc. -* **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create. -* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring. -* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation. -* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**. -* **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed. -* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions. -* A more formalized way to document other symbols, like type aliases, that could use Annotated. -* **Support** for apps using FastAPI, Typer and others. -* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer. - -## History - -I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition. - -The conclusion was that this was better done as an external effort, in a third-party library. - -So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/RECORD b/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/RECORD deleted file mode 100644 index e040ae2..0000000 --- a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/RECORD +++ /dev/null @@ -1,11 +0,0 @@ -annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566 -annotated_doc-0.0.4.dist-info/RECORD,, -annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90 -annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34 -annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086 -annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52 -annotated_doc/__pycache__/__init__.cpython-313.pyc,, -annotated_doc/__pycache__/main.cpython-313.pyc,, -annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075 -annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/WHEEL b/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/WHEEL deleted file mode 100644 index 045c8ac..0000000 --- a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: pdm-backend (2.4.5) -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt b/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt deleted file mode 100644 index c3ad472..0000000 --- a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/entry_points.txt +++ /dev/null @@ -1,4 +0,0 @@ -[console_scripts] - -[gui_scripts] - diff --git a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE b/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE deleted file mode 100644 index 7a25446..0000000 --- a/server/venv/Lib/site-packages/annotated_doc-0.0.4.dist-info/licenses/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2025 Sebastián Ramírez - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/annotated_doc/__init__.py b/server/venv/Lib/site-packages/annotated_doc/__init__.py deleted file mode 100644 index a0152a7..0000000 --- a/server/venv/Lib/site-packages/annotated_doc/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .main import Doc as Doc - -__version__ = "0.0.4" diff --git a/server/venv/Lib/site-packages/annotated_doc/main.py b/server/venv/Lib/site-packages/annotated_doc/main.py deleted file mode 100644 index 7063c59..0000000 --- a/server/venv/Lib/site-packages/annotated_doc/main.py +++ /dev/null @@ -1,36 +0,0 @@ -class Doc: - """Define the documentation of a type annotation using `Annotated`, to be - used in class attributes, function and method parameters, return values, - and variables. - - The value should be a positional-only string literal to allow static tools - like editors and documentation generators to use it. - - This complements docstrings. - - The string value passed is available in the attribute `documentation`. - - Example: - - ```Python - from typing import Annotated - from annotated_doc import Doc - - def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None: - print(f"Hi, {name}!") - ``` - """ - - def __init__(self, documentation: str, /) -> None: - self.documentation = documentation - - def __repr__(self) -> str: - return f"Doc({self.documentation!r})" - - def __hash__(self) -> int: - return hash(self.documentation) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, Doc): - return NotImplemented - return self.documentation == other.documentation diff --git a/server/venv/Lib/site-packages/annotated_doc/py.typed b/server/venv/Lib/site-packages/annotated_doc/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER b/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA b/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA deleted file mode 100644 index 3ac05cf..0000000 --- a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/METADATA +++ /dev/null @@ -1,295 +0,0 @@ -Metadata-Version: 2.3 -Name: annotated-types -Version: 0.7.0 -Summary: Reusable constraint types to use with typing.Annotated -Project-URL: Homepage, https://github.com/annotated-types/annotated-types -Project-URL: Source, https://github.com/annotated-types/annotated-types -Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases -Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin , Zac Hatfield-Dodds -License-File: LICENSE -Classifier: Development Status :: 4 - Beta -Classifier: Environment :: Console -Classifier: Environment :: MacOS X -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Information Technology -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: POSIX :: Linux -Classifier: Operating System :: Unix -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Typing :: Typed -Requires-Python: >=3.8 -Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9' -Description-Content-Type: text/markdown - -# annotated-types - -[![CI](https://github.com/annotated-types/annotated-types/workflows/CI/badge.svg?event=push)](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) -[![pypi](https://img.shields.io/pypi/v/annotated-types.svg)](https://pypi.python.org/pypi/annotated-types) -[![versions](https://img.shields.io/pypi/pyversions/annotated-types.svg)](https://github.com/annotated-types/annotated-types) -[![license](https://img.shields.io/github/license/annotated-types/annotated-types.svg)](https://github.com/annotated-types/annotated-types/blob/main/LICENSE) - -[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of -adding context-specific metadata to existing types, and specifies that -`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special -logic for `x`. - -This package provides metadata objects which can be used to represent common -constraints such as upper and lower bounds on scalar values and collection sizes, -a `Predicate` marker for runtime checks, and -descriptions of how we intend these metadata to be interpreted. In some cases, -we also note alternative representations which do not require this package. - -## Install - -```bash -pip install annotated-types -``` - -## Examples - -```python -from typing import Annotated -from annotated_types import Gt, Len, Predicate - -class MyClass: - age: Annotated[int, Gt(18)] # Valid: 19, 20, ... - # Invalid: 17, 18, "19", 19.0, ... - factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ... - # Invalid: 4, 8, -2, 5.0, "prime", ... - - my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50] - # Invalid: (1, 2), ["abc"], [0] * 20 -``` - -## Documentation - -_While `annotated-types` avoids runtime checks for performance, users should not -construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`. -Downstream implementors may choose to raise an error, emit a warning, silently ignore -a metadata item, etc., if the metadata objects described below are used with an -incompatible type - or for any other reason!_ - -### Gt, Ge, Lt, Le - -Express inclusive and/or exclusive bounds on orderable values - which may be numbers, -dates, times, strings, sets, etc. Note that the boundary value need not be of the -same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]` -is fine, for example, and implies that the value is an integer x such that `x > 1.5`. - -We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)` -as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on -the `annotated-types` package. - -To be explicit, these types have the following meanings: - -* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum -* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum -* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum -* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum - -### Interval - -`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single -metadata object. `None` attributes should be ignored, and non-`None` attributes -treated as per the single bounds above. - -### MultipleOf - -`MultipleOf(multiple_of=x)` might be interpreted in two ways: - -1. Python semantics, implying `value % multiple_of == 0`, or -2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1), - where `int(value / multiple_of) == value / multiple_of`. - -We encourage users to be aware of these two common interpretations and their -distinct behaviours, especially since very large or non-integer numbers make -it easy to cause silent data corruption due to floating-point imprecision. - -We encourage libraries to carefully document which interpretation they implement. - -### MinLen, MaxLen, Len - -`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive. - -As well as `Len()` which can optionally include upper and lower bounds, we also -provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)` -and `Len(max_length=y)` respectively. - -`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`. - -Examples of usage: - -* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less -* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less -* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more -* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6 -* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8 - -#### Changed in v0.4.0 - -* `min_inclusive` has been renamed to `min_length`, no change in meaning -* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive** -* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic - meaning of the upper bound in slices vs. `Len` - -See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion. - -### Timezone - -`Timezone` can be used with a `datetime` or a `time` to express which timezones -are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime. -`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis)) -expresses that any timezone-aware datetime is allowed. You may also pass a specific -timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) -object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only -allow a specific timezone, though we note that this is often a symptom of fragile design. - -#### Changed in v0.x.x - -* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of - `timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries. - -### Unit - -`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of -a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]` -would be a float representing a velocity in meters per second. - -Please note that `annotated_types` itself makes no attempt to parse or validate -the unit string in any way. That is left entirely to downstream libraries, -such as [`pint`](https://pint.readthedocs.io) or -[`astropy.units`](https://docs.astropy.org/en/stable/units/). - -An example of how a library might use this metadata: - -```python -from annotated_types import Unit -from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args - -# given a type annotated with a unit: -Meters = Annotated[float, Unit("m")] - - -# you can cast the annotation to a specific unit type with any -# callable that accepts a string and returns the desired type -T = TypeVar("T") -def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None: - if get_origin(tp) is Annotated: - for arg in get_args(tp): - if isinstance(arg, Unit): - return unit_cls(arg.unit) - return None - - -# using `pint` -import pint -pint_unit = cast_unit(Meters, pint.Unit) - - -# using `astropy.units` -import astropy.units as u -astropy_unit = cast_unit(Meters, u.Unit) -``` - -### Predicate - -`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values. -Users should prefer the statically inspectable metadata above, but if you need -the full power and flexibility of arbitrary runtime predicates... here it is. - -For some common constraints, we provide generic types: - -* `IsLower = Annotated[T, Predicate(str.islower)]` -* `IsUpper = Annotated[T, Predicate(str.isupper)]` -* `IsDigit = Annotated[T, Predicate(str.isdigit)]` -* `IsFinite = Annotated[T, Predicate(math.isfinite)]` -* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]` -* `IsNan = Annotated[T, Predicate(math.isnan)]` -* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]` -* `IsInfinite = Annotated[T, Predicate(math.isinf)]` -* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]` - -so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer -(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`. - -Some libraries might have special logic to handle known or understandable predicates, -for example by checking for `str.isdigit` and using its presence to both call custom -logic to enforce digit-only strings, and customise some generated external schema. -Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in -favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`. - -To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner. - -We do not specify what behaviour should be expected for predicates that raise -an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently -skip invalid constraints, or statically raise an error; or it might try calling it -and then propagate or discard the resulting -`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object` -exception. We encourage libraries to document the behaviour they choose. - -### Doc - -`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used. - -It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools. - -It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`. - -This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md). - -### Integrating downstream types with `GroupedMetadata` - -Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata. -This can help reduce verbosity and cognitive overhead for users. -For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata: - -```python -from dataclasses import dataclass -from typing import Iterator -from annotated_types import GroupedMetadata, Ge - -@dataclass -class Field(GroupedMetadata): - ge: int | None = None - description: str | None = None - - def __iter__(self) -> Iterator[object]: - # Iterating over a GroupedMetadata object should yield annotated-types - # constraint metadata objects which describe it as fully as possible, - # and may include other unknown objects too. - if self.ge is not None: - yield Ge(self.ge) - if self.description is not None: - yield Description(self.description) -``` - -Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently. - -Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself. - -Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`. - -### Consuming metadata - -We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103). - -It is up to the implementer to determine how this metadata is used. -You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases. - -## Design & History - -This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic -and Hypothesis, with the goal of making it as easy as possible for end-users to -provide more informative annotations for use by runtime libraries. - -It is deliberately minimal, and following PEP-593 allows considerable downstream -discretion in what (if anything!) they choose to support. Nonetheless, we expect -that staying simple and covering _only_ the most common use-cases will give users -and maintainers the best experience we can. If you'd like more constraints for your -types - follow our lead, by defining them and documenting them downstream! diff --git a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD b/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD deleted file mode 100644 index 01b9e66..0000000 --- a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/RECORD +++ /dev/null @@ -1,10 +0,0 @@ -annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046 -annotated_types-0.7.0.dist-info/RECORD,, -annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87 -annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083 -annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819 -annotated_types/__pycache__/__init__.cpython-313.pyc,, -annotated_types/__pycache__/test_cases.cpython-313.pyc,, -annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421 diff --git a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL b/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL deleted file mode 100644 index 516596c..0000000 --- a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: hatchling 1.24.2 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE b/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE deleted file mode 100644 index d99323a..0000000 --- a/server/venv/Lib/site-packages/annotated_types-0.7.0.dist-info/licenses/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2022 the contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/server/venv/Lib/site-packages/annotated_types/__init__.py b/server/venv/Lib/site-packages/annotated_types/__init__.py deleted file mode 100644 index 74e0dee..0000000 --- a/server/venv/Lib/site-packages/annotated_types/__init__.py +++ /dev/null @@ -1,432 +0,0 @@ -import math -import sys -import types -from dataclasses import dataclass -from datetime import tzinfo -from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union - -if sys.version_info < (3, 8): - from typing_extensions import Protocol, runtime_checkable -else: - from typing import Protocol, runtime_checkable - -if sys.version_info < (3, 9): - from typing_extensions import Annotated, Literal -else: - from typing import Annotated, Literal - -if sys.version_info < (3, 10): - EllipsisType = type(Ellipsis) - KW_ONLY = {} - SLOTS = {} -else: - from types import EllipsisType - - KW_ONLY = {"kw_only": True} - SLOTS = {"slots": True} - - -__all__ = ( - 'BaseMetadata', - 'GroupedMetadata', - 'Gt', - 'Ge', - 'Lt', - 'Le', - 'Interval', - 'MultipleOf', - 'MinLen', - 'MaxLen', - 'Len', - 'Timezone', - 'Predicate', - 'LowerCase', - 'UpperCase', - 'IsDigits', - 'IsFinite', - 'IsNotFinite', - 'IsNan', - 'IsNotNan', - 'IsInfinite', - 'IsNotInfinite', - 'doc', - 'DocInfo', - '__version__', -) - -__version__ = '0.7.0' - - -T = TypeVar('T') - - -# arguments that start with __ are considered -# positional only -# see https://peps.python.org/pep-0484/#positional-only-arguments - - -class SupportsGt(Protocol): - def __gt__(self: T, __other: T) -> bool: - ... - - -class SupportsGe(Protocol): - def __ge__(self: T, __other: T) -> bool: - ... - - -class SupportsLt(Protocol): - def __lt__(self: T, __other: T) -> bool: - ... - - -class SupportsLe(Protocol): - def __le__(self: T, __other: T) -> bool: - ... - - -class SupportsMod(Protocol): - def __mod__(self: T, __other: T) -> T: - ... - - -class SupportsDiv(Protocol): - def __div__(self: T, __other: T) -> T: - ... - - -class BaseMetadata: - """Base class for all metadata. - - This exists mainly so that implementers - can do `isinstance(..., BaseMetadata)` while traversing field annotations. - """ - - __slots__ = () - - -@dataclass(frozen=True, **SLOTS) -class Gt(BaseMetadata): - """Gt(gt=x) implies that the value must be greater than x. - - It can be used with any type that supports the ``>`` operator, - including numbers, dates and times, strings, sets, and so on. - """ - - gt: SupportsGt - - -@dataclass(frozen=True, **SLOTS) -class Ge(BaseMetadata): - """Ge(ge=x) implies that the value must be greater than or equal to x. - - It can be used with any type that supports the ``>=`` operator, - including numbers, dates and times, strings, sets, and so on. - """ - - ge: SupportsGe - - -@dataclass(frozen=True, **SLOTS) -class Lt(BaseMetadata): - """Lt(lt=x) implies that the value must be less than x. - - It can be used with any type that supports the ``<`` operator, - including numbers, dates and times, strings, sets, and so on. - """ - - lt: SupportsLt - - -@dataclass(frozen=True, **SLOTS) -class Le(BaseMetadata): - """Le(le=x) implies that the value must be less than or equal to x. - - It can be used with any type that supports the ``<=`` operator, - including numbers, dates and times, strings, sets, and so on. - """ - - le: SupportsLe - - -@runtime_checkable -class GroupedMetadata(Protocol): - """A grouping of multiple objects, like typing.Unpack. - - `GroupedMetadata` on its own is not metadata and has no meaning. - All of the constraints and metadata should be fully expressable - in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`. - - Concrete implementations should override `GroupedMetadata.__iter__()` - to add their own metadata. - For example: - - >>> @dataclass - >>> class Field(GroupedMetadata): - >>> gt: float | None = None - >>> description: str | None = None - ... - >>> def __iter__(self) -> Iterable[object]: - >>> if self.gt is not None: - >>> yield Gt(self.gt) - >>> if self.description is not None: - >>> yield Description(self.gt) - - Also see the implementation of `Interval` below for an example. - - Parsers should recognize this and unpack it so that it can be used - both with and without unpacking: - - - `Annotated[int, Field(...)]` (parser must unpack Field) - - `Annotated[int, *Field(...)]` (PEP-646) - """ # noqa: trailing-whitespace - - @property - def __is_annotated_types_grouped_metadata__(self) -> Literal[True]: - return True - - def __iter__(self) -> Iterator[object]: - ... - - if not TYPE_CHECKING: - __slots__ = () # allow subclasses to use slots - - def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None: - # Basic ABC like functionality without the complexity of an ABC - super().__init_subclass__(*args, **kwargs) - if cls.__iter__ is GroupedMetadata.__iter__: - raise TypeError("Can't subclass GroupedMetadata without implementing __iter__") - - def __iter__(self) -> Iterator[object]: # noqa: F811 - raise NotImplementedError # more helpful than "None has no attribute..." type errors - - -@dataclass(frozen=True, **KW_ONLY, **SLOTS) -class Interval(GroupedMetadata): - """Interval can express inclusive or exclusive bounds with a single object. - - It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which - are interpreted the same way as the single-bound constraints. - """ - - gt: Union[SupportsGt, None] = None - ge: Union[SupportsGe, None] = None - lt: Union[SupportsLt, None] = None - le: Union[SupportsLe, None] = None - - def __iter__(self) -> Iterator[BaseMetadata]: - """Unpack an Interval into zero or more single-bounds.""" - if self.gt is not None: - yield Gt(self.gt) - if self.ge is not None: - yield Ge(self.ge) - if self.lt is not None: - yield Lt(self.lt) - if self.le is not None: - yield Le(self.le) - - -@dataclass(frozen=True, **SLOTS) -class MultipleOf(BaseMetadata): - """MultipleOf(multiple_of=x) might be interpreted in two ways: - - 1. Python semantics, implying ``value % multiple_of == 0``, or - 2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of`` - - We encourage users to be aware of these two common interpretations, - and libraries to carefully document which they implement. - """ - - multiple_of: Union[SupportsDiv, SupportsMod] - - -@dataclass(frozen=True, **SLOTS) -class MinLen(BaseMetadata): - """ - MinLen() implies minimum inclusive length, - e.g. ``len(value) >= min_length``. - """ - - min_length: Annotated[int, Ge(0)] - - -@dataclass(frozen=True, **SLOTS) -class MaxLen(BaseMetadata): - """ - MaxLen() implies maximum inclusive length, - e.g. ``len(value) <= max_length``. - """ - - max_length: Annotated[int, Ge(0)] - - -@dataclass(frozen=True, **SLOTS) -class Len(GroupedMetadata): - """ - Len() implies that ``min_length <= len(value) <= max_length``. - - Upper bound may be omitted or ``None`` to indicate no upper length bound. - """ - - min_length: Annotated[int, Ge(0)] = 0 - max_length: Optional[Annotated[int, Ge(0)]] = None - - def __iter__(self) -> Iterator[BaseMetadata]: - """Unpack a Len into zone or more single-bounds.""" - if self.min_length > 0: - yield MinLen(self.min_length) - if self.max_length is not None: - yield MaxLen(self.max_length) - - -@dataclass(frozen=True, **SLOTS) -class Timezone(BaseMetadata): - """Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive). - - ``Annotated[datetime, Timezone(None)]`` must be a naive datetime. - ``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be - tz-aware but any timezone is allowed. - - You may also pass a specific timezone string or tzinfo object such as - ``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that - you only allow a specific timezone, though we note that this is often - a symptom of poor design. - """ - - tz: Union[str, tzinfo, EllipsisType, None] - - -@dataclass(frozen=True, **SLOTS) -class Unit(BaseMetadata): - """Indicates that the value is a physical quantity with the specified unit. - - It is intended for usage with numeric types, where the value represents the - magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]`` - or ``speed: Annotated[float, Unit('m/s')]``. - - Interpretation of the unit string is left to the discretion of the consumer. - It is suggested to follow conventions established by python libraries that work - with physical quantities, such as - - - ``pint`` : - - ``astropy.units``: - - For indicating a quantity with a certain dimensionality but without a specific unit - it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`. - Note, however, ``annotated_types`` itself makes no use of the unit string. - """ - - unit: str - - -@dataclass(frozen=True, **SLOTS) -class Predicate(BaseMetadata): - """``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values. - - Users should prefer statically inspectable metadata, but if you need the full - power and flexibility of arbitrary runtime predicates... here it is. - - We provide a few predefined predicates for common string constraints: - ``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and - ``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which - can be given special handling, and avoid indirection like ``lambda s: s.lower()``. - - Some libraries might have special logic to handle certain predicates, e.g. by - checking for `str.isdigit` and using its presence to both call custom logic to - enforce digit-only strings, and customise some generated external schema. - - We do not specify what behaviour should be expected for predicates that raise - an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently - skip invalid constraints, or statically raise an error; or it might try calling it - and then propagate or discard the resulting exception. - """ - - func: Callable[[Any], bool] - - def __repr__(self) -> str: - if getattr(self.func, "__name__", "") == "": - return f"{self.__class__.__name__}({self.func!r})" - if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and ( - namespace := getattr(self.func.__self__, "__name__", None) - ): - return f"{self.__class__.__name__}({namespace}.{self.func.__name__})" - if isinstance(self.func, type(str.isascii)): # method descriptor - return f"{self.__class__.__name__}({self.func.__qualname__})" - return f"{self.__class__.__name__}({self.func.__name__})" - - -@dataclass -class Not: - func: Callable[[Any], bool] - - def __call__(self, __v: Any) -> bool: - return not self.func(__v) - - -_StrType = TypeVar("_StrType", bound=str) - -LowerCase = Annotated[_StrType, Predicate(str.islower)] -""" -Return True if the string is a lowercase string, False otherwise. - -A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string. -""" # noqa: E501 -UpperCase = Annotated[_StrType, Predicate(str.isupper)] -""" -Return True if the string is an uppercase string, False otherwise. - -A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string. -""" # noqa: E501 -IsDigit = Annotated[_StrType, Predicate(str.isdigit)] -IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63 -""" -Return True if the string is a digit string, False otherwise. - -A string is a digit string if all characters in the string are digits and there is at least one character in the string. -""" # noqa: E501 -IsAscii = Annotated[_StrType, Predicate(str.isascii)] -""" -Return True if all characters in the string are ASCII, False otherwise. - -ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too. -""" - -_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex]) -IsFinite = Annotated[_NumericType, Predicate(math.isfinite)] -"""Return True if x is neither an infinity nor a NaN, and False otherwise.""" -IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))] -"""Return True if x is one of infinity or NaN, and False otherwise""" -IsNan = Annotated[_NumericType, Predicate(math.isnan)] -"""Return True if x is a NaN (not a number), and False otherwise.""" -IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))] -"""Return True if x is anything but NaN (not a number), and False otherwise.""" -IsInfinite = Annotated[_NumericType, Predicate(math.isinf)] -"""Return True if x is a positive or negative infinity, and False otherwise.""" -IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))] -"""Return True if x is neither a positive or negative infinity, and False otherwise.""" - -try: - from typing_extensions import DocInfo, doc # type: ignore [attr-defined] -except ImportError: - - @dataclass(frozen=True, **SLOTS) - class DocInfo: # type: ignore [no-redef] - """ " - The return value of doc(), mainly to be used by tools that want to extract the - Annotated documentation at runtime. - """ - - documentation: str - """The documentation string passed to doc().""" - - def doc( - documentation: str, - ) -> DocInfo: - """ - Add documentation to a type annotation inside of Annotated. - - For example: - - >>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ... - """ - return DocInfo(documentation) diff --git a/server/venv/Lib/site-packages/annotated_types/py.typed b/server/venv/Lib/site-packages/annotated_types/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/annotated_types/test_cases.py b/server/venv/Lib/site-packages/annotated_types/test_cases.py deleted file mode 100644 index d9164d6..0000000 --- a/server/venv/Lib/site-packages/annotated_types/test_cases.py +++ /dev/null @@ -1,151 +0,0 @@ -import math -import sys -from datetime import date, datetime, timedelta, timezone -from decimal import Decimal -from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple - -if sys.version_info < (3, 9): - from typing_extensions import Annotated -else: - from typing import Annotated - -import annotated_types as at - - -class Case(NamedTuple): - """ - A test case for `annotated_types`. - """ - - annotation: Any - valid_cases: Iterable[Any] - invalid_cases: Iterable[Any] - - -def cases() -> Iterable[Case]: - # Gt, Ge, Lt, Le - yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1)) - yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1)) - yield Case( - Annotated[datetime, at.Gt(datetime(2000, 1, 1))], - [datetime(2000, 1, 2), datetime(2000, 1, 3)], - [datetime(2000, 1, 1), datetime(1999, 12, 31)], - ) - yield Case( - Annotated[datetime, at.Gt(date(2000, 1, 1))], - [date(2000, 1, 2), date(2000, 1, 3)], - [date(2000, 1, 1), date(1999, 12, 31)], - ) - yield Case( - Annotated[datetime, at.Gt(Decimal('1.123'))], - [Decimal('1.1231'), Decimal('123')], - [Decimal('1.123'), Decimal('0')], - ) - - yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1)) - yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1)) - yield Case( - Annotated[datetime, at.Ge(datetime(2000, 1, 1))], - [datetime(2000, 1, 2), datetime(2000, 1, 3)], - [datetime(1998, 1, 1), datetime(1999, 12, 31)], - ) - - yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4)) - yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9)) - yield Case( - Annotated[datetime, at.Lt(datetime(2000, 1, 1))], - [datetime(1999, 12, 31), datetime(1999, 12, 31)], - [datetime(2000, 1, 2), datetime(2000, 1, 3)], - ) - - yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000)) - yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9)) - yield Case( - Annotated[datetime, at.Le(datetime(2000, 1, 1))], - [datetime(2000, 1, 1), datetime(1999, 12, 31)], - [datetime(2000, 1, 2), datetime(2000, 1, 3)], - ) - - # Interval - yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1)) - yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1)) - yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1)) - yield Case( - Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))], - [datetime(2000, 1, 2), datetime(2000, 1, 3)], - [datetime(2000, 1, 1), datetime(2000, 1, 4)], - ) - - yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4)) - yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1)) - - # lengths - - yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) - yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12')) - yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) - yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2])) - - yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10)) - yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10)) - yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) - yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10)) - - yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10)) - yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234')) - - yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}]) - yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4})) - yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4))) - - # Timezone - - yield Case( - Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)] - ) - yield Case( - Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)] - ) - yield Case( - Annotated[datetime, at.Timezone(timezone.utc)], - [datetime(2000, 1, 1, tzinfo=timezone.utc)], - [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], - ) - yield Case( - Annotated[datetime, at.Timezone('Europe/London')], - [datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))], - [datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))], - ) - - # Quantity - - yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m')) - - # predicate types - - yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom']) - yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC']) - yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2']) - yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀']) - - yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5]) - - yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf]) - yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23]) - yield Case(at.IsNan[float], [math.nan], [1.23, math.inf]) - yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan]) - yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23]) - yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf]) - - # check stacked predicates - yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan]) - - # doc - yield Case(Annotated[int, at.doc("A number")], [1, 2], []) - - # custom GroupedMetadata - class MyCustomGroupedMetadata(at.GroupedMetadata): - def __iter__(self) -> Iterator[at.Predicate]: - yield at.Predicate(lambda x: float(x).is_integer()) - - yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5]) diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/INSTALLER b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/METADATA b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/METADATA deleted file mode 100644 index 7840c30..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/METADATA +++ /dev/null @@ -1,107 +0,0 @@ -Metadata-Version: 2.4 -Name: anyio -Version: 4.14.0 -Summary: High-level concurrency and networking framework on top of asyncio or Trio -Author-email: Alex Grönholm -License-Expression: MIT -Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/ -Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html -Project-URL: Source code, https://github.com/agronholm/anyio -Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Framework :: AnyIO -Classifier: Typing :: Typed -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 -Classifier: Programming Language :: Python :: 3.15 -Requires-Python: >=3.10 -Description-Content-Type: text/x-rst -License-File: LICENSE -Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11" -Requires-Dist: idna>=2.8 -Requires-Dist: typing_extensions>=4.5; python_version < "3.13" -Provides-Extra: trio -Requires-Dist: trio>=0.32.0; extra == "trio" -Dynamic: license-file - -.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg - :target: https://github.com/agronholm/anyio/actions/workflows/test.yml - :alt: Build Status -.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master - :target: https://coveralls.io/github/agronholm/anyio?branch=master - :alt: Code Coverage -.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest - :target: https://anyio.readthedocs.io/en/latest/?badge=latest - :alt: Documentation -.. image:: https://badges.gitter.im/gitterHQ/gitter.svg - :target: https://gitter.im/python-trio/AnyIO - :alt: Gitter chat -.. image:: https://tidelift.com/badges/package/pypi/anyio - :target: https://tidelift.com/subscription/pkg/pypi-anyio - :alt: Tidelift - -AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or -Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony -with the native SC of Trio itself. - -Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or -Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full -refactoring necessary. It will blend in with the native libraries of your chosen backend. - -To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it -`here `_. - -Documentation -------------- - -View full documentation at: https://anyio.readthedocs.io/ - -Features --------- - -AnyIO offers the following functionality: - -* Task groups (nurseries_ in trio terminology) -* High-level networking (TCP, UDP and UNIX sockets) - - * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python - 3.8) - * async/await style UDP sockets (unlike asyncio where you still have to use Transports and - Protocols) - -* A versatile API for byte streams and object streams -* Inter-task synchronization and communication (locks, conditions, events, semaphores, object - streams) -* Worker threads -* Subprocesses -* Subinterpreter support for code parallelization (on Python 3.13 and later) -* Asynchronous file I/O (using worker threads) -* Signal handling -* Asynchronous versions of the functools_ and itertools_ modules - -AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures. -It even works with the popular Hypothesis_ library. - -.. _asyncio: https://docs.python.org/3/library/asyncio.html -.. _Trio: https://github.com/python-trio/trio -.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency -.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning -.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs -.. _pytest: https://docs.pytest.org/en/latest/ -.. _functools: https://docs.python.org/3/library/functools.html -.. _itertools: https://docs.python.org/3/library/itertools.html -.. _Hypothesis: https://hypothesis.works/ - -Security contact information ----------------------------- - -To report a security vulnerability, please use the `Tidelift security contact`_. -Tidelift will coordinate the fix and disclosure. - -.. _Tidelift security contact: https://tidelift.com/security diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/RECORD b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/RECORD deleted file mode 100644 index a691626..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/RECORD +++ /dev/null @@ -1,94 +0,0 @@ -anyio-4.14.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -anyio-4.14.0.dist-info/METADATA,sha256=ZozV8u8z7Ya7tmsk9Uetxz7-QFNpHH2nVBLy041h6xo,4645 -anyio-4.14.0.dist-info/RECORD,, -anyio-4.14.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 -anyio-4.14.0.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39 -anyio-4.14.0.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081 -anyio-4.14.0.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6 -anyio/__init__.py,sha256=HitUIfzvAojSeaHVmJ9rFn8k_yI63G6s_jUL2QChf4U,6405 -anyio/__pycache__/__init__.cpython-313.pyc,, -anyio/__pycache__/from_thread.cpython-313.pyc,, -anyio/__pycache__/functools.cpython-313.pyc,, -anyio/__pycache__/itertools.cpython-313.pyc,, -anyio/__pycache__/lowlevel.cpython-313.pyc,, -anyio/__pycache__/pytest_plugin.cpython-313.pyc,, -anyio/__pycache__/to_interpreter.cpython-313.pyc,, -anyio/__pycache__/to_process.cpython-313.pyc,, -anyio/__pycache__/to_thread.cpython-313.pyc,, -anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -anyio/_backends/__pycache__/__init__.cpython-313.pyc,, -anyio/_backends/__pycache__/_asyncio.cpython-313.pyc,, -anyio/_backends/__pycache__/_trio.cpython-313.pyc,, -anyio/_backends/_asyncio.py,sha256=kX_Zv3F_SnWqWqSLmfPsDFxvsJLaATSeivzg6Gho3Rs,101971 -anyio/_backends/_trio.py,sha256=vR0ZgxVnOo4AHhHcHVG0worMc-3ZNpAZ6Vxh0m0ZZC0,45189 -anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -anyio/_core/__pycache__/__init__.cpython-313.pyc,, -anyio/_core/__pycache__/_asyncio_selector_thread.cpython-313.pyc,, -anyio/_core/__pycache__/_contextmanagers.cpython-313.pyc,, -anyio/_core/__pycache__/_eventloop.cpython-313.pyc,, -anyio/_core/__pycache__/_exceptions.cpython-313.pyc,, -anyio/_core/__pycache__/_fileio.cpython-313.pyc,, -anyio/_core/__pycache__/_resources.cpython-313.pyc,, -anyio/_core/__pycache__/_signals.cpython-313.pyc,, -anyio/_core/__pycache__/_sockets.cpython-313.pyc,, -anyio/_core/__pycache__/_streams.cpython-313.pyc,, -anyio/_core/__pycache__/_subprocesses.cpython-313.pyc,, -anyio/_core/__pycache__/_synchronization.cpython-313.pyc,, -anyio/_core/__pycache__/_tasks.cpython-313.pyc,, -anyio/_core/__pycache__/_tempfile.cpython-313.pyc,, -anyio/_core/__pycache__/_testing.cpython-313.pyc,, -anyio/_core/__pycache__/_typedattr.cpython-313.pyc,, -anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626 -anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215 -anyio/_core/_eventloop.py,sha256=ByZUeJD9alMfcyTseRo5IzTO0IltEul_Gyq9iqSjqDk,6658 -anyio/_core/_exceptions.py,sha256=OfzLO4Z3Hog1TnipbIn72YNtkoYxS4lHW9MqKDeGc88,4936 -anyio/_core/_fileio.py,sha256=hHfyV0bXDL-R2ZNnInwse3nmTAd36AIz1cBxgmAwzAQ,31358 -anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435 -anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016 -anyio/_core/_sockets.py,sha256=9FU423j52XBBfGVr6MdzPTdyw8bGrzApZ5m338-AtsY,35286 -anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806 -anyio/_core/_subprocesses.py,sha256=tkmkPKEkEaiMD8C9WRZBlmgjOYRDRbZdte6e-unay2E,7916 -anyio/_core/_synchronization.py,sha256=U14eUp9tqcP95OcCG7s3A1MZU9WLw84lOS4YB-H795s,21591 -anyio/_core/_tasks.py,sha256=ELL2jscaSW0Jw_xA6MtQlm3xwvFEzjTbc1u9Tteyt0I,13244 -anyio/_core/_tempfile.py,sha256=jE2w59FRF3yRo4vjkjfZF2YcqsBZvc66VWRwrJGDYGk,19624 -anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340 -anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508 -anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869 -anyio/abc/__pycache__/__init__.cpython-313.pyc,, -anyio/abc/__pycache__/_eventloop.cpython-313.pyc,, -anyio/abc/__pycache__/_resources.cpython-313.pyc,, -anyio/abc/__pycache__/_sockets.cpython-313.pyc,, -anyio/abc/__pycache__/_streams.cpython-313.pyc,, -anyio/abc/__pycache__/_subprocesses.cpython-313.pyc,, -anyio/abc/__pycache__/_tasks.cpython-313.pyc,, -anyio/abc/__pycache__/_testing.cpython-313.pyc,, -anyio/abc/_eventloop.py,sha256=OqWYSEj0TmwL_xniCJt3_jHFWsuMk9THk8tCTGsKapI,10681 -anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783 -anyio/abc/_sockets.py,sha256=OmVDrfemVvF9c5K1tpBgQyV6fn5v0XyCExLAqBOGz9o,13124 -anyio/abc/_streams.py,sha256=HYvna1iZbWcwLROTO6IhLX79RTRLPShZMWe0sG1q54I,7481 -anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067 -anyio/abc/_tasks.py,sha256=m-FtE4phxeNIELSG7A3H7VUz3jA2Ib5J2JIew8-PS6o,6642 -anyio/abc/_testing.py,sha256=9YYM2AXsYFvf4PLjUEr6yRxDiUeB5QbY_gOg0X_C6lY,2034 -anyio/from_thread.py,sha256=JYsbaCaIB_Iit6kNhtXSteJGt4PcQ7ncq0nIpcelIrg,19265 -anyio/functools.py,sha256=T4JS8IXq-x1S0Lbo2owF8l9fza2KypO147QLeyz4cjs,11797 -anyio/itertools.py,sha256=QV-9mnRCr2yBph8g01QFvN-bQ_Yle-8Sl13YSydBlMI,16168 -anyio/lowlevel.py,sha256=WPtppHfI2qs1nokzjn8elL8LvyqI05AK5Zslhlo71A4,6242 -anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -anyio/pytest_plugin.py,sha256=paMpI_VMNQf2bir0LfvgMpXSiYJoHDzWdKUVTyoHmvQ,13609 -anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -anyio/streams/__pycache__/__init__.cpython-313.pyc,, -anyio/streams/__pycache__/buffered.cpython-313.pyc,, -anyio/streams/__pycache__/file.cpython-313.pyc,, -anyio/streams/__pycache__/memory.cpython-313.pyc,, -anyio/streams/__pycache__/stapled.cpython-313.pyc,, -anyio/streams/__pycache__/text.cpython-313.pyc,, -anyio/streams/__pycache__/tls.cpython-313.pyc,, -anyio/streams/buffered.py,sha256=v3xKtjFHgNV41g2SvMAkA_qd2t9WYlCI1_lNGCAatw0,6650 -anyio/streams/file.py,sha256=msnrotVKGMQomUu_Rj2qz9MvIdUp6d3JGr7MOEO8kV4,4428 -anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740 -anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390 -anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765 -anyio/streams/tls.py,sha256=DQVkXUvsTEYKkBO8dlVU7j_5H8QOtLy4sGi1Wrjqevo,15303 -anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100 -anyio/to_process.py,sha256=68qhLfce7MeXysid4fOpmhfWkgdo7Z7-9BC0VyUciIE,9809 -anyio/to_thread.py,sha256=f6h_k2d743GBv9FhAnhM_YpTvWgIrzBy9cOE0eJ1UJw,2693 diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/WHEEL b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/WHEEL deleted file mode 100644 index 14a883f..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: setuptools (82.0.1) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/entry_points.txt b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/entry_points.txt deleted file mode 100644 index 44dd9bd..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[pytest11] -anyio = anyio.pytest_plugin diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/licenses/LICENSE b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/licenses/LICENSE deleted file mode 100644 index 104eebf..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/licenses/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Alex Grönholm - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/top_level.txt b/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/top_level.txt deleted file mode 100644 index c77c069..0000000 --- a/server/venv/Lib/site-packages/anyio-4.14.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -anyio diff --git a/server/venv/Lib/site-packages/anyio/__init__.py b/server/venv/Lib/site-packages/anyio/__init__.py deleted file mode 100644 index 2502c76..0000000 --- a/server/venv/Lib/site-packages/anyio/__init__.py +++ /dev/null @@ -1,115 +0,0 @@ -from __future__ import annotations - -from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin -from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin -from ._core._eventloop import current_time as current_time -from ._core._eventloop import get_all_backends as get_all_backends -from ._core._eventloop import get_available_backends as get_available_backends -from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class -from ._core._eventloop import run as run -from ._core._eventloop import sleep as sleep -from ._core._eventloop import sleep_forever as sleep_forever -from ._core._eventloop import sleep_until as sleep_until -from ._core._exceptions import BrokenResourceError as BrokenResourceError -from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter -from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess -from ._core._exceptions import BusyResourceError as BusyResourceError -from ._core._exceptions import ClosedResourceError as ClosedResourceError -from ._core._exceptions import ConnectionFailed as ConnectionFailed -from ._core._exceptions import DelimiterNotFound as DelimiterNotFound -from ._core._exceptions import EndOfStream as EndOfStream -from ._core._exceptions import IncompleteRead as IncompleteRead -from ._core._exceptions import NoEventLoopError as NoEventLoopError -from ._core._exceptions import RunFinishedError as RunFinishedError -from ._core._exceptions import TaskCancelled as TaskCancelled -from ._core._exceptions import TaskFailed as TaskFailed -from ._core._exceptions import TaskNotFinished as TaskNotFinished -from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError -from ._core._exceptions import WouldBlock as WouldBlock -from ._core._fileio import AsyncFile as AsyncFile -from ._core._fileio import Path as Path -from ._core._fileio import open_file as open_file -from ._core._fileio import wrap_file as wrap_file -from ._core._resources import aclose_forcefully as aclose_forcefully -from ._core._signals import open_signal_receiver as open_signal_receiver -from ._core._sockets import TCPConnectable as TCPConnectable -from ._core._sockets import UNIXConnectable as UNIXConnectable -from ._core._sockets import as_connectable as as_connectable -from ._core._sockets import connect_tcp as connect_tcp -from ._core._sockets import connect_unix as connect_unix -from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket -from ._core._sockets import ( - create_connected_unix_datagram_socket as create_connected_unix_datagram_socket, -) -from ._core._sockets import create_tcp_listener as create_tcp_listener -from ._core._sockets import create_udp_socket as create_udp_socket -from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket -from ._core._sockets import create_unix_listener as create_unix_listener -from ._core._sockets import getaddrinfo as getaddrinfo -from ._core._sockets import getnameinfo as getnameinfo -from ._core._sockets import notify_closing as notify_closing -from ._core._sockets import wait_readable as wait_readable -from ._core._sockets import wait_socket_readable as wait_socket_readable -from ._core._sockets import wait_socket_writable as wait_socket_writable -from ._core._sockets import wait_writable as wait_writable -from ._core._streams import create_memory_object_stream as create_memory_object_stream -from ._core._subprocesses import open_process as open_process -from ._core._subprocesses import run_process as run_process -from ._core._synchronization import CapacityLimiter as CapacityLimiter -from ._core._synchronization import ( - CapacityLimiterStatistics as CapacityLimiterStatistics, -) -from ._core._synchronization import Condition as Condition -from ._core._synchronization import ConditionStatistics as ConditionStatistics -from ._core._synchronization import Event as Event -from ._core._synchronization import EventStatistics as EventStatistics -from ._core._synchronization import Lock as Lock -from ._core._synchronization import LockStatistics as LockStatistics -from ._core._synchronization import ResourceGuard as ResourceGuard -from ._core._synchronization import Semaphore as Semaphore -from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics -from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED -from ._core._tasks import CancelScope as CancelScope -from ._core._tasks import TaskHandle as TaskHandle -from ._core._tasks import create_task_group as create_task_group -from ._core._tasks import current_effective_deadline as current_effective_deadline -from ._core._tasks import fail_after as fail_after -from ._core._tasks import move_on_after as move_on_after -from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile -from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile -from ._core._tempfile import TemporaryDirectory as TemporaryDirectory -from ._core._tempfile import TemporaryFile as TemporaryFile -from ._core._tempfile import gettempdir as gettempdir -from ._core._tempfile import gettempdirb as gettempdirb -from ._core._tempfile import mkdtemp as mkdtemp -from ._core._tempfile import mkstemp as mkstemp -from ._core._testing import TaskInfo as TaskInfo -from ._core._testing import get_current_task as get_current_task -from ._core._testing import get_running_tasks as get_running_tasks -from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked -from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider -from ._core._typedattr import TypedAttributeSet as TypedAttributeSet -from ._core._typedattr import typed_attribute as typed_attribute - -# Re-export imports so they look like they live directly in this package -for __value in list(locals().values()): - if getattr(__value, "__module__", "").startswith("anyio."): - __value.__module__ = __name__ - - -del __value - - -def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]: - """Support deprecated aliases.""" - if attr == "BrokenWorkerIntepreter": - import warnings - - warnings.warn( - "The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.", - DeprecationWarning, - stacklevel=2, - ) - return BrokenWorkerInterpreter - - raise AttributeError(f"module {__name__!r} has no attribute {attr!r}") diff --git a/server/venv/Lib/site-packages/anyio/_backends/__init__.py b/server/venv/Lib/site-packages/anyio/_backends/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/anyio/_backends/_asyncio.py b/server/venv/Lib/site-packages/anyio/_backends/_asyncio.py deleted file mode 100644 index a51a150..0000000 --- a/server/venv/Lib/site-packages/anyio/_backends/_asyncio.py +++ /dev/null @@ -1,3073 +0,0 @@ -from __future__ import annotations - -import array -import asyncio -import concurrent.futures -import contextvars -import math -import os -import socket -import sys -import threading -import weakref -from asyncio import ( - AbstractEventLoop, - CancelledError, - all_tasks, - create_task, - current_task, - get_running_loop, - sleep, -) -from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined] -from collections import OrderedDict, deque -from collections.abc import ( - AsyncGenerator, - AsyncIterator, - Awaitable, - Callable, - Collection, - Coroutine, - Iterable, - Sequence, -) -from concurrent.futures import Future -from contextlib import AbstractContextManager -from contextvars import Context, copy_context -from dataclasses import dataclass, field -from functools import partial, wraps -from inspect import ( - CORO_RUNNING, - CORO_SUSPENDED, - getcoroutinestate, -) -from io import IOBase -from os import PathLike -from queue import Queue -from signal import Signals -from socket import AddressFamily, SocketKind -from threading import Thread -from types import CodeType, TracebackType -from typing import ( - IO, - TYPE_CHECKING, - Any, - Literal, - ParamSpec, - TypeVar, - cast, -) -from weakref import WeakKeyDictionary - -from .. import ( - CapacityLimiterStatistics, - EventStatistics, - LockStatistics, - TaskInfo, - abc, -) -from .._core._eventloop import ( - claim_worker_thread, - set_current_async_library, - threadlocals, -) -from .._core._exceptions import ( - BrokenResourceError, - BusyResourceError, - ClosedResourceError, - EndOfStream, - RunFinishedError, - WouldBlock, -) -from .._core._sockets import convert_ipv6_sockaddr -from .._core._streams import create_memory_object_stream -from .._core._synchronization import ( - CapacityLimiter as BaseCapacityLimiter, -) -from .._core._synchronization import Event as BaseEvent -from .._core._synchronization import Lock as BaseLock -from .._core._synchronization import ( - ResourceGuard, - SemaphoreStatistics, -) -from .._core._synchronization import Semaphore as BaseSemaphore -from .._core._tasks import CancelScope as BaseCancelScope -from .._core._tasks import TaskHandle -from ..abc import ( - AsyncBackend, - IPSockAddrType, - SocketListener, - UDPPacketType, - UNIXDatagramPacketType, -) -from ..abc._eventloop import StrOrBytesPath -from ..abc._tasks import call_for_coroutine, get_callable_name -from ..lowlevel import RunVar -from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream - -if TYPE_CHECKING: - from _typeshed import FileDescriptorLike -else: - FileDescriptorLike = object - -if sys.version_info >= (3, 11): - from asyncio import Runner - from typing import TypeVarTuple, Unpack -else: - import contextvars - import enum - import signal - from asyncio import coroutines, events, exceptions, tasks - - from exceptiongroup import BaseExceptionGroup - from typing_extensions import TypeVarTuple, Unpack - - class _State(enum.Enum): - CREATED = "created" - INITIALIZED = "initialized" - CLOSED = "closed" - - class Runner: - # Copied from CPython 3.11 - def __init__( - self, - *, - debug: bool | None = None, - loop_factory: Callable[[], AbstractEventLoop] | None = None, - ): - self._state = _State.CREATED - self._debug = debug - self._loop_factory = loop_factory - self._loop: AbstractEventLoop | None = None - self._context = None - self._interrupt_count = 0 - self._set_event_loop = False - - def __enter__(self) -> Runner: - self._lazy_init() - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.close() - - def close(self) -> None: - """Shutdown and close event loop.""" - loop = self._loop - if self._state is not _State.INITIALIZED or loop is None: - return - try: - _cancel_all_tasks(loop) - loop.run_until_complete(loop.shutdown_asyncgens()) - if hasattr(loop, "shutdown_default_executor"): - loop.run_until_complete(loop.shutdown_default_executor()) - else: - loop.run_until_complete(_shutdown_default_executor(loop)) - finally: - if self._set_event_loop: - events.set_event_loop(None) - loop.close() - self._loop = None - self._state = _State.CLOSED - - def get_loop(self) -> AbstractEventLoop: - """Return embedded event loop.""" - self._lazy_init() - return self._loop - - def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval: - """Run a coroutine inside the embedded event loop.""" - if not coroutines.iscoroutine(coro): - raise ValueError(f"a coroutine was expected, got {coro!r}") - - if events._get_running_loop() is not None: - # fail fast with short traceback - raise RuntimeError( - "Runner.run() cannot be called from a running event loop" - ) - - self._lazy_init() - - if context is None: - context = self._context - task = context.run(self._loop.create_task, coro) - - if ( - threading.current_thread() is threading.main_thread() - and signal.getsignal(signal.SIGINT) is signal.default_int_handler - ): - sigint_handler = partial(self._on_sigint, main_task=task) - try: - signal.signal(signal.SIGINT, sigint_handler) - except ValueError: - # `signal.signal` may throw if `threading.main_thread` does - # not support signals (e.g. embedded interpreter with signals - # not registered - see gh-91880) - sigint_handler = None - else: - sigint_handler = None - - self._interrupt_count = 0 - try: - return self._loop.run_until_complete(task) - except exceptions.CancelledError: - if self._interrupt_count > 0: - uncancel = getattr(task, "uncancel", None) - if uncancel is not None and uncancel() == 0: - raise KeyboardInterrupt # noqa: B904 - raise # CancelledError - finally: - if ( - sigint_handler is not None - and signal.getsignal(signal.SIGINT) is sigint_handler - ): - signal.signal(signal.SIGINT, signal.default_int_handler) - - def _lazy_init(self) -> None: - if self._state is _State.CLOSED: - raise RuntimeError("Runner is closed") - if self._state is _State.INITIALIZED: - return - if self._loop_factory is None: - self._loop = events.new_event_loop() - if not self._set_event_loop: - # Call set_event_loop only once to avoid calling - # attach_loop multiple times on child watchers - events.set_event_loop(self._loop) - self._set_event_loop = True - else: - self._loop = self._loop_factory() - if self._debug is not None: - self._loop.set_debug(self._debug) - self._context = contextvars.copy_context() - self._state = _State.INITIALIZED - - def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None: - self._interrupt_count += 1 - if self._interrupt_count == 1 and not main_task.done(): - main_task.cancel() - # wakeup loop if it is blocked by select() with long timeout - self._loop.call_soon_threadsafe(lambda: None) - return - raise KeyboardInterrupt() - - def _cancel_all_tasks(loop: AbstractEventLoop) -> None: - to_cancel = tasks.all_tasks(loop) - if not to_cancel: - return - - for task in to_cancel: - task.cancel() - - loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True)) - - for task in to_cancel: - if task.cancelled(): - continue - if task.exception() is not None: - loop.call_exception_handler( - { - "message": "unhandled exception during asyncio.run() shutdown", - "exception": task.exception(), - "task": task, - } - ) - - async def _shutdown_default_executor(loop: AbstractEventLoop) -> None: - """Schedule the shutdown of the default executor.""" - - def _do_shutdown(future: asyncio.futures.Future) -> None: - try: - loop._default_executor.shutdown(wait=True) # type: ignore[attr-defined] - loop.call_soon_threadsafe(future.set_result, None) - except Exception as ex: - loop.call_soon_threadsafe(future.set_exception, ex) - - loop._executor_shutdown_called = True - if loop._default_executor is None: - return - future = loop.create_future() - thread = threading.Thread(target=_do_shutdown, args=(future,)) - thread.start() - try: - await future - finally: - thread.join() - - -T_Retval = TypeVar("T_Retval") -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True) -PosArgsT = TypeVarTuple("PosArgsT") -P = ParamSpec("P") - -_root_task: RunVar[asyncio.Task | None] = RunVar("_root_task") - - -def find_root_task() -> asyncio.Task: - root_task = _root_task.get(None) - if root_task is not None and not root_task.done(): - return root_task - - # Look for a task that has been started via run_until_complete() - for task in all_tasks(): - if task._callbacks and not task.done(): - callbacks = [cb for cb, context in task._callbacks] - for cb in callbacks: - if ( - cb is _run_until_complete_cb - or getattr(cb, "__module__", None) == "uvloop.loop" - ): - _root_task.set(task) - return task - - # Look up the topmost task in the AnyIO task tree, if possible - task = cast(asyncio.Task, current_task()) - state = _task_states.get(task) - if state: - cancel_scope = state.cancel_scope - while cancel_scope and cancel_scope._parent_scope is not None: - cancel_scope = cancel_scope._parent_scope - - if cancel_scope is not None: - return cast(asyncio.Task, cancel_scope._host_task) - - return task - - -# -# Event loop -# - -_run_vars: WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = WeakKeyDictionary() - - -def _task_started(task: asyncio.Task) -> bool: - """Return ``True`` if the task has been started and has not finished.""" - # The task coro should never be None here, as we never add finished tasks to the - # task list - coro = task.get_coro() - assert coro is not None - return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED) - - -# -# Timeouts and cancellation -# - - -def is_anyio_cancellation(exc: CancelledError) -> bool: - # Sometimes third party frameworks catch a CancelledError and raise a new one, so as - # a workaround we have to look at the previous ones in __context__ too for a - # matching cancel message - while True: - if ( - exc.args - and isinstance(exc.args[0], str) - and exc.args[0].startswith("Cancelled via cancel scope ") - ): - return True - - if isinstance(exc.__context__, CancelledError): - exc = exc.__context__ - continue - - return False - - -class CancelScope(BaseCancelScope): - __slots__ = ( - "_active", - "_cancel_called", - "_cancel_handle", - "_cancel_reason", - "_cancelled_caught", - "_child_scopes", - "_deadline", - "_host_task", - "_parent_scope", - "_pending_uncancellations", - "_shield", - "_tasks", - "_timeout_handle", - ) - - def __new__( - cls, *, deadline: float = math.inf, shield: bool = False - ) -> CancelScope: - return object.__new__(cls) - - def __init__(self, deadline: float = math.inf, shield: bool = False): - self._deadline = deadline - self._shield = shield - self._parent_scope: CancelScope | None = None - self._child_scopes: set[CancelScope] = set() - self._cancel_called = False - self._cancel_reason: str | None = None - self._cancelled_caught = False - self._active = False - self._timeout_handle: asyncio.TimerHandle | None = None - self._cancel_handle: asyncio.Handle | None = None - self._tasks: set[asyncio.Task] = set() - self._host_task: asyncio.Task | None = None - if sys.version_info >= (3, 11): - self._pending_uncancellations: int | None = 0 - else: - self._pending_uncancellations = None - - def __enter__(self) -> CancelScope: - if self._active: - raise RuntimeError( - "Each CancelScope may only be used for a single 'with' block" - ) - - self._host_task = host_task = cast(asyncio.Task, current_task()) - self._tasks.add(host_task) - try: - task_state = _task_states[host_task] - except KeyError: - task_state = TaskState(None, self) - _task_states[host_task] = task_state - else: - self._parent_scope = task_state.cancel_scope - task_state.cancel_scope = self - if self._parent_scope is not None: - # If using an eager task factory, the parent scope may not even contain - # the host task - self._parent_scope._child_scopes.add(self) - self._parent_scope._tasks.discard(host_task) - - self._timeout() - self._active = True - - # Start cancelling the host task if the scope was cancelled before entering - if self._cancel_called: - self._deliver_cancellation(self) - - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - del exc_tb - - if not self._active: - raise RuntimeError("This cancel scope is not active") - if current_task() is not self._host_task: - raise RuntimeError( - "Attempted to exit cancel scope in a different task than it was " - "entered in" - ) - - assert self._host_task is not None - host_task_state = _task_states.get(self._host_task) - if host_task_state is None or host_task_state.cancel_scope is not self: - raise RuntimeError( - "Attempted to exit a cancel scope that isn't the current tasks's " - "current cancel scope" - ) - - try: - self._active = False - if self._timeout_handle: - self._timeout_handle.cancel() - self._timeout_handle = None - - self._tasks.remove(self._host_task) - if self._parent_scope is not None: - self._parent_scope._child_scopes.remove(self) - self._parent_scope._tasks.add(self._host_task) - - host_task_state.cancel_scope = self._parent_scope - - # Restart the cancellation effort in the closest visible, cancelled parent - # scope if necessary - self._restart_cancellation_in_parent() - - # We only swallow the exception iff it was an AnyIO CancelledError, either - # directly as exc_val or inside an exception group and there are no cancelled - # parent cancel scopes visible to us here - if self._cancel_called and not self._parent_cancellation_is_visible_to_us: - # For each level-cancel() call made on the host task, call uncancel() - while self._pending_uncancellations: - self._host_task.uncancel() - self._pending_uncancellations -= 1 - - # Update cancelled_caught and check for exceptions we must not swallow - if isinstance(exc_val, BaseExceptionGroup): - cancelleds_caught, remaining = exc_val.split( - lambda exc: ( - isinstance(exc, CancelledError) - and is_anyio_cancellation(exc) - ) - ) - - if cancelleds_caught is None: - return False - - self._cancelled_caught = True - - if remaining is None: - return True - - context = remaining.__context__ - try: - # Preserve __cause__ and __suppress_context__ by avoiding `raise - # ... from ...` - raise remaining - finally: - # Preserve __context__ - remaining.__context__ = context - del context - else: - if isinstance(exc_val, CancelledError) and is_anyio_cancellation( - exc_val - ): - self._cancelled_caught = True - return True - else: - return False - else: - if self._pending_uncancellations: - assert self._parent_scope is not None - assert self._parent_scope._pending_uncancellations is not None - self._parent_scope._pending_uncancellations += ( - self._pending_uncancellations - ) - self._pending_uncancellations = 0 - - return False - finally: - self._host_task = None - del exc_val - - @property - def _effectively_cancelled(self) -> bool: - cancel_scope: CancelScope | None = self - while cancel_scope is not None: - if cancel_scope._cancel_called: - return True - - if cancel_scope.shield: - return False - - cancel_scope = cancel_scope._parent_scope - - return False - - @property - def _parent_cancellation_is_visible_to_us(self) -> bool: - return ( - self._parent_scope is not None - and not self.shield - and self._parent_scope._effectively_cancelled - ) - - def _timeout(self) -> None: - if self._deadline != math.inf: - loop = get_running_loop() - if loop.time() >= self._deadline: - self.cancel("deadline exceeded") - else: - self._timeout_handle = loop.call_at(self._deadline, self._timeout) - - def _deliver_cancellation(self, origin: CancelScope) -> bool: - """ - Deliver cancellation to directly contained tasks and nested cancel scopes. - - Schedule another run at the end if we still have tasks eligible for - cancellation. - - :param origin: the cancel scope that originated the cancellation - :return: ``True`` if the delivery needs to be retried on the next cycle - - """ - should_retry = False - current = current_task() - for task in self._tasks: - should_retry = True - if task._must_cancel: # type: ignore[attr-defined] - continue - - # The task is eligible for cancellation if it has started - if task is not current and (task is self._host_task or _task_started(task)): - waiter = task._fut_waiter # type: ignore[attr-defined] - if not isinstance(waiter, asyncio.Future) or not waiter.done(): - task.cancel(origin._cancel_reason) - if ( - task is origin._host_task - and origin._pending_uncancellations is not None - ): - origin._pending_uncancellations += 1 - - # Deliver cancellation to child scopes that aren't shielded or running their own - # cancellation callbacks - for scope in self._child_scopes: - if not scope._shield and not scope.cancel_called: - should_retry = scope._deliver_cancellation(origin) or should_retry - - # Schedule another callback if there are still tasks left - if origin is self: - if should_retry: - self._cancel_handle = get_running_loop().call_soon( - self._deliver_cancellation, origin - ) - else: - self._cancel_handle = None - - return should_retry - - def _restart_cancellation_in_parent(self) -> None: - """ - Restart the cancellation effort in the closest directly cancelled parent scope. - - """ - scope = self._parent_scope - while scope is not None: - if scope._cancel_called: - if scope._cancel_handle is None: - scope._deliver_cancellation(scope) - - break - - # No point in looking beyond any shielded scope - if scope._shield: - break - - scope = scope._parent_scope - - def cancel(self, reason: str | None = None) -> None: - if not self._cancel_called: - if self._timeout_handle: - self._timeout_handle.cancel() - self._timeout_handle = None - - self._cancel_called = True - self._cancel_reason = f"Cancelled via cancel scope {id(self):x}" - if task := current_task(): - self._cancel_reason += f" by {task}" - - if reason: - self._cancel_reason += f"; reason: {reason}" - - if self._host_task is not None: - self._deliver_cancellation(self) - - @property - def deadline(self) -> float: - return self._deadline - - @deadline.setter - def deadline(self, value: float) -> None: - self._deadline = float(value) - if self._timeout_handle is not None: - self._timeout_handle.cancel() - self._timeout_handle = None - - if self._active and not self._cancel_called: - self._timeout() - - @property - def cancel_called(self) -> bool: - return self._cancel_called - - @property - def cancelled_caught(self) -> bool: - return self._cancelled_caught - - @property - def shield(self) -> bool: - return self._shield - - @shield.setter - def shield(self, value: bool) -> None: - if self._shield != value: - self._shield = value - if not value: - self._restart_cancellation_in_parent() - - -# -# Task states -# - - -class TaskState: - """ - Encapsulates auxiliary task information that cannot be added to the Task instance - itself because there are no guarantees about its implementation. - """ - - __slots__ = "parent_id", "cancel_scope", "__weakref__" - - def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None): - self.parent_id = parent_id - self.cancel_scope = cancel_scope - - -_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary() - - -# -# Task groups -# - - -class _AsyncioTaskStatus(abc.TaskStatus): - def __init__(self, future: asyncio.Future, parent_id: int): - self._future = future - self._parent_id = parent_id - - def started(self, value: T_contra | None = None) -> None: - try: - self._future.set_result(value) - except asyncio.InvalidStateError: - if not self._future.cancelled(): - raise RuntimeError( - "called 'started' twice on the same task status" - ) from None - - task = cast(asyncio.Task, current_task()) - _task_states[task].parent_id = self._parent_id - - -if sys.version_info >= (3, 12): - _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__ -else: - _eager_task_factory_code = None - - -class TaskGroup(abc.TaskGroup): - def __init__(self) -> None: - self.cancel_scope: CancelScope = CancelScope() - self._entered = False - self._exceptions: list[BaseException] = [] - self._tasks: set[asyncio.Task] = set() - self._on_completed_fut: asyncio.Future[None] | None = None - - async def __aenter__(self) -> TaskGroup: - if self._entered: - raise RuntimeError("TaskGroup cannot be entered more than once") - - self._entered = True - - self.cancel_scope.__enter__() - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - try: - if exc_val is not None: - self.cancel_scope.cancel() - if not isinstance(exc_val, CancelledError): - self._exceptions.append(exc_val) - - loop = get_running_loop() - try: - if self._tasks: - with CancelScope() as wait_scope: - while self._tasks: - self._on_completed_fut = loop.create_future() - - try: - await self._on_completed_fut - except CancelledError as exc: - # Shield the scope against further cancellation attempts, - # as they're not productive (#695) - wait_scope.shield = True - self.cancel_scope.cancel() - - # Set exc_val from the cancellation exception if it was - # previously unset. However, we should not replace a native - # cancellation exception with one raise by a cancel scope. - if exc_val is None or ( - isinstance(exc_val, CancelledError) - and not is_anyio_cancellation(exc) - ): - exc_val = exc - - self._on_completed_fut = None - else: - # If there are no child tasks to wait on, run at least one checkpoint - # anyway - await AsyncIOBackend.cancel_shielded_checkpoint() - - if self._exceptions: - # The exception that got us here should already have been - # added to self._exceptions so it's ok to break exception - # chaining and avoid adding a "During handling of above..." - # for each nesting level. - raise BaseExceptionGroup( - "unhandled errors in a TaskGroup", self._exceptions - ) from None - elif exc_val: - raise exc_val - except BaseException as exc: - if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__): - return True - - raise - - return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb) - finally: - del exc_val, exc_tb, self._exceptions - - def _spawn( - self, - coro: Coroutine[Any, Any, T_co], - name: object, - task_status_future: asyncio.Future | None = None, - ) -> TaskHandle[T_co]: - def task_done(_task: asyncio.Task) -> None: - if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: - asyncio.future_discard_from_awaited_by( - _task, self.cancel_scope._host_task - ) - - task_state = _task_states[_task] - assert task_state.cancel_scope is not None - assert _task in task_state.cancel_scope._tasks - task_state.cancel_scope._tasks.remove(_task) - self._tasks.remove(task) - del _task_states[_task] - - if self._on_completed_fut is not None and not self._tasks: - try: - self._on_completed_fut.set_result(None) - except asyncio.InvalidStateError: - pass - - try: - exc = _task.exception() - except CancelledError as e: - while isinstance(e.__context__, CancelledError): - e = e.__context__ - - exc = e - - if exc is not None: - # The future can only be in the cancelled state if the host task was - # cancelled, so return immediately instead of adding one more - # CancelledError to the exceptions list - if task_status_future is not None and task_status_future.cancelled(): - return - - if task_status_future is None or task_status_future.done(): - if not isinstance(exc, CancelledError): - self._exceptions.append(exc) - - if not self.cancel_scope._effectively_cancelled: - self.cancel_scope.cancel() - else: - task_status_future.set_exception(exc) - elif task_status_future is not None and not task_status_future.done(): - task_status_future.set_exception( - RuntimeError("Child exited without calling task_status.started()") - ) - - if task_status_future: - parent_id = id(current_task()) - else: - parent_id = id(self.cancel_scope._host_task) - - handle = TaskHandle(coro, name) - loop = asyncio.get_running_loop() - wrapper_coro = handle._run_coro() - if ( - (factory := loop.get_task_factory()) - and getattr(factory, "__code__", None) is _eager_task_factory_code - and (closure := getattr(factory, "__closure__", None)) - ): - custom_task_constructor = closure[0].cell_contents - task = custom_task_constructor(wrapper_coro, loop=loop, name=handle.name) - else: - task = loop.create_task(wrapper_coro, name=handle.name) - - # Make the spawned task inherit the task group's cancel scope - _task_states[task] = TaskState( - parent_id=parent_id, cancel_scope=self.cancel_scope - ) - self.cancel_scope._tasks.add(task) - self._tasks.add(task) - if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None: - asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task) - - task.add_done_callback(task_done) - return handle - - def create_task( - self, - coro: Coroutine[Any, Any, T_co], - *, - name: object = None, - context: Context | None = None, - ) -> TaskHandle[T_co]: - if not isinstance(coro, Coroutine): - raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}") - - if not self._entered or not self.cancel_scope._active: - coro.close() - raise RuntimeError( - "This task group is not active; no new tasks can be started." - ) - - if context is not None: - return context.run(self._spawn, coro, name=name) - else: - return self._spawn(coro, name=name) - - async def start( - self, - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - *args: Unpack[PosArgsT], - name: object = None, - return_handle: Literal[False] | Literal[True] = False, - ) -> Any: - if not self._entered or not self.cancel_scope._active: - raise RuntimeError( - "This task group is not active; no new tasks can be started." - ) - - future: asyncio.Future = asyncio.Future() - final_name = get_callable_name(func, name) - task_status = _AsyncioTaskStatus(future, id(self.cancel_scope._host_task)) - coro = call_for_coroutine(func, args, task_status=task_status) - handle = self._spawn(coro, final_name, future) - - # If the task raises an exception after sending a start value without a switch - # point between, the task group is cancelled and this method never proceeds to - # process the completed future. That's why we have to have a shielded cancel - # scope here. - try: - await future - except BaseException: - if handle.status is TaskHandle.Status.PENDING: - # Cancel the task and wait for it to exit before returning - handle.cancel() - with CancelScope(shield=True): - await handle.wait() - - raise - - if return_handle: - handle._start_value = future.result() - return handle - else: - return future.result() - - -# -# Threads -# - -_Retval_Queue_Type = tuple[T_Retval | None, BaseException | None] - - -class WorkerThread(Thread): - MAX_IDLE_TIME = 10 # seconds - - def __init__( - self, - root_task: asyncio.Task, - workers: set[WorkerThread], - idle_workers: deque[WorkerThread], - ): - super().__init__(name="AnyIO worker thread") - self.root_task = root_task - self.workers = workers - self.idle_workers = idle_workers - self.loop = root_task._loop - self.queue: Queue[ - tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None - ] = Queue(2) - self.idle_since = AsyncIOBackend.current_time() - self.stopping = False - - def _report_result( - self, future: asyncio.Future, result: Any, exc: BaseException | None - ) -> None: - self.idle_since = AsyncIOBackend.current_time() - if not self.stopping: - self.idle_workers.append(self) - - if not future.cancelled(): - if exc is not None: - if isinstance(exc, StopIteration): - new_exc = RuntimeError("coroutine raised StopIteration") - new_exc.__cause__ = exc - exc = new_exc - - future.set_exception(exc) - else: - future.set_result(result) - - def run(self) -> None: - with claim_worker_thread(AsyncIOBackend, self.loop): - while True: - item = self.queue.get() - if item is None: - # Shutdown command received - return - - context, func, args, future, cancel_scope = item - if not future.cancelled(): - result = None - exception: BaseException | None = None - threadlocals.current_cancel_scope = cancel_scope - try: - result = context.run(func, *args) - except BaseException as exc: - exception = exc - finally: - del threadlocals.current_cancel_scope - - if not self.loop.is_closed(): - self.loop.call_soon_threadsafe( - self._report_result, future, result, exception - ) - - del result, exception - - self.queue.task_done() - del item, context, func, args, future, cancel_scope - - def stop(self, f: asyncio.Task | None = None) -> None: - self.stopping = True - self.queue.put_nowait(None) - self.workers.discard(self) - try: - self.idle_workers.remove(self) - except ValueError: - pass - - -_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar( - "_threadpool_idle_workers" -) -_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers") - - -# -# Subprocesses -# - - -@dataclass(eq=False) -class StreamReaderWrapper(abc.ByteReceiveStream): - _stream: asyncio.StreamReader - - async def receive(self, max_bytes: int = 65536) -> bytes: - data = await self._stream.read(max_bytes) - if data: - return data - else: - raise EndOfStream - - async def aclose(self) -> None: - self._stream.set_exception(ClosedResourceError()) - await AsyncIOBackend.checkpoint() - - -@dataclass(eq=False) -class StreamWriterWrapper(abc.ByteSendStream): - _stream: asyncio.StreamWriter - _closed: bool = field(init=False, default=False) - - async def send(self, item: bytes) -> None: - await AsyncIOBackend.checkpoint_if_cancelled() - stream_paused = self._stream._protocol._paused # type: ignore[attr-defined] - try: - self._stream.write(item) - await self._stream.drain() - except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc: - # If closed by us and/or the peer: - # * on stdlib, drain() raises ConnectionResetError or BrokenPipeError - # * on uvloop and Winloop, write() eventually starts raising RuntimeError - if self._closed: - raise ClosedResourceError from exc - elif self._stream.is_closing(): - raise BrokenResourceError from exc - - raise - - if not stream_paused: - await AsyncIOBackend.cancel_shielded_checkpoint() - - async def aclose(self) -> None: - self._closed = True - self._stream.close() - await AsyncIOBackend.checkpoint() - - -@dataclass(eq=False) -class Process(abc.Process): - _process: asyncio.subprocess.Process - _stdin: StreamWriterWrapper | None - _stdout: StreamReaderWrapper | None - _stderr: StreamReaderWrapper | None - - async def aclose(self) -> None: - with CancelScope(shield=True) as scope: - if self._stdin: - await self._stdin.aclose() - if self._stdout: - await self._stdout.aclose() - if self._stderr: - await self._stderr.aclose() - - scope.shield = False - try: - await self.wait() - except BaseException: - scope.shield = True - self.kill() - await self.wait() - raise - - async def wait(self) -> int: - return await self._process.wait() - - def terminate(self) -> None: - self._process.terminate() - - def kill(self) -> None: - self._process.kill() - - def send_signal(self, signal: int) -> None: - self._process.send_signal(signal) - - @property - def pid(self) -> int: - return self._process.pid - - @property - def returncode(self) -> int | None: - return self._process.returncode - - @property - def stdin(self) -> abc.ByteSendStream | None: - return self._stdin - - @property - def stdout(self) -> abc.ByteReceiveStream | None: - return self._stdout - - @property - def stderr(self) -> abc.ByteReceiveStream | None: - return self._stderr - - -def _forcibly_shutdown_process_pool_on_exit( - workers: set[Process], _task: object -) -> None: - """ - Forcibly shuts down worker processes belonging to this event loop.""" - child_watcher: asyncio.AbstractChildWatcher | None = None # type: ignore[name-defined] - if sys.version_info < (3, 12): - try: - child_watcher = asyncio.get_event_loop_policy().get_child_watcher() - except NotImplementedError: - pass - - # Close as much as possible (w/o async/await) to avoid warnings - for process in workers.copy(): - if process.returncode is not None: - continue - - process._stdin._stream._transport.close() # type: ignore[union-attr] - process._stdout._stream._transport.close() # type: ignore[union-attr] - process._stderr._stream._transport.close() # type: ignore[union-attr] - process.kill() - if child_watcher: - child_watcher.remove_child_handler(process.pid) - - -async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None: - """ - Shuts down worker processes belonging to this event loop. - - NOTE: this only works when the event loop was started using asyncio.run() or - anyio.run(). - - """ - process: abc.Process - try: - await sleep(math.inf) - except asyncio.CancelledError: - workers = workers.copy() - for process in workers: - if process.returncode is None: - process.kill() - - for process in workers: - await process.aclose() - - -# -# Sockets and networking -# - - -class StreamProtocol(asyncio.Protocol): - read_queue: deque[bytes] - read_event: asyncio.Event - write_event: asyncio.Event - exception: Exception | None = None - is_at_eof: bool = False - - def connection_made(self, transport: asyncio.BaseTransport) -> None: - self.read_queue = deque() - self.read_event = asyncio.Event() - self.write_event = asyncio.Event() - self.write_event.set() - cast(asyncio.Transport, transport).set_write_buffer_limits(0) - - def connection_lost(self, exc: Exception | None) -> None: - if exc: - self.exception = exc - - self.read_event.set() - self.write_event.set() - - def data_received(self, data: bytes) -> None: - # ProactorEventloop sometimes sends bytearray instead of bytes - self.read_queue.append(bytes(data)) - self.read_event.set() - - def eof_received(self) -> bool | None: - self.is_at_eof = True - self.read_event.set() - return True - - def pause_writing(self) -> None: - self.write_event = asyncio.Event() - - def resume_writing(self) -> None: - self.write_event.set() - - -class DatagramProtocol(asyncio.DatagramProtocol): - read_queue: deque[tuple[bytes, IPSockAddrType]] - read_event: asyncio.Event - write_event: asyncio.Event - closed_event: asyncio.Event - exception: Exception | None = None - - def connection_made(self, transport: asyncio.BaseTransport) -> None: - self.read_queue = deque(maxlen=100) # arbitrary value - self.read_event = asyncio.Event() - self.write_event = asyncio.Event() - self.closed_event = asyncio.Event() - self.write_event.set() - - def connection_lost(self, exc: Exception | None) -> None: - self.read_event.set() - self.write_event.set() - self.closed_event.set() - - def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None: - addr = convert_ipv6_sockaddr(addr) - self.read_queue.append((data, addr)) - self.read_event.set() - - def error_received(self, exc: Exception) -> None: - self.exception = exc - - def pause_writing(self) -> None: - self.write_event.clear() - - def resume_writing(self) -> None: - self.write_event.set() - - -class SocketStream(abc.SocketStream): - def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol): - self._transport = transport - self._protocol = protocol - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - self._closed = False - - @property - def _raw_socket(self) -> socket.socket: - return self._transport.get_extra_info("socket") - - async def receive(self, max_bytes: int = 65536) -> bytes: - with self._receive_guard: - if ( - not self._protocol.read_event.is_set() - and not self._transport.is_closing() - and not self._protocol.is_at_eof - ): - self._transport.resume_reading() - await self._protocol.read_event.wait() - self._transport.pause_reading() - else: - await AsyncIOBackend.checkpoint() - - try: - chunk = self._protocol.read_queue.popleft() - except IndexError: - if self._closed: - raise ClosedResourceError from None - elif self._protocol.exception: - raise BrokenResourceError from self._protocol.exception - else: - raise EndOfStream from None - - if len(chunk) > max_bytes: - # Split the oversized chunk - chunk, leftover = chunk[:max_bytes], chunk[max_bytes:] - self._protocol.read_queue.appendleft(leftover) - - # If the read queue is empty, clear the flag so that the next call will - # block until data is available - if not self._protocol.read_queue: - self._protocol.read_event.clear() - - return chunk - - async def send(self, item: bytes) -> None: - with self._send_guard: - await AsyncIOBackend.checkpoint() - - if self._closed: - raise ClosedResourceError - elif self._protocol.exception is not None: - raise BrokenResourceError from self._protocol.exception - - try: - self._transport.write(item) - except RuntimeError as exc: - if self._transport.is_closing(): - raise BrokenResourceError from exc - else: - raise - - await self._protocol.write_event.wait() - - async def send_eof(self) -> None: - try: - self._transport.write_eof() - except OSError: - pass - - async def aclose(self) -> None: - self._closed = True - if not self._transport.is_closing(): - try: - self._transport.write_eof() - except OSError: - pass - - self._transport.close() - await sleep(0) - self._transport.abort() - - -class _RawSocketMixin: - _receive_future: asyncio.Future | None = None - _send_future: asyncio.Future | None = None - _closing = False - - def __init__(self, raw_socket: socket.socket): - self.__raw_socket = raw_socket - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - - @property - def _raw_socket(self) -> socket.socket: - return self.__raw_socket - - def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: - def callback(f: object) -> None: - del self._receive_future - loop.remove_reader(self.__raw_socket) - - f = self._receive_future = asyncio.Future() - loop.add_reader(self.__raw_socket, f.set_result, None) - f.add_done_callback(callback) - return f - - def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future: - def callback(f: object) -> None: - del self._send_future - loop.remove_writer(self.__raw_socket) - - f = self._send_future = asyncio.Future() - loop.add_writer(self.__raw_socket, f.set_result, None) - f.add_done_callback(callback) - return f - - async def aclose(self) -> None: - if not self._closing: - self._closing = True - if self.__raw_socket.fileno() != -1: - self.__raw_socket.close() - - if self._receive_future: - self._receive_future.set_result(None) - if self._send_future: - self._send_future.set_result(None) - - -class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream): - async def send_eof(self) -> None: - with self._send_guard: - self._raw_socket.shutdown(socket.SHUT_WR) - - async def receive(self, max_bytes: int = 65536) -> bytes: - loop = get_running_loop() - await AsyncIOBackend.checkpoint() - with self._receive_guard: - while True: - try: - data = self._raw_socket.recv(max_bytes) - except BlockingIOError: - await self._wait_until_readable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - if not data: - raise EndOfStream - - return data - - async def send(self, item: bytes) -> None: - loop = get_running_loop() - await AsyncIOBackend.checkpoint() - with self._send_guard: - view = memoryview(item) - while view: - try: - bytes_sent = self._raw_socket.send(view) - except BlockingIOError: - await self._wait_until_writable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - view = view[bytes_sent:] - - async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: - if not isinstance(msglen, int) or msglen < 0: - raise ValueError("msglen must be a non-negative integer") - if not isinstance(maxfds, int) or maxfds < 1: - raise ValueError("maxfds must be a positive integer") - - loop = get_running_loop() - fds = array.array("i") - await AsyncIOBackend.checkpoint() - with self._receive_guard: - while True: - try: - message, ancdata, flags, addr = self._raw_socket.recvmsg( - msglen, socket.CMSG_LEN(maxfds * fds.itemsize) - ) - except BlockingIOError: - await self._wait_until_readable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - if not message and not ancdata: - raise EndOfStream - - break - - for cmsg_level, cmsg_type, cmsg_data in ancdata: - if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: - raise RuntimeError( - f"Received unexpected ancillary data; message = {message!r}, " - f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" - ) - - fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) - - return message, list(fds) - - async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: - if not message: - raise ValueError("message must not be empty") - if not fds: - raise ValueError("fds must not be empty") - - loop = get_running_loop() - filenos: list[int] = [] - for fd in fds: - if isinstance(fd, int): - filenos.append(fd) - elif isinstance(fd, IOBase): - filenos.append(fd.fileno()) - - fdarray = array.array("i", filenos) - await AsyncIOBackend.checkpoint() - with self._send_guard: - while True: - try: - # The ignore can be removed after mypy picks up - # https://github.com/python/typeshed/pull/5545 - self._raw_socket.sendmsg( - [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)] - ) - break - except BlockingIOError: - await self._wait_until_writable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - - -class TCPSocketListener(abc.SocketListener): - _accept_scope: CancelScope | None = None - _closed = False - - def __init__(self, raw_socket: socket.socket): - self.__raw_socket = raw_socket - self._loop = cast(asyncio.BaseEventLoop, get_running_loop()) - self._accept_guard = ResourceGuard("accepting connections from") - - @property - def _raw_socket(self) -> socket.socket: - return self.__raw_socket - - async def accept(self) -> abc.SocketStream: - if self._closed: - raise ClosedResourceError - - with self._accept_guard: - await AsyncIOBackend.checkpoint() - with CancelScope() as self._accept_scope: - try: - client_sock, _addr = await self._loop.sock_accept(self._raw_socket) - except asyncio.CancelledError: - # Workaround for https://bugs.python.org/issue41317 - try: - self._loop.remove_reader(self._raw_socket) - except (ValueError, NotImplementedError): - pass - - if self._closed: - raise ClosedResourceError from None - - raise - finally: - self._accept_scope = None - - client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - transport, protocol = await self._loop.connect_accepted_socket( - StreamProtocol, client_sock - ) - return SocketStream(transport, protocol) - - async def aclose(self) -> None: - if self._closed: - return - - self._closed = True - if self._accept_scope: - # Workaround for https://bugs.python.org/issue41317 - try: - self._loop.remove_reader(self._raw_socket) - except (ValueError, NotImplementedError): - pass - - self._accept_scope.cancel() - await sleep(0) - - self._raw_socket.close() - - -class UNIXSocketListener(abc.SocketListener): - def __init__(self, raw_socket: socket.socket): - self.__raw_socket = raw_socket - self._loop = get_running_loop() - self._accept_guard = ResourceGuard("accepting connections from") - self._closed = False - - async def accept(self) -> abc.SocketStream: - await AsyncIOBackend.checkpoint() - with self._accept_guard: - while True: - try: - client_sock, _ = self.__raw_socket.accept() - client_sock.setblocking(False) - return UNIXSocketStream(client_sock) - except BlockingIOError: - f: asyncio.Future = asyncio.Future() - self._loop.add_reader(self.__raw_socket, f.set_result, None) - f.add_done_callback( - lambda _: self._loop.remove_reader(self.__raw_socket) - ) - await f - except OSError as exc: - if self._closed: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - - async def aclose(self) -> None: - self._closed = True - self.__raw_socket.close() - - @property - def _raw_socket(self) -> socket.socket: - return self.__raw_socket - - -class UDPSocket(abc.UDPSocket): - def __init__( - self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol - ): - self._transport = transport - self._protocol = protocol - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - self._closed = False - - @property - def _raw_socket(self) -> socket.socket: - return self._transport.get_extra_info("socket") - - async def aclose(self) -> None: - self._closed = True - if not self._transport.is_closing(): - self._transport.close() - - await self._protocol.closed_event.wait() - - async def receive(self) -> tuple[bytes, IPSockAddrType]: - with self._receive_guard: - await AsyncIOBackend.checkpoint() - - # If the buffer is empty, ask for more data - if not self._protocol.read_queue and not self._transport.is_closing(): - self._protocol.read_event.clear() - await self._protocol.read_event.wait() - - try: - return self._protocol.read_queue.popleft() - except IndexError: - if self._closed: - raise ClosedResourceError from None - else: - raise BrokenResourceError from None - - async def send(self, item: UDPPacketType) -> None: - with self._send_guard: - await AsyncIOBackend.checkpoint() - await self._protocol.write_event.wait() - if self._closed: - raise ClosedResourceError - elif self._transport.is_closing(): - raise BrokenResourceError - else: - self._transport.sendto(*item) - - -class ConnectedUDPSocket(abc.ConnectedUDPSocket): - def __init__( - self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol - ): - self._transport = transport - self._protocol = protocol - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - self._closed = False - - @property - def _raw_socket(self) -> socket.socket: - return self._transport.get_extra_info("socket") - - async def aclose(self) -> None: - self._closed = True - if not self._transport.is_closing(): - self._transport.close() - - await self._protocol.closed_event.wait() - - async def receive(self) -> bytes: - with self._receive_guard: - await AsyncIOBackend.checkpoint() - - # If the buffer is empty, ask for more data - if not self._protocol.read_queue and not self._transport.is_closing(): - self._protocol.read_event.clear() - await self._protocol.read_event.wait() - - try: - packet = self._protocol.read_queue.popleft() - except IndexError: - if self._closed: - raise ClosedResourceError from None - else: - raise BrokenResourceError from None - - return packet[0] - - async def send(self, item: bytes) -> None: - with self._send_guard: - await AsyncIOBackend.checkpoint() - await self._protocol.write_event.wait() - if self._closed: - raise ClosedResourceError - elif self._transport.is_closing(): - raise BrokenResourceError - else: - self._transport.sendto(item) - - -class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket): - async def receive(self) -> UNIXDatagramPacketType: - loop = get_running_loop() - await AsyncIOBackend.checkpoint() - with self._receive_guard: - while True: - try: - data = self._raw_socket.recvfrom(65536) - except BlockingIOError: - await self._wait_until_readable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - return data - - async def send(self, item: UNIXDatagramPacketType) -> None: - loop = get_running_loop() - await AsyncIOBackend.checkpoint() - with self._send_guard: - while True: - try: - self._raw_socket.sendto(*item) - except BlockingIOError: - await self._wait_until_writable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - return - - -class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket): - async def receive(self) -> bytes: - loop = get_running_loop() - await AsyncIOBackend.checkpoint() - with self._receive_guard: - while True: - try: - data = self._raw_socket.recv(65536) - except BlockingIOError: - await self._wait_until_readable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - return data - - async def send(self, item: bytes) -> None: - loop = get_running_loop() - await AsyncIOBackend.checkpoint() - with self._send_guard: - while True: - try: - self._raw_socket.send(item) - except BlockingIOError: - await self._wait_until_writable(loop) - except OSError as exc: - if self._closing: - raise ClosedResourceError from None - else: - raise BrokenResourceError from exc - else: - return - - -_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events") -_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events") - - -# -# Synchronization -# - - -class Event(BaseEvent): - __slots__ = ("_event",) - - def __new__(cls) -> Event: - return object.__new__(cls) - - def __init__(self) -> None: - self._event = asyncio.Event() - - def set(self) -> None: - self._event.set() - - def is_set(self) -> bool: - return self._event.is_set() - - async def wait(self) -> None: - if self.is_set(): - await AsyncIOBackend.checkpoint() - else: - await self._event.wait() - - def statistics(self) -> EventStatistics: - return EventStatistics(len(self._event._waiters)) - - -class Lock(BaseLock): - __slots__ = "_fast_acquire", "_owner_task", "_waiters" - - def __new__(cls, *, fast_acquire: bool = False) -> Lock: - return object.__new__(cls) - - def __init__(self, *, fast_acquire: bool = False) -> None: - self._fast_acquire = fast_acquire - self._owner_task: asyncio.Task | None = None - self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque() - - async def acquire(self) -> None: - task = cast(asyncio.Task, current_task()) - if self._owner_task is None and not self._waiters: - await AsyncIOBackend.checkpoint_if_cancelled() - self._owner_task = task - - # Unless on the "fast path", yield control of the event loop so that other - # tasks can run too - if not self._fast_acquire: - try: - await AsyncIOBackend.cancel_shielded_checkpoint() - except CancelledError: - self.release() - raise - - return - - if self._owner_task == task: - raise RuntimeError("Attempted to acquire an already held Lock") - - fut: asyncio.Future[None] = asyncio.Future() - item = task, fut - self._waiters.append(item) - try: - await fut - except CancelledError: - if fut.cancelled(): - try: - self._waiters.remove(item) - except ValueError: - pass - else: - self.release() - - raise - - def acquire_nowait(self) -> None: - task = cast(asyncio.Task, current_task()) - if self._owner_task is None and not self._waiters: - self._owner_task = task - return - - if self._owner_task is task: - raise RuntimeError("Attempted to acquire an already held Lock") - - raise WouldBlock - - def locked(self) -> bool: - return self._owner_task is not None - - def release(self) -> None: - if self._owner_task != current_task(): - raise RuntimeError("The current task is not holding this lock") - - # A cancelled waiter that already received ownership removes itself from - # _waiters before calling release(); any cancelled waiter still queued here - # was cancelled before being woken, so drop it. - while self._waiters: - task, fut = self._waiters.popleft() - if fut.cancelled(): - continue - - self._owner_task = task - fut.set_result(None) - return - - self._owner_task = None - - def statistics(self) -> LockStatistics: - task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None - return LockStatistics(self.locked(), task_info, len(self._waiters)) - - -class Semaphore(BaseSemaphore): - __slots__ = "_value", "_max_value", "_fast_acquire", "_waiters" - - def __new__( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> Semaphore: - return object.__new__(cls) - - def __init__( - self, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ): - super().__init__(initial_value, max_value=max_value) - self._value = initial_value - self._max_value = max_value - self._fast_acquire = fast_acquire - self._waiters: deque[asyncio.Future[None]] = deque() - - async def acquire(self) -> None: - if self._value > 0 and not self._waiters: - await AsyncIOBackend.checkpoint_if_cancelled() - self._value -= 1 - - # Unless on the "fast path", yield control of the event loop so that other - # tasks can run too - if not self._fast_acquire: - try: - await AsyncIOBackend.cancel_shielded_checkpoint() - except CancelledError: - self.release() - raise - - return - - fut: asyncio.Future[None] = asyncio.Future() - self._waiters.append(fut) - try: - await fut - except CancelledError: - if fut.cancelled(): - try: - self._waiters.remove(fut) - except ValueError: - pass - else: - self.release() - - raise - - def acquire_nowait(self) -> None: - if self._value == 0: - raise WouldBlock - - self._value -= 1 - - def release(self) -> None: - if self._max_value is not None and self._value == self._max_value: - raise ValueError("semaphore released too many times") - - while self._waiters: - fut = self._waiters.popleft() - if fut.cancelled(): - continue - - fut.set_result(None) - return - - self._value += 1 - - @property - def value(self) -> int: - return self._value - - @property - def max_value(self) -> int | None: - return self._max_value - - def statistics(self) -> SemaphoreStatistics: - return SemaphoreStatistics(len(self._waiters)) - - -class CapacityLimiter(BaseCapacityLimiter): - __slots__ = "_total_tokens", "_borrowers", "_wait_queue" - - def __new__(cls, total_tokens: float) -> CapacityLimiter: - return object.__new__(cls) - - def __init__(self, total_tokens: float): - self._total_tokens: float = 0 - self._borrowers: set[Any] = set() - self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict() - self.total_tokens = total_tokens - - async def __aenter__(self) -> None: - await self.acquire() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.release() - - @property - def total_tokens(self) -> float: - return self._total_tokens - - @total_tokens.setter - def total_tokens(self, value: float) -> None: - if not isinstance(value, int) and not math.isinf(value): - raise TypeError("total_tokens must be an int or math.inf") - - if value < 0: - raise ValueError("total_tokens must be >= 0") - - waiters_to_notify = max(value - self._total_tokens, 0) - self._total_tokens = value - - # Notify waiting tasks that they have acquired the limiter - while self._wait_queue and waiters_to_notify: - event = self._wait_queue.popitem(last=False)[1] - event.set() - waiters_to_notify -= 1 - - @property - def borrowed_tokens(self) -> int: - return len(self._borrowers) - - @property - def available_tokens(self) -> float: - return self._total_tokens - len(self._borrowers) - - def _notify_next_waiter(self) -> None: - """Notify the next task in line if this limiter has free capacity now.""" - if self._wait_queue and len(self._borrowers) < self._total_tokens: - event = self._wait_queue.popitem(last=False)[1] - event.set() - - def acquire_nowait(self) -> None: - self.acquire_on_behalf_of_nowait(current_task()) - - def acquire_on_behalf_of_nowait(self, borrower: object) -> None: - if borrower in self._borrowers: - raise RuntimeError( - "this borrower is already holding one of this CapacityLimiter's tokens" - ) - - if self._wait_queue or len(self._borrowers) >= self._total_tokens: - raise WouldBlock - - self._borrowers.add(borrower) - - async def acquire(self) -> None: - return await self.acquire_on_behalf_of(current_task()) - - async def acquire_on_behalf_of(self, borrower: object) -> None: - await AsyncIOBackend.checkpoint_if_cancelled() - try: - self.acquire_on_behalf_of_nowait(borrower) - except WouldBlock: - event = asyncio.Event() - self._wait_queue[borrower] = event - try: - await event.wait() - except BaseException: - self._wait_queue.pop(borrower, None) - if event.is_set(): - self._notify_next_waiter() - - raise - - self._borrowers.add(borrower) - else: - try: - await AsyncIOBackend.cancel_shielded_checkpoint() - except BaseException: - self.release() - raise - - def release(self) -> None: - self.release_on_behalf_of(current_task()) - - def release_on_behalf_of(self, borrower: object) -> None: - try: - self._borrowers.remove(borrower) - except KeyError: - raise RuntimeError( - "this borrower isn't holding any of this CapacityLimiter's tokens" - ) from None - - self._notify_next_waiter() - - def statistics(self) -> CapacityLimiterStatistics: - return CapacityLimiterStatistics( - self.borrowed_tokens, - self.total_tokens, - tuple(self._borrowers), - len(self._wait_queue), - ) - - -_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter") - - -# -# Operating system signals -# - - -class _SignalReceiver: - def __init__(self, signals: tuple[Signals, ...]): - self._signals = signals - self._loop = get_running_loop() - self._signal_queue: deque[Signals] = deque() - self._future: asyncio.Future = asyncio.Future() - self._handled_signals: set[Signals] = set() - - def _deliver(self, signum: Signals) -> None: - self._signal_queue.append(signum) - if not self._future.done(): - self._future.set_result(None) - - def __enter__(self) -> _SignalReceiver: - for sig in set(self._signals): - self._loop.add_signal_handler(sig, self._deliver, sig) - self._handled_signals.add(sig) - - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - for sig in self._handled_signals: - self._loop.remove_signal_handler(sig) - - def __aiter__(self) -> _SignalReceiver: - return self - - async def __anext__(self) -> Signals: - await AsyncIOBackend.checkpoint() - if not self._signal_queue: - self._future = asyncio.Future() - await self._future - - return self._signal_queue.popleft() - - -# -# Testing and debugging -# - - -class AsyncIOTaskInfo(TaskInfo): - def __init__(self, task: asyncio.Task): - task_state = _task_states.get(task) - if task_state is None: - parent_id = None - else: - parent_id = task_state.parent_id - - coro = task.get_coro() - assert coro is not None, "created TaskInfo from a completed Task" - super().__init__(id(task), parent_id, task.get_name(), coro) - self._task = weakref.ref(task) - - def has_pending_cancellation(self) -> bool: - if not (task := self._task()): - # If the task isn't around anymore, it won't have a pending cancellation - return False - - if task._must_cancel: # type: ignore[attr-defined] - return True - elif ( - isinstance(task._fut_waiter, asyncio.Future) # type: ignore[attr-defined] - and task._fut_waiter.cancelled() # type: ignore[attr-defined] - ): - return True - - if task_state := _task_states.get(task): - if cancel_scope := task_state.cancel_scope: - return cancel_scope._effectively_cancelled - - return False - - -class TestRunner(abc.TestRunner): - _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]] - - def __init__( - self, - *, - debug: bool | None = None, - use_uvloop: bool = False, - loop_factory: Callable[[], AbstractEventLoop] | None = None, - ) -> None: - if use_uvloop and loop_factory is None: - if sys.platform != "win32": - import uvloop - - loop_factory = uvloop.new_event_loop - else: - import winloop - - loop_factory = winloop.new_event_loop - - self._runner = Runner(debug=debug, loop_factory=loop_factory) - self._exceptions: list[BaseException] = [] - self._runner_task: asyncio.Task | None = None - - def __enter__(self) -> TestRunner: - self._runner.__enter__() - self.get_loop().set_exception_handler(self._exception_handler) - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self._runner.__exit__(exc_type, exc_val, exc_tb) - - def get_loop(self) -> AbstractEventLoop: - return self._runner.get_loop() - - def is_running(self) -> bool: - try: - asyncio.get_running_loop() - return True - except RuntimeError: - return False - - def _exception_handler( - self, loop: asyncio.AbstractEventLoop, context: dict[str, Any] - ) -> None: - if isinstance(context.get("exception"), Exception): - self._exceptions.append(context["exception"]) - else: - loop.default_exception_handler(context) - - def _raise_async_exceptions(self) -> None: - # Re-raise any exceptions raised in asynchronous callbacks - if self._exceptions: - exceptions, self._exceptions = self._exceptions, [] - if len(exceptions) == 1: - raise exceptions[0] - elif exceptions: - raise BaseExceptionGroup( - "Multiple exceptions occurred in asynchronous callbacks", exceptions - ) - - async def _run_tests_and_fixtures( - self, - receive_stream: MemoryObjectReceiveStream[ - tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]] - ], - ) -> None: - from _pytest.outcomes import OutcomeException - - with receive_stream, self._send_stream: - async for coro, future in receive_stream: - try: - retval = await coro - except CancelledError as exc: - if not future.cancelled(): - future.cancel(*exc.args) - - raise - except BaseException as exc: - if not future.cancelled(): - future.set_exception(exc) - - if not isinstance(exc, (Exception, OutcomeException)): - raise - else: - if not future.cancelled(): - future.set_result(retval) - - async def _call_in_runner_task( - self, - func: Callable[P, Awaitable[T_Retval]], - /, - *args: P.args, - **kwargs: P.kwargs, - ) -> T_Retval: - if not self._runner_task: - self._send_stream, receive_stream = create_memory_object_stream[ - tuple[Awaitable[Any], asyncio.Future] - ](1) - self._runner_task = self.get_loop().create_task( - self._run_tests_and_fixtures(receive_stream) - ) - - coro = func(*args, **kwargs) - future: asyncio.Future[T_Retval] = self.get_loop().create_future() - self._send_stream.send_nowait((coro, future)) - return await future - - def run_asyncgen_fixture( - self, - fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], - kwargs: dict[str, Any], - ) -> Iterable[T_Retval]: - asyncgen = fixture_func(**kwargs) - fixturevalue: T_Retval = self.get_loop().run_until_complete( - self._call_in_runner_task(asyncgen.asend, None) - ) - self._raise_async_exceptions() - - yield fixturevalue - - try: - self.get_loop().run_until_complete( - self._call_in_runner_task(asyncgen.asend, None) - ) - except StopAsyncIteration: - self._raise_async_exceptions() - else: - self.get_loop().run_until_complete(asyncgen.aclose()) - raise RuntimeError("Async generator fixture did not stop") - - def run_fixture( - self, - fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], - kwargs: dict[str, Any], - ) -> T_Retval: - retval = self.get_loop().run_until_complete( - self._call_in_runner_task(fixture_func, **kwargs) - ) - self._raise_async_exceptions() - return retval - - def run_test( - self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] - ) -> None: - try: - self.get_loop().run_until_complete( - self._call_in_runner_task(test_func, **kwargs) - ) - except Exception as exc: - self._exceptions.append(exc) - except BaseException: - # A BaseException (e.g. KeyboardInterrupt, SystemExit) interrupted the event loop before - # the test completed. Cancel _runner_task so it does not resume when the event - # loop is re-entered during async generator fixture teardown. - if self._runner_task is not None and not self._runner_task.done(): - self._runner_task.cancel() - self._send_stream.close() - try: - self.get_loop().run_until_complete(self._runner_task) - except CancelledError: - pass - finally: - self._runner_task = None - raise - self._raise_async_exceptions() - - -class AsyncIOBackend(AsyncBackend): - @classmethod - def run( - cls, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], - args: tuple[Unpack[PosArgsT]], - kwargs: dict[str, Any], - options: dict[str, Any], - ) -> T_Retval: - @wraps(func) - async def wrapper() -> T_Retval: - task = cast(asyncio.Task, current_task()) - task.set_name(get_callable_name(func)) - _task_states[task] = TaskState(None, None) - - try: - return await func(*args) - finally: - del _task_states[task] - - debug = options.get("debug", None) - loop_factory = options.get("loop_factory", None) - if loop_factory is None and options.get("use_uvloop", False): - if sys.platform != "win32": - import uvloop - - loop_factory = uvloop.new_event_loop - else: - import winloop - - loop_factory = winloop.new_event_loop - - with Runner(debug=debug, loop_factory=loop_factory) as runner: - return runner.run(wrapper()) - - @classmethod - def current_token(cls) -> object: - return get_running_loop() - - @classmethod - def current_time(cls) -> float: - return get_running_loop().time() - - @classmethod - def cancelled_exception_class(cls) -> type[BaseException]: - return CancelledError - - @classmethod - async def checkpoint(cls) -> None: - await sleep(0) - - @classmethod - async def checkpoint_if_cancelled(cls) -> None: - task = current_task() - if task is None: - return - - try: - cancel_scope = _task_states[task].cancel_scope - except KeyError: - return - - while cancel_scope: - if cancel_scope.cancel_called: - await sleep(0) - elif cancel_scope.shield: - break - else: - cancel_scope = cancel_scope._parent_scope - - @classmethod - async def cancel_shielded_checkpoint(cls) -> None: - with CancelScope(shield=True): - await sleep(0) - - @classmethod - async def sleep(cls, delay: float) -> None: - await sleep(delay) - - @classmethod - def create_cancel_scope( - cls, *, deadline: float = math.inf, shield: bool = False - ) -> CancelScope: - return CancelScope(deadline=deadline, shield=shield) - - @classmethod - def current_effective_deadline(cls) -> float: - if (task := current_task()) is None: - return math.inf - - try: - cancel_scope = _task_states[task].cancel_scope - except KeyError: - return math.inf - - deadline = math.inf - while cancel_scope: - deadline = min(deadline, cancel_scope.deadline) - if cancel_scope._cancel_called: - deadline = -math.inf - break - elif cancel_scope.shield: - break - else: - cancel_scope = cancel_scope._parent_scope - - return deadline - - @classmethod - def create_task_group(cls) -> abc.TaskGroup: - return TaskGroup() - - @classmethod - def create_event(cls) -> abc.Event: - return Event() - - @classmethod - def create_lock(cls, *, fast_acquire: bool) -> abc.Lock: - return Lock(fast_acquire=fast_acquire) - - @classmethod - def create_semaphore( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> abc.Semaphore: - return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) - - @classmethod - def create_capacity_limiter(cls, total_tokens: float) -> abc.CapacityLimiter: - return CapacityLimiter(total_tokens) - - @classmethod - async def run_sync_in_worker_thread( # type: ignore[return] - cls, - func: Callable[[Unpack[PosArgsT]], T_Retval], - args: tuple[Unpack[PosArgsT]], - abandon_on_cancel: bool = False, - limiter: abc.CapacityLimiter | None = None, - ) -> T_Retval: - await cls.checkpoint() - - # If this is the first run in this event loop thread, set up the necessary - # variables - try: - idle_workers = _threadpool_idle_workers.get() - workers = _threadpool_workers.get() - except LookupError: - idle_workers = deque() - workers = set() - _threadpool_idle_workers.set(idle_workers) - _threadpool_workers.set(workers) - - async with limiter or cls.current_default_thread_limiter(): - with CancelScope(shield=not abandon_on_cancel) as scope: - future = asyncio.Future[T_Retval]() - root_task = find_root_task() - if not idle_workers: - worker = WorkerThread(root_task, workers, idle_workers) - worker.start() - workers.add(worker) - root_task.add_done_callback( - worker.stop, context=contextvars.Context() - ) - else: - worker = idle_workers.pop() - - # Prune any other workers that have been idle for MAX_IDLE_TIME - # seconds or longer - now = cls.current_time() - while idle_workers: - if ( - now - idle_workers[0].idle_since - < WorkerThread.MAX_IDLE_TIME - ): - break - - expired_worker = idle_workers.popleft() - expired_worker.root_task.remove_done_callback( - expired_worker.stop - ) - expired_worker.stop() - - context = copy_context() - context.run(set_current_async_library, None) - if abandon_on_cancel or scope._parent_scope is None: - worker_scope = scope - else: - worker_scope = scope._parent_scope - - worker.queue.put_nowait((context, func, args, future, worker_scope)) - return await future - - @classmethod - def check_cancelled(cls) -> None: - scope: CancelScope | None = threadlocals.current_cancel_scope - while scope is not None: - if scope.cancel_called: - raise CancelledError(f"Cancelled via cancel scope {id(scope):x}") - - if scope.shield: - return - - scope = scope._parent_scope - - @classmethod - def run_async_from_thread( - cls, - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - args: tuple[Unpack[PosArgsT]], - token: object, - ) -> T_co: - async def task_wrapper() -> T_co: - __tracebackhide__ = True - if scope is not None: - task = cast(asyncio.Task, current_task()) - _task_states[task] = TaskState(None, scope) - scope._tasks.add(task) - try: - return await func(*args) - except CancelledError as exc: - raise concurrent.futures.CancelledError(str(exc)) from None - finally: - if scope is not None: - scope._tasks.discard(task) - - loop = cast( - "AbstractEventLoop", token or threadlocals.current_token.native_token - ) - if loop.is_closed(): - raise RunFinishedError - - context = copy_context() - context.run(set_current_async_library, "asyncio") - scope = getattr(threadlocals, "current_cancel_scope", None) - f: concurrent.futures.Future[T_co] = context.run( - asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop - ) - return f.result() - - @classmethod - def run_sync_from_thread( - cls, - func: Callable[[Unpack[PosArgsT]], T_Retval], - args: tuple[Unpack[PosArgsT]], - token: object, - ) -> T_Retval: - @wraps(func) - def wrapper() -> None: - try: - set_current_async_library("asyncio") - f.set_result(func(*args)) - except BaseException as exc: - f.set_exception(exc) - if not isinstance(exc, Exception): - raise - - loop = cast( - "AbstractEventLoop", token or threadlocals.current_token.native_token - ) - if loop.is_closed(): - raise RunFinishedError - - f: concurrent.futures.Future[T_Retval] = Future() - loop.call_soon_threadsafe(wrapper) - return f.result() - - @classmethod - async def open_process( - cls, - command: StrOrBytesPath | Sequence[StrOrBytesPath], - *, - stdin: int | IO[Any] | None, - stdout: int | IO[Any] | None, - stderr: int | IO[Any] | None, - **kwargs: Any, - ) -> Process: - await cls.checkpoint() - if isinstance(command, PathLike): - command = os.fspath(command) - - if isinstance(command, (str, bytes)): - process = await asyncio.create_subprocess_shell( - command, - stdin=stdin, - stdout=stdout, - stderr=stderr, - **kwargs, - ) - else: - process = await asyncio.create_subprocess_exec( - *command, - stdin=stdin, - stdout=stdout, - stderr=stderr, - **kwargs, - ) - - stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None - stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None - stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None - return Process(process, stdin_stream, stdout_stream, stderr_stream) - - @classmethod - def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: - create_task( - _shutdown_process_pool_on_exit(workers), - name="AnyIO process pool shutdown task", - ) - find_root_task().add_done_callback( - partial(_forcibly_shutdown_process_pool_on_exit, workers) # type:ignore[arg-type] - ) - - @classmethod - async def connect_tcp( - cls, host: str, port: int, local_address: IPSockAddrType | None = None - ) -> abc.SocketStream: - transport, protocol = cast( - tuple[asyncio.Transport, StreamProtocol], - await get_running_loop().create_connection( - StreamProtocol, host, port, local_addr=local_address - ), - ) - transport.pause_reading() - return SocketStream(transport, protocol) - - @classmethod - async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: - await cls.checkpoint() - loop = get_running_loop() - raw_socket = socket.socket(socket.AF_UNIX) - raw_socket.setblocking(False) - while True: - try: - raw_socket.connect(path) - except BlockingIOError: - f: asyncio.Future = asyncio.Future() - loop.add_writer(raw_socket, f.set_result, None) - f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) - await f - except BaseException: - raw_socket.close() - raise - else: - return UNIXSocketStream(raw_socket) - - @classmethod - def create_tcp_listener(cls, sock: socket.socket) -> SocketListener: - return TCPSocketListener(sock) - - @classmethod - def create_unix_listener(cls, sock: socket.socket) -> SocketListener: - return UNIXSocketListener(sock) - - @classmethod - async def create_udp_socket( - cls, - family: AddressFamily, - local_address: IPSockAddrType | None, - remote_address: IPSockAddrType | None, - reuse_port: bool, - ) -> UDPSocket | ConnectedUDPSocket: - transport, protocol = await get_running_loop().create_datagram_endpoint( - DatagramProtocol, - local_addr=local_address, - remote_addr=remote_address, - family=family, - reuse_port=reuse_port, - ) - if protocol.exception: - transport.close() - raise protocol.exception - - if not remote_address: - return UDPSocket(transport, protocol) - else: - return ConnectedUDPSocket(transport, protocol) - - @classmethod - async def create_unix_datagram_socket( # type: ignore[override] - cls, raw_socket: socket.socket, remote_path: str | bytes | None - ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: - await cls.checkpoint() - loop = get_running_loop() - - if remote_path: - while True: - try: - raw_socket.connect(remote_path) - except BlockingIOError: - f: asyncio.Future = asyncio.Future() - loop.add_writer(raw_socket, f.set_result, None) - f.add_done_callback(lambda _: loop.remove_writer(raw_socket)) - await f - except BaseException: - raw_socket.close() - raise - else: - return ConnectedUNIXDatagramSocket(raw_socket) - else: - return UNIXDatagramSocket(raw_socket) - - @classmethod - async def getaddrinfo( - cls, - host: bytes | str | None, - port: str | int | None, - *, - family: int | AddressFamily = 0, - type: int | SocketKind = 0, - proto: int = 0, - flags: int = 0, - ) -> Sequence[ - tuple[ - AddressFamily, - SocketKind, - int, - str, - tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], - ] - ]: - return await get_running_loop().getaddrinfo( - host, port, family=family, type=type, proto=proto, flags=flags - ) - - @classmethod - async def getnameinfo( - cls, sockaddr: IPSockAddrType, flags: int = 0 - ) -> tuple[str, str]: - return await get_running_loop().getnameinfo(sockaddr, flags) - - @classmethod - async def wait_readable(cls, obj: FileDescriptorLike) -> None: - try: - read_events = _read_events.get() - except LookupError: - read_events = {} - _read_events.set(read_events) - - fd = obj if isinstance(obj, int) else obj.fileno() - if read_events.get(fd): - raise BusyResourceError("reading from") - - loop = get_running_loop() - fut: asyncio.Future[bool] = loop.create_future() - - def cb() -> None: - try: - del read_events[fd] - except KeyError: - pass - else: - remove_reader(fd) - - try: - fut.set_result(True) - except asyncio.InvalidStateError: - pass - - try: - loop.add_reader(fd, cb) - except NotImplementedError: - from anyio._core._asyncio_selector_thread import get_selector - - selector = get_selector() - selector.add_reader(fd, cb) - remove_reader = selector.remove_reader - else: - remove_reader = loop.remove_reader - - read_events[fd] = fut - try: - success = await fut - finally: - try: - del read_events[fd] - except KeyError: - pass - else: - remove_reader(fd) - - if not success: - raise ClosedResourceError - - @classmethod - async def wait_writable(cls, obj: FileDescriptorLike) -> None: - try: - write_events = _write_events.get() - except LookupError: - write_events = {} - _write_events.set(write_events) - - fd = obj if isinstance(obj, int) else obj.fileno() - if write_events.get(fd): - raise BusyResourceError("writing to") - - loop = get_running_loop() - fut: asyncio.Future[bool] = loop.create_future() - - def cb() -> None: - try: - del write_events[fd] - except KeyError: - pass - else: - remove_writer(fd) - - try: - fut.set_result(True) - except asyncio.InvalidStateError: - pass - - try: - loop.add_writer(fd, cb) - except NotImplementedError: - from anyio._core._asyncio_selector_thread import get_selector - - selector = get_selector() - selector.add_writer(fd, cb) - remove_writer = selector.remove_writer - else: - remove_writer = loop.remove_writer - - write_events[fd] = fut - try: - success = await fut - finally: - try: - del write_events[fd] - except KeyError: - pass - else: - remove_writer(fd) - - if not success: - raise ClosedResourceError - - @classmethod - def notify_closing(cls, obj: FileDescriptorLike) -> None: - fd = obj if isinstance(obj, int) else obj.fileno() - loop = get_running_loop() - - try: - write_events = _write_events.get() - except LookupError: - pass - else: - try: - fut = write_events.pop(fd) - except KeyError: - pass - else: - try: - fut.set_result(False) - except asyncio.InvalidStateError: - pass - - try: - loop.remove_writer(fd) - except NotImplementedError: - from anyio._core._asyncio_selector_thread import get_selector - - get_selector().remove_writer(fd) - - try: - read_events = _read_events.get() - except LookupError: - pass - else: - try: - fut = read_events.pop(fd) - except KeyError: - pass - else: - try: - fut.set_result(False) - except asyncio.InvalidStateError: - pass - - try: - loop.remove_reader(fd) - except NotImplementedError: - from anyio._core._asyncio_selector_thread import get_selector - - get_selector().remove_reader(fd) - - @classmethod - async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener: - if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: - return UNIXSocketListener(sock) - - return TCPSocketListener(sock) - - @classmethod - async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: - transport, protocol = await get_running_loop().create_connection( - StreamProtocol, sock=sock - ) - return SocketStream(transport, protocol) - - @classmethod - async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: - return UNIXSocketStream(sock) - - @classmethod - async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: - transport, protocol = await get_running_loop().create_datagram_endpoint( - DatagramProtocol, sock=sock - ) - return UDPSocket(transport, protocol) - - @classmethod - async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: - transport, protocol = await get_running_loop().create_datagram_endpoint( - DatagramProtocol, sock=sock - ) - return ConnectedUDPSocket(transport, protocol) - - @classmethod - async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: - return UNIXDatagramSocket(sock) - - @classmethod - async def wrap_connected_unix_datagram_socket( - cls, sock: socket.socket - ) -> ConnectedUNIXDatagramSocket: - return ConnectedUNIXDatagramSocket(sock) - - @classmethod - def current_default_thread_limiter(cls) -> CapacityLimiter: - try: - return _default_thread_limiter.get() - except LookupError: - limiter = CapacityLimiter(40) - _default_thread_limiter.set(limiter) - return limiter - - @classmethod - def open_signal_receiver( - cls, *signals: Signals - ) -> AbstractContextManager[AsyncIterator[Signals]]: - return _SignalReceiver(signals) - - @classmethod - def get_current_task(cls) -> TaskInfo: - return AsyncIOTaskInfo(current_task()) # type: ignore[arg-type] - - @classmethod - def get_running_tasks(cls) -> Sequence[TaskInfo]: - return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()] - - @classmethod - async def wait_all_tasks_blocked(cls) -> None: - await cls.checkpoint() - this_task = current_task() - while True: - for task in all_tasks(): - if task is this_task: - continue - - waiter = task._fut_waiter # type: ignore[attr-defined] - if waiter is None or waiter.done(): - await sleep(0.1) - break - else: - return - - @classmethod - def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: - return TestRunner(**options) - - -backend_class = AsyncIOBackend diff --git a/server/venv/Lib/site-packages/anyio/_backends/_trio.py b/server/venv/Lib/site-packages/anyio/_backends/_trio.py deleted file mode 100644 index 091c78c..0000000 --- a/server/venv/Lib/site-packages/anyio/_backends/_trio.py +++ /dev/null @@ -1,1456 +0,0 @@ -from __future__ import annotations - -import array -import math -import os -import socket -import sys -import types -import weakref -from collections.abc import ( - AsyncGenerator, - AsyncIterator, - Awaitable, - Callable, - Collection, - Coroutine, - Iterable, - Sequence, -) -from contextlib import AbstractContextManager -from contextvars import Context -from dataclasses import dataclass -from functools import partial, wraps -from io import IOBase -from os import PathLike -from signal import Signals -from socket import AddressFamily, SocketKind -from types import TracebackType -from typing import ( - IO, - TYPE_CHECKING, - Any, - Generic, - Literal, - NoReturn, - ParamSpec, - TypeVar, - cast, - overload, -) - -import trio.from_thread -import trio.lowlevel -from outcome import Error, Outcome, Value -from trio.lowlevel import ( - current_root_task, - current_task, - notify_closing, - wait_readable, - wait_writable, -) -from trio.socket import SocketType as TrioSocketType -from trio.to_thread import run_sync - -from .. import ( - CapacityLimiterStatistics, - EventStatistics, - LockStatistics, - RunFinishedError, - TaskInfo, - WouldBlock, - abc, -) -from .._core._eventloop import claim_worker_thread -from .._core._exceptions import ( - BrokenResourceError, - BusyResourceError, - ClosedResourceError, - EndOfStream, -) -from .._core._sockets import convert_ipv6_sockaddr -from .._core._streams import create_memory_object_stream -from .._core._synchronization import ( - CapacityLimiter as BaseCapacityLimiter, -) -from .._core._synchronization import Event as BaseEvent -from .._core._synchronization import Lock as BaseLock -from .._core._synchronization import ( - ResourceGuard, - SemaphoreStatistics, -) -from .._core._synchronization import Semaphore as BaseSemaphore -from .._core._tasks import CancelScope as BaseCancelScope -from .._core._tasks import TaskHandle -from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType -from ..abc._eventloop import AsyncBackend, StrOrBytesPath -from ..abc._tasks import T_contra, call_for_coroutine, get_callable_name -from ..streams.memory import MemoryObjectSendStream - -if TYPE_CHECKING: - from _typeshed import FileDescriptorLike - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from exceptiongroup import BaseExceptionGroup - from typing_extensions import TypeVarTuple, Unpack - -T = TypeVar("T") -T_Retval = TypeVar("T_Retval") -T_co = TypeVar("T_co", covariant=True) -T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType) -PosArgsT = TypeVarTuple("PosArgsT") -P = ParamSpec("P") - - -def ensure_returns_coro( - func: Callable[P, Awaitable[T_Retval]], -) -> Callable[P, Coroutine[Any, Any, T_Retval]]: - @wraps(func) - def wrapper(*args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T_Retval]: - awaitable = func(*args, **kwargs) - # Check the common case first. - if isinstance(awaitable, Coroutine): - return awaitable - elif not isinstance(awaitable, Awaitable): - # The user violated the type annotations. Still, we should pass this on to - # Trio so it can raise with an appropriate message. - return awaitable - else: - - @wraps(func) - async def inner_wrapper() -> T_Retval: - return await awaitable - - return inner_wrapper() - - return wrapper - - -# -# Event loop -# - -RunVar = trio.lowlevel.RunVar - - -# -# Timeouts and cancellation -# - - -class CancelScope(BaseCancelScope): - __slots__ = ("__original",) - - def __new__( - cls, original: trio.CancelScope | None = None, **kwargs: object - ) -> CancelScope: - return object.__new__(cls) - - def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None: - self.__original = original or trio.CancelScope(**kwargs) - - def __enter__(self) -> CancelScope: - self.__original.__enter__() - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - return self.__original.__exit__(exc_type, exc_val, exc_tb) - - def cancel(self, reason: str | None = None) -> None: - self.__original.cancel(reason) - - @property - def deadline(self) -> float: - return self.__original.deadline - - @deadline.setter - def deadline(self, value: float) -> None: - self.__original.deadline = value - - @property - def cancel_called(self) -> bool: - return self.__original.cancel_called - - @property - def cancelled_caught(self) -> bool: - return self.__original.cancelled_caught - - @property - def shield(self) -> bool: - return self.__original.shield - - @shield.setter - def shield(self, value: bool) -> None: - self.__original.shield = value - - -# -# Task groups -# - -empty_start_value = object() - - -class _TrioTaskStatus(Generic[T_contra], abc.TaskStatus[T_contra]): - early_start_value: T_contra | object = empty_start_value - real_task_status: trio.TaskStatus[T_contra | None] | None = None - - def started(self, value: T_contra | None = None) -> None: - if self.real_task_status is None: - if self.early_start_value is not empty_start_value: - raise RuntimeError("called 'started' twice on the same task status") - - self.early_start_value = value - else: - self.real_task_status.started(value) - - -class TaskGroup(abc.TaskGroup): - def __init__(self) -> None: - self._entered = False - self._active = False - self._nursery_manager = trio.open_nursery(strict_exception_groups=True) - self.cancel_scope = None # type: ignore[assignment] - - async def __aenter__(self) -> TaskGroup: - if self._entered: - raise RuntimeError("TaskGroup cannot be entered more than once") - - self._entered = True - self._active = True - self._nursery = await self._nursery_manager.__aenter__() - self.cancel_scope = CancelScope(self._nursery.cancel_scope) - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - try: - # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type - return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb) # type: ignore[return-value] - except BaseExceptionGroup as exc: - if not exc.split(trio.Cancelled)[1]: - raise trio.Cancelled._create() from exc - - raise - finally: - del exc_val, exc_tb - self._active = False - - def _check_active(self, coro: Coroutine | None = None) -> None: - if not self._active: - if coro is not None: - coro.close() - - raise RuntimeError( - "This task group is not active; no new tasks can be started." - ) - - def create_task( - self, - coro: Coroutine[Any, Any, T_co], - *, - name: object = None, - context: Context | None = None, - ) -> TaskHandle[T_co]: - if not isinstance(coro, Coroutine): - raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}") - - self._check_active(coro) - handle = TaskHandle(coro, name) - if context is not None: - context.run( - partial(self._nursery.start_soon, handle._run_coro, name=handle.name) - ) - else: - self._nursery.start_soon(handle._run_coro, name=handle.name) - - return handle - - async def start( - self, - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - *args: Unpack[PosArgsT], - name: object = None, - return_handle: Literal[False] | Literal[True] = False, - ) -> Any: - handle: TaskHandle[T_co] - - async def run_coro_with_task_status( - *, task_status: trio.TaskStatus[Any] - ) -> None: - nonlocal handle - wrapper_task_status = _TrioTaskStatus() - coro = call_for_coroutine(func, args, task_status=wrapper_task_status) - if wrapper_task_status.early_start_value is not empty_start_value: - task_status.started(wrapper_task_status.early_start_value) - else: - wrapper_task_status.real_task_status = task_status - - handle = TaskHandle(coro, name) - await handle._run_coro() - - self._check_active() - final_name = get_callable_name(func, name) - start_value = await self._nursery.start( - run_coro_with_task_status, name=final_name - ) - if return_handle: - handle._start_value = start_value - return handle - else: - return start_value - - -# -# Subprocesses -# - - -@dataclass(eq=False) -class ReceiveStreamWrapper(abc.ByteReceiveStream): - _stream: trio.abc.ReceiveStream - - async def receive(self, max_bytes: int | None = None) -> bytes: - try: - data = await self._stream.receive_some(max_bytes) - except trio.ClosedResourceError as exc: - raise ClosedResourceError from exc.__cause__ - except trio.BrokenResourceError as exc: - raise BrokenResourceError from exc.__cause__ - - if data: - return bytes(data) - else: - raise EndOfStream - - async def aclose(self) -> None: - await self._stream.aclose() - - -@dataclass(eq=False) -class SendStreamWrapper(abc.ByteSendStream): - _stream: trio.abc.SendStream - - async def send(self, item: bytes) -> None: - try: - await self._stream.send_all(item) - except trio.ClosedResourceError as exc: - raise ClosedResourceError from exc.__cause__ - except trio.BrokenResourceError as exc: - raise BrokenResourceError from exc.__cause__ - - async def aclose(self) -> None: - await self._stream.aclose() - - -@dataclass(eq=False) -class Process(abc.Process): - _process: trio.Process - _stdin: abc.ByteSendStream | None - _stdout: abc.ByteReceiveStream | None - _stderr: abc.ByteReceiveStream | None - - async def aclose(self) -> None: - with CancelScope(shield=True): - if self._stdin: - await self._stdin.aclose() - if self._stdout: - await self._stdout.aclose() - if self._stderr: - await self._stderr.aclose() - - try: - await self.wait() - except BaseException: - self.kill() - with CancelScope(shield=True): - await self.wait() - raise - - async def wait(self) -> int: - return await self._process.wait() - - def terminate(self) -> None: - self._process.terminate() - - def kill(self) -> None: - self._process.kill() - - def send_signal(self, signal: Signals) -> None: - self._process.send_signal(signal) - - @property - def pid(self) -> int: - return self._process.pid - - @property - def returncode(self) -> int | None: - return self._process.returncode - - @property - def stdin(self) -> abc.ByteSendStream | None: - return self._stdin - - @property - def stdout(self) -> abc.ByteReceiveStream | None: - return self._stdout - - @property - def stderr(self) -> abc.ByteReceiveStream | None: - return self._stderr - - -class _ProcessPoolShutdownInstrument(trio.abc.Instrument): - def after_run(self) -> None: - super().after_run() - - -current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar( - "current_default_worker_process_limiter" -) - - -async def _shutdown_process_pool(workers: set[abc.Process]) -> None: - try: - await trio.sleep(math.inf) - except trio.Cancelled: - for process in workers: - if process.returncode is None: - process.kill() - - with CancelScope(shield=True): - for process in workers: - await process.aclose() - - -# -# Sockets and networking -# - - -class _TrioSocketMixin(Generic[T_SockAddr]): - def __init__(self, trio_socket: TrioSocketType) -> None: - self._trio_socket = trio_socket - self._closed = False - - def _check_closed(self) -> None: - if self._closed: - raise ClosedResourceError - if self._trio_socket.fileno() < 0: - raise BrokenResourceError - - @property - def _raw_socket(self) -> socket.socket: - return self._trio_socket._sock # type: ignore[attr-defined] - - async def aclose(self) -> None: - if self._trio_socket.fileno() >= 0: - self._closed = True - self._trio_socket.close() - - def _convert_socket_error(self, exc: BaseException) -> NoReturn: - if isinstance(exc, trio.ClosedResourceError): - raise ClosedResourceError from exc - elif self._trio_socket.fileno() < 0 and self._closed: - raise ClosedResourceError from None - elif isinstance(exc, OSError): - raise BrokenResourceError from exc - else: - raise exc - - -class SocketStream(_TrioSocketMixin, abc.SocketStream): - def __init__(self, trio_socket: TrioSocketType) -> None: - super().__init__(trio_socket) - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - - async def receive(self, max_bytes: int = 65536) -> bytes: - with self._receive_guard: - try: - data = await self._trio_socket.recv(max_bytes) - except BaseException as exc: - self._convert_socket_error(exc) - - if data: - return data - else: - raise EndOfStream - - async def send(self, item: bytes) -> None: - with self._send_guard: - view = memoryview(item) - while view: - try: - bytes_sent = await self._trio_socket.send(view) - except BaseException as exc: - self._convert_socket_error(exc) - - view = view[bytes_sent:] - - async def send_eof(self) -> None: - self._trio_socket.shutdown(socket.SHUT_WR) - - -class UNIXSocketStream(SocketStream, abc.UNIXSocketStream): - async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: - if not isinstance(msglen, int) or msglen < 0: - raise ValueError("msglen must be a non-negative integer") - if not isinstance(maxfds, int) or maxfds < 1: - raise ValueError("maxfds must be a positive integer") - - fds = array.array("i") - await trio.lowlevel.checkpoint() - with self._receive_guard: - while True: - try: - message, ancdata, flags, addr = await self._trio_socket.recvmsg( - msglen, socket.CMSG_LEN(maxfds * fds.itemsize) - ) - except BaseException as exc: - self._convert_socket_error(exc) - else: - if not message and not ancdata: - raise EndOfStream - - break - - for cmsg_level, cmsg_type, cmsg_data in ancdata: - if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS: - raise RuntimeError( - f"Received unexpected ancillary data; message = {message!r}, " - f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}" - ) - - fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) - - return message, list(fds) - - async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: - if not message: - raise ValueError("message must not be empty") - if not fds: - raise ValueError("fds must not be empty") - - filenos: list[int] = [] - for fd in fds: - if isinstance(fd, int): - filenos.append(fd) - elif isinstance(fd, IOBase): - filenos.append(fd.fileno()) - - fdarray = array.array("i", filenos) - await trio.lowlevel.checkpoint() - with self._send_guard: - while True: - try: - await self._trio_socket.sendmsg( - [message], - [ - ( - socket.SOL_SOCKET, - socket.SCM_RIGHTS, - fdarray, - ) - ], - ) - break - except BaseException as exc: - self._convert_socket_error(exc) - - -class TCPSocketListener(_TrioSocketMixin, abc.SocketListener): - def __init__(self, raw_socket: socket.socket): - super().__init__(trio.socket.from_stdlib_socket(raw_socket)) - self._accept_guard = ResourceGuard("accepting connections from") - - async def accept(self) -> SocketStream: - with self._accept_guard: - try: - trio_socket, _addr = await self._trio_socket.accept() - except BaseException as exc: - self._convert_socket_error(exc) - - trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - return SocketStream(trio_socket) - - -class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener): - def __init__(self, raw_socket: socket.socket): - super().__init__(trio.socket.from_stdlib_socket(raw_socket)) - self._accept_guard = ResourceGuard("accepting connections from") - - async def accept(self) -> UNIXSocketStream: - with self._accept_guard: - try: - trio_socket, _addr = await self._trio_socket.accept() - except BaseException as exc: - self._convert_socket_error(exc) - - return UNIXSocketStream(trio_socket) - - -class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket): - def __init__(self, trio_socket: TrioSocketType) -> None: - super().__init__(trio_socket) - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - - async def receive(self) -> tuple[bytes, IPSockAddrType]: - with self._receive_guard: - try: - data, addr = await self._trio_socket.recvfrom(65536) - return data, convert_ipv6_sockaddr(addr) - except BaseException as exc: - self._convert_socket_error(exc) - - async def send(self, item: UDPPacketType) -> None: - with self._send_guard: - try: - await self._trio_socket.sendto(*item) - except BaseException as exc: - self._convert_socket_error(exc) - - -class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket): - def __init__(self, trio_socket: TrioSocketType) -> None: - super().__init__(trio_socket) - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - - async def receive(self) -> bytes: - with self._receive_guard: - try: - return await self._trio_socket.recv(65536) - except BaseException as exc: - self._convert_socket_error(exc) - - async def send(self, item: bytes) -> None: - with self._send_guard: - try: - await self._trio_socket.send(item) - except BaseException as exc: - self._convert_socket_error(exc) - - -class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket): - def __init__(self, trio_socket: TrioSocketType) -> None: - super().__init__(trio_socket) - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - - async def receive(self) -> UNIXDatagramPacketType: - with self._receive_guard: - try: - data, addr = await self._trio_socket.recvfrom(65536) - return data, addr - except BaseException as exc: - self._convert_socket_error(exc) - - async def send(self, item: UNIXDatagramPacketType) -> None: - with self._send_guard: - try: - await self._trio_socket.sendto(*item) - except BaseException as exc: - self._convert_socket_error(exc) - - -class ConnectedUNIXDatagramSocket( - _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket -): - def __init__(self, trio_socket: TrioSocketType) -> None: - super().__init__(trio_socket) - self._receive_guard = ResourceGuard("reading from") - self._send_guard = ResourceGuard("writing to") - - async def receive(self) -> bytes: - with self._receive_guard: - try: - return await self._trio_socket.recv(65536) - except BaseException as exc: - self._convert_socket_error(exc) - - async def send(self, item: bytes) -> None: - with self._send_guard: - try: - await self._trio_socket.send(item) - except BaseException as exc: - self._convert_socket_error(exc) - - -# -# Synchronization -# - - -class Event(BaseEvent): - __slots__ = ("__original",) - - def __new__(cls) -> Event: - return object.__new__(cls) - - def __init__(self) -> None: - self.__original = trio.Event() - - def is_set(self) -> bool: - return self.__original.is_set() - - async def wait(self) -> None: - return await self.__original.wait() - - def statistics(self) -> EventStatistics: - orig_statistics = self.__original.statistics() - return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting) - - def set(self) -> None: - self.__original.set() - - -class Lock(BaseLock): - __slots__ = "_fast_acquire", "__original" - - def __new__(cls, *, fast_acquire: bool = False) -> Lock: - return object.__new__(cls) - - def __init__(self, *, fast_acquire: bool = False) -> None: - self._fast_acquire = fast_acquire - self.__original = trio.Lock() - - @staticmethod - def _convert_runtime_error_msg(exc: RuntimeError) -> None: - if exc.args == ("attempt to re-acquire an already held Lock",): - exc.args = ("Attempted to acquire an already held Lock",) - - async def acquire(self) -> None: - if not self._fast_acquire: - try: - await self.__original.acquire() - except RuntimeError as exc: - self._convert_runtime_error_msg(exc) - raise - - return - - # This is the "fast path" where we don't let other tasks run - await trio.lowlevel.checkpoint_if_cancelled() - try: - self.__original.acquire_nowait() - except trio.WouldBlock: - await self.__original._lot.park() - except RuntimeError as exc: - self._convert_runtime_error_msg(exc) - raise - - def acquire_nowait(self) -> None: - try: - self.__original.acquire_nowait() - except trio.WouldBlock: - raise WouldBlock from None - except RuntimeError as exc: - self._convert_runtime_error_msg(exc) - raise - - def locked(self) -> bool: - return self.__original.locked() - - def release(self) -> None: - self.__original.release() - - def statistics(self) -> LockStatistics: - orig_statistics = self.__original.statistics() - owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None - return LockStatistics( - orig_statistics.locked, owner, orig_statistics.tasks_waiting - ) - - -class Semaphore(BaseSemaphore): - __slots__ = ("__original",) - - def __new__( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> Semaphore: - return object.__new__(cls) - - def __init__( - self, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> None: - super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) - self.__original = trio.Semaphore(initial_value, max_value=max_value) - - async def acquire(self) -> None: - if not self._fast_acquire: - await self.__original.acquire() - return - - # This is the "fast path" where we don't let other tasks run - await trio.lowlevel.checkpoint_if_cancelled() - try: - self.__original.acquire_nowait() - except trio.WouldBlock: - await self.__original._lot.park() - - def acquire_nowait(self) -> None: - try: - self.__original.acquire_nowait() - except trio.WouldBlock: - raise WouldBlock from None - - @property - def max_value(self) -> int | None: - return self.__original.max_value - - @property - def value(self) -> int: - return self.__original.value - - def release(self) -> None: - self.__original.release() - - def statistics(self) -> SemaphoreStatistics: - orig_statistics = self.__original.statistics() - return SemaphoreStatistics(orig_statistics.tasks_waiting) - - -class CapacityLimiter(BaseCapacityLimiter): - __slots__ = ("__original",) - - def __new__( - cls, - total_tokens: float | None = None, - *, - original: trio.CapacityLimiter | None = None, - ) -> CapacityLimiter: - return object.__new__(cls) - - def __init__( - self, - total_tokens: float | None = None, - *, - original: trio.CapacityLimiter | None = None, - ) -> None: - if original is not None: - self.__original = original - else: - assert total_tokens is not None - self.__original = trio.CapacityLimiter(total_tokens) - - async def __aenter__(self) -> None: - return await self.__original.__aenter__() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - await self.__original.__aexit__(exc_type, exc_val, exc_tb) - - @property - def total_tokens(self) -> float: - return self.__original.total_tokens - - @total_tokens.setter - def total_tokens(self, value: float) -> None: - self.__original.total_tokens = value - - @property - def borrowed_tokens(self) -> int: - return self.__original.borrowed_tokens - - @property - def available_tokens(self) -> float: - return self.__original.available_tokens - - def acquire_nowait(self) -> None: - self.__original.acquire_nowait() - - def acquire_on_behalf_of_nowait(self, borrower: object) -> None: - self.__original.acquire_on_behalf_of_nowait(borrower) - - async def acquire(self) -> None: - await self.__original.acquire() - - async def acquire_on_behalf_of(self, borrower: object) -> None: - await self.__original.acquire_on_behalf_of(borrower) - - def release(self) -> None: - return self.__original.release() - - def release_on_behalf_of(self, borrower: object) -> None: - return self.__original.release_on_behalf_of(borrower) - - def statistics(self) -> CapacityLimiterStatistics: - orig = self.__original.statistics() - return CapacityLimiterStatistics( - borrowed_tokens=orig.borrowed_tokens, - total_tokens=orig.total_tokens, - borrowers=tuple(orig.borrowers), - tasks_waiting=orig.tasks_waiting, - ) - - -_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper") - - -# -# Signal handling -# - - -class _SignalReceiver: - _iterator: AsyncIterator[int] - - def __init__(self, signals: tuple[Signals, ...]): - self._signals = signals - - def __enter__(self) -> _SignalReceiver: - self._cm = trio.open_signal_receiver(*self._signals) - self._iterator = self._cm.__enter__() - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool | None: - return self._cm.__exit__(exc_type, exc_val, exc_tb) - - def __aiter__(self) -> _SignalReceiver: - return self - - async def __anext__(self) -> Signals: - signum = await self._iterator.__anext__() - return Signals(signum) - - -# -# Testing and debugging -# - - -class TestRunner(abc.TestRunner): - def __init__(self, **options: Any) -> None: - from queue import Queue - - self._call_queue: Queue[Callable[[], object]] = Queue() - self._send_stream: ( - MemoryObjectSendStream[tuple[Awaitable[Any], list[Outcome]]] | None - ) = None - self._options = options - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> None: - if self._send_stream: - self._send_stream.close() - while self._send_stream is not None: - self._call_queue.get()() - - def is_running(self) -> bool: - return trio.lowlevel.in_trio_task() - - async def _run_tests_and_fixtures(self) -> None: - self._send_stream, receive_stream = create_memory_object_stream[ - tuple[Awaitable[Any], list[Outcome]] - ](1) - with receive_stream: - async for awaitable, outcome_holder in receive_stream: - try: - retval = await awaitable - except BaseException as exc: - outcome_holder.append(Error(exc)) - else: - outcome_holder.append(Value(retval)) - - def _main_task_finished(self, outcome: object) -> None: - self._send_stream = None - - def _call_in_runner_task( - self, - func: Callable[P, Awaitable[T_Retval]], - /, - *args: P.args, - **kwargs: P.kwargs, - ) -> T_Retval: - if self._send_stream is None: - trio.lowlevel.start_guest_run( - self._run_tests_and_fixtures, - run_sync_soon_threadsafe=self._call_queue.put, - done_callback=self._main_task_finished, - **self._options, - ) - while self._send_stream is None: - self._call_queue.get()() - - outcome_holder: list[Outcome] = [] - self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder)) - while not outcome_holder: - self._call_queue.get()() - - return outcome_holder[0].unwrap() - - def run_asyncgen_fixture( - self, - fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]], - kwargs: dict[str, Any], - ) -> Iterable[T_Retval]: - asyncgen = fixture_func(**kwargs) - fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None) - - yield fixturevalue - - try: - self._call_in_runner_task(asyncgen.asend, None) - except StopAsyncIteration: - pass - else: - self._call_in_runner_task(asyncgen.aclose) - raise RuntimeError("Async generator fixture did not stop") - - def run_fixture( - self, - fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]], - kwargs: dict[str, Any], - ) -> T_Retval: - return self._call_in_runner_task(fixture_func, **kwargs) - - def run_test( - self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] - ) -> None: - self._call_in_runner_task(test_func, **kwargs) - - -class TrioTaskInfo(TaskInfo): - def __init__(self, task: trio.lowlevel.Task): - parent_id = None - if task.parent_nursery and task.parent_nursery.parent_task: - parent_id = id(task.parent_nursery.parent_task) - - super().__init__(id(task), parent_id, task.name, task.coro) - self._task = weakref.proxy(task) - - def has_pending_cancellation(self) -> bool: - try: - return self._task._cancel_status.effectively_cancelled - except ReferenceError: - # If the task is no longer around, it surely doesn't have a cancellation - # pending - return False - - -class TrioBackend(AsyncBackend): - @classmethod - def run( - cls, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], - args: tuple[Unpack[PosArgsT]], - kwargs: dict[str, Any], - options: dict[str, Any], - ) -> T_Retval: - assert not kwargs, "unreachable, and not supported by Trio" - return trio.run(ensure_returns_coro(func), *args, **options) - - @classmethod - def current_token(cls) -> object: - return trio.lowlevel.current_trio_token() - - @classmethod - def current_time(cls) -> float: - return trio.current_time() - - @classmethod - def cancelled_exception_class(cls) -> type[BaseException]: - return trio.Cancelled - - @classmethod - async def checkpoint(cls) -> None: - await trio.lowlevel.checkpoint() - - @classmethod - async def checkpoint_if_cancelled(cls) -> None: - await trio.lowlevel.checkpoint_if_cancelled() - - @classmethod - async def cancel_shielded_checkpoint(cls) -> None: - await trio.lowlevel.cancel_shielded_checkpoint() - - @classmethod - async def sleep(cls, delay: float) -> None: - await trio.sleep(delay) - - @classmethod - def create_cancel_scope( - cls, *, deadline: float = math.inf, shield: bool = False - ) -> abc.CancelScope: - return CancelScope(deadline=deadline, shield=shield) - - @classmethod - def current_effective_deadline(cls) -> float: - return trio.current_effective_deadline() - - @classmethod - def create_task_group(cls) -> abc.TaskGroup: - return TaskGroup() - - @classmethod - def create_event(cls) -> abc.Event: - return Event() - - @classmethod - def create_lock(cls, *, fast_acquire: bool) -> Lock: - return Lock(fast_acquire=fast_acquire) - - @classmethod - def create_semaphore( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> abc.Semaphore: - return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire) - - @classmethod - def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: - return CapacityLimiter(total_tokens) - - @classmethod - async def run_sync_in_worker_thread( - cls, - func: Callable[[Unpack[PosArgsT]], T_Retval], - args: tuple[Unpack[PosArgsT]], - abandon_on_cancel: bool = False, - limiter: abc.CapacityLimiter | None = None, - ) -> T_Retval: - def wrapper() -> T_Retval: - with claim_worker_thread(TrioBackend, token): - return func(*args) - - token = TrioBackend.current_token() - return await run_sync( - wrapper, - abandon_on_cancel=abandon_on_cancel, - limiter=cast(trio.CapacityLimiter, limiter), - ) - - @classmethod - def check_cancelled(cls) -> None: - trio.from_thread.check_cancelled() - - @classmethod - def run_async_from_thread( - cls, - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - args: tuple[Unpack[PosArgsT]], - token: object, - ) -> T_co: - trio_token = cast("trio.lowlevel.TrioToken | None", token) - try: - return trio.from_thread.run(func, *args, trio_token=trio_token) - except trio.RunFinishedError: - raise RunFinishedError from None - - @classmethod - def run_sync_from_thread( - cls, - func: Callable[[Unpack[PosArgsT]], T_Retval], - args: tuple[Unpack[PosArgsT]], - token: object, - ) -> T_Retval: - trio_token = cast("trio.lowlevel.TrioToken | None", token) - try: - return trio.from_thread.run_sync(func, *args, trio_token=trio_token) - except trio.RunFinishedError: - raise RunFinishedError from None - - @classmethod - async def open_process( - cls, - command: StrOrBytesPath | Sequence[StrOrBytesPath], - *, - stdin: int | IO[Any] | None, - stdout: int | IO[Any] | None, - stderr: int | IO[Any] | None, - **kwargs: Any, - ) -> Process: - def convert_item(item: StrOrBytesPath) -> str: - str_or_bytes = os.fspath(item) - if isinstance(str_or_bytes, str): - return str_or_bytes - else: - return os.fsdecode(str_or_bytes) - - if isinstance(command, (str, bytes, PathLike)): - process = await trio.lowlevel.open_process( - convert_item(command), - stdin=stdin, - stdout=stdout, - stderr=stderr, - shell=True, - **kwargs, - ) - else: - process = await trio.lowlevel.open_process( - [convert_item(item) for item in command], - stdin=stdin, - stdout=stdout, - stderr=stderr, - shell=False, - **kwargs, - ) - - stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None - stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None - stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None - return Process(process, stdin_stream, stdout_stream, stderr_stream) - - @classmethod - def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None: - trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers) - - @classmethod - async def connect_tcp( - cls, host: str, port: int, local_address: IPSockAddrType | None = None - ) -> SocketStream: - family = socket.AF_INET6 if ":" in host else socket.AF_INET - trio_socket = trio.socket.socket(family) - trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - if local_address: - await trio_socket.bind(local_address) - - try: - await trio_socket.connect((host, port)) - except BaseException: - trio_socket.close() - raise - - return SocketStream(trio_socket) - - @classmethod - async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream: - trio_socket = trio.socket.socket(socket.AF_UNIX) - try: - await trio_socket.connect(path) - except BaseException: - trio_socket.close() - raise - - return UNIXSocketStream(trio_socket) - - @classmethod - def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener: - return TCPSocketListener(sock) - - @classmethod - def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener: - return UNIXSocketListener(sock) - - @classmethod - async def create_udp_socket( - cls, - family: socket.AddressFamily, - local_address: IPSockAddrType | None, - remote_address: IPSockAddrType | None, - reuse_port: bool, - ) -> UDPSocket | ConnectedUDPSocket: - trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM) - - if reuse_port: - trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - - if local_address: - await trio_socket.bind(local_address) - - if remote_address: - await trio_socket.connect(remote_address) - return ConnectedUDPSocket(trio_socket) - else: - return UDPSocket(trio_socket) - - @classmethod - @overload - async def create_unix_datagram_socket( - cls, raw_socket: socket.socket, remote_path: None - ) -> abc.UNIXDatagramSocket: ... - - @classmethod - @overload - async def create_unix_datagram_socket( - cls, raw_socket: socket.socket, remote_path: str | bytes - ) -> abc.ConnectedUNIXDatagramSocket: ... - - @classmethod - async def create_unix_datagram_socket( - cls, raw_socket: socket.socket, remote_path: str | bytes | None - ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket: - trio_socket = trio.socket.from_stdlib_socket(raw_socket) - - if remote_path: - await trio_socket.connect(remote_path) - return ConnectedUNIXDatagramSocket(trio_socket) - else: - return UNIXDatagramSocket(trio_socket) - - @classmethod - async def getaddrinfo( - cls, - host: bytes | str | None, - port: str | int | None, - *, - family: int | AddressFamily = 0, - type: int | SocketKind = 0, - proto: int = 0, - flags: int = 0, - ) -> Sequence[ - tuple[ - AddressFamily, - SocketKind, - int, - str, - tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], - ] - ]: - return await trio.socket.getaddrinfo(host, port, family, type, proto, flags) - - @classmethod - async def getnameinfo( - cls, sockaddr: IPSockAddrType, flags: int = 0 - ) -> tuple[str, str]: - return await trio.socket.getnameinfo(sockaddr, flags) - - @classmethod - async def wait_readable(cls, obj: FileDescriptorLike) -> None: - try: - await wait_readable(obj) - except trio.ClosedResourceError as exc: - raise ClosedResourceError().with_traceback(exc.__traceback__) from None - except trio.BusyResourceError: - raise BusyResourceError("reading from") from None - - @classmethod - async def wait_writable(cls, obj: FileDescriptorLike) -> None: - try: - await wait_writable(obj) - except trio.ClosedResourceError as exc: - raise ClosedResourceError().with_traceback(exc.__traceback__) from None - except trio.BusyResourceError: - raise BusyResourceError("writing to") from None - - @classmethod - def notify_closing(cls, obj: FileDescriptorLike) -> None: - notify_closing(obj) - - @classmethod - async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener: - if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: - return UNIXSocketListener(sock) - - return TCPSocketListener(sock) - - @classmethod - async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream: - trio_sock = trio.socket.from_stdlib_socket(sock) - return SocketStream(trio_sock) - - @classmethod - async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream: - trio_sock = trio.socket.from_stdlib_socket(sock) - return UNIXSocketStream(trio_sock) - - @classmethod - async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket: - trio_sock = trio.socket.from_stdlib_socket(sock) - return UDPSocket(trio_sock) - - @classmethod - async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket: - trio_sock = trio.socket.from_stdlib_socket(sock) - return ConnectedUDPSocket(trio_sock) - - @classmethod - async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket: - trio_sock = trio.socket.from_stdlib_socket(sock) - return UNIXDatagramSocket(trio_sock) - - @classmethod - async def wrap_connected_unix_datagram_socket( - cls, sock: socket.socket - ) -> ConnectedUNIXDatagramSocket: - trio_sock = trio.socket.from_stdlib_socket(sock) - return ConnectedUNIXDatagramSocket(trio_sock) - - @classmethod - def current_default_thread_limiter(cls) -> CapacityLimiter: - try: - return _capacity_limiter_wrapper.get() - except LookupError: - limiter = CapacityLimiter( - original=trio.to_thread.current_default_thread_limiter() - ) - _capacity_limiter_wrapper.set(limiter) - return limiter - - @classmethod - def open_signal_receiver( - cls, *signals: Signals - ) -> AbstractContextManager[AsyncIterator[Signals]]: - return _SignalReceiver(signals) - - @classmethod - def get_current_task(cls) -> TaskInfo: - task = current_task() - return TrioTaskInfo(task) - - @classmethod - def get_running_tasks(cls) -> Sequence[TaskInfo]: - root_task = current_root_task() - assert root_task - task_infos = [TrioTaskInfo(root_task)] - nurseries = root_task.child_nurseries - while nurseries: - new_nurseries: list[trio.Nursery] = [] - for nursery in nurseries: - for task in nursery.child_tasks: - task_infos.append(TrioTaskInfo(task)) - new_nurseries.extend(task.child_nurseries) - - nurseries = new_nurseries - - return task_infos - - @classmethod - async def wait_all_tasks_blocked(cls) -> None: - from trio.testing import wait_all_tasks_blocked - - await wait_all_tasks_blocked() - - @classmethod - def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: - return TestRunner(**options) - - -backend_class = TrioBackend diff --git a/server/venv/Lib/site-packages/anyio/_core/__init__.py b/server/venv/Lib/site-packages/anyio/_core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/anyio/_core/_asyncio_selector_thread.py b/server/venv/Lib/site-packages/anyio/_core/_asyncio_selector_thread.py deleted file mode 100644 index 9f35bae..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_asyncio_selector_thread.py +++ /dev/null @@ -1,167 +0,0 @@ -from __future__ import annotations - -import asyncio -import socket -import threading -from collections.abc import Callable -from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector -from typing import TYPE_CHECKING, Any - -if TYPE_CHECKING: - from _typeshed import FileDescriptorLike - -_selector_lock = threading.Lock() -_selector: Selector | None = None - - -class Selector: - def __init__(self) -> None: - self._thread = threading.Thread(target=self.run, name="AnyIO socket selector") - self._selector = DefaultSelector() - self._send, self._receive = socket.socketpair() - self._send.setblocking(False) - self._receive.setblocking(False) - # This somewhat reduces the amount of memory wasted queueing up data - # for wakeups. With these settings, maximum number of 1-byte sends - # before getting BlockingIOError: - # Linux 4.8: 6 - # macOS (darwin 15.5): 1 - # Windows 10: 525347 - # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send - # blocking, even on non-blocking sockets, so don't do that.) - self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1) - self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1) - # On Windows this is a TCP socket so this might matter. On other - # platforms this fails b/c AF_UNIX sockets aren't actually TCP. - try: - self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - except OSError: - pass - - self._selector.register(self._receive, EVENT_READ) - self._closed = False - - def start(self) -> None: - self._thread.start() - threading._register_atexit(self._stop) # type: ignore[attr-defined] - - def _stop(self) -> None: - global _selector - self._closed = True - self._notify_self() - self._send.close() - self._thread.join() - self._selector.unregister(self._receive) - self._receive.close() - self._selector.close() - _selector = None - assert not self._selector.get_map(), ( - "selector still has registered file descriptors after shutdown" - ) - - def _notify_self(self) -> None: - try: - self._send.send(b"\x00") - except BlockingIOError: - pass - - def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: - loop = asyncio.get_running_loop() - try: - key = self._selector.get_key(fd) - except KeyError: - self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)}) - else: - if EVENT_READ in key.data: - raise ValueError( - "this file descriptor is already registered for reading" - ) - - key.data[EVENT_READ] = loop, callback - self._selector.modify(fd, key.events | EVENT_READ, key.data) - - self._notify_self() - - def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None: - loop = asyncio.get_running_loop() - try: - key = self._selector.get_key(fd) - except KeyError: - self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)}) - else: - if EVENT_WRITE in key.data: - raise ValueError( - "this file descriptor is already registered for writing" - ) - - key.data[EVENT_WRITE] = loop, callback - self._selector.modify(fd, key.events | EVENT_WRITE, key.data) - - self._notify_self() - - def remove_reader(self, fd: FileDescriptorLike) -> bool: - try: - key = self._selector.get_key(fd) - except KeyError: - return False - - if new_events := key.events ^ EVENT_READ: - del key.data[EVENT_READ] - self._selector.modify(fd, new_events, key.data) - else: - self._selector.unregister(fd) - - return True - - def remove_writer(self, fd: FileDescriptorLike) -> bool: - try: - key = self._selector.get_key(fd) - except KeyError: - return False - - if new_events := key.events ^ EVENT_WRITE: - del key.data[EVENT_WRITE] - self._selector.modify(fd, new_events, key.data) - else: - self._selector.unregister(fd) - - return True - - def run(self) -> None: - while not self._closed: - for key, events in self._selector.select(): - if key.fileobj is self._receive: - try: - while self._receive.recv(4096): - pass - except BlockingIOError: - pass - - continue - - if events & EVENT_READ: - loop, callback = key.data[EVENT_READ] - self.remove_reader(key.fd) - try: - loop.call_soon_threadsafe(callback) - except RuntimeError: - pass # the loop was already closed - - if events & EVENT_WRITE: - loop, callback = key.data[EVENT_WRITE] - self.remove_writer(key.fd) - try: - loop.call_soon_threadsafe(callback) - except RuntimeError: - pass # the loop was already closed - - -def get_selector() -> Selector: - global _selector - - with _selector_lock: - if _selector is None: - _selector = Selector() - _selector.start() - - return _selector diff --git a/server/venv/Lib/site-packages/anyio/_core/_contextmanagers.py b/server/venv/Lib/site-packages/anyio/_core/_contextmanagers.py deleted file mode 100644 index 302f32b..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_contextmanagers.py +++ /dev/null @@ -1,200 +0,0 @@ -from __future__ import annotations - -from abc import abstractmethod -from contextlib import AbstractAsyncContextManager, AbstractContextManager -from inspect import isasyncgen, iscoroutine, isgenerator -from types import TracebackType -from typing import Protocol, TypeVar, cast, final - -_T_co = TypeVar("_T_co", covariant=True) -_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None") - - -class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]): - def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ... - - -class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]): - def __asynccontextmanager__( - self, - ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ... - - -class ContextManagerMixin: - """ - Mixin class providing context manager functionality via a generator-based - implementation. - - This class allows you to implement a context manager via :meth:`__contextmanager__` - which should return a generator. The mechanics are meant to mirror those of - :func:`@contextmanager `. - - .. note:: Classes using this mix-in are not reentrant as context managers, meaning - that once you enter it, you can't re-enter before first exiting it. - - .. seealso:: :doc:`contextmanagers` - """ - - __cm: AbstractContextManager[object, bool | None] | None = None - - @final - def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co: - # Needed for mypy to assume self still has the __cm member - assert isinstance(self, ContextManagerMixin) - if self.__cm is not None: - raise RuntimeError( - f"this {self.__class__.__qualname__} has already been entered" - ) - - cm = self.__contextmanager__() - if not isinstance(cm, AbstractContextManager): - if isgenerator(cm): - raise TypeError( - "__contextmanager__() returned a generator object instead of " - "a context manager. Did you forget to add the @contextmanager " - "decorator?" - ) - - raise TypeError( - f"__contextmanager__() did not return a context manager object, " - f"but {cm.__class__!r}" - ) - - if cm is self: - raise TypeError( - f"{self.__class__.__qualname__}.__contextmanager__() returned " - f"self. Did you forget to add the @contextmanager decorator and a " - f"'yield' statement?" - ) - - value = cm.__enter__() - self.__cm = cm - return value - - @final - def __exit__( - self: _SupportsCtxMgr[object, _ExitT_co], - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> _ExitT_co: - # Needed for mypy to assume self still has the __cm member - assert isinstance(self, ContextManagerMixin) - if self.__cm is None: - raise RuntimeError( - f"this {self.__class__.__qualname__} has not been entered yet" - ) - - # Prevent circular references - cm = self.__cm - del self.__cm - - return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb)) - - @abstractmethod - def __contextmanager__(self) -> AbstractContextManager[object, bool | None]: - """ - Implement your context manager logic here. - - This method **must** be decorated with - :func:`@contextmanager `. - - .. note:: Remember that the ``yield`` will raise any exception raised in the - enclosed context block, so use a ``finally:`` block to clean up resources! - - :return: a context manager object - """ - - -class AsyncContextManagerMixin: - """ - Mixin class providing async context manager functionality via a generator-based - implementation. - - This class allows you to implement a context manager via - :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of - :func:`@asynccontextmanager `. - - .. note:: Classes using this mix-in are not reentrant as context managers, meaning - that once you enter it, you can't re-enter before first exiting it. - - .. seealso:: :doc:`contextmanagers` - """ - - __cm: AbstractAsyncContextManager[object, bool | None] | None = None - - @final - async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co: - # Needed for mypy to assume self still has the __cm member - assert isinstance(self, AsyncContextManagerMixin) - if self.__cm is not None: - raise RuntimeError( - f"this {self.__class__.__qualname__} has already been entered" - ) - - cm = self.__asynccontextmanager__() - if not isinstance(cm, AbstractAsyncContextManager): - if isasyncgen(cm): - raise TypeError( - "__asynccontextmanager__() returned an async generator instead of " - "an async context manager. Did you forget to add the " - "@asynccontextmanager decorator?" - ) - elif iscoroutine(cm): - cm.close() - raise TypeError( - "__asynccontextmanager__() returned a coroutine object instead of " - "an async context manager. Did you forget to add the " - "@asynccontextmanager decorator and a 'yield' statement?" - ) - - raise TypeError( - f"__asynccontextmanager__() did not return an async context manager, " - f"but {cm.__class__!r}" - ) - - if cm is self: - raise TypeError( - f"{self.__class__.__qualname__}.__asynccontextmanager__() returned " - f"self. Did you forget to add the @asynccontextmanager decorator and a " - f"'yield' statement?" - ) - - value = await cm.__aenter__() - self.__cm = cm - return value - - @final - async def __aexit__( - self: _SupportsAsyncCtxMgr[object, _ExitT_co], - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> _ExitT_co: - assert isinstance(self, AsyncContextManagerMixin) - if self.__cm is None: - raise RuntimeError( - f"this {self.__class__.__qualname__} has not been entered yet" - ) - - # Prevent circular references - cm = self.__cm - del self.__cm - - return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb)) - - @abstractmethod - def __asynccontextmanager__( - self, - ) -> AbstractAsyncContextManager[object, bool | None]: - """ - Implement your async context manager logic here. - - This method **must** be decorated with - :func:`@asynccontextmanager `. - - .. note:: Remember that the ``yield`` will raise any exception raised in the - enclosed context block, so use a ``finally:`` block to clean up resources! - - :return: an async context manager object - """ diff --git a/server/venv/Lib/site-packages/anyio/_core/_eventloop.py b/server/venv/Lib/site-packages/anyio/_core/_eventloop.py deleted file mode 100644 index a3e2ab1..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_eventloop.py +++ /dev/null @@ -1,240 +0,0 @@ -from __future__ import annotations - -import math -import sys -import threading -from collections.abc import Awaitable, Callable, Generator -from contextlib import contextmanager -from contextvars import Token -from importlib import import_module -from typing import TYPE_CHECKING, Any, TypeVar - -from ._exceptions import NoEventLoopError - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -sniffio: Any -try: - import sniffio -except ModuleNotFoundError: - sniffio = None - -if TYPE_CHECKING: - from ..abc import AsyncBackend - -# This must be updated when new backends are introduced -BACKENDS = "asyncio", "trio" - -T_Retval = TypeVar("T_Retval") -PosArgsT = TypeVarTuple("PosArgsT") - -threadlocals = threading.local() -loaded_backends: dict[str, type[AsyncBackend]] = {} - - -def run( - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], - *args: Unpack[PosArgsT], - backend: str = "asyncio", - backend_options: dict[str, Any] | None = None, -) -> T_Retval: - """ - Run the given coroutine function in an asynchronous event loop. - - The current thread must not be already running an event loop. - - :param func: a coroutine function - :param args: positional arguments to ``func`` - :param backend: name of the asynchronous event loop implementation – currently - either ``asyncio`` or ``trio`` - :param backend_options: keyword arguments to call the backend ``run()`` - implementation with (documented :ref:`here `) - :return: the return value of the coroutine function - :raises RuntimeError: if an asynchronous event loop is already running in this - thread - :raises LookupError: if the named backend is not found - - """ - if asynclib_name := current_async_library(): - raise RuntimeError(f"Already running {asynclib_name} in this thread") - - try: - async_backend = get_async_backend(backend) - except ImportError as exc: - if backend in BACKENDS: - raise LookupError( - f"Backend {backend!r} is not available. " - f"Install it with: pip install anyio[{backend}]" - ) from exc - - raise LookupError(f"No such backend: {backend}") from exc - - token = None - if asynclib_name is None: - # Since we're in control of the event loop, we can cache the name of the async - # library - token = set_current_async_library(backend) - - try: - backend_options = backend_options or {} - return async_backend.run(func, args, {}, backend_options) - finally: - reset_current_async_library(token) - - -async def sleep(delay: float) -> None: - """ - Pause the current task for the specified duration. - - :param delay: the duration, in seconds - - """ - return await get_async_backend().sleep(delay) - - -async def sleep_forever() -> None: - """ - Pause the current task until it's cancelled. - - This is a shortcut for ``sleep(math.inf)``. - - .. versionadded:: 3.1 - - """ - await sleep(math.inf) - - -async def sleep_until(deadline: float) -> None: - """ - Pause the current task until the given time. - - :param deadline: the absolute time to wake up at (according to the internal - monotonic clock of the event loop) - - .. versionadded:: 3.1 - - """ - now = current_time() - await sleep(max(deadline - now, 0)) - - -def current_time() -> float: - """ - Return the current value of the event loop's internal clock. - - :return: the clock value (seconds) - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().current_time() - - -def get_all_backends() -> tuple[str, ...]: - """Return a tuple of the names of all built-in backends.""" - return BACKENDS - - -def get_available_backends() -> tuple[str, ...]: - """ - Test for the availability of built-in backends. - - :return a tuple of the built-in backend names that were successfully imported - - .. versionadded:: 4.12 - - """ - available_backends: list[str] = [] - for backend_name in get_all_backends(): - try: - get_async_backend(backend_name) - except ImportError: - continue - - available_backends.append(backend_name) - - return tuple(available_backends) - - -def get_cancelled_exc_class() -> type[BaseException]: - """ - Return the current async library's cancellation exception class. - - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().cancelled_exception_class() - - -# -# Private API -# - - -@contextmanager -def claim_worker_thread( - backend_class: type[AsyncBackend], token: object -) -> Generator[Any, None, None]: - from ..lowlevel import EventLoopToken - - threadlocals.current_token = EventLoopToken(backend_class, token) - try: - yield - finally: - del threadlocals.current_token - - -def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]: - if asynclib_name is None: - asynclib_name = current_async_library() - if not asynclib_name: - raise NoEventLoopError( - f"Not currently running on any asynchronous event loop. " - f"Available async backends: {', '.join(get_all_backends())}" - ) - - # We use our own dict instead of sys.modules to get the already imported back-end - # class because the appropriate modules in sys.modules could potentially be only - # partially initialized - try: - return loaded_backends[asynclib_name] - except KeyError: - module = import_module(f"anyio._backends._{asynclib_name}") - loaded_backends[asynclib_name] = module.backend_class - return module.backend_class - - -def current_async_library() -> str | None: - if sniffio is None: - # If sniffio is not installed, we assume we're either running asyncio or nothing - import asyncio - - try: - asyncio.get_running_loop() - return "asyncio" - except RuntimeError: - pass - else: - try: - return sniffio.current_async_library() - except sniffio.AsyncLibraryNotFoundError: - pass - - return None - - -def set_current_async_library(asynclib_name: str | None) -> Token | None: - # no-op if sniffio is not installed - if sniffio is None: - return None - - return sniffio.current_async_library_cvar.set(asynclib_name) - - -def reset_current_async_library(token: Token | None) -> None: - if token is not None: - sniffio.current_async_library_cvar.reset(token) diff --git a/server/venv/Lib/site-packages/anyio/_core/_exceptions.py b/server/venv/Lib/site-packages/anyio/_core/_exceptions.py deleted file mode 100644 index cd6eb9b..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_exceptions.py +++ /dev/null @@ -1,177 +0,0 @@ -from __future__ import annotations - -import sys -from collections.abc import Generator -from textwrap import dedent -from typing import Any - -if sys.version_info < (3, 11): - from exceptiongroup import BaseExceptionGroup - - -class BrokenResourceError(Exception): - """ - Raised when trying to use a resource that has been rendered unusable due to external - causes (e.g. a send stream whose peer has disconnected). - """ - - -class BrokenWorkerProcess(Exception): - """ - Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or - otherwise misbehaves. - """ - - -class BrokenWorkerInterpreter(Exception): - """ - Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is - raised in the subinterpreter. - """ - - def __init__(self, excinfo: Any): - # This was adapted from concurrent.futures.interpreter.ExecutionFailed - msg = excinfo.formatted - if not msg: - if excinfo.type and excinfo.msg: - msg = f"{excinfo.type.__name__}: {excinfo.msg}" - else: - msg = excinfo.type.__name__ or excinfo.msg - - super().__init__(msg) - self.excinfo = excinfo - - def __str__(self) -> str: - try: - formatted = self.excinfo.errdisplay - except Exception: - return super().__str__() - else: - return dedent( - f""" - {super().__str__()} - - Uncaught in the interpreter: - - {formatted} - """.strip() - ) - - -class BusyResourceError(Exception): - """ - Raised when two tasks are trying to read from or write to the same resource - concurrently. - """ - - def __init__(self, action: str): - super().__init__(f"Another task is already {action} this resource") - - -class ClosedResourceError(Exception): - """Raised when trying to use a resource that has been closed.""" - - -class ConnectionFailed(OSError): - """ - Raised when a connection attempt fails. - - .. note:: This class inherits from :exc:`OSError` for backwards compatibility. - """ - - -def iterate_exceptions( - exception: BaseException, -) -> Generator[BaseException, None, None]: - if isinstance(exception, BaseExceptionGroup): - for exc in exception.exceptions: - yield from iterate_exceptions(exc) - else: - yield exception - - -class DelimiterNotFound(Exception): - """ - Raised during - :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the - maximum number of bytes has been read without the delimiter being found. - """ - - def __init__(self, max_bytes: int) -> None: - super().__init__( - f"The delimiter was not found among the first {max_bytes} bytes" - ) - - -class EndOfStream(Exception): - """ - Raised when trying to read from a stream that has been closed from the other end. - """ - - -class IncompleteRead(Exception): - """ - Raised during - :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or - :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the - connection is closed before the requested amount of bytes has been read. - """ - - def __init__(self) -> None: - super().__init__( - "The stream was closed before the read operation could be completed" - ) - - -class TypedAttributeLookupError(LookupError): - """ - Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute - is not found and no default value has been given. - """ - - -class WouldBlock(Exception): - """Raised by ``X_nowait`` functions if ``X()`` would block.""" - - -class NoEventLoopError(RuntimeError): - """ - Raised by several functions that require an event loop to be running in the current - thread when there is no running event loop. - - This is also raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` - if not calling from an AnyIO worker thread, and no ``token`` was passed. - """ - - -class RunFinishedError(RuntimeError): - """ - Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event - loop associated with the explicitly passed token has already finished. - """ - - def __init__(self) -> None: - super().__init__( - "The event loop associated with the given token has already finished" - ) - - -class TaskFailed(Exception): - """ - Raised when awaiting on, or attempting to access the return value of, a - :class:`.TaskHandle` that raised an exception. - """ - - -class TaskCancelled(TaskFailed): - """ - Raised when awaiting on, or attempting to access the return value of, a - :class:`.TaskHandle` that was cancelled. - """ - - -class TaskNotFinished(Exception): - """ - Raised when attempting to access the return value or exception of a - :class:`.TaskHandle` that is still pending completion. - """ diff --git a/server/venv/Lib/site-packages/anyio/_core/_fileio.py b/server/venv/Lib/site-packages/anyio/_core/_fileio.py deleted file mode 100644 index 692c754..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_fileio.py +++ /dev/null @@ -1,960 +0,0 @@ -from __future__ import annotations - -import os -import pathlib -import sys -from collections.abc import ( - AsyncIterator, - Callable, - Iterable, - Iterator, - Sequence, -) -from dataclasses import dataclass -from functools import partial -from os import PathLike -from typing import ( - IO, - TYPE_CHECKING, - Any, - AnyStr, - ClassVar, - Final, - Generic, - TypeVar, - overload, -) - -from .. import to_thread -from ..abc import AsyncResource -from ._synchronization import CapacityLimiter - -if sys.version_info >= (3, 11): - from typing import Self -else: - from typing_extensions import Self - -if sys.version_info >= (3, 14): - from pathlib.types import PathInfo - -if TYPE_CHECKING: - from types import ModuleType - - from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer -else: - ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object - - -T = TypeVar("T", bound="Path") - - -class AsyncFile(AsyncResource, Generic[AnyStr]): - """ - An asynchronous file object. - - This class wraps a standard file object and provides async friendly versions of the - following blocking methods (where available on the original file object): - - * read - * read1 - * readline - * readlines - * readinto - * readinto1 - * write - * writelines - * truncate - * seek - * tell - * flush - - All other methods are directly passed through. - - This class supports the asynchronous context manager protocol which closes the - underlying file at the end of the context block. - - This class also supports asynchronous iteration:: - - async with await open_file(...) as f: - async for line in f: - print(line) - """ - - def __init__( - self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None - ) -> None: - if limiter is not None and not isinstance(limiter, CapacityLimiter): - raise TypeError( - f"limiter must be a CapacityLimiter or None, not " - f"{limiter.__class__.__name__}" - ) - - self._fp: Any = fp - self._limiter = limiter - - def __getattr__(self, name: str) -> object: - return getattr(self._fp, name) - - @property - def limiter(self) -> CapacityLimiter | None: - """The capacity limiter used by this file object, if not the global limiter.""" - return self._limiter - - @property - def wrapped(self) -> IO[AnyStr]: - """The wrapped file object.""" - return self._fp - - async def __aiter__(self) -> AsyncIterator[AnyStr]: - while True: - line = await self.readline() - if line: - yield line - else: - break - - async def aclose(self) -> None: - return await to_thread.run_sync(self._fp.close, limiter=self._limiter) - - async def read(self, size: int = -1) -> AnyStr: - return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter) - - async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes: - return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter) - - async def readline(self) -> AnyStr: - return await to_thread.run_sync(self._fp.readline, limiter=self._limiter) - - async def readlines(self) -> list[AnyStr]: - return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter) - - async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int: - return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter) - - async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int: - return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter) - - @overload - async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ... - - @overload - async def write(self: AsyncFile[str], b: str) -> int: ... - - async def write(self, b: ReadableBuffer | str) -> int: - return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter) - - @overload - async def writelines( - self: AsyncFile[bytes], lines: Iterable[ReadableBuffer] - ) -> None: ... - - @overload - async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ... - - async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None: - return await to_thread.run_sync( - self._fp.writelines, lines, limiter=self._limiter - ) - - async def truncate(self, size: int | None = None) -> int: - return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter) - - async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: - return await to_thread.run_sync( - self._fp.seek, offset, whence, limiter=self._limiter - ) - - async def tell(self) -> int: - return await to_thread.run_sync(self._fp.tell, limiter=self._limiter) - - async def flush(self) -> None: - return await to_thread.run_sync(self._fp.flush, limiter=self._limiter) - - -@overload -async def open_file( - file: str | PathLike[str] | int, - mode: OpenBinaryMode, - buffering: int = ..., - encoding: str | None = ..., - errors: str | None = ..., - newline: str | None = ..., - closefd: bool = ..., - opener: Callable[[str, int], int] | None = ..., - *, - limiter: CapacityLimiter | None = ..., -) -> AsyncFile[bytes]: ... - - -@overload -async def open_file( - file: str | PathLike[str] | int, - mode: OpenTextMode = ..., - buffering: int = ..., - encoding: str | None = ..., - errors: str | None = ..., - newline: str | None = ..., - closefd: bool = ..., - opener: Callable[[str, int], int] | None = ..., - *, - limiter: CapacityLimiter | None = ..., -) -> AsyncFile[str]: ... - - -async def open_file( - file: str | PathLike[str] | int, - mode: str = "r", - buffering: int = -1, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - closefd: bool = True, - opener: Callable[[str, int], int] | None = None, - *, - limiter: CapacityLimiter | None = None, -) -> AsyncFile[Any]: - """ - Open a file asynchronously. - - Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`. - - :param limiter: an optional capacity limiter to use with the file - instead of the default one - :return: an asynchronous file object - - .. versionchanged:: 4.14.0 - Added the ``limiter`` keyword argument. - - """ - fp = await to_thread.run_sync( - open, - file, - mode, - buffering, - encoding, - errors, - newline, - closefd, - opener, - limiter=limiter, - ) - return AsyncFile(fp, limiter=limiter) - - -def wrap_file( - file: IO[AnyStr], *, limiter: CapacityLimiter | None = None -) -> AsyncFile[AnyStr]: - """ - Wrap an existing file as an asynchronous file. - - :param file: an existing file-like object - :param limiter: an optional capacity limiter to use with the file - instead of the default one - :return: an asynchronous file object - - .. versionchanged:: 4.14.0 - Added the ``limiter`` keyword argument. - - """ - return AsyncFile(file, limiter=limiter) - - -@dataclass(eq=False) -class _PathIterator(AsyncIterator[T]): - iterator: Iterator[PathLike[str]] - limiter: CapacityLimiter | None - # This was added to ensure that iterating over a subclass of Path yields instances - # of that subclass rather than the base Path class. - path_cls: type[T] - - async def __anext__(self) -> T: - nextval = await to_thread.run_sync( - next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter - ) - if nextval is None: - raise StopAsyncIteration from None - - return self.path_cls(nextval, limiter=self.limiter) - - -class Path: - """ - An asynchronous version of :class:`pathlib.Path`. - - This class cannot be substituted for :class:`pathlib.Path` or - :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike` - interface. - - It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for - the deprecated :meth:`~pathlib.Path.link_to` method. - - Some methods may be unavailable or have limited functionality, based on the Python - version: - - * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later) - * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later) - * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later) - * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later) - * :attr:`~pathlib.Path.info` (available on Python 3.14 or later) - * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later) - * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only - available on Python 3.13 or later) - * :meth:`~pathlib.Path.move` (available on Python 3.14 or later) - * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later) - * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available - on Python 3.12 or later) - * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later) - - Any methods that do disk I/O need to be awaited on. These methods are: - - * :meth:`~pathlib.Path.absolute` - * :meth:`~pathlib.Path.chmod` - * :meth:`~pathlib.Path.cwd` - * :meth:`~pathlib.Path.exists` - * :meth:`~pathlib.Path.expanduser` - * :meth:`~pathlib.Path.group` - * :meth:`~pathlib.Path.hardlink_to` - * :meth:`~pathlib.Path.home` - * :meth:`~pathlib.Path.is_block_device` - * :meth:`~pathlib.Path.is_char_device` - * :meth:`~pathlib.Path.is_dir` - * :meth:`~pathlib.Path.is_fifo` - * :meth:`~pathlib.Path.is_file` - * :meth:`~pathlib.Path.is_junction` - * :meth:`~pathlib.Path.is_mount` - * :meth:`~pathlib.Path.is_socket` - * :meth:`~pathlib.Path.is_symlink` - * :meth:`~pathlib.Path.lchmod` - * :meth:`~pathlib.Path.lstat` - * :meth:`~pathlib.Path.mkdir` - * :meth:`~pathlib.Path.open` - * :meth:`~pathlib.Path.owner` - * :meth:`~pathlib.Path.read_bytes` - * :meth:`~pathlib.Path.read_text` - * :meth:`~pathlib.Path.readlink` - * :meth:`~pathlib.Path.rename` - * :meth:`~pathlib.Path.replace` - * :meth:`~pathlib.Path.resolve` - * :meth:`~pathlib.Path.rmdir` - * :meth:`~pathlib.Path.samefile` - * :meth:`~pathlib.Path.stat` - * :meth:`~pathlib.Path.symlink_to` - * :meth:`~pathlib.Path.touch` - * :meth:`~pathlib.Path.unlink` - * :meth:`~pathlib.Path.walk` - * :meth:`~pathlib.Path.write_bytes` - * :meth:`~pathlib.Path.write_text` - - Additionally, the following methods return an async iterator yielding - :class:`~.Path` objects: - - * :meth:`~pathlib.Path.glob` - * :meth:`~pathlib.Path.iterdir` - * :meth:`~pathlib.Path.rglob` - - .. versionchanged:: 4.14.0 - Added the ``limiter`` keyword argument. - """ - - __slots__ = "_path", "_limiter", "__weakref__" - - __weakref__: Any - - def __init__( - self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None - ) -> None: - if limiter is not None and not isinstance(limiter, CapacityLimiter): - raise TypeError( - f"limiter must be a CapacityLimiter or None, not " - f"{limiter.__class__.__name__}" - ) - - self._path: Final[pathlib.Path] = pathlib.Path(*args) - self._limiter = limiter - - def __fspath__(self) -> str: - return self._path.__fspath__() - - if sys.version_info >= (3, 15): - - def __vfspath__(self) -> str: - return self._path.__vfspath__() - - def __str__(self) -> str: - return self._path.__str__() - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.as_posix()!r})" - - def __bytes__(self) -> bytes: - return self._path.__bytes__() - - def __hash__(self) -> int: - return self._path.__hash__() - - def __eq__(self, other: object) -> bool: - target = other._path if isinstance(other, Path) else other - return self._path.__eq__(target) - - def __lt__(self, other: pathlib.PurePath | Path) -> bool: - target = other._path if isinstance(other, Path) else other - return self._path.__lt__(target) - - def __le__(self, other: pathlib.PurePath | Path) -> bool: - target = other._path if isinstance(other, Path) else other - return self._path.__le__(target) - - def __gt__(self, other: pathlib.PurePath | Path) -> bool: - target = other._path if isinstance(other, Path) else other - return self._path.__gt__(target) - - def __ge__(self, other: pathlib.PurePath | Path) -> bool: - target = other._path if isinstance(other, Path) else other - return self._path.__ge__(target) - - def __truediv__(self, other: str | PathLike[str]) -> Self: - return type(self)(self._path / other, limiter=self._limiter) - - def __rtruediv__(self, other: str | PathLike[str]) -> Self: - return type(self)(other, limiter=self._limiter) / self - - @property - def limiter(self) -> CapacityLimiter | None: - """The capacity limiter used by this path, if not the global limiter.""" - return self._limiter - - @property - def parts(self) -> tuple[str, ...]: - return self._path.parts - - @property - def drive(self) -> str: - return self._path.drive - - @property - def root(self) -> str: - return self._path.root - - @property - def anchor(self) -> str: - return self._path.anchor - - @property - def parents(self) -> Sequence[Self]: - return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents) - - @property - def parent(self) -> Self: - return type(self)(self._path.parent, limiter=self._limiter) - - @property - def name(self) -> str: - return self._path.name - - @property - def suffix(self) -> str: - return self._path.suffix - - @property - def suffixes(self) -> list[str]: - return self._path.suffixes - - @property - def stem(self) -> str: - return self._path.stem - - async def absolute(self) -> Self: - path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter) - return type(self)(path, limiter=self._limiter) - - def as_posix(self) -> str: - return self._path.as_posix() - - def as_uri(self) -> str: - return self._path.as_uri() - - if sys.version_info >= (3, 13): - parser: ClassVar[ModuleType] = pathlib.Path.parser - - @classmethod - def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self: - return cls(pathlib.Path.from_uri(uri), limiter=limiter) - - def full_match( - self, path_pattern: str, *, case_sensitive: bool | None = None - ) -> bool: - return self._path.full_match(path_pattern, case_sensitive=case_sensitive) - - def match( - self, path_pattern: str, *, case_sensitive: bool | None = None - ) -> bool: - return self._path.match(path_pattern, case_sensitive=case_sensitive) - else: - - def match(self, path_pattern: str) -> bool: - return self._path.match(path_pattern) - - if sys.version_info >= (3, 14): - - @property - def info(self) -> PathInfo: - return self._path.info - - async def copy( - self, - target: str | os.PathLike[str], - *, - follow_symlinks: bool = True, - preserve_metadata: bool = False, - ) -> Self: - func = partial( - self._path.copy, - follow_symlinks=follow_symlinks, - preserve_metadata=preserve_metadata, - ) - return type(self)( - await to_thread.run_sync( - func, pathlib.Path(target), limiter=self._limiter - ), - limiter=self._limiter, - ) - - async def copy_into( - self, - target_dir: str | os.PathLike[str], - *, - follow_symlinks: bool = True, - preserve_metadata: bool = False, - ) -> Self: - func = partial( - self._path.copy_into, - follow_symlinks=follow_symlinks, - preserve_metadata=preserve_metadata, - ) - return type(self)( - await to_thread.run_sync( - func, pathlib.Path(target_dir), limiter=self._limiter - ), - limiter=self._limiter, - ) - - async def move(self, target: str | os.PathLike[str]) -> Self: - # Upstream does not handle anyio.Path properly as a PathLike - target = pathlib.Path(target) - return type(self)( - await to_thread.run_sync( - self._path.move, target, limiter=self._limiter - ), - limiter=self._limiter, - ) - - async def move_into( - self, - target_dir: str | os.PathLike[str], - ) -> Self: - return type(self)( - await to_thread.run_sync( - self._path.move_into, target_dir, limiter=self._limiter - ), - limiter=self._limiter, - ) - - def is_relative_to(self, other: str | PathLike[str]) -> bool: - try: - self.relative_to(other) - return True - except ValueError: - return False - - async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None: - func = partial(os.chmod, follow_symlinks=follow_symlinks) - return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter) - - @classmethod - async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self: - path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter) - return cls(path, limiter=limiter) - - async def exists(self) -> bool: - return await to_thread.run_sync( - self._path.exists, abandon_on_cancel=True, limiter=self._limiter - ) - - async def expanduser(self) -> Self: - return type(self)( - await to_thread.run_sync( - self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter - ), - limiter=self._limiter, - ) - - if sys.version_info < (3, 12): - # Python 3.11 and earlier - def glob(self, pattern: str) -> AsyncIterator[Self]: - gen = self._path.glob(pattern) - return _PathIterator(gen, self._limiter, type(self)) - elif (3, 12) <= sys.version_info < (3, 13): - # changed in Python 3.12: - # - The case_sensitive parameter was added. - def glob( - self, - pattern: str, - *, - case_sensitive: bool | None = None, - ) -> AsyncIterator[Self]: - gen = self._path.glob(pattern, case_sensitive=case_sensitive) - return _PathIterator(gen, self._limiter, type(self)) - elif sys.version_info >= (3, 13): - # Changed in Python 3.13: - # - The recurse_symlinks parameter was added. - # - The pattern parameter accepts a path-like object. - def glob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block - self, - pattern: str | PathLike[str], - *, - case_sensitive: bool | None = None, - recurse_symlinks: bool = False, - ) -> AsyncIterator[Self]: - gen = self._path.glob( - pattern, # type: ignore[arg-type] - case_sensitive=case_sensitive, - recurse_symlinks=recurse_symlinks, - ) - return _PathIterator(gen, self._limiter, type(self)) - - async def group(self) -> str: - return await to_thread.run_sync( - self._path.group, abandon_on_cancel=True, limiter=self._limiter - ) - - async def hardlink_to( - self, target: str | bytes | PathLike[str] | PathLike[bytes] - ) -> None: - if isinstance(target, Path): - target = target._path - - await to_thread.run_sync(os.link, target, self, limiter=self._limiter) - - @classmethod - async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self: - home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter) - return cls(home_path, limiter=limiter) - - def is_absolute(self) -> bool: - return self._path.is_absolute() - - async def is_block_device(self) -> bool: - return await to_thread.run_sync( - self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter - ) - - async def is_char_device(self) -> bool: - return await to_thread.run_sync( - self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter - ) - - async def is_dir(self) -> bool: - return await to_thread.run_sync( - self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter - ) - - async def is_fifo(self) -> bool: - return await to_thread.run_sync( - self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter - ) - - async def is_file(self) -> bool: - return await to_thread.run_sync( - self._path.is_file, abandon_on_cancel=True, limiter=self._limiter - ) - - if sys.version_info >= (3, 12): - - async def is_junction(self) -> bool: - return await to_thread.run_sync( - self._path.is_junction, limiter=self._limiter - ) - - async def is_mount(self) -> bool: - return await to_thread.run_sync( - os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter - ) - - if sys.version_info < (3, 15): - - def is_reserved(self) -> bool: - return self._path.is_reserved() - - async def is_socket(self) -> bool: - return await to_thread.run_sync( - self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter - ) - - async def is_symlink(self) -> bool: - return await to_thread.run_sync( - self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter - ) - - async def iterdir(self) -> AsyncIterator[Self]: - gen = ( - self._path.iterdir() - if sys.version_info < (3, 13) - else await to_thread.run_sync( - self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter - ) - ) - async for path in _PathIterator(gen, self._limiter, type(self)): - yield path - - def joinpath(self, *args: str | PathLike[str]) -> Self: - return type(self)(self._path.joinpath(*args), limiter=self._limiter) - - async def lchmod(self, mode: int) -> None: - await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter) - - async def lstat(self) -> os.stat_result: - return await to_thread.run_sync( - self._path.lstat, abandon_on_cancel=True, limiter=self._limiter - ) - - async def mkdir( - self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False - ) -> None: - await to_thread.run_sync( - self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter - ) - - @overload - async def open( - self, - mode: OpenBinaryMode, - buffering: int = ..., - encoding: str | None = ..., - errors: str | None = ..., - newline: str | None = ..., - ) -> AsyncFile[bytes]: ... - - @overload - async def open( - self, - mode: OpenTextMode = ..., - buffering: int = ..., - encoding: str | None = ..., - errors: str | None = ..., - newline: str | None = ..., - ) -> AsyncFile[str]: ... - - async def open( - self, - mode: str = "r", - buffering: int = -1, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - ) -> AsyncFile[Any]: - fp = await to_thread.run_sync( - self._path.open, - mode, - buffering, - encoding, - errors, - newline, - limiter=self._limiter, - ) - return AsyncFile(fp, limiter=self._limiter) - - async def owner(self) -> str: - return await to_thread.run_sync( - self._path.owner, abandon_on_cancel=True, limiter=self._limiter - ) - - async def read_bytes(self) -> bytes: - return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter) - - async def read_text( - self, encoding: str | None = None, errors: str | None = None - ) -> str: - return await to_thread.run_sync( - self._path.read_text, encoding, errors, limiter=self._limiter - ) - - if sys.version_info >= (3, 12): - - def relative_to( - self, *other: str | PathLike[str], walk_up: bool = False - ) -> Self: - # relative_to() should work with any PathLike but it doesn't - others = [pathlib.Path(other) for other in other] - return type(self)( - self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter - ) - - else: - - def relative_to(self, *other: str | PathLike[str]) -> Self: - return type(self)(self._path.relative_to(*other), limiter=self._limiter) - - async def readlink(self) -> Self: - target = await to_thread.run_sync( - os.readlink, self._path, limiter=self._limiter - ) - return type(self)(target, limiter=self._limiter) - - async def rename(self, target: str | pathlib.PurePath | Path) -> Self: - if isinstance(target, Path): - target = target._path - - await to_thread.run_sync(self._path.rename, target, limiter=self._limiter) - return type(self)(target, limiter=self._limiter) - - async def replace(self, target: str | pathlib.PurePath | Path) -> Self: - if isinstance(target, Path): - target = target._path - - await to_thread.run_sync(self._path.replace, target, limiter=self._limiter) - return type(self)(target, limiter=self._limiter) - - async def resolve(self, strict: bool = False) -> Self: - func = partial(self._path.resolve, strict=strict) - return type(self)( - await to_thread.run_sync( - func, abandon_on_cancel=True, limiter=self._limiter - ), - limiter=self._limiter, - ) - - if sys.version_info < (3, 12): - # Pre Python 3.12 - def rglob(self, pattern: str) -> AsyncIterator[Self]: - gen = self._path.rglob(pattern) - return _PathIterator(gen, self._limiter, type(self)) - elif (3, 12) <= sys.version_info < (3, 13): - # Changed in Python 3.12: - # - The case_sensitive parameter was added. - def rglob( - self, pattern: str, *, case_sensitive: bool | None = None - ) -> AsyncIterator[Self]: - gen = self._path.rglob(pattern, case_sensitive=case_sensitive) - return _PathIterator(gen, self._limiter, type(self)) - elif sys.version_info >= (3, 13): - # Changed in Python 3.13: - # - The recurse_symlinks parameter was added. - # - The pattern parameter accepts a path-like object. - def rglob( # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block - self, - pattern: str | PathLike[str], - *, - case_sensitive: bool | None = None, - recurse_symlinks: bool = False, - ) -> AsyncIterator[Self]: - gen = self._path.rglob( - pattern, # type: ignore[arg-type] - case_sensitive=case_sensitive, - recurse_symlinks=recurse_symlinks, - ) - return _PathIterator(gen, self._limiter, type(self)) - - async def rmdir(self) -> None: - await to_thread.run_sync(self._path.rmdir, limiter=self._limiter) - - async def samefile(self, other_path: str | PathLike[str]) -> bool: - if isinstance(other_path, Path): - other_path = other_path._path - - return await to_thread.run_sync( - self._path.samefile, - other_path, - abandon_on_cancel=True, - limiter=self._limiter, - ) - - async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result: - func = partial(os.stat, follow_symlinks=follow_symlinks) - return await to_thread.run_sync( - func, self._path, abandon_on_cancel=True, limiter=self._limiter - ) - - async def symlink_to( - self, - target: str | bytes | PathLike[str] | PathLike[bytes], - target_is_directory: bool = False, - ) -> None: - if isinstance(target, Path): - target = target._path - - await to_thread.run_sync( - self._path.symlink_to, target, target_is_directory, limiter=self._limiter - ) - - async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None: - await to_thread.run_sync( - self._path.touch, mode, exist_ok, limiter=self._limiter - ) - - async def unlink(self, missing_ok: bool = False) -> None: - try: - await to_thread.run_sync(self._path.unlink, limiter=self._limiter) - except FileNotFoundError: - if not missing_ok: - raise - - if sys.version_info >= (3, 12): - - async def walk( - self, - top_down: bool = True, - on_error: Callable[[OSError], object] | None = None, - follow_symlinks: bool = False, - ) -> AsyncIterator[tuple[Self, list[str], list[str]]]: - def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None: - try: - return next(gen) - except StopIteration: - return None - - gen = self._path.walk(top_down, on_error, follow_symlinks) - while True: - value = await to_thread.run_sync(get_next_value, limiter=self._limiter) - if value is None: - return - - root, dirs, paths = value - yield type(self)(root, limiter=self._limiter), dirs, paths - - def with_name(self, name: str) -> Self: - return type(self)(self._path.with_name(name), limiter=self._limiter) - - def with_stem(self, stem: str) -> Self: - return type(self)( - self._path.with_name(stem + self._path.suffix), limiter=self._limiter - ) - - def with_suffix(self, suffix: str) -> Self: - return type(self)(self._path.with_suffix(suffix), limiter=self._limiter) - - def with_segments(self, *pathsegments: str | PathLike[str]) -> Self: - return type(self)(*pathsegments, limiter=self._limiter) - - async def write_bytes(self, data: ReadableBuffer) -> int: - return await to_thread.run_sync( - self._path.write_bytes, data, limiter=self._limiter - ) - - async def write_text( - self, - data: str, - encoding: str | None = None, - errors: str | None = None, - newline: str | None = None, - ) -> int: - return await to_thread.run_sync( - self._path.write_text, - data, - encoding, - errors, - newline, - limiter=self._limiter, - ) - - -PathLike.register(Path) diff --git a/server/venv/Lib/site-packages/anyio/_core/_resources.py b/server/venv/Lib/site-packages/anyio/_core/_resources.py deleted file mode 100644 index b9a5344..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_resources.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import annotations - -from ..abc import AsyncResource -from ._tasks import CancelScope - - -async def aclose_forcefully(resource: AsyncResource) -> None: - """ - Close an asynchronous resource in a cancelled scope. - - Doing this closes the resource without waiting on anything. - - :param resource: the resource to close - - """ - with CancelScope() as scope: - scope.cancel() - await resource.aclose() diff --git a/server/venv/Lib/site-packages/anyio/_core/_signals.py b/server/venv/Lib/site-packages/anyio/_core/_signals.py deleted file mode 100644 index e24c79e..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_signals.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from collections.abc import AsyncIterator -from contextlib import AbstractContextManager -from signal import Signals - -from ._eventloop import get_async_backend - - -def open_signal_receiver( - *signals: Signals, -) -> AbstractContextManager[AsyncIterator[Signals]]: - """ - Start receiving operating system signals. - - :param signals: signals to receive (e.g. ``signal.SIGINT``) - :return: an asynchronous context manager for an asynchronous iterator which yields - signal numbers - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - .. warning:: Windows does not support signals natively so it is best to avoid - relying on this in cross-platform applications. - - .. warning:: On asyncio, this permanently replaces any previous signal handler for - the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`. - - """ - return get_async_backend().open_signal_receiver(*signals) diff --git a/server/venv/Lib/site-packages/anyio/_core/_sockets.py b/server/venv/Lib/site-packages/anyio/_core/_sockets.py deleted file mode 100644 index 29f7332..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_sockets.py +++ /dev/null @@ -1,1011 +0,0 @@ -from __future__ import annotations - -import errno -import os -import socket -import ssl -import stat -import sys -from collections.abc import Awaitable -from dataclasses import dataclass -from ipaddress import IPv4Address, IPv6Address, ip_address -from os import PathLike, chmod -from socket import AddressFamily, SocketKind -from typing import TYPE_CHECKING, Any, Literal, cast, overload - -from .. import ConnectionFailed, to_thread -from ..abc import ( - ByteStreamConnectable, - ConnectedUDPSocket, - ConnectedUNIXDatagramSocket, - IPAddressType, - IPSockAddrType, - SocketListener, - SocketStream, - UDPSocket, - UNIXDatagramSocket, - UNIXSocketStream, -) -from ..streams.stapled import MultiListener -from ..streams.tls import TLSConnectable, TLSStream -from ._eventloop import get_async_backend -from ._resources import aclose_forcefully -from ._synchronization import Event -from ._tasks import create_task_group, move_on_after - -if TYPE_CHECKING: - from _typeshed import FileDescriptorLike -else: - FileDescriptorLike = object - -if sys.version_info < (3, 11): - from exceptiongroup import ExceptionGroup - -if sys.version_info >= (3, 12): - from typing import override -else: - from typing_extensions import override - -if sys.version_info < (3, 13): - from typing_extensions import deprecated -else: - from warnings import deprecated - -IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41) # https://bugs.python.org/issue29515 - -AnyIPAddressFamily = Literal[ - AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6 -] -IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6] - - -# tls_hostname given -@overload -async def connect_tcp( - remote_host: IPAddressType, - remote_port: int, - *, - local_host: IPAddressType | None = ..., - local_port: int | None = ..., - ssl_context: ssl.SSLContext | None = ..., - tls_standard_compatible: bool = ..., - tls_hostname: str, - happy_eyeballs_delay: float = ..., -) -> TLSStream: ... - - -# ssl_context given -@overload -async def connect_tcp( - remote_host: IPAddressType, - remote_port: int, - *, - local_host: IPAddressType | None = ..., - local_port: int | None = ..., - ssl_context: ssl.SSLContext, - tls_standard_compatible: bool = ..., - tls_hostname: str | None = ..., - happy_eyeballs_delay: float = ..., -) -> TLSStream: ... - - -# tls=True -@overload -async def connect_tcp( - remote_host: IPAddressType, - remote_port: int, - *, - local_host: IPAddressType | None = ..., - local_port: int | None = ..., - tls: Literal[True], - ssl_context: ssl.SSLContext | None = ..., - tls_standard_compatible: bool = ..., - tls_hostname: str | None = ..., - happy_eyeballs_delay: float = ..., -) -> TLSStream: ... - - -# tls=False -@overload -async def connect_tcp( - remote_host: IPAddressType, - remote_port: int, - *, - local_host: IPAddressType | None = ..., - local_port: int | None = ..., - tls: Literal[False], - ssl_context: ssl.SSLContext | None = ..., - tls_standard_compatible: bool = ..., - tls_hostname: str | None = ..., - happy_eyeballs_delay: float = ..., -) -> SocketStream: ... - - -# No TLS arguments -@overload -async def connect_tcp( - remote_host: IPAddressType, - remote_port: int, - *, - local_host: IPAddressType | None = ..., - local_port: int | None = ..., - happy_eyeballs_delay: float = ..., -) -> SocketStream: ... - - -async def connect_tcp( - remote_host: IPAddressType, - remote_port: int, - *, - local_host: IPAddressType | None = None, - local_port: int | None = None, - tls: bool = False, - ssl_context: ssl.SSLContext | None = None, - tls_standard_compatible: bool = True, - tls_hostname: str | None = None, - happy_eyeballs_delay: float = 0.25, -) -> SocketStream | TLSStream: - """ - Connect to a host using the TCP protocol. - - This function implements the stateless version of the Happy Eyeballs algorithm (RFC - 6555). If ``remote_host`` is a host name that resolves to multiple IP addresses, - each one is tried until one connection attempt succeeds. If the first attempt does - not connected within 250 milliseconds, a second attempt is started using the next - address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if - available) is tried first. - - When the connection has been established, a TLS handshake will be done if either - ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``. - - :param remote_host: the IP address or host name to connect to - :param remote_port: port on the target host to connect to - :param local_host: the interface address or name to bind the socket to before - connecting - :param local_port: the local port to bind to (requires ``local_host`` to also be - set) - :param tls: ``True`` to do a TLS handshake with the connected stream and return a - :class:`~anyio.streams.tls.TLSStream` instead - :param ssl_context: the SSL context object to use (if omitted, a default context is - created) - :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake - before closing the stream and requires that the server does this as well. - Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream. - Some protocols, such as HTTP, require this option to be ``False``. - See :meth:`~ssl.SSLContext.wrap_socket` for details. - :param tls_hostname: host name to check the server certificate against (defaults to - the value of ``remote_host``) - :param happy_eyeballs_delay: delay (in seconds) before starting the next connection - attempt - :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream - :raises ConnectionFailed: if the connection fails - - """ - # Placed here due to https://github.com/python/mypy/issues/7057 - connected_stream: SocketStream | None = None - - async def try_connect(remote_host: str, event: Event) -> None: - nonlocal connected_stream - try: - stream = await asynclib.connect_tcp(remote_host, remote_port, local_address) - except OSError as exc: - oserrors.append(exc) - return - else: - if connected_stream is None: - connected_stream = stream - tg.cancel_scope.cancel() - else: - await stream.aclose() - finally: - event.set() - - asynclib = get_async_backend() - local_address: IPSockAddrType | None = None - family = socket.AF_UNSPEC - if local_host: - gai_res = await getaddrinfo(str(local_host), local_port) - family, *_, local_address = gai_res[0] - - target_host = str(remote_host) - try: - addr_obj = ip_address(remote_host) - except ValueError: - addr_obj = None - - if addr_obj is not None: - if isinstance(addr_obj, IPv6Address): - target_addrs = [(socket.AF_INET6, addr_obj.compressed)] - else: - target_addrs = [(socket.AF_INET, addr_obj.compressed)] - else: - # getaddrinfo() will raise an exception if name resolution fails - gai_res = await getaddrinfo( - target_host, remote_port, family=family, type=socket.SOCK_STREAM - ) - - # Organize the list so that the first address is an IPv6 address (if available) - # and the second one is an IPv4 addresses. The rest can be in whatever order. - v6_found = v4_found = False - target_addrs = [] - for af, *_, sa in gai_res: - if af == socket.AF_INET6 and not v6_found: - v6_found = True - target_addrs.insert(0, (af, sa[0])) - elif af == socket.AF_INET and not v4_found and v6_found: - v4_found = True - target_addrs.insert(1, (af, sa[0])) - else: - target_addrs.append((af, sa[0])) - - oserrors: list[OSError] = [] - try: - async with create_task_group() as tg: - for _af, addr in target_addrs: - event = Event() - tg.start_soon(try_connect, addr, event) - with move_on_after(happy_eyeballs_delay): - await event.wait() - - if connected_stream is None: - cause = ( - oserrors[0] - if len(oserrors) == 1 - else ExceptionGroup("multiple connection attempts failed", oserrors) - ) - raise OSError("All connection attempts failed") from cause - finally: - oserrors.clear() - - if tls or tls_hostname or ssl_context: - try: - return await TLSStream.wrap( - connected_stream, - server_side=False, - hostname=tls_hostname or str(remote_host), - ssl_context=ssl_context, - standard_compatible=tls_standard_compatible, - ) - except BaseException: - await aclose_forcefully(connected_stream) - raise - - return connected_stream - - -async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream: - """ - Connect to the given UNIX socket. - - Not available on Windows. - - :param path: path to the socket - :return: a socket stream object - :raises ConnectionFailed: if the connection fails - - """ - path = os.fspath(path) - return await get_async_backend().connect_unix(path) - - -async def create_tcp_listener( - *, - local_host: IPAddressType | None = None, - local_port: int = 0, - family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC, - backlog: int = 65536, - reuse_port: bool = False, -) -> MultiListener[SocketStream]: - """ - Create a TCP socket listener. - - :param local_port: port number to listen on - :param local_host: IP address of the interface to listen on. If omitted, listen on - all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address - family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6. - :param family: address family (used if ``local_host`` was omitted) - :param backlog: maximum number of queued incoming connections (up to a maximum of - 2**16, or 65536) - :param reuse_port: ``True`` to allow multiple sockets to bind to the same - address/port (not supported on Windows) - :return: a multi-listener object containing one or more socket listeners - :raises OSError: if there's an error creating a socket, or binding to one or more - interfaces failed - - """ - asynclib = get_async_backend() - backlog = min(backlog, 65536) - local_host = str(local_host) if local_host is not None else None - - def setup_raw_socket( - fam: AddressFamily, - bind_addr: tuple[str, int] | tuple[str, int, int, int], - *, - v6only: bool = True, - ) -> socket.socket: - sock = socket.socket(fam) - try: - sock.setblocking(False) - - if fam == AddressFamily.AF_INET6: - sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only) - - # For Windows, enable exclusive address use. For others, enable address - # reuse. - if sys.platform == "win32": - sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) - else: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - - if reuse_port: - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) - - # Workaround for #554 - if fam == socket.AF_INET6 and "%" in bind_addr[0]: - addr, scope_id = bind_addr[0].split("%", 1) - bind_addr = (addr, bind_addr[1], 0, int(scope_id)) - - sock.bind(bind_addr) - sock.listen(backlog) - except BaseException: - sock.close() - raise - - return sock - - # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug - # where we don't get the correct scope ID for IPv6 link-local addresses when passing - # type=socket.SOCK_STREAM to getaddrinfo(): - # https://github.com/MagicStack/uvloop/issues/539 - gai_res = await getaddrinfo( - local_host, - local_port, - family=family, - type=socket.SOCK_STREAM if sys.platform == "win32" else 0, - flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, - ) - - # The set comprehension is here to work around a glibc bug: - # https://sourceware.org/bugzilla/show_bug.cgi?id=14969 - sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM}) - - # Special case for dual-stack binding on the "any" interface - if ( - local_host is None - and family == AddressFamily.AF_UNSPEC - and socket.has_dualstack_ipv6() - and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res) - ): - raw_socket = setup_raw_socket( - AddressFamily.AF_INET6, ("::", local_port), v6only=False - ) - listener = asynclib.create_tcp_listener(raw_socket) - return MultiListener([listener]) - - errors: list[OSError] = [] - try: - for _ in range(len(sockaddrs)): - listeners: list[SocketListener] = [] - bound_ephemeral_port = local_port - try: - for fam, *_, sockaddr in sockaddrs: - sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:] - raw_socket = setup_raw_socket(fam, sockaddr) - - # Store the assigned port if an ephemeral port was requested, so - # we'll bind to the same port on all interfaces - if local_port == 0 and len(gai_res) > 1: - bound_ephemeral_port = raw_socket.getsockname()[1] - - listeners.append(asynclib.create_tcp_listener(raw_socket)) - except BaseException as exc: - for listener in listeners: - await listener.aclose() - - # If an ephemeral port was requested but binding the assigned port - # failed for another interface, rotate the address list and try again - if ( - isinstance(exc, OSError) - and exc.errno == errno.EADDRINUSE - and local_port == 0 - and bound_ephemeral_port - ): - errors.append(exc) - sockaddrs.append(sockaddrs.pop(0)) - continue - - raise - - return MultiListener(listeners) - - raise OSError( - f"Could not create {len(sockaddrs)} listeners with a consistent port" - ) from ExceptionGroup("Several bind attempts failed", errors) - finally: - del errors # Prevent reference cycles - - -async def create_unix_listener( - path: str | bytes | PathLike[Any], - *, - mode: int | None = None, - backlog: int = 65536, -) -> SocketListener: - """ - Create a UNIX socket listener. - - Not available on Windows. - - :param path: path of the socket - :param mode: permissions to set on the socket - :param backlog: maximum number of queued incoming connections (up to a maximum of - 2**16, or 65536) - :return: a listener object - - .. versionchanged:: 3.0 - If a socket already exists on the file system in the given path, it will be - removed first. - - """ - backlog = min(backlog, 65536) - raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM) - try: - raw_socket.listen(backlog) - return get_async_backend().create_unix_listener(raw_socket) - except BaseException: - raw_socket.close() - raise - - -async def create_udp_socket( - family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, - *, - local_host: IPAddressType | None = None, - local_port: int = 0, - reuse_port: bool = False, -) -> UDPSocket: - """ - Create a UDP socket. - - If ``port`` has been given, the socket will be bound to this port on the local - machine, making this socket suitable for providing UDP based services. - - :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically - determined from ``local_host`` if omitted - :param local_host: IP address or host name of the local interface to bind to - :param local_port: local port to bind to - :param reuse_port: ``True`` to allow multiple sockets to bind to the same - address/port (not supported on Windows) - :return: a UDP socket - - """ - if family is AddressFamily.AF_UNSPEC and not local_host: - raise ValueError('Either "family" or "local_host" must be given') - - if local_host: - gai_res = await getaddrinfo( - str(local_host), - local_port, - family=family, - type=socket.SOCK_DGRAM, - flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, - ) - family = cast(AnyIPAddressFamily, gai_res[0][0]) - local_address = gai_res[0][-1] - elif family is AddressFamily.AF_INET6: - local_address = ("::", 0) - else: - local_address = ("0.0.0.0", 0) - - sock = await get_async_backend().create_udp_socket( - family, local_address, None, reuse_port - ) - return cast(UDPSocket, sock) - - -async def create_connected_udp_socket( - remote_host: IPAddressType, - remote_port: int, - *, - family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC, - local_host: IPAddressType | None = None, - local_port: int = 0, - reuse_port: bool = False, -) -> ConnectedUDPSocket: - """ - Create a connected UDP socket. - - Connected UDP sockets can only communicate with the specified remote host/port, an - any packets sent from other sources are dropped. - - :param remote_host: remote host to set as the default target - :param remote_port: port on the remote host to set as the default target - :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically - determined from ``local_host`` or ``remote_host`` if omitted - :param local_host: IP address or host name of the local interface to bind to - :param local_port: local port to bind to - :param reuse_port: ``True`` to allow multiple sockets to bind to the same - address/port (not supported on Windows) - :return: a connected UDP socket - - """ - local_address = None - if local_host: - gai_res = await getaddrinfo( - str(local_host), - local_port, - family=family, - type=socket.SOCK_DGRAM, - flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG, - ) - family = cast(AnyIPAddressFamily, gai_res[0][0]) - local_address = gai_res[0][-1] - - gai_res = await getaddrinfo( - str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM - ) - family = cast(AnyIPAddressFamily, gai_res[0][0]) - remote_address = gai_res[0][-1] - - sock = await get_async_backend().create_udp_socket( - family, local_address, remote_address, reuse_port - ) - return cast(ConnectedUDPSocket, sock) - - -async def create_unix_datagram_socket( - *, - local_path: None | str | bytes | PathLike[Any] = None, - local_mode: int | None = None, -) -> UNIXDatagramSocket: - """ - Create a UNIX datagram socket. - - Not available on Windows. - - If ``local_path`` has been given, the socket will be bound to this path, making this - socket suitable for receiving datagrams from other processes. Other processes can - send datagrams to this socket only if ``local_path`` is set. - - If a socket already exists on the file system in the ``local_path``, it will be - removed first. - - :param local_path: the path on which to bind to - :param local_mode: permissions to set on the local socket - :return: a UNIX datagram socket - - """ - raw_socket = await setup_unix_local_socket( - local_path, local_mode, socket.SOCK_DGRAM - ) - return await get_async_backend().create_unix_datagram_socket(raw_socket, None) - - -async def create_connected_unix_datagram_socket( - remote_path: str | bytes | PathLike[Any], - *, - local_path: None | str | bytes | PathLike[Any] = None, - local_mode: int | None = None, -) -> ConnectedUNIXDatagramSocket: - """ - Create a connected UNIX datagram socket. - - Connected datagram sockets can only communicate with the specified remote path. - - If ``local_path`` has been given, the socket will be bound to this path, making - this socket suitable for receiving datagrams from other processes. Other processes - can send datagrams to this socket only if ``local_path`` is set. - - If a socket already exists on the file system in the ``local_path``, it will be - removed first. - - :param remote_path: the path to set as the default target - :param local_path: the path on which to bind to - :param local_mode: permissions to set on the local socket - :return: a connected UNIX datagram socket - - """ - remote_path = os.fspath(remote_path) - raw_socket = await setup_unix_local_socket( - local_path, local_mode, socket.SOCK_DGRAM - ) - return await get_async_backend().create_unix_datagram_socket( - raw_socket, remote_path - ) - - -async def getaddrinfo( - host: bytes | str | None, - port: str | int | None, - *, - family: int | AddressFamily = 0, - type: int | SocketKind = 0, - proto: int = 0, - flags: int = 0, -) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]: - """ - Look up a numeric IP address given a host name. - - Internationalized domain names are translated according to the (non-transitional) - IDNA 2008 standard. - - .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of - (host, port), unlike what :func:`socket.getaddrinfo` does. - - :param host: host name - :param port: port number - :param family: socket family (`'AF_INET``, ...) - :param type: socket type (``SOCK_STREAM``, ...) - :param proto: protocol number - :param flags: flags to pass to upstream ``getaddrinfo()`` - :return: list of tuples containing (family, type, proto, canonname, sockaddr) - - .. seealso:: :func:`socket.getaddrinfo` - - """ - # Handle unicode hostnames - if isinstance(host, str): - try: - encoded_host: bytes | None = host.encode("ascii") - except UnicodeEncodeError: - import idna - - encoded_host = idna.encode(host, uts46=True) - else: - encoded_host = host - - gai_res = await get_async_backend().getaddrinfo( - encoded_host, port, family=family, type=type, proto=proto, flags=flags - ) - return [ - (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr)) - for family, type, proto, canonname, sockaddr in gai_res - # filter out IPv6 results when IPv6 is disabled - if not isinstance(sockaddr[0], int) - ] - - -def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]: - """ - Look up the host name of an IP address. - - :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4) - :param flags: flags to pass to upstream ``getnameinfo()`` - :return: a tuple of (host name, service name) - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - .. seealso:: :func:`socket.getnameinfo` - - """ - return get_async_backend().getnameinfo(sockaddr, flags) - - -@deprecated("This function is deprecated; use `wait_readable` instead") -def wait_socket_readable(sock: socket.socket) -> Awaitable[None]: - """ - .. deprecated:: 4.7.0 - Use :func:`wait_readable` instead. - - Wait until the given socket has data to be read. - - .. warning:: Only use this on raw sockets that have not been wrapped by any higher - level constructs like socket streams! - - :param sock: a socket object - :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the - socket to become readable - :raises ~anyio.BusyResourceError: if another task is already waiting for the socket - to become readable - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().wait_readable(sock.fileno()) - - -@deprecated("This function is deprecated; use `wait_writable` instead") -def wait_socket_writable(sock: socket.socket) -> Awaitable[None]: - """ - .. deprecated:: 4.7.0 - Use :func:`wait_writable` instead. - - Wait until the given socket can be written to. - - This does **NOT** work on Windows when using the asyncio backend with a proactor - event loop (default on py3.8+). - - .. warning:: Only use this on raw sockets that have not been wrapped by any higher - level constructs like socket streams! - - :param sock: a socket object - :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the - socket to become writable - :raises ~anyio.BusyResourceError: if another task is already waiting for the socket - to become writable - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().wait_writable(sock.fileno()) - - -def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]: - """ - Wait until the given object has data to be read. - - On Unix systems, ``obj`` must either be an integer file descriptor, or else an - object with a ``.fileno()`` method which returns an integer file descriptor. Any - kind of file descriptor can be passed, though the exact semantics will depend on - your kernel. For example, this probably won't do anything useful for on-disk files. - - On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an - object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File - descriptors aren't supported, and neither are handles that refer to anything besides - a ``SOCKET``. - - On backends where this functionality is not natively provided (asyncio - ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread - which is set to shut down when the interpreter shuts down. - - .. warning:: Don't use this on raw sockets that have been wrapped by any higher - level constructs like socket streams! - - :param obj: an object with a ``.fileno()`` method or an integer handle - :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the - object to become readable - :raises ~anyio.BusyResourceError: if another task is already waiting for the object - to become readable - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().wait_readable(obj) - - -def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]: - """ - Wait until the given object can be written to. - - :param obj: an object with a ``.fileno()`` method or an integer handle - :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the - object to become writable - :raises ~anyio.BusyResourceError: if another task is already waiting for the object - to become writable - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - .. seealso:: See the documentation of :func:`wait_readable` for the definition of - ``obj`` and notes on backend compatibility. - - .. warning:: Don't use this on raw sockets that have been wrapped by any higher - level constructs like socket streams! - - """ - return get_async_backend().wait_writable(obj) - - -def notify_closing(obj: FileDescriptorLike) -> None: - """ - Call this before closing a file descriptor (on Unix) or socket (on - Windows). This will cause any `wait_readable` or `wait_writable` - calls on the given object to immediately wake up and raise - `~anyio.ClosedResourceError`. - - This doesn't actually close the object – you still have to do that - yourself afterwards. Also, you want to be careful to make sure no - new tasks start waiting on the object in between when you call this - and when it's actually closed. So to close something properly, you - usually want to do these steps in order: - - 1. Explicitly mark the object as closed, so that any new attempts - to use it will abort before they start. - 2. Call `notify_closing` to wake up any already-existing users. - 3. Actually close the object. - - It's also possible to do them in a different order if that's more - convenient, *but only if* you make sure not to have any checkpoints in - between the steps. This way they all happen in a single atomic - step, so other tasks won't be able to tell what order they happened - in anyway. - - :param obj: an object with a ``.fileno()`` method or an integer handle - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - get_async_backend().notify_closing(obj) - - -# -# Private API -# - - -def convert_ipv6_sockaddr( - sockaddr: tuple[str, int, int, int] | tuple[str, int], -) -> tuple[str, int]: - """ - Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format. - - If the scope ID is nonzero, it is added to the address, separated with ``%``. - Otherwise the flow id and scope id are simply cut off from the tuple. - Any other kinds of socket addresses are returned as-is. - - :param sockaddr: the result of :meth:`~socket.socket.getsockname` - :return: the converted socket address - - """ - # This is more complicated than it should be because of MyPy - if isinstance(sockaddr, tuple) and len(sockaddr) == 4: - host, port, flowinfo, scope_id = sockaddr - if scope_id: - # PyPy (as of v7.3.11) leaves the interface name in the result, so - # we discard it and only get the scope ID from the end - # (https://foss.heptapod.net/pypy/pypy/-/issues/3938) - host = host.split("%")[0] - - # Add scope_id to the address - return f"{host}%{scope_id}", port - else: - return host, port - else: - return sockaddr - - -async def setup_unix_local_socket( - path: None | str | bytes | PathLike[Any], - mode: int | None, - socktype: int, -) -> socket.socket: - """ - Create a UNIX local socket object, deleting the socket at the given path if it - exists. - - Not available on Windows. - - :param path: path of the socket - :param mode: permissions to set on the socket - :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM - - """ - path_str: str | None - if path is not None: - path_str = os.fsdecode(path) - - # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call - if not path_str.startswith("\0"): - # Copied from pathlib... - try: - stat_result = os.stat(path) - except OSError as e: - if e.errno not in ( - errno.ENOENT, - errno.ENOTDIR, - errno.EBADF, - errno.ELOOP, - ): - raise - else: - if stat.S_ISSOCK(stat_result.st_mode): - os.unlink(path) - else: - path_str = None - - raw_socket = socket.socket(socket.AF_UNIX, socktype) - raw_socket.setblocking(False) - - if path_str is not None: - try: - await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True) - if mode is not None: - await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True) - except BaseException: - raw_socket.close() - raise - - return raw_socket - - -@dataclass -class TCPConnectable(ByteStreamConnectable): - """ - Connects to a TCP server at the given host and port. - - :param host: host name or IP address of the server - :param port: TCP port number of the server - """ - - host: str | IPv4Address | IPv6Address - port: int - - def __post_init__(self) -> None: - if self.port < 1 or self.port > 65535: - raise ValueError("TCP port number out of range") - - @override - async def connect(self) -> SocketStream: - try: - return await connect_tcp(self.host, self.port) - except OSError as exc: - raise ConnectionFailed( - f"error connecting to {self.host}:{self.port}: {exc}" - ) from exc - - -@dataclass -class UNIXConnectable(ByteStreamConnectable): - """ - Connects to a UNIX domain socket at the given path. - - :param path: the file system path of the socket - """ - - path: str | bytes | PathLike[str] | PathLike[bytes] - - @override - async def connect(self) -> UNIXSocketStream: - try: - return await connect_unix(self.path) - except OSError as exc: - raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc - - -def as_connectable( - remote: ByteStreamConnectable - | tuple[str | IPv4Address | IPv6Address, int] - | str - | bytes - | PathLike[str], - /, - *, - tls: bool = False, - ssl_context: ssl.SSLContext | None = None, - tls_hostname: str | None = None, - tls_standard_compatible: bool = True, -) -> ByteStreamConnectable: - """ - Return a byte stream connectable from the given object. - - If a bytestream connectable is given, it is returned unchanged. - If a tuple of (host, port) is given, a TCP connectable is returned. - If a string or bytes path is given, a UNIX connectable is returned. - - If ``tls=True``, the connectable will be wrapped in a - :class:`~.streams.tls.TLSConnectable`. - - :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket - :param tls: if ``True``, wrap the plaintext connectable in a - :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings) - :param ssl_context: if ``tls=True``, the SSLContext object to use (if not provided, - a secure default will be created) - :param tls_hostname: if ``tls=True``, host name of the server to use for checking - the server certificate (defaults to the host portion of the address for TCP - connectables) - :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream - skip the closing handshake when closing the connection, so it won't raise an - exception if the server does the same - - """ - connectable: TCPConnectable | UNIXConnectable | TLSConnectable - if isinstance(remote, ByteStreamConnectable): - return remote - elif isinstance(remote, tuple) and len(remote) == 2: - connectable = TCPConnectable(*remote) - elif isinstance(remote, (str, bytes, PathLike)): - connectable = UNIXConnectable(remote) - else: - raise TypeError(f"cannot convert {remote!r} to a connectable") - - if tls: - if not tls_hostname and isinstance(connectable, TCPConnectable): - tls_hostname = str(connectable.host) - - connectable = TLSConnectable( - connectable, - ssl_context=ssl_context, - hostname=tls_hostname, - standard_compatible=tls_standard_compatible, - ) - - return connectable diff --git a/server/venv/Lib/site-packages/anyio/_core/_streams.py b/server/venv/Lib/site-packages/anyio/_core/_streams.py deleted file mode 100644 index 2b9c7df..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_streams.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import annotations - -import math -from typing import TypeVar -from warnings import warn - -from ..streams.memory import ( - MemoryObjectReceiveStream, - MemoryObjectSendStream, - _MemoryObjectStreamState, -) - -T_Item = TypeVar("T_Item") - - -class create_memory_object_stream( - tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]], -): - """ - Create a memory object stream. - - The stream's item type can be annotated like - :func:`create_memory_object_stream[T_Item]`. - - :param max_buffer_size: number of items held in the buffer until ``send()`` starts - blocking - :param item_type: old way of marking the streams with the right generic type for - static typing (does nothing on AnyIO 4) - - .. deprecated:: 4.0 - Use ``create_memory_object_stream[YourItemType](...)`` instead. - :return: a tuple of (send stream, receive stream) - - """ - - def __new__( # type: ignore[misc] - cls, max_buffer_size: float = 0, item_type: object = None - ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]: - if max_buffer_size != math.inf and not isinstance(max_buffer_size, int): - raise ValueError("max_buffer_size must be either an integer or math.inf") - if max_buffer_size < 0: - raise ValueError("max_buffer_size cannot be negative") - if item_type is not None: - warn( - "The item_type argument has been deprecated in AnyIO 4.0. " - "Use create_memory_object_stream[YourItemType](...) instead.", - DeprecationWarning, - stacklevel=2, - ) - - state = _MemoryObjectStreamState[T_Item](max_buffer_size) - return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state)) diff --git a/server/venv/Lib/site-packages/anyio/_core/_subprocesses.py b/server/venv/Lib/site-packages/anyio/_core/_subprocesses.py deleted file mode 100644 index 9796f8b..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_subprocesses.py +++ /dev/null @@ -1,196 +0,0 @@ -from __future__ import annotations - -from collections.abc import AsyncIterable, Iterable, Mapping, Sequence -from io import BytesIO -from os import PathLike -from subprocess import PIPE, CalledProcessError, CompletedProcess -from typing import IO, Any, TypeAlias, cast - -from ..abc import Process -from ._eventloop import get_async_backend -from ._tasks import create_task_group - -StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] - - -async def run_process( - command: StrOrBytesPath | Sequence[StrOrBytesPath], - *, - input: bytes | None = None, - stdin: int | IO[Any] | None = None, - stdout: int | IO[Any] | None = PIPE, - stderr: int | IO[Any] | None = PIPE, - check: bool = True, - cwd: StrOrBytesPath | None = None, - env: Mapping[str, str] | None = None, - startupinfo: Any = None, - creationflags: int = 0, - start_new_session: bool = False, - pass_fds: Sequence[int] = (), - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, -) -> CompletedProcess[bytes]: - """ - Run an external command in a subprocess and wait until it completes. - - .. seealso:: :func:`subprocess.run` - - :param command: either a string to pass to the shell, or an iterable of strings - containing the executable name or path and its arguments - :param input: bytes passed to the standard input of the subprocess - :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, - a file-like object, or `None`; ``input`` overrides this - :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, - a file-like object, or `None` - :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, - :data:`subprocess.STDOUT`, a file-like object, or `None` - :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the - process terminates with a return code other than 0 - :param cwd: If not ``None``, change the working directory to this before running the - command - :param env: if not ``None``, this mapping replaces the inherited environment - variables from the parent process - :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used - to specify process startup parameters (Windows only) - :param creationflags: flags that can be used to control the creation of the - subprocess (see :class:`subprocess.Popen` for the specifics) - :param start_new_session: if ``true`` the setsid() system call will be made in the - child process prior to the execution of the subprocess. (POSIX only) - :param pass_fds: sequence of file descriptors to keep open between the parent and - child processes. (POSIX only) - :param user: effective user to run the process as (Python >= 3.9, POSIX only) - :param group: effective group to run the process as (Python >= 3.9, POSIX only) - :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9, - POSIX only) - :param umask: if not negative, this umask is applied in the child process before - running the given command (Python >= 3.9, POSIX only) - :return: an object representing the completed process - :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process - exits with a nonzero return code - - """ - - async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None: - buffer = BytesIO() - async for chunk in stream: - buffer.write(chunk) - - stream_contents[index] = buffer.getvalue() - - if stdin is not None and input is not None: - raise ValueError("only one of stdin and input is allowed") - - async with await open_process( - command, - stdin=PIPE if input else stdin, - stdout=stdout, - stderr=stderr, - cwd=cwd, - env=env, - startupinfo=startupinfo, - creationflags=creationflags, - start_new_session=start_new_session, - pass_fds=pass_fds, - user=user, - group=group, - extra_groups=extra_groups, - umask=umask, - ) as process: - stream_contents: list[bytes | None] = [None, None] - async with create_task_group() as tg: - if process.stdout: - tg.start_soon(drain_stream, process.stdout, 0) - - if process.stderr: - tg.start_soon(drain_stream, process.stderr, 1) - - if process.stdin and input: - await process.stdin.send(input) - await process.stdin.aclose() - - await process.wait() - - output, errors = stream_contents - if check and process.returncode != 0: - raise CalledProcessError(cast(int, process.returncode), command, output, errors) - - return CompletedProcess(command, cast(int, process.returncode), output, errors) - - -async def open_process( - command: StrOrBytesPath | Sequence[StrOrBytesPath], - *, - stdin: int | IO[Any] | None = PIPE, - stdout: int | IO[Any] | None = PIPE, - stderr: int | IO[Any] | None = PIPE, - cwd: StrOrBytesPath | None = None, - env: Mapping[str, str] | None = None, - startupinfo: Any = None, - creationflags: int = 0, - start_new_session: bool = False, - pass_fds: Sequence[int] = (), - user: str | int | None = None, - group: str | int | None = None, - extra_groups: Iterable[str | int] | None = None, - umask: int = -1, -) -> Process: - """ - Start an external command in a subprocess. - - .. seealso:: :class:`subprocess.Popen` - - :param command: either a string to pass to the shell, or an iterable of strings - containing the executable name or path and its arguments - :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a - file-like object, or ``None`` - :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, - a file-like object, or ``None`` - :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, - :data:`subprocess.STDOUT`, a file-like object, or ``None`` - :param cwd: If not ``None``, the working directory is changed before executing - :param env: If env is not ``None``, it must be a mapping that defines the - environment variables for the new process - :param creationflags: flags that can be used to control the creation of the - subprocess (see :class:`subprocess.Popen` for the specifics) - :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used - to specify process startup parameters (Windows only) - :param start_new_session: if ``true`` the setsid() system call will be made in the - child process prior to the execution of the subprocess. (POSIX only) - :param pass_fds: sequence of file descriptors to keep open between the parent and - child processes. (POSIX only) - :param user: effective user to run the process as (POSIX only) - :param group: effective group to run the process as (POSIX only) - :param extra_groups: supplementary groups to set in the subprocess (POSIX only) - :param umask: if not negative, this umask is applied in the child process before - running the given command (POSIX only) - :return: an asynchronous process object - - """ - kwargs: dict[str, Any] = {} - if user is not None: - kwargs["user"] = user - - if group is not None: - kwargs["group"] = group - - if extra_groups is not None: - kwargs["extra_groups"] = group - - if umask >= 0: - kwargs["umask"] = umask - - return await get_async_backend().open_process( - command, - stdin=stdin, - stdout=stdout, - stderr=stderr, - cwd=cwd, - env=env, - startupinfo=startupinfo, - creationflags=creationflags, - start_new_session=start_new_session, - pass_fds=pass_fds, - **kwargs, - ) diff --git a/server/venv/Lib/site-packages/anyio/_core/_synchronization.py b/server/venv/Lib/site-packages/anyio/_core/_synchronization.py deleted file mode 100644 index ed19116..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_synchronization.py +++ /dev/null @@ -1,772 +0,0 @@ -from __future__ import annotations - -import math -from collections import deque -from collections.abc import Callable -from dataclasses import dataclass -from types import TracebackType -from typing import TypeVar - -from ..lowlevel import checkpoint_if_cancelled -from ._eventloop import get_async_backend -from ._exceptions import BusyResourceError, NoEventLoopError -from ._tasks import CancelScope -from ._testing import TaskInfo, get_current_task - -T = TypeVar("T") - - -@dataclass(frozen=True) -class EventStatistics: - """ - :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait` - """ - - tasks_waiting: int - - -@dataclass(frozen=True) -class CapacityLimiterStatistics: - """ - :ivar int borrowed_tokens: number of tokens currently borrowed by tasks - :ivar float total_tokens: total number of available tokens - :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from - this limiter - :ivar int tasks_waiting: number of tasks waiting on - :meth:`~.CapacityLimiter.acquire` or - :meth:`~.CapacityLimiter.acquire_on_behalf_of` - """ - - borrowed_tokens: int - total_tokens: float - borrowers: tuple[object, ...] - tasks_waiting: int - - -@dataclass(frozen=True) -class LockStatistics: - """ - :ivar bool locked: flag indicating if this lock is locked or not - :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the - lock is not held by any task) - :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire` - """ - - locked: bool - owner: TaskInfo | None - tasks_waiting: int - - -@dataclass(frozen=True) -class ConditionStatistics: - """ - :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait` - :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying - :class:`~.Lock` - """ - - tasks_waiting: int - lock_statistics: LockStatistics - - -@dataclass(frozen=True) -class SemaphoreStatistics: - """ - :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire` - - """ - - tasks_waiting: int - - -class Event: - __slots__ = ("__weakref__",) - - def __new__(cls) -> Event: - try: - return get_async_backend().create_event() - except NoEventLoopError: - return EventAdapter() - - def set(self) -> None: - """Set the flag, notifying all listeners.""" - raise NotImplementedError - - def is_set(self) -> bool: - """Return ``True`` if the flag is set, ``False`` if not.""" - raise NotImplementedError - - async def wait(self) -> None: - """ - Wait until the flag has been set. - - If the flag has already been set when this method is called, it returns - immediately. - - """ - raise NotImplementedError - - def statistics(self) -> EventStatistics: - """Return statistics about the current state of this event.""" - raise NotImplementedError - - -class EventAdapter(Event): - __slots__ = "_internal_event", "_is_set" - - def __new__(cls) -> EventAdapter: - return object.__new__(cls) - - def __init__(self) -> None: - self._internal_event: Event | None = None - self._is_set = False - - @property - def _event(self) -> Event: - if self._internal_event is None: - self._internal_event = get_async_backend().create_event() - if self._is_set: - self._internal_event.set() - - return self._internal_event - - def set(self) -> None: - if self._internal_event is None: - self._is_set = True - else: - self._event.set() - - def is_set(self) -> bool: - if self._internal_event is None: - return self._is_set - - return self._internal_event.is_set() - - async def wait(self) -> None: - await self._event.wait() - - def statistics(self) -> EventStatistics: - if self._internal_event is None: - return EventStatistics(tasks_waiting=0) - - return self._internal_event.statistics() - - -class Lock: - __slots__ = ("__weakref__",) - - def __new__(cls, *, fast_acquire: bool = False) -> Lock: - try: - return get_async_backend().create_lock(fast_acquire=fast_acquire) - except NoEventLoopError: - return LockAdapter(fast_acquire=fast_acquire) - - async def __aenter__(self) -> None: - await self.acquire() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.release() - - async def acquire(self) -> None: - """Acquire the lock.""" - raise NotImplementedError - - def acquire_nowait(self) -> None: - """ - Acquire the lock, without blocking. - - :raises ~anyio.WouldBlock: if the operation would block - - """ - raise NotImplementedError - - def release(self) -> None: - """Release the lock.""" - raise NotImplementedError - - def locked(self) -> bool: - """Return True if the lock is currently held.""" - raise NotImplementedError - - def statistics(self) -> LockStatistics: - """ - Return statistics about the current state of this lock. - - .. versionadded:: 3.0 - """ - raise NotImplementedError - - -class LockAdapter(Lock): - __slots__ = "_internal_lock", "_fast_acquire" - - def __new__(cls, *, fast_acquire: bool = False) -> LockAdapter: - return object.__new__(cls) - - def __init__(self, *, fast_acquire: bool = False): - self._internal_lock: Lock | None = None - self._fast_acquire = fast_acquire - - @property - def _lock(self) -> Lock: - if self._internal_lock is None: - self._internal_lock = get_async_backend().create_lock( - fast_acquire=self._fast_acquire - ) - - return self._internal_lock - - async def __aenter__(self) -> None: - await self._lock.acquire() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - if self._internal_lock is not None: - self._internal_lock.release() - - async def acquire(self) -> None: - """Acquire the lock.""" - await self._lock.acquire() - - def acquire_nowait(self) -> None: - """ - Acquire the lock, without blocking. - - :raises ~anyio.WouldBlock: if the operation would block - - """ - self._lock.acquire_nowait() - - def release(self) -> None: - """Release the lock.""" - self._lock.release() - - def locked(self) -> bool: - """Return True if the lock is currently held.""" - return self._lock.locked() - - def statistics(self) -> LockStatistics: - """ - Return statistics about the current state of this lock. - - .. versionadded:: 3.0 - - """ - if self._internal_lock is None: - return LockStatistics(False, None, 0) - - return self._internal_lock.statistics() - - -class Condition: - __slots__ = "__weakref__", "_owner_task", "_lock", "_waiters" - - def __init__(self, lock: Lock | None = None): - self._owner_task: TaskInfo | None = None - self._lock = lock or Lock() - self._waiters: deque[Event] = deque() - - async def __aenter__(self) -> None: - await self.acquire() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.release() - - def _check_acquired(self) -> None: - if self._owner_task != get_current_task(): - raise RuntimeError("The current task is not holding the underlying lock") - - async def acquire(self) -> None: - """Acquire the underlying lock.""" - await self._lock.acquire() - self._owner_task = get_current_task() - - def acquire_nowait(self) -> None: - """ - Acquire the underlying lock, without blocking. - - :raises ~anyio.WouldBlock: if the operation would block - - """ - self._lock.acquire_nowait() - self._owner_task = get_current_task() - - def release(self) -> None: - """Release the underlying lock.""" - self._lock.release() - - def locked(self) -> bool: - """Return True if the lock is set.""" - return self._lock.locked() - - def notify(self, n: int = 1) -> None: - """Notify exactly n listeners.""" - self._check_acquired() - for _ in range(n): - try: - event = self._waiters.popleft() - except IndexError: - break - - event.set() - - def notify_all(self) -> None: - """Notify all the listeners.""" - self._check_acquired() - for event in self._waiters: - event.set() - - self._waiters.clear() - - async def wait(self) -> None: - """Wait for a notification.""" - await checkpoint_if_cancelled() - self._check_acquired() - event = Event() - self._waiters.append(event) - self.release() - try: - await event.wait() - except BaseException: - if not event.is_set(): - self._waiters.remove(event) - elif self._waiters: - # This task was notified by could not act on it, so pass - # it on to the next task - self._waiters.popleft().set() - - raise - finally: - with CancelScope(shield=True): - await self.acquire() - - async def wait_for(self, predicate: Callable[[], T]) -> T: - """ - Wait until a predicate becomes true. - - :param predicate: a callable that returns a truthy value when the condition is - met - :return: the result of the predicate - - .. versionadded:: 4.11.0 - - """ - while not (result := predicate()): - await self.wait() - - return result - - def statistics(self) -> ConditionStatistics: - """ - Return statistics about the current state of this condition. - - .. versionadded:: 3.0 - """ - return ConditionStatistics(len(self._waiters), self._lock.statistics()) - - -class Semaphore: - __slots__ = "__weakref__", "_fast_acquire" - - def __new__( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> Semaphore: - try: - return get_async_backend().create_semaphore( - initial_value, max_value=max_value, fast_acquire=fast_acquire - ) - except NoEventLoopError: - return SemaphoreAdapter(initial_value, max_value=max_value) - - def __init__( - self, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ): - if not isinstance(initial_value, int): - raise TypeError("initial_value must be an integer") - if initial_value < 0: - raise ValueError("initial_value must be >= 0") - if max_value is not None: - if not isinstance(max_value, int): - raise TypeError("max_value must be an integer or None") - if max_value < initial_value: - raise ValueError( - "max_value must be equal to or higher than initial_value" - ) - - self._fast_acquire = fast_acquire - - async def __aenter__(self) -> Semaphore: - await self.acquire() - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.release() - - async def acquire(self) -> None: - """Decrement the semaphore value, blocking if necessary.""" - raise NotImplementedError - - def acquire_nowait(self) -> None: - """ - Acquire the underlying lock, without blocking. - - :raises ~anyio.WouldBlock: if the operation would block - - """ - raise NotImplementedError - - def release(self) -> None: - """Increment the semaphore value.""" - raise NotImplementedError - - @property - def value(self) -> int: - """The current value of the semaphore.""" - raise NotImplementedError - - @property - def max_value(self) -> int | None: - """The maximum value of the semaphore.""" - raise NotImplementedError - - def statistics(self) -> SemaphoreStatistics: - """ - Return statistics about the current state of this semaphore. - - .. versionadded:: 3.0 - """ - raise NotImplementedError - - -class SemaphoreAdapter(Semaphore): - __slots__ = "_internal_semaphore", "_initial_value", "_max_value" - - def __new__( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> SemaphoreAdapter: - return object.__new__(cls) - - def __init__( - self, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> None: - super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire) - self._internal_semaphore: Semaphore | None = None - self._initial_value = initial_value - self._max_value = max_value - - @property - def _semaphore(self) -> Semaphore: - if self._internal_semaphore is None: - self._internal_semaphore = get_async_backend().create_semaphore( - self._initial_value, max_value=self._max_value - ) - - return self._internal_semaphore - - async def acquire(self) -> None: - await self._semaphore.acquire() - - def acquire_nowait(self) -> None: - self._semaphore.acquire_nowait() - - def release(self) -> None: - self._semaphore.release() - - @property - def value(self) -> int: - if self._internal_semaphore is None: - return self._initial_value - - return self._semaphore.value - - @property - def max_value(self) -> int | None: - return self._max_value - - def statistics(self) -> SemaphoreStatistics: - if self._internal_semaphore is None: - return SemaphoreStatistics(tasks_waiting=0) - - return self._semaphore.statistics() - - -class CapacityLimiter: - __slots__ = ("__weakref__",) - - def __new__(cls, total_tokens: float) -> CapacityLimiter: - try: - return get_async_backend().create_capacity_limiter(total_tokens) - except NoEventLoopError: - return CapacityLimiterAdapter(total_tokens) - - async def __aenter__(self) -> None: - raise NotImplementedError - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - raise NotImplementedError - - @property - def total_tokens(self) -> float: - """ - The total number of tokens available for borrowing. - - This is a read-write property. If the total number of tokens is increased, the - proportionate number of tasks waiting on this limiter will be granted their - tokens. - - .. versionchanged:: 3.0 - The property is now writable. - .. versionchanged:: 4.12 - The value can now be set to 0. - - """ - raise NotImplementedError - - @total_tokens.setter - def total_tokens(self, value: float) -> None: - raise NotImplementedError - - @property - def borrowed_tokens(self) -> int: - """The number of tokens that have currently been borrowed.""" - raise NotImplementedError - - @property - def available_tokens(self) -> float: - """The number of tokens currently available to be borrowed""" - raise NotImplementedError - - def acquire_nowait(self) -> None: - """ - Acquire a token for the current task without waiting for one to become - available. - - :raises ~anyio.WouldBlock: if there are no tokens available for borrowing - - """ - raise NotImplementedError - - def acquire_on_behalf_of_nowait(self, borrower: object) -> None: - """ - Acquire a token without waiting for one to become available. - - :param borrower: the entity borrowing a token - :raises ~anyio.WouldBlock: if there are no tokens available for borrowing - - """ - raise NotImplementedError - - async def acquire(self) -> None: - """ - Acquire a token for the current task, waiting if necessary for one to become - available. - - """ - raise NotImplementedError - - async def acquire_on_behalf_of(self, borrower: object) -> None: - """ - Acquire a token, waiting if necessary for one to become available. - - :param borrower: the entity borrowing a token - - """ - raise NotImplementedError - - def release(self) -> None: - """ - Release the token held by the current task. - - :raises RuntimeError: if the current task has not borrowed a token from this - limiter. - - """ - raise NotImplementedError - - def release_on_behalf_of(self, borrower: object) -> None: - """ - Release the token held by the given borrower. - - :raises RuntimeError: if the borrower has not borrowed a token from this - limiter. - - """ - raise NotImplementedError - - def statistics(self) -> CapacityLimiterStatistics: - """ - Return statistics about the current state of this limiter. - - .. versionadded:: 3.0 - - """ - raise NotImplementedError - - -class CapacityLimiterAdapter(CapacityLimiter): - __slots__ = "_internal_limiter", "_total_tokens" - - def __new__(cls, total_tokens: float) -> CapacityLimiterAdapter: - return object.__new__(cls) - - def __init__(self, total_tokens: float) -> None: - self._internal_limiter: CapacityLimiter | None = None - self.total_tokens = total_tokens - - @property - def _limiter(self) -> CapacityLimiter: - if self._internal_limiter is None: - self._internal_limiter = get_async_backend().create_capacity_limiter( - self._total_tokens - ) - - return self._internal_limiter - - async def __aenter__(self) -> None: - await self._limiter.__aenter__() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - return await self._limiter.__aexit__(exc_type, exc_val, exc_tb) - - @property - def total_tokens(self) -> float: - if self._internal_limiter is None: - return self._total_tokens - - return self._internal_limiter.total_tokens - - @total_tokens.setter - def total_tokens(self, value: float) -> None: - if not isinstance(value, int) and value is not math.inf: - raise TypeError("total_tokens must be an int or math.inf") - elif value < 1: - raise ValueError("total_tokens must be >= 1") - - if self._internal_limiter is None: - self._total_tokens = value - return - - self._limiter.total_tokens = value - - @property - def borrowed_tokens(self) -> int: - if self._internal_limiter is None: - return 0 - - return self._internal_limiter.borrowed_tokens - - @property - def available_tokens(self) -> float: - if self._internal_limiter is None: - return self._total_tokens - - return self._internal_limiter.available_tokens - - def acquire_nowait(self) -> None: - self._limiter.acquire_nowait() - - def acquire_on_behalf_of_nowait(self, borrower: object) -> None: - self._limiter.acquire_on_behalf_of_nowait(borrower) - - async def acquire(self) -> None: - await self._limiter.acquire() - - async def acquire_on_behalf_of(self, borrower: object) -> None: - await self._limiter.acquire_on_behalf_of(borrower) - - def release(self) -> None: - self._limiter.release() - - def release_on_behalf_of(self, borrower: object) -> None: - self._limiter.release_on_behalf_of(borrower) - - def statistics(self) -> CapacityLimiterStatistics: - if self._internal_limiter is None: - return CapacityLimiterStatistics( - borrowed_tokens=0, - total_tokens=self.total_tokens, - borrowers=(), - tasks_waiting=0, - ) - - return self._internal_limiter.statistics() - - -class ResourceGuard: - """ - A context manager for ensuring that a resource is only used by a single task at a - time. - - Entering this context manager while the previous has not exited it yet will trigger - :exc:`BusyResourceError`. - - :param action: the action to guard against (visible in the :exc:`BusyResourceError` - when triggered, e.g. "Another task is already {action} this resource") - - .. versionadded:: 4.1 - """ - - __slots__ = "__weakref__", "action", "_guarded" - - def __init__(self, action: str = "using"): - self.action: str = action - self._guarded = False - - def __enter__(self) -> None: - if self._guarded: - raise BusyResourceError(self.action) - - self._guarded = True - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self._guarded = False diff --git a/server/venv/Lib/site-packages/anyio/_core/_tasks.py b/server/venv/Lib/site-packages/anyio/_core/_tasks.py deleted file mode 100644 index 108393e..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_tasks.py +++ /dev/null @@ -1,415 +0,0 @@ -from __future__ import annotations - -import math -import sys -from collections.abc import ( - Coroutine, - Generator, -) -from contextlib import ( - contextmanager, -) -from contextvars import ContextVar -from enum import Enum, auto -from inspect import iscoroutine -from types import TracebackType -from typing import Any, Generic, final - -from ..abc import TaskGroup, TaskStatus -from ._eventloop import get_async_backend, get_cancelled_exc_class -from ._exceptions import TaskCancelled, TaskFailed, TaskNotFinished - -if sys.version_info >= (3, 13): - from typing import TypeVar -else: - from typing_extensions import TypeVar - -if sys.version_info >= (3, 11): - from typing import Never, TypeVarTuple -else: - from typing_extensions import Never, TypeVarTuple - -T = TypeVar("T") -T_co = TypeVar("T_co", covariant=True) -T_startval = TypeVar("T_startval", covariant=True, default=Never) -PosArgsT = TypeVarTuple("PosArgsT") - -_current_task_handle: ContextVar[TaskHandle] = ContextVar("_current_task_handle") - - -class _IgnoredTaskStatus(TaskStatus[object]): - def started(self, value: object = None) -> None: - pass - - -TASK_STATUS_IGNORED = _IgnoredTaskStatus() - - -class CancelScope: - """ - Wraps a unit of work that can be made separately cancellable. - - :param deadline: The time (clock value) when this scope is cancelled automatically - :param shield: ``True`` to shield the cancel scope from external cancellation - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - """ - - __slots__ = ("__weakref__",) - - def __new__( - cls, *, deadline: float = math.inf, shield: bool = False - ) -> CancelScope: - return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline) - - def cancel(self, reason: str | None = None) -> None: - """ - Cancel this scope immediately. - - :param reason: a message describing the reason for the cancellation - - """ - raise NotImplementedError - - @property - def deadline(self) -> float: - """ - The time (clock value) when this scope is cancelled automatically. - - Will be ``float('inf')`` if no timeout has been set. - - """ - raise NotImplementedError - - @deadline.setter - def deadline(self, value: float) -> None: - raise NotImplementedError - - @property - def cancel_called(self) -> bool: - """``True`` if :meth:`cancel` has been called.""" - raise NotImplementedError - - @property - def cancelled_caught(self) -> bool: - """ - ``True`` if this scope suppressed a cancellation exception it itself raised. - - This is typically used to check if any work was interrupted, or to see if the - scope was cancelled due to its deadline being reached. The value will, however, - only be ``True`` if the cancellation was triggered by the scope itself (and not - an outer scope). - - """ - raise NotImplementedError - - @property - def shield(self) -> bool: - """ - ``True`` if this scope is shielded from external cancellation. - - While a scope is shielded, it will not receive cancellations from outside. - - """ - raise NotImplementedError - - @shield.setter - def shield(self, value: bool) -> None: - raise NotImplementedError - - def __enter__(self) -> CancelScope: - raise NotImplementedError - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - raise NotImplementedError - - -@contextmanager -def fail_after( - delay: float | None, shield: bool = False -) -> Generator[CancelScope, None, None]: - """ - Create a context manager which raises a :class:`TimeoutError` if does not finish in - time. - - :param delay: maximum allowed time (in seconds) before raising the exception, or - ``None`` to disable the timeout - :param shield: ``True`` to shield the cancel scope from external cancellation - :return: a context manager that yields a cancel scope - :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\] - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - current_time = get_async_backend().current_time - deadline = (current_time() + delay) if delay is not None else math.inf - with get_async_backend().create_cancel_scope( - deadline=deadline, shield=shield - ) as cancel_scope: - yield cancel_scope - - if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline: - raise TimeoutError - - -def move_on_after(delay: float | None, shield: bool = False) -> CancelScope: - """ - Create a cancel scope with a deadline that expires after the given delay. - - :param delay: maximum allowed time (in seconds) before exiting the context block, or - ``None`` to disable the timeout - :param shield: ``True`` to shield the cancel scope from external cancellation - :return: a cancel scope - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - deadline = ( - (get_async_backend().current_time() + delay) if delay is not None else math.inf - ) - return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield) - - -def current_effective_deadline() -> float: - """ - Return the nearest deadline among all the cancel scopes effective for the current - task. - - :return: a clock value from the event loop's internal clock (or ``float('inf')`` if - there is no deadline in effect, or ``float('-inf')`` if the current scope has - been cancelled) - :rtype: float - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().current_effective_deadline() - - -def create_task_group() -> TaskGroup: - """ - Create a task group. - - :return: a task group - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().create_task_group() - - -@final -class TaskHandle(Generic[T_co, T_startval]): - """ - Returned from the task-spawning methods of :class:`TaskGroup`. Can be awaited on to - get the return value of the task (or the raised exception). If the task was - terminated by a :exc:`BaseException`, :exc:`TaskFailed` will be raised (or its - subclass :exc:`TaskCancelled` if the task was cancelled). - - .. versionadded:: 4.14.0 - """ - - class Status(Enum): - """ - The status of a task handle. - - .. attribute:: PENDING - - The task has not finished yet. - .. attribute:: FINISHED - - The task has finished with a return value. - .. attribute:: CANCELLING - - The task has been cancelled but has not finished yet. - .. attribute:: CANCELLED - - The task was cancelled and has finished since. - .. attribute:: FAILED - - The task raised an exception. - """ - - PENDING = auto() - FINISHED = auto() - CANCELLING = auto() - CANCELLED = auto() - FAILED = auto() - - __slots__ = ( - "__weakref__", - "_coro", - "_name", - "_cancel_scope", - "_finished_event", - "_return_value", - "_start_value", - "_exception", - ) - - _return_value: T_co - _start_value: T_startval - - def __init__(self, coro: Coroutine[Any, Any, T_co], name: object) -> None: - from ._synchronization import Event - - self._coro = coro - self._cancel_scope = CancelScope() - self._finished_event = Event() - self._exception: BaseException | None = None - - if name is not None: - self._name = str(name) - elif iscoroutine(coro): - self._name = coro.__qualname__ - else: - self._name = str(coro) # coroutine-like object (e.g. asend() objects) - - async def _run_coro(self) -> None: - __tracebackhide__ = True - - with self._cancel_scope: - try: - retval = await self._coro - except BaseException as exc: - self._exception = exc - raise - else: - self._return_value = retval - finally: - self._finished_event.set() - del self # Break the reference cycle - - def cancel(self) -> None: - """ - Set the task to a cancelled state. - - This will interrupt any interruptible asynchronous operation, and will cause - any further awaits on this task to get immediately cancelled, unless done in - a shielded cancel scope. - - If the task has already finished, this method has no effect. - """ - if not self._finished_event.is_set(): - self._cancel_scope.cancel() - - @property - def coro(self) -> Coroutine[Any, Any, T_co]: - """ - The coroutine object that was passed to one of the task-spawning methods in - :class:`TaskGroup`. - """ - return self._coro - - @property - def status(self) -> TaskHandle.Status: - """ - The current status of the task. - - Every task starts in the :attr:`~TaskHandle.Status.PENDING` state. - If a task is cancelled while in this state, it will transition to the - :attr:`~TaskHandle.Status.CANCELLING` state. When the task finishes, it will - transition to one of the three final states ( - :attr:`~TaskHandle.Status.FINISHED`, :attr:`~TaskHandle.Status.FAILED`, or - :attr:`~TaskHandle.Status.CANCELLING`) depending on the exception the task - raised, if any. No other status transitions will happen. - """ - if not self._finished_event.is_set(): - if self._cancel_scope.cancel_called: - return TaskHandle.Status.CANCELLING - else: - return TaskHandle.Status.PENDING - elif self._exception is not None: - if isinstance(self._exception, get_cancelled_exc_class()): - return TaskHandle.Status.CANCELLED - else: - return TaskHandle.Status.FAILED - else: - return TaskHandle.Status.FINISHED - - @property - def name(self) -> str: - """The name of the task.""" - return self._name - - @property - def exception(self) -> BaseException | None: - """ - The exception raised by the task, or ``None`` if it finished without raising. - - :raises TaskNotFinished: if the task has not finished yet - :raises TaskCancelled: if the task was cancelled - - """ - match self.status: - case TaskHandle.Status.PENDING: - raise TaskNotFinished("the task has not finished yet") - case TaskHandle.Status.FINISHED: - return None - case TaskHandle.Status.CANCELLING: - raise TaskCancelled("the task was cancelled") - case TaskHandle.Status.CANCELLED: - raise TaskCancelled("the task was cancelled") from self._exception - case TaskHandle.Status.FAILED: - return self._exception - - @property - def return_value(self) -> T_co: - """ - The return value of the task. - - :raises TaskNotFinished: if the task has not finished yet - :raises TaskCancelled: if the task was cancelled - :raises TaskFailed: if the task raised an exception - - """ - match self.status: - case TaskHandle.Status.PENDING: - raise TaskNotFinished("the task has not finished yet") - case TaskHandle.Status.FINISHED: - return self._return_value - case TaskHandle.Status.CANCELLING: - raise TaskCancelled("the task was cancelled") - case TaskHandle.Status.CANCELLED: - raise TaskCancelled("the task was cancelled") from self._exception - case TaskHandle.Status.FAILED: - raise TaskFailed("the task raised an exception") from self._exception - - @property - def start_value(self) -> T_startval: - """ - The value passed to :meth:`task_status.started() <.abc.TaskStatus.started>`, - - :raises RuntimeError: if the task was not started with :meth:`TaskGroup.start() - <.abc.TaskGroup.start>` - """ - try: - return self._start_value - except AttributeError: - raise RuntimeError( - "the task was not started with TaskGroup.start()" - ) from None - - async def wait(self) -> None: - """ - Wait for the task to finish. - - This method will return as soon as the task has finished, no matter how it - happened. - """ - await self._finished_event.wait() - - def __await__(self) -> Generator[Any, Any, T_co]: - yield from self._finished_event.wait().__await__() - return self.return_value - - def __repr__(self) -> str: - return ( - f"<{self.__class__.__name__} {self.status.name.lower()} " - f"name={self._name!r} coro={self._coro!r}>" - ) diff --git a/server/venv/Lib/site-packages/anyio/_core/_tempfile.py b/server/venv/Lib/site-packages/anyio/_core/_tempfile.py deleted file mode 100644 index 75a09f7..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_tempfile.py +++ /dev/null @@ -1,613 +0,0 @@ -from __future__ import annotations - -import os -import sys -import tempfile -from collections.abc import Iterable -from io import BytesIO, TextIOWrapper -from types import TracebackType -from typing import ( - TYPE_CHECKING, - Any, - AnyStr, - Generic, - overload, -) - -from .. import to_thread -from .._core._fileio import AsyncFile -from ..lowlevel import checkpoint_if_cancelled - -if TYPE_CHECKING: - from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer - - -class TemporaryFile(Generic[AnyStr]): - """ - An asynchronous temporary file that is automatically created and cleaned up. - - This class provides an asynchronous context manager interface to a temporary file. - The file is created using Python's standard `tempfile.TemporaryFile` function in a - background thread, and is wrapped as an asynchronous file using `AsyncFile`. - - :param mode: The mode in which the file is opened. Defaults to "w+b". - :param buffering: The buffering policy (-1 means the default buffering). - :param encoding: The encoding used to decode or encode the file. Only applicable in - text mode. - :param newline: Controls how universal newlines mode works (only applicable in text - mode). - :param suffix: The suffix for the temporary file name. - :param prefix: The prefix for the temporary file name. - :param dir: The directory in which the temporary file is created. - :param errors: The error handling scheme used for encoding/decoding errors. - """ - - _async_file: AsyncFile[AnyStr] - - @overload - def __init__( - self: TemporaryFile[bytes], - mode: OpenBinaryMode = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: str | None = ..., - prefix: str | None = ..., - dir: str | None = ..., - *, - errors: str | None = ..., - ): ... - @overload - def __init__( - self: TemporaryFile[str], - mode: OpenTextMode, - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: str | None = ..., - prefix: str | None = ..., - dir: str | None = ..., - *, - errors: str | None = ..., - ): ... - - def __init__( - self, - mode: OpenTextMode | OpenBinaryMode = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - *, - errors: str | None = None, - ) -> None: - self.mode = mode - self.buffering = buffering - self.encoding = encoding - self.newline = newline - self.suffix: str | None = suffix - self.prefix: str | None = prefix - self.dir: str | None = dir - self.errors = errors - - async def __aenter__(self) -> AsyncFile[AnyStr]: - fp = await to_thread.run_sync( - lambda: tempfile.TemporaryFile( - self.mode, - self.buffering, - self.encoding, - self.newline, - self.suffix, - self.prefix, - self.dir, - errors=self.errors, - ) - ) - self._async_file = AsyncFile(fp) - return self._async_file - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: TracebackType | None, - ) -> None: - await self._async_file.aclose() - - -class NamedTemporaryFile(Generic[AnyStr]): - """ - An asynchronous named temporary file that is automatically created and cleaned up. - - This class provides an asynchronous context manager for a temporary file with a - visible name in the file system. It uses Python's standard - :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with - :class:`AsyncFile` for asynchronous operations. - - :param mode: The mode in which the file is opened. Defaults to "w+b". - :param buffering: The buffering policy (-1 means the default buffering). - :param encoding: The encoding used to decode or encode the file. Only applicable in - text mode. - :param newline: Controls how universal newlines mode works (only applicable in text - mode). - :param suffix: The suffix for the temporary file name. - :param prefix: The prefix for the temporary file name. - :param dir: The directory in which the temporary file is created. - :param delete: Whether to delete the file when it is closed. - :param errors: The error handling scheme used for encoding/decoding errors. - :param delete_on_close: (Python 3.12+) Whether to delete the file on close. - """ - - _async_file: AsyncFile[AnyStr] - - @overload - def __init__( - self: NamedTemporaryFile[bytes], - mode: OpenBinaryMode = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: str | None = ..., - prefix: str | None = ..., - dir: str | None = ..., - delete: bool = ..., - *, - errors: str | None = ..., - delete_on_close: bool = ..., - ): ... - @overload - def __init__( - self: NamedTemporaryFile[str], - mode: OpenTextMode, - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: str | None = ..., - prefix: str | None = ..., - dir: str | None = ..., - delete: bool = ..., - *, - errors: str | None = ..., - delete_on_close: bool = ..., - ): ... - - def __init__( - self, - mode: OpenBinaryMode | OpenTextMode = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - delete: bool = True, - *, - errors: str | None = None, - delete_on_close: bool = True, - ) -> None: - self._params: dict[str, Any] = { - "mode": mode, - "buffering": buffering, - "encoding": encoding, - "newline": newline, - "suffix": suffix, - "prefix": prefix, - "dir": dir, - "delete": delete, - "errors": errors, - } - if sys.version_info >= (3, 12): - self._params["delete_on_close"] = delete_on_close - - async def __aenter__(self) -> AsyncFile[AnyStr]: - fp = await to_thread.run_sync( - lambda: tempfile.NamedTemporaryFile(**self._params) - ) - self._async_file = AsyncFile(fp) - return self._async_file - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: TracebackType | None, - ) -> None: - await self._async_file.aclose() - - -class SpooledTemporaryFile(AsyncFile[AnyStr]): - """ - An asynchronous spooled temporary file that starts in memory and is spooled to disk. - - This class provides an asynchronous interface to a spooled temporary file, much like - Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous - write operations and provides a method to force a rollover to disk. - - :param max_size: Maximum size in bytes before the file is rolled over to disk. - :param mode: The mode in which the file is opened. Defaults to "w+b". - :param buffering: The buffering policy (-1 means the default buffering). - :param encoding: The encoding used to decode or encode the file (text mode only). - :param newline: Controls how universal newlines mode works (text mode only). - :param suffix: The suffix for the temporary file name. - :param prefix: The prefix for the temporary file name. - :param dir: The directory in which the temporary file is created. - :param errors: The error handling scheme used for encoding/decoding errors. - """ - - _rolled: bool = False - - @overload - def __init__( - self: SpooledTemporaryFile[bytes], - max_size: int = ..., - mode: OpenBinaryMode = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: str | None = ..., - prefix: str | None = ..., - dir: str | None = ..., - *, - errors: str | None = ..., - ): ... - @overload - def __init__( - self: SpooledTemporaryFile[str], - max_size: int = ..., - mode: OpenTextMode = ..., - buffering: int = ..., - encoding: str | None = ..., - newline: str | None = ..., - suffix: str | None = ..., - prefix: str | None = ..., - dir: str | None = ..., - *, - errors: str | None = ..., - ): ... - - def __init__( - self, - max_size: int = 0, - mode: OpenBinaryMode | OpenTextMode = "w+b", - buffering: int = -1, - encoding: str | None = None, - newline: str | None = None, - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - *, - errors: str | None = None, - ) -> None: - self._tempfile_params: dict[str, Any] = { - "mode": mode, - "buffering": buffering, - "encoding": encoding, - "newline": newline, - "suffix": suffix, - "prefix": prefix, - "dir": dir, - "errors": errors, - } - self._max_size = max_size - if "b" in mode: - super().__init__(BytesIO()) # type: ignore[arg-type] - else: - super().__init__( - TextIOWrapper( # type: ignore[arg-type] - BytesIO(), - encoding=encoding, - errors=errors, - newline=newline, - write_through=True, - ) - ) - - async def aclose(self) -> None: - if not self._rolled: - self._fp.close() - return - - await super().aclose() - - async def _check(self) -> None: - if self._rolled or self._fp.tell() <= self._max_size: - return - - await self.rollover() - - async def rollover(self) -> None: - if self._rolled: - return - - self._rolled = True - buffer = self._fp - buffer.seek(0) - self._fp = await to_thread.run_sync( - lambda: tempfile.TemporaryFile(**self._tempfile_params) - ) - await self.write(buffer.read()) - buffer.close() - - @property - def closed(self) -> bool: - return self._fp.closed - - async def read(self, size: int = -1) -> AnyStr: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.read(size) - - return await super().read(size) # type: ignore[return-value] - - async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.read1(size) - - return await super().read1(size) - - async def readline(self) -> AnyStr: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.readline() - - return await super().readline() # type: ignore[return-value] - - async def readlines(self) -> list[AnyStr]: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.readlines() - - return await super().readlines() # type: ignore[return-value] - - async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: - if not self._rolled: - await checkpoint_if_cancelled() - self._fp.readinto(b) - - return await super().readinto(b) - - async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int: - if not self._rolled: - await checkpoint_if_cancelled() - self._fp.readinto(b) - - return await super().readinto1(b) - - async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.seek(offset, whence) - - return await super().seek(offset, whence) - - async def tell(self) -> int: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.tell() - - return await super().tell() - - async def truncate(self, size: int | None = None) -> int: - if not self._rolled: - await checkpoint_if_cancelled() - return self._fp.truncate(size) - - return await super().truncate(size) - - @overload - async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ... - @overload - async def write(self: SpooledTemporaryFile[str], b: str) -> int: ... - - async def write(self, b: ReadableBuffer | str) -> int: - """ - Asynchronously write data to the spooled temporary file. - - If the file has not yet been rolled over, the data is written synchronously, - and a rollover is triggered if the size exceeds the maximum size. - - :param s: The data to write. - :return: The number of bytes written. - :raises RuntimeError: If the underlying file is not initialized. - - """ - if not self._rolled: - await checkpoint_if_cancelled() - result = self._fp.write(b) - await self._check() - return result - - return await super().write(b) # type: ignore[misc] - - @overload - async def writelines( - self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer] - ) -> None: ... - @overload - async def writelines( - self: SpooledTemporaryFile[str], lines: Iterable[str] - ) -> None: ... - - async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None: - """ - Asynchronously write a list of lines to the spooled temporary file. - - If the file has not yet been rolled over, the lines are written synchronously, - and a rollover is triggered if the size exceeds the maximum size. - - :param lines: An iterable of lines to write. - :raises RuntimeError: If the underlying file is not initialized. - - """ - if not self._rolled: - await checkpoint_if_cancelled() - result = self._fp.writelines(lines) - await self._check() - return result - - return await super().writelines(lines) # type: ignore[misc] - - -class TemporaryDirectory(Generic[AnyStr]): - """ - An asynchronous temporary directory that is created and cleaned up automatically. - - This class provides an asynchronous context manager for creating a temporary - directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to - perform directory creation and cleanup operations in a background thread. - - :param suffix: Suffix to be added to the temporary directory name. - :param prefix: Prefix to be added to the temporary directory name. - :param dir: The parent directory where the temporary directory is created. - :param ignore_cleanup_errors: Whether to ignore errors during cleanup - :param delete: Whether to delete the directory upon closing (Python 3.12+). - """ - - def __init__( - self, - suffix: AnyStr | None = None, - prefix: AnyStr | None = None, - dir: AnyStr | None = None, - *, - ignore_cleanup_errors: bool = False, - delete: bool = True, - ) -> None: - self.suffix: AnyStr | None = suffix - self.prefix: AnyStr | None = prefix - self.dir: AnyStr | None = dir - self.ignore_cleanup_errors = ignore_cleanup_errors - self.delete = delete - - self._tempdir: tempfile.TemporaryDirectory | None = None - - async def __aenter__(self) -> str: - params: dict[str, Any] = { - "suffix": self.suffix, - "prefix": self.prefix, - "dir": self.dir, - "ignore_cleanup_errors": self.ignore_cleanup_errors, - } - if sys.version_info >= (3, 12): - params["delete"] = self.delete - - self._tempdir = await to_thread.run_sync( - lambda: tempfile.TemporaryDirectory(**params) - ) - return await to_thread.run_sync(self._tempdir.__enter__) - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: TracebackType | None, - ) -> None: - if self._tempdir is not None: - await to_thread.run_sync( - self._tempdir.__exit__, exc_type, exc_value, traceback - ) - - async def cleanup(self) -> None: - if self._tempdir is not None: - await to_thread.run_sync(self._tempdir.cleanup) - - -@overload -async def mkstemp( - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, - text: bool = False, -) -> tuple[int, str]: ... - - -@overload -async def mkstemp( - suffix: bytes | None = None, - prefix: bytes | None = None, - dir: bytes | None = None, - text: bool = False, -) -> tuple[int, bytes]: ... - - -async def mkstemp( - suffix: AnyStr | None = None, - prefix: AnyStr | None = None, - dir: AnyStr | None = None, - text: bool = False, -) -> tuple[int, str | bytes]: - """ - Asynchronously create a temporary file and return an OS-level handle and the file - name. - - This function wraps `tempfile.mkstemp` and executes it in a background thread. - - :param suffix: Suffix to be added to the file name. - :param prefix: Prefix to be added to the file name. - :param dir: Directory in which the temporary file is created. - :param text: Whether the file is opened in text mode. - :return: A tuple containing the file descriptor and the file name. - - """ - return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text) - - -@overload -async def mkdtemp( - suffix: str | None = None, - prefix: str | None = None, - dir: str | None = None, -) -> str: ... - - -@overload -async def mkdtemp( - suffix: bytes | None = None, - prefix: bytes | None = None, - dir: bytes | None = None, -) -> bytes: ... - - -async def mkdtemp( - suffix: AnyStr | None = None, - prefix: AnyStr | None = None, - dir: AnyStr | None = None, -) -> str | bytes: - """ - Asynchronously create a temporary directory and return its path. - - This function wraps `tempfile.mkdtemp` and executes it in a background thread. - - :param suffix: Suffix to be added to the directory name. - :param prefix: Prefix to be added to the directory name. - :param dir: Parent directory where the temporary directory is created. - :return: The path of the created temporary directory. - - """ - return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir) - - -async def gettempdir() -> str: - """ - Asynchronously return the name of the directory used for temporary files. - - This function wraps `tempfile.gettempdir` and executes it in a background thread. - - :return: The path of the temporary directory as a string. - - """ - return await to_thread.run_sync(tempfile.gettempdir) - - -async def gettempdirb() -> bytes: - """ - Asynchronously return the name of the directory used for temporary files in bytes. - - This function wraps `tempfile.gettempdirb` and executes it in a background thread. - - :return: The path of the temporary directory as bytes. - - """ - return await to_thread.run_sync(tempfile.gettempdirb) diff --git a/server/venv/Lib/site-packages/anyio/_core/_testing.py b/server/venv/Lib/site-packages/anyio/_core/_testing.py deleted file mode 100644 index 369e65c..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_testing.py +++ /dev/null @@ -1,82 +0,0 @@ -from __future__ import annotations - -from collections.abc import Awaitable, Generator -from typing import Any, cast - -from ._eventloop import get_async_backend - - -class TaskInfo: - """ - Represents an asynchronous task. - - :ivar int id: the unique identifier of the task - :ivar parent_id: the identifier of the parent task, if any - :vartype parent_id: Optional[int] - :ivar str name: the description of the task (if any) - :ivar ~collections.abc.Coroutine coro: the coroutine object of the task - """ - - __slots__ = "_name", "id", "parent_id", "name", "coro" - - def __init__( - self, - id: int, - parent_id: int | None, - name: str | None, - coro: Generator[Any, Any, Any] | Awaitable[Any], - ): - func = get_current_task - self._name = f"{func.__module__}.{func.__qualname__}" - self.id: int = id - self.parent_id: int | None = parent_id - self.name: str | None = name - self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro - - def __eq__(self, other: object) -> bool: - if isinstance(other, TaskInfo): - return self.id == other.id - - return NotImplemented - - def __hash__(self) -> int: - return hash(self.id) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})" - - def has_pending_cancellation(self) -> bool: - """ - Return ``True`` if the task has a cancellation pending, ``False`` otherwise. - - """ - return False - - -def get_current_task() -> TaskInfo: - """ - Return the current task. - - :return: a representation of the current task - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().get_current_task() - - -def get_running_tasks() -> list[TaskInfo]: - """ - Return a list of running tasks in the current event loop. - - :return: a list of task info objects - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return cast("list[TaskInfo]", get_async_backend().get_running_tasks()) - - -async def wait_all_tasks_blocked() -> None: - """Wait until all other tasks are waiting for something.""" - await get_async_backend().wait_all_tasks_blocked() diff --git a/server/venv/Lib/site-packages/anyio/_core/_typedattr.py b/server/venv/Lib/site-packages/anyio/_core/_typedattr.py deleted file mode 100644 index f358a44..0000000 --- a/server/venv/Lib/site-packages/anyio/_core/_typedattr.py +++ /dev/null @@ -1,81 +0,0 @@ -from __future__ import annotations - -from collections.abc import Callable, Mapping -from typing import Any, TypeVar, final, overload - -from ._exceptions import TypedAttributeLookupError - -T_Attr = TypeVar("T_Attr") -T_Default = TypeVar("T_Default") -undefined = object() - - -def typed_attribute() -> Any: - """Return a unique object, used to mark typed attributes.""" - return object() - - -class TypedAttributeSet: - """ - Superclass for typed attribute collections. - - Checks that every public attribute of every subclass has a type annotation. - """ - - def __init_subclass__(cls) -> None: - annotations: dict[str, Any] = getattr(cls, "__annotations__", {}) - for attrname in dir(cls): - if not attrname.startswith("_") and attrname not in annotations: - raise TypeError( - f"Attribute {attrname!r} is missing its type annotation" - ) - - super().__init_subclass__() - - -class TypedAttributeProvider: - """Base class for classes that wish to provide typed extra attributes.""" - - @property - def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]: - """ - A mapping of the extra attributes to callables that return the corresponding - values. - - If the provider wraps another provider, the attributes from that wrapper should - also be included in the returned mapping (but the wrapper may override the - callables from the wrapped instance). - - """ - return {} - - @overload - def extra(self, attribute: T_Attr) -> T_Attr: ... - - @overload - def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ... - - @final - def extra(self, attribute: Any, default: object = undefined) -> object: - """ - extra(attribute, default=undefined) - - Return the value of the given typed extra attribute. - - :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to - look for - :param default: the value that should be returned if no value is found for the - attribute - :raises ~anyio.TypedAttributeLookupError: if the search failed and no default - value was given - - """ - try: - getter = self.extra_attributes[attribute] - except KeyError: - if default is undefined: - raise TypedAttributeLookupError("Attribute not found") from None - else: - return default - - return getter() diff --git a/server/venv/Lib/site-packages/anyio/abc/__init__.py b/server/venv/Lib/site-packages/anyio/abc/__init__.py deleted file mode 100644 index d560ce3..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/__init__.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import annotations - -from ._eventloop import AsyncBackend as AsyncBackend -from ._resources import AsyncResource as AsyncResource -from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket -from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket -from ._sockets import IPAddressType as IPAddressType -from ._sockets import IPSockAddrType as IPSockAddrType -from ._sockets import SocketAttribute as SocketAttribute -from ._sockets import SocketListener as SocketListener -from ._sockets import SocketStream as SocketStream -from ._sockets import UDPPacketType as UDPPacketType -from ._sockets import UDPSocket as UDPSocket -from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType -from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket -from ._sockets import UNIXSocketStream as UNIXSocketStream -from ._streams import AnyByteReceiveStream as AnyByteReceiveStream -from ._streams import AnyByteSendStream as AnyByteSendStream -from ._streams import AnyByteStream as AnyByteStream -from ._streams import AnyByteStreamConnectable as AnyByteStreamConnectable -from ._streams import AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream -from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream -from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream -from ._streams import ByteReceiveStream as ByteReceiveStream -from ._streams import ByteSendStream as ByteSendStream -from ._streams import ByteStream as ByteStream -from ._streams import ByteStreamConnectable as ByteStreamConnectable -from ._streams import Listener as Listener -from ._streams import ObjectReceiveStream as ObjectReceiveStream -from ._streams import ObjectSendStream as ObjectSendStream -from ._streams import ObjectStream as ObjectStream -from ._streams import ObjectStreamConnectable as ObjectStreamConnectable -from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream -from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream -from ._streams import UnreliableObjectStream as UnreliableObjectStream -from ._subprocesses import Process as Process -from ._tasks import TaskGroup as TaskGroup -from ._tasks import TaskStatus as TaskStatus -from ._testing import TestRunner as TestRunner - -# Re-exported here, for backwards compatibility -# isort: off -from .._core._synchronization import ( - CapacityLimiter as CapacityLimiter, - Condition as Condition, - Event as Event, - Lock as Lock, - Semaphore as Semaphore, -) -from .._core._tasks import CancelScope as CancelScope -from ..from_thread import BlockingPortal as BlockingPortal - -# Re-export imports so they look like they live directly in this package -for __value in list(locals().values()): - if getattr(__value, "__module__", "").startswith("anyio.abc."): - __value.__module__ = __name__ - -del __value diff --git a/server/venv/Lib/site-packages/anyio/abc/_eventloop.py b/server/venv/Lib/site-packages/anyio/abc/_eventloop.py deleted file mode 100644 index cad3fa7..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_eventloop.py +++ /dev/null @@ -1,410 +0,0 @@ -from __future__ import annotations - -import math -import sys -from abc import ABCMeta, abstractmethod -from collections.abc import AsyncIterator, Awaitable, Callable, Coroutine, Sequence -from contextlib import AbstractContextManager -from os import PathLike -from signal import Signals -from socket import AddressFamily, SocketKind, socket -from typing import ( - IO, - TYPE_CHECKING, - Any, - TypeAlias, - TypeVar, - overload, -) - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -if TYPE_CHECKING: - from _typeshed import FileDescriptorLike - - from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore - from .._core._tasks import CancelScope - from .._core._testing import TaskInfo - from ._sockets import ( - ConnectedUDPSocket, - ConnectedUNIXDatagramSocket, - IPSockAddrType, - SocketListener, - SocketStream, - UDPSocket, - UNIXDatagramSocket, - UNIXSocketStream, - ) - from ._subprocesses import Process - from ._tasks import TaskGroup - from ._testing import TestRunner - -T_Retval = TypeVar("T_Retval") -T_co = TypeVar("T_co", covariant=True) -PosArgsT = TypeVarTuple("PosArgsT") -StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes] - - -class AsyncBackend(metaclass=ABCMeta): - @classmethod - @abstractmethod - def run( - cls, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], - args: tuple[Unpack[PosArgsT]], - kwargs: dict[str, Any], - options: dict[str, Any], - ) -> T_Retval: - """ - Run the given coroutine function in an asynchronous event loop. - - The current thread must not be already running an event loop. - - :param func: a coroutine function - :param args: positional arguments to ``func`` - :param kwargs: positional arguments to ``func`` - :param options: keyword arguments to call the backend ``run()`` implementation - with - :return: the return value of the coroutine function - """ - - @classmethod - @abstractmethod - def current_token(cls) -> object: - """ - Return an object that allows other threads to run code inside the event loop. - - :return: a token object, specific to the event loop running in the current - thread - """ - - @classmethod - @abstractmethod - def current_time(cls) -> float: - """ - Return the current value of the event loop's internal clock. - - :return: the clock value (seconds) - """ - - @classmethod - @abstractmethod - def cancelled_exception_class(cls) -> type[BaseException]: - """Return the exception class that is raised in a task if it's cancelled.""" - - @classmethod - @abstractmethod - async def checkpoint(cls) -> None: - """ - Check if the task has been cancelled, and allow rescheduling of other tasks. - - This is effectively the same as running :meth:`checkpoint_if_cancelled` and then - :meth:`cancel_shielded_checkpoint`. - """ - - @classmethod - async def checkpoint_if_cancelled(cls) -> None: - """ - Check if the current task group has been cancelled. - - This will check if the task has been cancelled, but will not allow other tasks - to be scheduled if not. - - """ - if cls.current_effective_deadline() == -math.inf: - await cls.checkpoint() - - @classmethod - async def cancel_shielded_checkpoint(cls) -> None: - """ - Allow the rescheduling of other tasks. - - This will give other tasks the opportunity to run, but without checking if the - current task group has been cancelled, unlike with :meth:`checkpoint`. - - """ - with cls.create_cancel_scope(shield=True): - await cls.sleep(0) - - @classmethod - @abstractmethod - async def sleep(cls, delay: float) -> None: - """ - Pause the current task for the specified duration. - - :param delay: the duration, in seconds - """ - - @classmethod - @abstractmethod - def create_cancel_scope( - cls, *, deadline: float = math.inf, shield: bool = False - ) -> CancelScope: - pass - - @classmethod - @abstractmethod - def current_effective_deadline(cls) -> float: - """ - Return the nearest deadline among all the cancel scopes effective for the - current task. - - :return: - - a clock value from the event loop's internal clock - - ``inf`` if there is no deadline in effect - - ``-inf`` if the current scope has been cancelled - :rtype: float - """ - - @classmethod - @abstractmethod - def create_task_group(cls) -> TaskGroup: - pass - - @classmethod - @abstractmethod - def create_event(cls) -> Event: - pass - - @classmethod - @abstractmethod - def create_lock(cls, *, fast_acquire: bool) -> Lock: - pass - - @classmethod - @abstractmethod - def create_semaphore( - cls, - initial_value: int, - *, - max_value: int | None = None, - fast_acquire: bool = False, - ) -> Semaphore: - pass - - @classmethod - @abstractmethod - def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter: - pass - - @classmethod - @abstractmethod - async def run_sync_in_worker_thread( - cls, - func: Callable[[Unpack[PosArgsT]], T_Retval], - args: tuple[Unpack[PosArgsT]], - abandon_on_cancel: bool = False, - limiter: CapacityLimiter | None = None, - ) -> T_Retval: - pass - - @classmethod - @abstractmethod - def check_cancelled(cls) -> None: - pass - - @classmethod - @abstractmethod - def run_async_from_thread( - cls, - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - args: tuple[Unpack[PosArgsT]], - token: object, - ) -> T_co: - pass - - @classmethod - @abstractmethod - def run_sync_from_thread( - cls, - func: Callable[[Unpack[PosArgsT]], T_Retval], - args: tuple[Unpack[PosArgsT]], - token: object, - ) -> T_Retval: - pass - - @classmethod - @abstractmethod - async def open_process( - cls, - command: StrOrBytesPath | Sequence[StrOrBytesPath], - *, - stdin: int | IO[Any] | None, - stdout: int | IO[Any] | None, - stderr: int | IO[Any] | None, - **kwargs: Any, - ) -> Process: - pass - - @classmethod - @abstractmethod - def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None: - pass - - @classmethod - @abstractmethod - async def connect_tcp( - cls, host: str, port: int, local_address: IPSockAddrType | None = None - ) -> SocketStream: - pass - - @classmethod - @abstractmethod - async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream: - pass - - @classmethod - @abstractmethod - def create_tcp_listener(cls, sock: socket) -> SocketListener: - pass - - @classmethod - @abstractmethod - def create_unix_listener(cls, sock: socket) -> SocketListener: - pass - - @classmethod - @abstractmethod - async def create_udp_socket( - cls, - family: AddressFamily, - local_address: IPSockAddrType | None, - remote_address: IPSockAddrType | None, - reuse_port: bool, - ) -> UDPSocket | ConnectedUDPSocket: - pass - - @classmethod - @overload - async def create_unix_datagram_socket( - cls, raw_socket: socket, remote_path: None - ) -> UNIXDatagramSocket: ... - - @classmethod - @overload - async def create_unix_datagram_socket( - cls, raw_socket: socket, remote_path: str | bytes - ) -> ConnectedUNIXDatagramSocket: ... - - @classmethod - @abstractmethod - async def create_unix_datagram_socket( - cls, raw_socket: socket, remote_path: str | bytes | None - ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket: - pass - - @classmethod - @abstractmethod - async def getaddrinfo( - cls, - host: bytes | str | None, - port: str | int | None, - *, - family: int | AddressFamily = 0, - type: int | SocketKind = 0, - proto: int = 0, - flags: int = 0, - ) -> Sequence[ - tuple[ - AddressFamily, - SocketKind, - int, - str, - tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes], - ] - ]: - pass - - @classmethod - @abstractmethod - async def getnameinfo( - cls, sockaddr: IPSockAddrType, flags: int = 0 - ) -> tuple[str, str]: - pass - - @classmethod - @abstractmethod - async def wait_readable(cls, obj: FileDescriptorLike) -> None: - pass - - @classmethod - @abstractmethod - async def wait_writable(cls, obj: FileDescriptorLike) -> None: - pass - - @classmethod - @abstractmethod - def notify_closing(cls, obj: FileDescriptorLike) -> None: - pass - - @classmethod - @abstractmethod - async def wrap_listener_socket(cls, sock: socket) -> SocketListener: - pass - - @classmethod - @abstractmethod - async def wrap_stream_socket(cls, sock: socket) -> SocketStream: - pass - - @classmethod - @abstractmethod - async def wrap_unix_stream_socket(cls, sock: socket) -> UNIXSocketStream: - pass - - @classmethod - @abstractmethod - async def wrap_udp_socket(cls, sock: socket) -> UDPSocket: - pass - - @classmethod - @abstractmethod - async def wrap_connected_udp_socket(cls, sock: socket) -> ConnectedUDPSocket: - pass - - @classmethod - @abstractmethod - async def wrap_unix_datagram_socket(cls, sock: socket) -> UNIXDatagramSocket: - pass - - @classmethod - @abstractmethod - async def wrap_connected_unix_datagram_socket( - cls, sock: socket - ) -> ConnectedUNIXDatagramSocket: - pass - - @classmethod - @abstractmethod - def current_default_thread_limiter(cls) -> CapacityLimiter: - pass - - @classmethod - @abstractmethod - def open_signal_receiver( - cls, *signals: Signals - ) -> AbstractContextManager[AsyncIterator[Signals]]: - pass - - @classmethod - @abstractmethod - def get_current_task(cls) -> TaskInfo: - pass - - @classmethod - @abstractmethod - def get_running_tasks(cls) -> Sequence[TaskInfo]: - pass - - @classmethod - @abstractmethod - async def wait_all_tasks_blocked(cls) -> None: - pass - - @classmethod - @abstractmethod - def create_test_runner(cls, options: dict[str, Any]) -> TestRunner: - pass diff --git a/server/venv/Lib/site-packages/anyio/abc/_resources.py b/server/venv/Lib/site-packages/anyio/abc/_resources.py deleted file mode 100644 index 10df115..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_resources.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations - -from abc import ABCMeta, abstractmethod -from types import TracebackType -from typing import TypeVar - -T = TypeVar("T") - - -class AsyncResource(metaclass=ABCMeta): - """ - Abstract base class for all closeable asynchronous resources. - - Works as an asynchronous context manager which returns the instance itself on enter, - and calls :meth:`aclose` on exit. - """ - - __slots__ = () - - async def __aenter__(self: T) -> T: - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - await self.aclose() - - @abstractmethod - async def aclose(self) -> None: - """Close the resource.""" diff --git a/server/venv/Lib/site-packages/anyio/abc/_sockets.py b/server/venv/Lib/site-packages/anyio/abc/_sockets.py deleted file mode 100644 index feb26bd..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_sockets.py +++ /dev/null @@ -1,399 +0,0 @@ -from __future__ import annotations - -import errno -import socket -from abc import abstractmethod -from collections.abc import Callable, Collection, Mapping -from contextlib import AsyncExitStack -from io import IOBase -from ipaddress import IPv4Address, IPv6Address -from socket import AddressFamily -from typing import Any, TypeAlias, TypeVar - -from .._core._eventloop import get_async_backend -from .._core._typedattr import ( - TypedAttributeProvider, - TypedAttributeSet, - typed_attribute, -) -from ._streams import ByteStream, Listener, UnreliableObjectStream -from ._tasks import TaskGroup - -IPAddressType: TypeAlias = str | IPv4Address | IPv6Address -IPSockAddrType: TypeAlias = tuple[str, int] -SockAddrType: TypeAlias = IPSockAddrType | str -UDPPacketType: TypeAlias = tuple[bytes, IPSockAddrType] -UNIXDatagramPacketType: TypeAlias = tuple[bytes, str] -T_Retval = TypeVar("T_Retval") - - -def _validate_socket( - sock_or_fd: socket.socket | int, - sock_type: socket.SocketKind, - addr_family: socket.AddressFamily = socket.AF_UNSPEC, - *, - require_connected: bool = False, - require_bound: bool = False, -) -> socket.socket: - if isinstance(sock_or_fd, int): - try: - sock = socket.socket(fileno=sock_or_fd) - except OSError as exc: - if exc.errno == errno.ENOTSOCK: - raise ValueError( - "the file descriptor does not refer to a socket" - ) from exc - elif require_connected: - raise ValueError("the socket must be connected") from exc - elif require_bound: - raise ValueError("the socket must be bound to a local address") from exc - else: - raise - elif isinstance(sock_or_fd, socket.socket): - sock = sock_or_fd - else: - raise TypeError( - f"expected an int or socket, got {type(sock_or_fd).__qualname__} instead" - ) - - try: - if require_connected: - try: - sock.getpeername() - except OSError as exc: - raise ValueError("the socket must be connected") from exc - - if require_bound: - try: - if sock.family in (socket.AF_INET, socket.AF_INET6): - bound_addr = sock.getsockname()[1] - else: - bound_addr = sock.getsockname() - except OSError: - bound_addr = None - - if not bound_addr: - raise ValueError("the socket must be bound to a local address") - - if addr_family != socket.AF_UNSPEC and sock.family != addr_family: - raise ValueError( - f"address family mismatch: expected {addr_family.name}, got " - f"{sock.family.name}" - ) - - if sock.type != sock_type: - raise ValueError( - f"socket type mismatch: expected {sock_type.name}, got {sock.type.name}" - ) - except BaseException: - # Avoid ResourceWarning from the locally constructed socket object - if isinstance(sock_or_fd, int): - sock.detach() - - raise - - sock.setblocking(False) - return sock - - -class SocketAttribute(TypedAttributeSet): - """ - .. attribute:: family - :type: socket.AddressFamily - - the address family of the underlying socket - - .. attribute:: local_address - :type: tuple[str, int] | str - - the local address the underlying socket is connected to - - .. attribute:: local_port - :type: int - - for IP based sockets, the local port the underlying socket is bound to - - .. attribute:: raw_socket - :type: socket.socket - - the underlying stdlib socket object - - .. attribute:: remote_address - :type: tuple[str, int] | str - - the remote address the underlying socket is connected to - - .. attribute:: remote_port - :type: int - - for IP based sockets, the remote port the underlying socket is connected to - """ - - family: AddressFamily = typed_attribute() - local_address: SockAddrType = typed_attribute() - local_port: int = typed_attribute() - raw_socket: socket.socket = typed_attribute() - remote_address: SockAddrType = typed_attribute() - remote_port: int = typed_attribute() - - -class _SocketProvider(TypedAttributeProvider): - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - from .._core._sockets import convert_ipv6_sockaddr as convert - - attributes: dict[Any, Callable[[], Any]] = { - SocketAttribute.family: lambda: self._raw_socket.family, - SocketAttribute.local_address: lambda: convert( - self._raw_socket.getsockname() - ), - SocketAttribute.raw_socket: lambda: self._raw_socket, - } - try: - peername: tuple[str, int] | None = convert(self._raw_socket.getpeername()) - except OSError: - peername = None - - # Provide the remote address for connected sockets - if peername is not None: - attributes[SocketAttribute.remote_address] = lambda: peername - - # Provide local and remote ports for IP based sockets - if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6): - attributes[SocketAttribute.local_port] = lambda: ( - self._raw_socket.getsockname()[1] - ) - if peername is not None: - remote_port = peername[1] - attributes[SocketAttribute.remote_port] = lambda: remote_port - - return attributes - - @property - @abstractmethod - def _raw_socket(self) -> socket.socket: - pass - - -class SocketStream(ByteStream, _SocketProvider): - """ - Transports bytes over a socket. - - Supports all relevant extra attributes from :class:`~SocketAttribute`. - """ - - @classmethod - async def from_socket(cls, sock_or_fd: socket.socket | int) -> SocketStream: - """ - Wrap an existing socket object or file descriptor as a socket stream. - - The newly created socket wrapper takes ownership of the socket being passed in. - The existing socket must already be connected. - - :param sock_or_fd: a socket object or file descriptor - :return: a socket stream - - """ - sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_connected=True) - return await get_async_backend().wrap_stream_socket(sock) - - -class UNIXSocketStream(SocketStream): - @classmethod - async def from_socket(cls, sock_or_fd: socket.socket | int) -> UNIXSocketStream: - """ - Wrap an existing socket object or file descriptor as a UNIX socket stream. - - The newly created socket wrapper takes ownership of the socket being passed in. - The existing socket must already be connected. - - :param sock_or_fd: a socket object or file descriptor - :return: a UNIX socket stream - - """ - sock = _validate_socket( - sock_or_fd, socket.SOCK_STREAM, socket.AF_UNIX, require_connected=True - ) - return await get_async_backend().wrap_unix_stream_socket(sock) - - @abstractmethod - async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None: - """ - Send file descriptors along with a message to the peer. - - :param message: a non-empty bytestring - :param fds: a collection of files (either numeric file descriptors or open file - or socket objects) - """ - - @abstractmethod - async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]: - """ - Receive file descriptors along with a message from the peer. - - :param msglen: length of the message to expect from the peer - :param maxfds: maximum number of file descriptors to expect from the peer - :return: a tuple of (message, file descriptors) - """ - - -class SocketListener(Listener[SocketStream], _SocketProvider): - """ - Listens to incoming socket connections. - - Supports all relevant extra attributes from :class:`~SocketAttribute`. - """ - - @classmethod - async def from_socket( - cls, - sock_or_fd: socket.socket | int, - ) -> SocketListener: - """ - Wrap an existing socket object or file descriptor as a socket listener. - - The newly created listener takes ownership of the socket being passed in. - - :param sock_or_fd: a socket object or file descriptor - :return: a socket listener - - """ - sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_bound=True) - return await get_async_backend().wrap_listener_socket(sock) - - @abstractmethod - async def accept(self) -> SocketStream: - """Accept an incoming connection.""" - - async def serve( - self, - handler: Callable[[SocketStream], Any], - task_group: TaskGroup | None = None, - ) -> None: - from .. import create_task_group - - async with AsyncExitStack() as stack: - if task_group is None: - task_group = await stack.enter_async_context(create_task_group()) - - while True: - stream = await self.accept() - task_group.start_soon(handler, stream) - - -class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider): - """ - Represents an unconnected UDP socket. - - Supports all relevant extra attributes from :class:`~SocketAttribute`. - """ - - @classmethod - async def from_socket(cls, sock_or_fd: socket.socket | int) -> UDPSocket: - """ - Wrap an existing socket object or file descriptor as a UDP socket. - - The newly created socket wrapper takes ownership of the socket being passed in. - The existing socket must be bound to a local address. - - :param sock_or_fd: a socket object or file descriptor - :return: a UDP socket - - """ - sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, require_bound=True) - return await get_async_backend().wrap_udp_socket(sock) - - async def sendto(self, data: bytes, host: str, port: int) -> None: - """ - Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))). - - """ - return await self.send((data, (host, port))) - - -class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider): - """ - Represents an connected UDP socket. - - Supports all relevant extra attributes from :class:`~SocketAttribute`. - """ - - @classmethod - async def from_socket(cls, sock_or_fd: socket.socket | int) -> ConnectedUDPSocket: - """ - Wrap an existing socket object or file descriptor as a connected UDP socket. - - The newly created socket wrapper takes ownership of the socket being passed in. - The existing socket must already be connected. - - :param sock_or_fd: a socket object or file descriptor - :return: a connected UDP socket - - """ - sock = _validate_socket( - sock_or_fd, - socket.SOCK_DGRAM, - require_connected=True, - ) - return await get_async_backend().wrap_connected_udp_socket(sock) - - -class UNIXDatagramSocket( - UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider -): - """ - Represents an unconnected Unix datagram socket. - - Supports all relevant extra attributes from :class:`~SocketAttribute`. - """ - - @classmethod - async def from_socket( - cls, - sock_or_fd: socket.socket | int, - ) -> UNIXDatagramSocket: - """ - Wrap an existing socket object or file descriptor as a UNIX datagram - socket. - - The newly created socket wrapper takes ownership of the socket being passed in. - - :param sock_or_fd: a socket object or file descriptor - :return: a UNIX datagram socket - - """ - sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX) - return await get_async_backend().wrap_unix_datagram_socket(sock) - - async def sendto(self, data: bytes, path: str) -> None: - """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path)).""" - return await self.send((data, path)) - - -class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider): - """ - Represents a connected Unix datagram socket. - - Supports all relevant extra attributes from :class:`~SocketAttribute`. - """ - - @classmethod - async def from_socket( - cls, - sock_or_fd: socket.socket | int, - ) -> ConnectedUNIXDatagramSocket: - """ - Wrap an existing socket object or file descriptor as a connected UNIX datagram - socket. - - The newly created socket wrapper takes ownership of the socket being passed in. - The existing socket must already be connected. - - :param sock_or_fd: a socket object or file descriptor - :return: a connected UNIX datagram socket - - """ - sock = _validate_socket( - sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX, require_connected=True - ) - return await get_async_backend().wrap_connected_unix_datagram_socket(sock) diff --git a/server/venv/Lib/site-packages/anyio/abc/_streams.py b/server/venv/Lib/site-packages/anyio/abc/_streams.py deleted file mode 100644 index 186e3f5..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_streams.py +++ /dev/null @@ -1,233 +0,0 @@ -from __future__ import annotations - -from abc import ABCMeta, abstractmethod -from collections.abc import Callable -from typing import Any, Generic, TypeAlias, TypeVar - -from .._core._exceptions import EndOfStream -from .._core._typedattr import TypedAttributeProvider -from ._resources import AsyncResource -from ._tasks import TaskGroup - -T_Item = TypeVar("T_Item") -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True) - - -class UnreliableObjectReceiveStream( - Generic[T_co], AsyncResource, TypedAttributeProvider -): - """ - An interface for receiving objects. - - This interface makes no guarantees that the received messages arrive in the order in - which they were sent, or that no messages are missed. - - Asynchronously iterating over objects of this type will yield objects matching the - given type parameter. - """ - - def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]: - return self - - async def __anext__(self) -> T_co: - try: - return await self.receive() - except EndOfStream: - raise StopAsyncIteration from None - - @abstractmethod - async def receive(self) -> T_co: - """ - Receive the next item. - - :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly - closed - :raises ~anyio.EndOfStream: if this stream has been closed from the other end - :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable - due to external causes - """ - - -class UnreliableObjectSendStream( - Generic[T_contra], AsyncResource, TypedAttributeProvider -): - """ - An interface for sending objects. - - This interface makes no guarantees that the messages sent will reach the - recipient(s) in the same order in which they were sent, or at all. - """ - - @abstractmethod - async def send(self, item: T_contra) -> None: - """ - Send an item to the peer(s). - - :param item: the item to send - :raises ~anyio.ClosedResourceError: if the send stream has been explicitly - closed - :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable - due to external causes - """ - - -class UnreliableObjectStream( - UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item] -): - """ - A bidirectional message stream which does not guarantee the order or reliability of - message delivery. - """ - - -class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]): - """ - A receive message stream which guarantees that messages are received in the same - order in which they were sent, and that no messages are missed. - """ - - -class ObjectSendStream(UnreliableObjectSendStream[T_contra]): - """ - A send message stream which guarantees that messages are delivered in the same order - in which they were sent, without missing any messages in the middle. - """ - - -class ObjectStream( - ObjectReceiveStream[T_Item], - ObjectSendStream[T_Item], - UnreliableObjectStream[T_Item], -): - """ - A bidirectional message stream which guarantees the order and reliability of message - delivery. - """ - - @abstractmethod - async def send_eof(self) -> None: - """ - Send an end-of-file indication to the peer. - - You should not try to send any further data to this stream after calling this - method. This method is idempotent (does nothing on successive calls). - """ - - -class ByteReceiveStream(AsyncResource, TypedAttributeProvider): - """ - An interface for receiving bytes from a single peer. - - Iterating this byte stream will yield a byte string of arbitrary length, but no more - than 65536 bytes. - """ - - def __aiter__(self) -> ByteReceiveStream: - return self - - async def __anext__(self) -> bytes: - try: - return await self.receive() - except EndOfStream: - raise StopAsyncIteration from None - - @abstractmethod - async def receive(self, max_bytes: int = 65536) -> bytes: - """ - Receive at most ``max_bytes`` bytes from the peer. - - .. note:: Implementers of this interface should not return an empty - :class:`bytes` object, and users should ignore them. - - :param max_bytes: maximum number of bytes to receive - :return: the received bytes - :raises ~anyio.EndOfStream: if this stream has been closed from the other end - """ - - -class ByteSendStream(AsyncResource, TypedAttributeProvider): - """An interface for sending bytes to a single peer.""" - - @abstractmethod - async def send(self, item: bytes) -> None: - """ - Send the given bytes to the peer. - - :param item: the bytes to send - """ - - -class ByteStream(ByteReceiveStream, ByteSendStream): - """A bidirectional byte stream.""" - - @abstractmethod - async def send_eof(self) -> None: - """ - Send an end-of-file indication to the peer. - - You should not try to send any further data to this stream after calling this - method. This method is idempotent (does nothing on successive calls). - """ - - -#: Type alias for all unreliable bytes-oriented receive streams. -AnyUnreliableByteReceiveStream: TypeAlias = ( - UnreliableObjectReceiveStream[bytes] | ByteReceiveStream -) -#: Type alias for all unreliable bytes-oriented send streams. -AnyUnreliableByteSendStream: TypeAlias = ( - UnreliableObjectSendStream[bytes] | ByteSendStream -) -#: Type alias for all unreliable bytes-oriented streams. -AnyUnreliableByteStream: TypeAlias = UnreliableObjectStream[bytes] | ByteStream -#: Type alias for all bytes-oriented receive streams. -AnyByteReceiveStream: TypeAlias = ObjectReceiveStream[bytes] | ByteReceiveStream -#: Type alias for all bytes-oriented send streams. -AnyByteSendStream: TypeAlias = ObjectSendStream[bytes] | ByteSendStream -#: Type alias for all bytes-oriented streams. -AnyByteStream: TypeAlias = ObjectStream[bytes] | ByteStream - - -class Listener(Generic[T_co], AsyncResource, TypedAttributeProvider): - """An interface for objects that let you accept incoming connections.""" - - @abstractmethod - async def serve( - self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None - ) -> None: - """ - Accept incoming connections as they come in and start tasks to handle them. - - :param handler: a callable that will be used to handle each accepted connection - :param task_group: the task group that will be used to start tasks for handling - each accepted connection (if omitted, an ad-hoc task group will be created) - """ - - -class ObjectStreamConnectable(Generic[T_co], metaclass=ABCMeta): - @abstractmethod - async def connect(self) -> ObjectStream[T_co]: - """ - Connect to the remote endpoint. - - :return: an object stream connected to the remote end - :raises ConnectionFailed: if the connection fails - """ - - -class ByteStreamConnectable(metaclass=ABCMeta): - @abstractmethod - async def connect(self) -> ByteStream: - """ - Connect to the remote endpoint. - - :return: a bytestream connected to the remote end - :raises ConnectionFailed: if the connection fails - """ - - -#: Type alias for all connectables returning bytestreams or bytes-oriented object streams -AnyByteStreamConnectable: TypeAlias = ( - ObjectStreamConnectable[bytes] | ByteStreamConnectable -) diff --git a/server/venv/Lib/site-packages/anyio/abc/_subprocesses.py b/server/venv/Lib/site-packages/anyio/abc/_subprocesses.py deleted file mode 100644 index ce0564c..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_subprocesses.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import annotations - -from abc import abstractmethod -from signal import Signals - -from ._resources import AsyncResource -from ._streams import ByteReceiveStream, ByteSendStream - - -class Process(AsyncResource): - """An asynchronous version of :class:`subprocess.Popen`.""" - - @abstractmethod - async def wait(self) -> int: - """ - Wait until the process exits. - - :return: the exit code of the process - """ - - @abstractmethod - def terminate(self) -> None: - """ - Terminates the process, gracefully if possible. - - On Windows, this calls ``TerminateProcess()``. - On POSIX systems, this sends ``SIGTERM`` to the process. - - .. seealso:: :meth:`subprocess.Popen.terminate` - """ - - @abstractmethod - def kill(self) -> None: - """ - Kills the process. - - On Windows, this calls ``TerminateProcess()``. - On POSIX systems, this sends ``SIGKILL`` to the process. - - .. seealso:: :meth:`subprocess.Popen.kill` - """ - - @abstractmethod - def send_signal(self, signal: Signals) -> None: - """ - Send a signal to the subprocess. - - .. seealso:: :meth:`subprocess.Popen.send_signal` - - :param signal: the signal number (e.g. :data:`signal.SIGHUP`) - """ - - @property - @abstractmethod - def pid(self) -> int: - """The process ID of the process.""" - - @property - @abstractmethod - def returncode(self) -> int | None: - """ - The return code of the process. If the process has not yet terminated, this will - be ``None``. - """ - - @property - @abstractmethod - def stdin(self) -> ByteSendStream | None: - """The stream for the standard input of the process.""" - - @property - @abstractmethod - def stdout(self) -> ByteReceiveStream | None: - """The stream for the standard output of the process.""" - - @property - @abstractmethod - def stderr(self) -> ByteReceiveStream | None: - """The stream for the standard error output of the process.""" diff --git a/server/venv/Lib/site-packages/anyio/abc/_tasks.py b/server/venv/Lib/site-packages/anyio/abc/_tasks.py deleted file mode 100644 index 44ee3a7..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_tasks.py +++ /dev/null @@ -1,209 +0,0 @@ -from __future__ import annotations - -import sys -from abc import ABCMeta, abstractmethod -from collections.abc import Callable, Coroutine -from contextvars import Context -from types import TracebackType -from typing import TYPE_CHECKING, Any, Literal, Protocol, final, overload - -if sys.version_info >= (3, 13): - from typing import TypeVar -else: - from typing_extensions import TypeVar - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -if TYPE_CHECKING: - from .._core._tasks import CancelScope, TaskHandle - -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True, default=None) -PosArgsT = TypeVarTuple("PosArgsT") - - -def get_callable_name(func: Callable, override: object = None) -> str: - if override is not None: - return str(override) - - module = getattr(func, "__module__", None) - qualname = getattr(func, "__qualname__", None) - return ".".join([x for x in (module, qualname) if x]) - - -def call_for_coroutine( - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - args: tuple[Unpack[PosArgsT]], - **kwargs: Any, -) -> Coroutine[Any, Any, T_co]: - """ - Call the given function with the given positional and keyword arguments. - - :return: the resulting coroutine - :raises TypeError: if the return value was not a coroutine object - - """ - coro = func(*args, **kwargs) - if not isinstance(coro, Coroutine): - prefix = f"{func.__module__}." if hasattr(func, "__module__") else "" - raise TypeError( - f"Expected {prefix}{func.__qualname__}() to return a coroutine, but " - f"the return value ({coro!r}) is not a coroutine object" - ) - - return coro - - -class TaskStatus(Protocol[T_contra]): - @overload - def started(self: TaskStatus[None]) -> None: ... - - @overload - def started(self, value: T_contra) -> None: ... - - def started(self, value: T_contra | None = None) -> None: - """ - Signal that the task has started. - - :param value: object passed back to the starter of the task - """ - - -class TaskGroup(metaclass=ABCMeta): - """ - Groups several asynchronous tasks together. - - :ivar cancel_scope: the cancel scope inherited by all child tasks - :vartype cancel_scope: CancelScope - - .. note:: On asyncio, support for eager task factories is considered to be - **experimental**. In particular, they don't follow the usual semantics of new - tasks being scheduled on the next iteration of the event loop, and may thus - cause unexpected behavior in code that wasn't written with such semantics in - mind. - """ - - cancel_scope: CancelScope - - def cancel(self, reason: str | None = None) -> None: - """ - Cancel this task group's cancel scope immediately. - - This is a shortcut for calling ``.cancel_scope.cancel()`` on the task group. - - :param reason: a message describing the reason for the cancellation - - .. versionadded:: 4.14.0 - - """ - self.cancel_scope.cancel(reason) - - @abstractmethod - def create_task( - self, - coro: Coroutine[Any, Any, T_co], - *, - name: object = None, - context: Context | None = None, - ) -> TaskHandle[T_co]: - """ - Create a new task from a coroutine object and schedule it to run. - - :param coro: a coroutine object - :param name: optional name to give the task - :param context: optional context to run the task in - :return: a task handle - - .. versionadded:: 4.14.0 - """ - - @final - def start_soon( - self, - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - *args: Unpack[PosArgsT], - name: object = None, - ) -> TaskHandle[T_co]: - """ - Start a new task in this task group. - - :param func: a coroutine function - :param args: positional arguments to call the function with - :param name: name of the task, for the purposes of introspection and debugging - :return: a task handle - - .. versionadded:: 3.0 - .. versionchanged:: 4.14.0 - This method now returns a task handle. - - """ - final_name = get_callable_name(func, name) - return self.create_task(call_for_coroutine(func, args), name=final_name) - - @overload - async def start( - self, - func: Callable[..., Coroutine[Any, Any, T_co]], - *args: object, - name: object = None, - return_handle: Literal[False] = ..., - ) -> Any: ... - - @overload - async def start( - self, - func: Callable[..., Coroutine[Any, Any, T_co]], - *args: object, - name: object = None, - return_handle: Literal[True], - ) -> TaskHandle[T_co, Any]: ... - - @abstractmethod - async def start( - self, - func: Callable[..., Coroutine[Any, Any, T_co]], - *args: object, - name: object = None, - return_handle: Literal[False] | Literal[True] = False, - ) -> Any: - """ - Start a new task and wait until it signals for readiness. - - The target callable must accept a keyword argument ``task_status`` (of type - :class:`TaskStatus`). Awaiting on this method will return whatever was passed to - ``task_status.started()`` (``None`` by default). - - .. note:: The :class:`TaskStatus` class is generic, and the type argument should - indicate the type of the value that will be passed to - ``task_status.started()``. - - :param func: a coroutine function that accepts the ``task_status`` keyword - argument - :param args: positional arguments to call the function with - :param name: an optional name for the task, for introspection and debugging - :param return_handle: if ``True``, return a :class:`TaskHandle` which also - contains the start value in ``start_value`` - :return: the value passed to ``task_status.started()`` - :raises RuntimeError: if the task finishes without calling - ``task_status.started()`` - - .. seealso:: :ref:`start_initialize` - - .. versionadded:: 3.0 - """ - - @abstractmethod - async def __aenter__(self) -> TaskGroup: - """Enter the task group context and allow starting new tasks.""" - - @abstractmethod - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - """Exit the task group context waiting for all tasks to finish.""" diff --git a/server/venv/Lib/site-packages/anyio/abc/_testing.py b/server/venv/Lib/site-packages/anyio/abc/_testing.py deleted file mode 100644 index 2a93fb7..0000000 --- a/server/venv/Lib/site-packages/anyio/abc/_testing.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import annotations - -import types -from abc import ABCMeta, abstractmethod -from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable -from typing import Any, TypeVar - -_T = TypeVar("_T") - - -class TestRunner(metaclass=ABCMeta): - """ - Encapsulates a running event loop. Every call made through this object will use the - same event loop. - """ - - def __enter__(self) -> TestRunner: - return self - - @abstractmethod - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: types.TracebackType | None, - ) -> bool | None: ... - - @abstractmethod - def run_asyncgen_fixture( - self, - fixture_func: Callable[..., AsyncGenerator[_T, Any]], - kwargs: dict[str, Any], - ) -> Iterable[_T]: - """ - Run an async generator fixture. - - :param fixture_func: the fixture function - :param kwargs: keyword arguments to call the fixture function with - :return: an iterator yielding the value yielded from the async generator - """ - - @abstractmethod - def run_fixture( - self, - fixture_func: Callable[..., Coroutine[Any, Any, _T]], - kwargs: dict[str, Any], - ) -> _T: - """ - Run an async fixture. - - :param fixture_func: the fixture function - :param kwargs: keyword arguments to call the fixture function with - :return: the return value of the fixture function - """ - - @abstractmethod - def run_test( - self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any] - ) -> None: - """ - Run an async test function. - - :param test_func: the test function - :param kwargs: keyword arguments to call the test function with - """ - - @abstractmethod - def is_running(self) -> bool: - """ - Check if the test runner is running. - - :return: ``True`` if the coroutine is currently being run, ``False`` otherwise. - """ diff --git a/server/venv/Lib/site-packages/anyio/from_thread.py b/server/venv/Lib/site-packages/anyio/from_thread.py deleted file mode 100644 index 8c7914c..0000000 --- a/server/venv/Lib/site-packages/anyio/from_thread.py +++ /dev/null @@ -1,582 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "BlockingPortal", - "BlockingPortalProvider", - "check_cancelled", - "run", - "run_sync", - "start_blocking_portal", -) - -import sys -from collections.abc import Awaitable, Callable, Coroutine, Generator -from concurrent.futures import Future -from contextlib import ( - AbstractAsyncContextManager, - AbstractContextManager, - contextmanager, -) -from dataclasses import dataclass, field -from functools import partial -from inspect import isawaitable -from threading import Lock, Thread, current_thread, get_ident -from types import TracebackType -from typing import ( - Any, - Generic, - TypeVar, - cast, - overload, -) - -from ._core._eventloop import ( - get_cancelled_exc_class, - threadlocals, -) -from ._core._eventloop import run as run_eventloop -from ._core._exceptions import NoEventLoopError -from ._core._synchronization import Event -from ._core._tasks import CancelScope, create_task_group -from .abc._tasks import TaskStatus -from .lowlevel import EventLoopToken, current_token - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -T_Retval = TypeVar("T_Retval") -T_co = TypeVar("T_co", covariant=True) -PosArgsT = TypeVarTuple("PosArgsT") - - -def _token_or_error(token: EventLoopToken | None) -> EventLoopToken: - if token is not None: - return token - - try: - return threadlocals.current_token - except AttributeError: - raise NoEventLoopError( - "Not running inside an AnyIO worker thread, and no event loop token was " - "provided" - ) from None - - -def run( - func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]], - *args: Unpack[PosArgsT], - token: EventLoopToken | None = None, -) -> T_co: - """ - Call a coroutine function from a worker thread. - - :param func: a coroutine function - :param args: positional arguments for the callable - :param token: an event loop token to use to get back to the event loop thread - (required if calling this function from outside an AnyIO worker thread) - :return: the return value of the coroutine function - :raises MissingTokenError: if no token was provided and called from outside an - AnyIO worker thread - :raises RunFinishedError: if the event loop tied to ``token`` is no longer running - - .. versionchanged:: 4.11.0 - Added the ``token`` parameter. - - """ - explicit_token = token is not None - token = _token_or_error(token) - return token.backend_class.run_async_from_thread( - func, args, token=token.native_token if explicit_token else None - ) - - -def run_sync( - func: Callable[[Unpack[PosArgsT]], T_Retval], - *args: Unpack[PosArgsT], - token: EventLoopToken | None = None, -) -> T_Retval: - """ - Call a function in the event loop thread from a worker thread. - - :param func: a callable - :param args: positional arguments for the callable - :param token: an event loop token to use to get back to the event loop thread - (required if calling this function from outside an AnyIO worker thread) - :return: the return value of the callable - :raises MissingTokenError: if no token was provided and called from outside an - AnyIO worker thread - :raises RunFinishedError: if the event loop tied to ``token`` is no longer running - - .. versionchanged:: 4.11.0 - Added the ``token`` parameter. - - """ - explicit_token = token is not None - token = _token_or_error(token) - return token.backend_class.run_sync_from_thread( - func, args, token=token.native_token if explicit_token else None - ) - - -class _BlockingAsyncContextManager(Generic[T_co], AbstractContextManager): - _enter_future: Future[T_co] - _exit_future: Future[bool | None] - _exit_event: Event - _exit_exc_info: tuple[ - type[BaseException] | None, BaseException | None, TracebackType | None - ] = (None, None, None) - - def __init__( - self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal - ): - self._async_cm = async_cm - self._portal = portal - - async def run_async_cm(self) -> bool | None: - try: - self._exit_event = Event() - value = await self._async_cm.__aenter__() - except BaseException as exc: - self._enter_future.set_exception(exc) - raise - else: - self._enter_future.set_result(value) - - try: - # Wait for the sync context manager to exit. - # This next statement can raise `get_cancelled_exc_class()` if - # something went wrong in a task group in this async context - # manager. - await self._exit_event.wait() - finally: - # In case of cancellation, it could be that we end up here before - # `_BlockingAsyncContextManager.__exit__` is called, and an - # `_exit_exc_info` has been set. - result = await self._async_cm.__aexit__(*self._exit_exc_info) - - return result - - def __enter__(self) -> T_co: - self._enter_future = Future() - self._exit_future = self._portal.start_task_soon(self.run_async_cm) - return self._enter_future.result() - - def __exit__( - self, - __exc_type: type[BaseException] | None, - __exc_value: BaseException | None, - __traceback: TracebackType | None, - ) -> bool | None: - self._exit_exc_info = __exc_type, __exc_value, __traceback - self._portal.call(self._exit_event.set) - return self._exit_future.result() - - -class _BlockingPortalTaskStatus(TaskStatus): - def __init__(self, future: Future): - self._future = future - - def started(self, value: object = None) -> None: - self._future.set_result(value) - - -class BlockingPortal: - """ - An object that lets external threads run code in an asynchronous event loop. - - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - """ - - def __init__(self) -> None: - self._token = current_token() - self._event_loop_thread_id: int | None = get_ident() - self._stop_event = Event() - self._task_group = create_task_group() - - async def __aenter__(self) -> BlockingPortal: - await self._task_group.__aenter__() - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> bool: - await self.stop() - return await self._task_group.__aexit__(exc_type, exc_val, exc_tb) - - def _check_running(self) -> None: - if self._event_loop_thread_id is None: - raise RuntimeError("This portal is not running") - if self._event_loop_thread_id == get_ident(): - raise RuntimeError( - "This method cannot be called from the event loop thread" - ) - - async def sleep_until_stopped(self) -> None: - """Sleep until :meth:`stop` is called.""" - await self._stop_event.wait() - - async def stop(self, cancel_remaining: bool = False) -> None: - """ - Signal the portal to shut down. - - This marks the portal as no longer accepting new calls and exits from - :meth:`sleep_until_stopped`. - - :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False`` - to let them finish before returning - - """ - self._event_loop_thread_id = None - self._stop_event.set() - if cancel_remaining: - self._task_group.cancel_scope.cancel("the blocking portal is shutting down") - - async def _call_func( - self, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], - args: tuple[Unpack[PosArgsT]], - kwargs: dict[str, Any], - future: Future[T_Retval], - ) -> None: - event_loop_thread_id = self._event_loop_thread_id - - def callback(f: Future[T_Retval]) -> None: - if f.cancelled(): - if event_loop_thread_id == get_ident(): - scope.cancel("the future was cancelled") - elif event_loop_thread_id is not None: - run_sync( - scope.cancel, "the future was cancelled", token=self._token - ) - - try: - retval_or_awaitable = func(*args, **kwargs) - if isawaitable(retval_or_awaitable): - with CancelScope() as scope: - future.add_done_callback(callback) - retval = await retval_or_awaitable - else: - retval = retval_or_awaitable - except get_cancelled_exc_class(): - future.cancel() - future.set_running_or_notify_cancel() - except BaseException as exc: - if not future.cancelled(): - future.set_exception(exc) - - # Let base exceptions fall through - if not isinstance(exc, Exception): - raise - else: - if not future.cancelled(): - future.set_result(retval) - finally: - scope = None # type: ignore[assignment] - - def _spawn_task_from_thread( - self, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], - args: tuple[Unpack[PosArgsT]], - kwargs: dict[str, Any], - name: object, - future: Future[T_Retval], - ) -> None: - """ - Spawn a new task using the given callable. - - :param func: a callable - :param args: positional arguments to be passed to the callable - :param kwargs: keyword arguments to be passed to the callable - :param name: name of the task (will be coerced to a string if not ``None``) - :param future: a future that will resolve to the return value of the callable, - or the exception raised during its execution - - """ - run_sync( - partial(self._task_group.start_soon, name=name), - self._call_func, - func, - args, - kwargs, - future, - token=self._token, - ) - - @overload - def call( - self, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], - *args: Unpack[PosArgsT], - ) -> T_Retval: ... - - @overload - def call( - self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] - ) -> T_Retval: ... - - def call( - self, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], - *args: Unpack[PosArgsT], - ) -> T_Retval: - """ - Call the given function in the event loop thread. - - If the callable returns a coroutine object, it is awaited on. - - :param func: any callable - :raises RuntimeError: if the portal is not running or if this method is called - from within the event loop thread - - """ - return cast(T_Retval, self.start_task_soon(func, *args).result()) - - @overload - def start_task_soon( - self, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]], - *args: Unpack[PosArgsT], - name: object = None, - ) -> Future[T_Retval]: ... - - @overload - def start_task_soon( - self, - func: Callable[[Unpack[PosArgsT]], T_Retval], - *args: Unpack[PosArgsT], - name: object = None, - ) -> Future[T_Retval]: ... - - def start_task_soon( - self, - func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval], - *args: Unpack[PosArgsT], - name: object = None, - ) -> Future[T_Retval]: - """ - Start a task in the portal's task group. - - The task will be run inside a cancel scope which can be cancelled by cancelling - the returned future. - - :param func: the target function - :param args: positional arguments passed to ``func`` - :param name: name of the task (will be coerced to a string if not ``None``) - :return: a future that resolves with the return value of the callable if the - task completes successfully, or with the exception raised in the task - :raises RuntimeError: if the portal is not running or if this method is called - from within the event loop thread - :rtype: concurrent.futures.Future[T_Retval] - - .. versionadded:: 3.0 - - """ - self._check_running() - f: Future[T_Retval] = Future() - self._spawn_task_from_thread(func, args, {}, name, f) - return f - - def start_task( - self, - func: Callable[..., Awaitable[T_Retval]], - *args: object, - name: object = None, - ) -> tuple[Future[T_Retval], Any]: - """ - Start a task in the portal's task group and wait until it signals for readiness. - - This method works the same way as :meth:`.abc.TaskGroup.start`. - - :param func: the target function - :param args: positional arguments passed to ``func`` - :param name: name of the task (will be coerced to a string if not ``None``) - :return: a tuple of (future, task_status_value) where the ``task_status_value`` - is the value passed to ``task_status.started()`` from within the target - function - :rtype: tuple[concurrent.futures.Future[T_Retval], Any] - - .. versionadded:: 3.0 - - """ - - def task_done(future: Future[T_Retval]) -> None: - if not task_status_future.done(): - if future.cancelled(): - task_status_future.cancel() - elif future.exception(): - task_status_future.set_exception(future.exception()) - else: - exc = RuntimeError( - "Task exited without calling task_status.started()" - ) - task_status_future.set_exception(exc) - - self._check_running() - task_status_future: Future = Future() - task_status = _BlockingPortalTaskStatus(task_status_future) - f: Future = Future() - f.add_done_callback(task_done) - self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f) - return f, task_status_future.result() - - def wrap_async_context_manager( - self, cm: AbstractAsyncContextManager[T_co] - ) -> AbstractContextManager[T_co]: - """ - Wrap an async context manager as a synchronous context manager via this portal. - - Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping - in the middle until the synchronous context manager exits. - - :param cm: an asynchronous context manager - :return: a synchronous context manager - - .. versionadded:: 2.1 - - """ - return _BlockingAsyncContextManager(cm, self) - - -@dataclass -class BlockingPortalProvider: - """ - A manager for a blocking portal. Used as a context manager. The first thread to - enter this context manager causes a blocking portal to be started with the specific - parameters, and the last thread to exit causes the portal to be shut down. Thus, - there will be exactly one blocking portal running in this context as long as at - least one thread has entered this context manager. - - The parameters are the same as for :func:`~anyio.run`. - - :param backend: name of the backend - :param backend_options: backend options - - .. versionadded:: 4.4 - """ - - backend: str = "asyncio" - backend_options: dict[str, Any] | None = None - _lock: Lock = field(init=False, default_factory=Lock) - _leases: int = field(init=False, default=0) - _portal: BlockingPortal = field(init=False) - _portal_cm: AbstractContextManager[BlockingPortal] | None = field( - init=False, default=None - ) - - def __enter__(self) -> BlockingPortal: - with self._lock: - if self._portal_cm is None: - self._portal_cm = start_blocking_portal( - self.backend, self.backend_options - ) - self._portal = self._portal_cm.__enter__() - - self._leases += 1 - return self._portal - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - portal_cm: AbstractContextManager[BlockingPortal] | None = None - with self._lock: - assert self._portal_cm - assert self._leases > 0 - self._leases -= 1 - if not self._leases: - portal_cm = self._portal_cm - self._portal_cm = None - del self._portal - - if portal_cm: - portal_cm.__exit__(None, None, None) - - -@contextmanager -def start_blocking_portal( - backend: str = "asyncio", - backend_options: dict[str, Any] | None = None, - *, - name: str | None = None, -) -> Generator[BlockingPortal, Any, None]: - """ - Start a new event loop in a new thread and run a blocking portal in its main task. - - The parameters are the same as for :func:`~anyio.run`. - - :param backend: name of the backend - :param backend_options: backend options - :param name: name of the thread - :return: a context manager that yields a blocking portal - - .. versionchanged:: 3.0 - Usage as a context manager is now required. - - """ - - async def run_portal() -> None: - async with BlockingPortal() as portal_: - if name is None: - current_thread().name = f"{backend}-portal-{id(portal_):x}" - - future.set_result(portal_) - await portal_.sleep_until_stopped() - - def run_blocking_portal() -> None: - if future.set_running_or_notify_cancel(): - try: - run_eventloop( - run_portal, backend=backend, backend_options=backend_options - ) - except BaseException as exc: - if not future.done(): - future.set_exception(exc) - - future: Future[BlockingPortal] = Future() - thread = Thread(target=run_blocking_portal, daemon=True, name=name) - thread.start() - try: - cancel_remaining_tasks = False - portal = future.result() - try: - yield portal - except BaseException: - cancel_remaining_tasks = True - raise - finally: - try: - portal.call(portal.stop, cancel_remaining_tasks) - except RuntimeError: - pass - finally: - thread.join() - - -def check_cancelled() -> None: - """ - Check if the cancel scope of the host task's running the current worker thread has - been cancelled. - - If the host task's current cancel scope has indeed been cancelled, the - backend-specific cancellation exception will be raised. - - :raises RuntimeError: if the current thread was not spawned by - :func:`.to_thread.run_sync` - - """ - try: - token: EventLoopToken = threadlocals.current_token - except AttributeError: - raise NoEventLoopError( - "This function can only be called inside an AnyIO worker thread" - ) from None - - token.backend_class.check_cancelled() diff --git a/server/venv/Lib/site-packages/anyio/functools.py b/server/venv/Lib/site-packages/anyio/functools.py deleted file mode 100644 index b0bdfb4..0000000 --- a/server/venv/Lib/site-packages/anyio/functools.py +++ /dev/null @@ -1,400 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "AsyncCacheInfo", - "AsyncCacheParameters", - "AsyncLRUCacheWrapper", - "cache", - "lru_cache", - "reduce", -) - -import functools -from collections import OrderedDict -from collections.abc import ( - AsyncIterable, - Awaitable, - Callable, - Coroutine, - Hashable, - Iterable, -) -from functools import update_wrapper -from inspect import iscoroutinefunction -from typing import ( - Any, - Generic, - NamedTuple, - ParamSpec, - TypedDict, - TypeVar, - cast, - final, - overload, -) -from weakref import WeakKeyDictionary - -from ._core._eventloop import current_time -from ._core._synchronization import Lock -from .lowlevel import RunVar, checkpoint - -T = TypeVar("T") -S = TypeVar("S") -P = ParamSpec("P") -lru_cache_items: RunVar[ - WeakKeyDictionary[ - AsyncLRUCacheWrapper[Any, Any], - OrderedDict[ - Hashable, - tuple[_InitialMissingType, Lock, float | None] - | tuple[Any, None, float | None], - ], - ] -] = RunVar("lru_cache_items") - - -class _InitialMissingType: - pass - - -initial_missing: _InitialMissingType = _InitialMissingType() - - -class AsyncCacheInfo(NamedTuple): - hits: int - misses: int - maxsize: int | None - currsize: int - ttl: int | None - - -class AsyncCacheParameters(TypedDict): - maxsize: int | None - typed: bool - always_checkpoint: bool - ttl: int | None - - -class _LRUMethodWrapper(Generic[T]): - def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: object): - self.__wrapper = wrapper - self.__instance = instance - - def cache_info(self) -> AsyncCacheInfo: - return self.__wrapper.cache_info() - - def cache_parameters(self) -> AsyncCacheParameters: - return self.__wrapper.cache_parameters() - - def cache_clear(self) -> None: - self.__wrapper.cache_clear() - - async def __call__(self, *args: Any, **kwargs: Any) -> T: - if self.__instance is None: - return await self.__wrapper(*args, **kwargs) - - return await self.__wrapper(self.__instance, *args, **kwargs) - - -@final -class AsyncLRUCacheWrapper(Generic[P, T]): - def __init__( - self, - func: Callable[P, Awaitable[T]], - maxsize: int | None, - typed: bool, - always_checkpoint: bool, - ttl: int | None, - ): - self.__wrapped__ = func - self._hits: int = 0 - self._misses: int = 0 - self._maxsize = max(maxsize, 0) if maxsize is not None else None - self._currsize: int = 0 - self._typed = typed - self._always_checkpoint = always_checkpoint - self._ttl = ttl - update_wrapper(self, func) - - def cache_info(self) -> AsyncCacheInfo: - return AsyncCacheInfo( - self._hits, self._misses, self._maxsize, self._currsize, self._ttl - ) - - def cache_parameters(self) -> AsyncCacheParameters: - return { - "maxsize": self._maxsize, - "typed": self._typed, - "always_checkpoint": self._always_checkpoint, - "ttl": self._ttl, - } - - def cache_clear(self) -> None: - if cache := lru_cache_items.get(None): - cache.pop(self, None) - self._hits = self._misses = self._currsize = 0 - - async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: - # Easy case first: if maxsize == 0, no caching is done - if self._maxsize == 0: - value = await self.__wrapped__(*args, **kwargs) - self._misses += 1 - return value - - # The key is constructed as a flat tuple to avoid memory overhead - key: tuple[Any, ...] = args - if kwargs: - # initial_missing is used as a separator - key += (initial_missing,) + sum(kwargs.items(), ()) - - if self._typed: - key += tuple(type(arg) for arg in args) - if kwargs: - key += (initial_missing,) + tuple(type(val) for val in kwargs.values()) - - try: - cache = lru_cache_items.get() - except LookupError: - cache = WeakKeyDictionary() - lru_cache_items.set(cache) - - try: - cache_entry = cache[self] - except KeyError: - cache_entry = cache[self] = OrderedDict() - - cached_value: T | _InitialMissingType - try: - cached_value, lock, expires_at = cache_entry[key] - except KeyError: - # We're the first task to call this function - cached_value, lock, expires_at = ( - initial_missing, - Lock(fast_acquire=not self._always_checkpoint), - None, - ) - cache_entry[key] = cached_value, lock, expires_at - - if lock is None: - if expires_at is not None and current_time() >= expires_at: - self._currsize -= 1 - cached_value, lock, expires_at = ( - initial_missing, - Lock(fast_acquire=not self._always_checkpoint), - None, - ) - cache_entry[key] = cached_value, lock, expires_at - else: - # The value was already cached - self._hits += 1 - cache_entry.move_to_end(key) - if self._always_checkpoint: - await checkpoint() - - return cast(T, cached_value) - - async with lock: - # Check if another task filled the cache while we acquired the lock - if (cached_value := cache_entry[key][0]) is initial_missing: - self._misses += 1 - if self._maxsize is not None and self._currsize >= self._maxsize: - cache_entry.popitem(last=False) - else: - self._currsize += 1 - - value = await self.__wrapped__(*args, **kwargs) - expires_at = ( - current_time() + self._ttl if self._ttl is not None else None - ) - cache_entry[key] = value, None, expires_at - else: - # Another task filled the cache while we were waiting for the lock - self._hits += 1 - cache_entry.move_to_end(key) - value = cast(T, cached_value) - - return value - - def __get__( - self, instance: object, owner: type | None = None - ) -> _LRUMethodWrapper[T]: - wrapper = _LRUMethodWrapper(self, instance) - update_wrapper(wrapper, self.__wrapped__) - return wrapper - - -class _LRUCacheWrapper: - def __init__( - self, maxsize: int | None, typed: bool, always_checkpoint: bool, ttl: int | None - ): - self._maxsize = maxsize - self._typed = typed - self._always_checkpoint = always_checkpoint - self._ttl = ttl - - @overload - def __call__( # type: ignore[overload-overlap] - self, func: Callable[P, Coroutine[Any, Any, T]], / - ) -> AsyncLRUCacheWrapper[P, T]: ... - - @overload - def __call__( - self, func: Callable[..., T], / - ) -> functools._lru_cache_wrapper[T]: ... - - def __call__( - self, f: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T], / - ) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]: - if iscoroutinefunction(f): - return AsyncLRUCacheWrapper( - f, self._maxsize, self._typed, self._always_checkpoint, self._ttl - ) - - return functools.lru_cache(maxsize=self._maxsize, typed=self._typed)(f) # type: ignore[arg-type] - - -@overload -def cache( # type: ignore[overload-overlap] - func: Callable[P, Coroutine[Any, Any, T]], / -) -> AsyncLRUCacheWrapper[P, T]: ... - - -@overload -def cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... - - -def cache(func: Callable[..., Any] | Callable[P, Coroutine[Any, Any, Any]], /) -> Any: - """ - A convenient shortcut for :func:`lru_cache` with ``maxsize=None``. - - This is the asynchronous equivalent to :func:`functools.cache`. - - """ - return lru_cache(maxsize=None)(func) - - -@overload -def lru_cache( - *, - maxsize: int | None = ..., - typed: bool = ..., - always_checkpoint: bool = ..., - ttl: int | None = ..., -) -> _LRUCacheWrapper: ... - - -@overload -def lru_cache( # type: ignore[overload-overlap] - func: Callable[P, Coroutine[Any, Any, T]], / -) -> AsyncLRUCacheWrapper[P, T]: ... - - -@overload -def lru_cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ... - - -def lru_cache( - func: Callable[..., Coroutine[Any, Any, Any]] | Callable[..., Any] | None = None, - /, - *, - maxsize: int | None = 128, - typed: bool = False, - always_checkpoint: bool = False, - ttl: int | None = None, -) -> Any: - """ - An asynchronous version of :func:`functools.lru_cache`. - - If a synchronous function is passed, the standard library - :func:`functools.lru_cache` is applied instead. - - :param always_checkpoint: if ``True``, every call to the cached function will be - guaranteed to yield control to the event loop at least once - :param ttl: time in seconds after which to invalidate cache entries - - .. note:: Caches and locks are managed on a per-event loop basis. - - """ - if func is None: - return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl) - - if not callable(func): - raise TypeError("the first argument must be callable") - - return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl)(func) - - -@overload -async def reduce( - function: Callable[[T, S], Awaitable[T]], - iterable: Iterable[S] | AsyncIterable[S], - /, - initial: T, -) -> T: ... - - -@overload -async def reduce( - function: Callable[[T, T], Awaitable[T]], - iterable: Iterable[T] | AsyncIterable[T], - /, -) -> T: ... - - -async def reduce( # type: ignore[misc] - function: Callable[[T, T], Awaitable[T]] | Callable[[T, S], Awaitable[T]], - iterable: Iterable[T] | Iterable[S] | AsyncIterable[T] | AsyncIterable[S], - /, - initial: T | _InitialMissingType = initial_missing, -) -> T: - """ - Asynchronous version of :func:`functools.reduce`. - - :param function: a coroutine function that takes two arguments: the accumulated - value and the next element from the iterable - :param iterable: an iterable or async iterable - :param initial: the initial value (if missing, the first element of the iterable is - used as the initial value) - - """ - element: Any - function_called = False - if isinstance(iterable, AsyncIterable): - async_it = iterable.__aiter__() - if initial is initial_missing: - try: - value = cast(T, await async_it.__anext__()) - except StopAsyncIteration: - raise TypeError( - "reduce() of empty sequence with no initial value" - ) from None - else: - value = cast(T, initial) - - async for element in async_it: - value = await function(value, element) - function_called = True - elif isinstance(iterable, Iterable): - it = iter(iterable) - if initial is initial_missing: - try: - value = cast(T, next(it)) - except StopIteration: - raise TypeError( - "reduce() of empty sequence with no initial value" - ) from None - else: - value = cast(T, initial) - - for element in it: - value = await function(value, element) - function_called = True - else: - raise TypeError("reduce() argument 2 must be an iterable or async iterable") - - # Make sure there is at least one checkpoint, even if an empty iterable and an - # initial value were given - if not function_called: - await checkpoint() - - return value diff --git a/server/venv/Lib/site-packages/anyio/itertools.py b/server/venv/Lib/site-packages/anyio/itertools.py deleted file mode 100644 index 7e5248e..0000000 --- a/server/venv/Lib/site-packages/anyio/itertools.py +++ /dev/null @@ -1,626 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "accumulate", - "batched", - "Chain", - "combinations", - "combinations_with_replacement", - "compress", - "count", - "cycle", - "dropwhile", - "filterfalse", - "groupby", - "islice", - "pairwise", - "permutations", - "product", - "repeat", - "starmap", - "tee", - "takewhile", - "zip_longest", -) - -import itertools -import operator -import sys -from collections.abc import ( - AsyncGenerator, - AsyncIterable, - AsyncIterator, - Awaitable, - Callable, - Iterable, - Iterator, -) -from dataclasses import dataclass, field -from typing import Any, Generic, TypeVar, cast, overload - -from ._core._synchronization import Lock -from ._core._tasks import CancelScope -from .lowlevel import cancel_shielded_checkpoint, checkpoint, checkpoint_if_cancelled - -T = TypeVar("T") -R = TypeVar("R") -_tee_end = object() - - -@dataclass(eq=False) -class _IterableAsyncIterator(AsyncIterator[T]): - iterator: Iterator[T] - - async def __anext__(self) -> T: - await checkpoint_if_cancelled() - try: - result = next(self.iterator) - except StopIteration: - await cancel_shielded_checkpoint() - raise StopAsyncIteration from None - - await cancel_shielded_checkpoint() - return result - - -def _iterate(iterable: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[T]: - if isinstance(iterable, AsyncIterator): - return iterable - - if isinstance(iterable, AsyncIterable): - return iterable.__aiter__() - - return _IterableAsyncIterator(iter(iterable)) - - -@dataclass(eq=False) -class _TeeLink(Generic[T]): - value: object | None = None - next: _TeeLink[T] | None = None - filled: bool = False - - -@dataclass(eq=False) -class _TeeState(Generic[T]): - iterator: AsyncIterator[T] - lock: Lock = field(default_factory=Lock) - - async def fill(self, link: _TeeLink[T]) -> bool: - if link.filled: - return False - - async with self.lock: - if link.filled: - return True - - link.value = await anext(self.iterator, _tee_end) - if link.value is not _tee_end: - link.next = _TeeLink() - - link.filled = True - return True - - -class _TeeAsyncIterator(AsyncIterator[T]): - _state: _TeeState[T] - _link: _TeeLink[T] - _element_yielded: bool - - def __init__( - self, iterable: Iterable[T] | AsyncIterable[T] | _TeeAsyncIterator[T] - ) -> None: - if isinstance(iterable, _TeeAsyncIterator): - self._state = iterable._state - self._link = iterable._link - else: - self._state = _TeeState(_iterate(iterable)) - self._link = _TeeLink() - - self._element_yielded = False - - async def __anext__(self) -> T: - had_yieldpoint = await self._state.fill(self._link) - if self._link.value is _tee_end: - if not self._element_yielded: - await checkpoint() - - raise StopAsyncIteration - - if not had_yieldpoint: - await checkpoint_if_cancelled() - - self._element_yielded = True - value = cast(T, self._link.value) - next_link = self._link.next - assert next_link is not None - self._link = next_link - if not had_yieldpoint: - await cancel_shielded_checkpoint() - - return value - - -async def _operator_add(x: T, y: T) -> T: - return operator.add(x, y) - - -async def accumulate( - iterable: Iterable[T] | AsyncIterable[T], - function: Callable[[T, T], Awaitable[T]] = _operator_add, - *, - initial: T | None = None, -) -> AsyncGenerator[T, None]: - iterator = _iterate(iterable) - if initial is None: - try: - total = await anext(iterator) - except StopAsyncIteration: - await checkpoint() - return - else: - await checkpoint_if_cancelled() - total = initial - await cancel_shielded_checkpoint() - - yield total - - async for element in iterator: - total = await function(total, element) - yield total - - -async def batched( - iterable: Iterable[T] | AsyncIterable[T], n: int, *, strict: bool = False -) -> AsyncGenerator[tuple[T, ...], None]: - if n < 1: - raise ValueError("n must be at least one") - - iterator = _iterate(iterable) - - while True: - batch: list[T] = [] - for _ in range(n): - try: - batch.append(await anext(iterator)) - except StopAsyncIteration: - if not batch: - await checkpoint() - return - if strict: - raise ValueError("batched(): incomplete batch") from None - - yield tuple(batch) - return - - yield tuple(batch) - - -class Chain: - def __call__( - self, *iterables: Iterable[T] | AsyncIterable[T] - ) -> AsyncGenerator[T, None]: - return self.from_iterable(iterables) - - async def from_iterable( - self, - iterables: ( - Iterable[Iterable[T] | AsyncIterable[T]] - | AsyncIterable[Iterable[T] | AsyncIterable[T]] - ), - ) -> AsyncGenerator[T, None]: - element_yielded = False - outer_iter = _iterate(iterables) - - try: - async for iterable in outer_iter: - async for element in _iterate(iterable): - element_yielded = True - yield element - finally: - aclose = getattr(outer_iter, "aclose", None) - if aclose is not None: - with CancelScope(shield=True): - await aclose() - - if not element_yielded: - await checkpoint() - - -chain: Chain = Chain() - - -async def combinations( - iterable: Iterable[T] | AsyncIterable[T], r: int -) -> AsyncGenerator[tuple[T, ...], None]: - pool: list[T] = [element async for element in _iterate(iterable)] - async for combination in _iterate(itertools.combinations(pool, r)): - yield combination - - -async def combinations_with_replacement( - iterable: Iterable[T] | AsyncIterable[T], r: int -) -> AsyncGenerator[tuple[T, ...], None]: - pool: list[T] = [element async for element in _iterate(iterable)] - async for combination in _iterate(itertools.combinations_with_replacement(pool, r)): - yield combination - - -async def compress( - data: Iterable[T] | AsyncIterable[T], - selectors: Iterable[object] | AsyncIterable[object], -) -> AsyncGenerator[T, None]: - data_iterator = _iterate(data) - selector_iterator = _iterate(selectors) - element_yielded = False - - while True: - try: - datum = await anext(data_iterator) - selector = await anext(selector_iterator) - except StopAsyncIteration: - if not element_yielded: - await checkpoint() - - return - - if selector: - element_yielded = True - yield datum - - -async def count(start: int = 0, step: int = 1) -> AsyncGenerator[int, None]: - n = start - while True: - await checkpoint_if_cancelled() - value = n - n += step - await cancel_shielded_checkpoint() - yield value - - -async def cycle( - iterable: Iterable[T] | AsyncIterable[T], -) -> AsyncGenerator[T, None]: - saved: list[T] = [] - async for element in _iterate(iterable): - saved.append(element) - yield element - - if not saved: - await checkpoint() - return - - while True: - for element in saved: - await checkpoint() - yield element - - -async def dropwhile( - predicate: Callable[[T], Awaitable[object]], - iterable: Iterable[T] | AsyncIterable[T], -) -> AsyncGenerator[T, None]: - element_yielded = False - dropping = True - - async for element in _iterate(iterable): - if dropping and await predicate(element): - continue - - dropping = False - element_yielded = True - yield element - - if not element_yielded: - await checkpoint() - - -async def filterfalse( - predicate: Callable[[T], Awaitable[object]], - iterable: Iterable[T] | AsyncIterable[T], -) -> AsyncGenerator[T, None]: - element_yielded = False - - async for element in _iterate(iterable): - if not await predicate(element): - element_yielded = True - yield element - - if not element_yielded: - await checkpoint() - - -@overload -def groupby( - iterable: Iterable[T] | AsyncIterable[T], -) -> AsyncGenerator[tuple[T, list[T]], None]: ... - - -@overload -def groupby( - iterable: Iterable[T] | AsyncIterable[T], - key: Callable[[T], Awaitable[R]], -) -> AsyncGenerator[tuple[R, list[T]], None]: ... - - -async def groupby( - iterable: Iterable[T] | AsyncIterable[T], - key: Callable[[T], Awaitable[object]] | None = None, -) -> AsyncGenerator[tuple[object, list[T]], None]: - iterator = _iterate(iterable) - try: - element = await anext(iterator) - except StopAsyncIteration: - await checkpoint() - return - - group_key = element if key is None else await key(element) - values = [element] - - async for element in iterator: - next_key = element if key is None else await key(element) - if next_key != group_key: - completed_group = group_key, values - group_key = next_key - values = [element] - yield completed_group - else: - values.append(element) - - yield group_key, values - - -@overload -def islice( - iterable: Iterable[T] | AsyncIterable[T], - stop: int | None, - /, -) -> AsyncGenerator[T, None]: ... - - -@overload -def islice( - iterable: Iterable[T] | AsyncIterable[T], - start: int | None, - stop: int | None, - step: int | None = 1, - /, -) -> AsyncGenerator[T, None]: ... - - -async def islice( - iterable: Iterable[T] | AsyncIterable[T], - *args: int | None, -) -> AsyncGenerator[T, None]: - if not args: - raise TypeError("islice expected at least 2 arguments, got 1") - if len(args) > 3: - raise TypeError(f"islice expected at most 4 arguments, got {len(args) + 1}") - - slice_args = slice(*args) - - start_message = ( - "Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize." - ) - stop_message = ( - "Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize." - ) - step_message = "Step for islice() must be a positive integer or None." - - def normalize_index(value: object, message: str) -> int: - try: - index = operator.index(cast(Any, value)) - except TypeError: - raise ValueError(message) from None - - if index < 0 or index > sys.maxsize: - raise ValueError(message) - - return index - - start = ( - 0 - if slice_args.start is None - else normalize_index(slice_args.start, start_message) - ) - stop = ( - None - if slice_args.stop is None - else normalize_index(slice_args.stop, stop_message) - ) - step = ( - 1 if slice_args.step is None else normalize_index(slice_args.step, step_message) - ) - - if step <= 0: - raise ValueError(step_message) - - if stop == 0 or start == stop: - await checkpoint() - return - - iterator = _iterate(iterable) - index = 0 - element_yielded = False - - while stop is None or index < stop: - try: - element = await anext(iterator) - except StopAsyncIteration: - if not element_yielded: - await checkpoint() - - return - - if index >= start and (index - start) % step == 0: - index += 1 - element_yielded = True - yield element - else: - index += 1 - - if not element_yielded: - await checkpoint() - - -async def pairwise( - iterable: Iterable[T] | AsyncIterable[T], -) -> AsyncGenerator[tuple[T, T], None]: - iterator = _iterate(iterable) - try: - previous = await anext(iterator) - except StopAsyncIteration: - await checkpoint() - return - - element_yielded = False - async for element in iterator: - element_yielded = True - pair = (previous, element) - previous = element - yield pair - - if not element_yielded: - await checkpoint() - - -async def permutations( - iterable: Iterable[T] | AsyncIterable[T], r: int | None = None -) -> AsyncGenerator[tuple[T, ...], None]: - pool: list[T] = [element async for element in _iterate(iterable)] - n = len(pool) - if r is None: - r = n - elif not isinstance(r, int): - raise TypeError("Expected int as r") - elif r < 0: - raise ValueError("r must be non-negative") - - async for permutation in _iterate(itertools.permutations(pool, r)): - yield permutation - - -async def product( - *iterables: Iterable[T] | AsyncIterable[T], repeat: int = 1 -) -> AsyncGenerator[tuple[T, ...], None]: - repeat = operator.index(repeat) - if repeat < 0: - raise ValueError("repeat argument cannot be negative") - - pools: list[tuple[T, ...]] = [] - for iterable in iterables: - pool: list[T] = [element async for element in _iterate(iterable)] - pools.append(tuple(pool)) - - async for value in _iterate(itertools.product(*pools, repeat=repeat)): - yield value - - -async def repeat(element: T, times: int | None = None) -> AsyncGenerator[T, None]: - if times is None: - while True: - await checkpoint() - yield element - - remaining = operator.index(cast(Any, times)) - if remaining <= 0: - await checkpoint() - return - - while remaining > 0: - await checkpoint_if_cancelled() - remaining -= 1 - await cancel_shielded_checkpoint() - yield element - - -async def starmap( - function: Callable[..., Awaitable[R]], - iterable: ( - Iterable[Iterable[object] | AsyncIterable[object]] - | AsyncIterable[Iterable[object] | AsyncIterable[object]] - ), -) -> AsyncGenerator[R, None]: - result_yielded = False - - async for args_iterable in _iterate(iterable): - args = [element async for element in _iterate(args_iterable)] - result_yielded = True - yield await function(*args) - - if not result_yielded: - await checkpoint() - - -def tee( - iterable: Iterable[T] | AsyncIterable[T], n: int = 2 -) -> tuple[AsyncIterator[T], ...]: - n = operator.index(cast(Any, n)) - if n < 0: - raise ValueError("n must be >= 0") - if n == 0: - return () - - iterator = _TeeAsyncIterator(iterable) - iterators: list[AsyncIterator[T]] = [iterator] - iterators.extend(_TeeAsyncIterator(iterator) for _ in range(n - 1)) - return tuple(iterators) - - -async def takewhile( - predicate: Callable[[T], Awaitable[object]], - iterable: Iterable[T] | AsyncIterable[T], -) -> AsyncGenerator[T, None]: - element_yielded = False - - async for element in _iterate(iterable): - if not await predicate(element): - if not element_yielded: - await checkpoint() - - return - - element_yielded = True - yield element - - if not element_yielded: - await checkpoint() - - -async def zip_longest( - *iterables: Iterable[object] | AsyncIterable[object], - fillvalue: object = None, -) -> AsyncGenerator[tuple[object, ...], None]: - iterators = [_iterate(iterable) for iterable in iterables] - num_active = len(iterators) - if not num_active: - await checkpoint() - return - - active = [True] * num_active - tuple_yielded = False - - while True: - values: list[object] = [] - for index, iterator in enumerate(iterators): - if not active[index]: - values.append(fillvalue) - continue - - try: - value = await anext(iterator) - except StopAsyncIteration: - active[index] = False - num_active -= 1 - if not num_active: - if not tuple_yielded: - await checkpoint() - - return - - value = fillvalue - - values.append(value) - - tuple_yielded = True - yield tuple(values) diff --git a/server/venv/Lib/site-packages/anyio/lowlevel.py b/server/venv/Lib/site-packages/anyio/lowlevel.py deleted file mode 100644 index d045791..0000000 --- a/server/venv/Lib/site-packages/anyio/lowlevel.py +++ /dev/null @@ -1,226 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "EventLoopToken", - "RunvarToken", - "RunVar", - "checkpoint", - "checkpoint_if_cancelled", - "cancel_shielded_checkpoint", - "current_token", -) - -import enum -from dataclasses import dataclass -from types import TracebackType -from typing import Any, Generic, Literal, TypeVar, final, overload -from weakref import WeakKeyDictionary - -from ._core._eventloop import get_async_backend -from .abc import AsyncBackend - -T = TypeVar("T") -D = TypeVar("D") - - -async def checkpoint() -> None: - """ - Check for cancellation and allow the scheduler to switch to another task. - - Equivalent to (but more efficient than):: - - await checkpoint_if_cancelled() - await cancel_shielded_checkpoint() - - .. versionadded:: 3.0 - - """ - await get_async_backend().checkpoint() - - -async def checkpoint_if_cancelled() -> None: - """ - Enter a checkpoint if the enclosing cancel scope has been cancelled. - - This does not allow the scheduler to switch to a different task. - - .. versionadded:: 3.0 - - """ - await get_async_backend().checkpoint_if_cancelled() - - -async def cancel_shielded_checkpoint() -> None: - """ - Allow the scheduler to switch to another task but without checking for cancellation. - - Equivalent to (but potentially more efficient than):: - - with CancelScope(shield=True): - await checkpoint() - - .. versionadded:: 3.0 - - """ - await get_async_backend().cancel_shielded_checkpoint() - - -@final -@dataclass(frozen=True, repr=False) -class EventLoopToken: - """ - An opaque object that holds a reference to an event loop. - - .. versionadded:: 4.11.0 - """ - - backend_class: type[AsyncBackend] - native_token: object - - -def current_token() -> EventLoopToken: - """ - Return a token object that can be used to call code in the current event loop from - another thread. - - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - .. versionadded:: 4.11.0 - - """ - backend_class = get_async_backend() - raw_token = backend_class.current_token() - return EventLoopToken(backend_class, raw_token) - - -_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary() - - -class _NoValueSet(enum.Enum): - NO_VALUE_SET = enum.auto() - - -class RunvarToken(Generic[T]): - """ - A token that can be used to restore a :class:`RunVar` to its previous value. - - Returned by :meth:`RunVar.set`. Can be used as a context manager to automatically - reset the variable on exit, or passed directly to :meth:`RunVar.reset`. - """ - - __slots__ = "_var", "_value", "_redeemed" - - def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]): - self._var = var - self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value - self._redeemed = False - - def __enter__(self) -> RunvarToken[T]: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self._var.reset(self) - - -class RunVar(Generic[T]): - """ - Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop. - - Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that - will reset the variable to its previous value when the context block is exited. - """ - - __slots__ = "_name", "_default" - - NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET - - def __init__( - self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET - ): - self._name = name - self._default = default - - @property - def _current_vars(self) -> dict[RunVar[T], T]: - native_token = current_token().native_token - try: - return _run_vars[native_token] - except KeyError: - run_vars = _run_vars[native_token] = {} - return run_vars - - @overload - def get(self, default: D) -> T | D: ... - - @overload - def get(self) -> T: ... - - def get( - self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET - ) -> T | D: - """ - Return the current value of this run variable. - - :param default: a fallback value to return if no value has been set - :return: the current value, the provided default, or the variable's own default - :raises LookupError: if no value is set and no default is available - - """ - try: - return self._current_vars[self] - except KeyError: - if default is not RunVar.NO_VALUE_SET: - return default - elif self._default is not RunVar.NO_VALUE_SET: - return self._default - - raise LookupError( - f'Run variable "{self._name}" has no value and no default set' - ) - - def set(self, value: T) -> RunvarToken[T]: - """ - Set the value of this run variable for the current event loop. - - :param value: the new value - :return: a token that can be used to restore the previous value - - """ - current_vars = self._current_vars - token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET)) - current_vars[self] = value - return token - - def reset(self, token: RunvarToken[T]) -> None: - """ - Restore this run variable to the value it held before the matching :meth:`set`. - - :param token: the token returned by :meth:`set` - :raises ValueError: if the token belongs to a different :class:`RunVar` or the token - has already been used - - """ - if token._var is not self: - raise ValueError("This token does not belong to this RunVar") - - if token._redeemed: - raise ValueError("This token has already been used") - - if token._value is _NoValueSet.NO_VALUE_SET: - try: - del self._current_vars[self] - except KeyError: - pass - else: - self._current_vars[self] = token._value - - token._redeemed = True - - def __repr__(self) -> str: - return f"" diff --git a/server/venv/Lib/site-packages/anyio/py.typed b/server/venv/Lib/site-packages/anyio/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/anyio/pytest_plugin.py b/server/venv/Lib/site-packages/anyio/pytest_plugin.py deleted file mode 100644 index 5c66759..0000000 --- a/server/venv/Lib/site-packages/anyio/pytest_plugin.py +++ /dev/null @@ -1,375 +0,0 @@ -from __future__ import annotations - -import dataclasses -import socket -import sys -from collections.abc import Callable, Generator, Iterator -from contextlib import ExitStack, contextmanager -from inspect import isasyncgenfunction, iscoroutinefunction, ismethod -from typing import Any, cast - -import pytest -from _pytest.fixtures import FuncFixtureInfo, SubRequest -from _pytest.outcomes import Exit -from _pytest.python import CallSpec2 -from _pytest.scope import Scope - -from . import get_available_backends -from ._core._eventloop import ( - current_async_library, - get_async_backend, - reset_current_async_library, - set_current_async_library, -) -from ._core._exceptions import iterate_exceptions -from .abc import TestRunner - -if sys.version_info < (3, 11): - from exceptiongroup import ExceptionGroup - -_current_runner: TestRunner | None = None -_runner_stack: ExitStack | None = None -_runner_leases = 0 - - -def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]: - if isinstance(backend, str): - return backend, {} - elif isinstance(backend, tuple) and len(backend) == 2: - if isinstance(backend[0], str) and isinstance(backend[1], dict): - return cast(tuple[str, dict[str, Any]], backend) - - raise TypeError("anyio_backend must be either a string or tuple of (string, dict)") - - -@contextmanager -def get_runner( - backend_name: str, backend_options: dict[str, Any] -) -> Iterator[TestRunner]: - global _current_runner, _runner_leases, _runner_stack - if _current_runner is None: - asynclib = get_async_backend(backend_name) - _runner_stack = ExitStack() - if current_async_library() is None: - # Since we're in control of the event loop, we can cache the name of the - # async library - token = set_current_async_library(backend_name) - _runner_stack.callback(reset_current_async_library, token) - - backend_options = backend_options or {} - _current_runner = _runner_stack.enter_context( - asynclib.create_test_runner(backend_options) - ) - - _runner_leases += 1 - try: - yield _current_runner - finally: - _runner_leases -= 1 - if not _runner_leases: - assert _runner_stack is not None - _runner_stack.close() - _runner_stack = _current_runner = None - - -def pytest_addoption(parser: pytest.Parser) -> None: - parser.addini( - "anyio_mode", - default="strict", - help='AnyIO plugin mode (either "strict" or "auto")', - ) - - -def pytest_configure(config: pytest.Config) -> None: - config.addinivalue_line( - "markers", - "anyio: mark the (coroutine function) test to be run asynchronously via anyio.", - ) - if ( - config.getini("anyio_mode") == "auto" - and config.pluginmanager.has_plugin("asyncio") - and config.getini("asyncio_mode") == "auto" - ): - config.issue_config_time_warning( - pytest.PytestConfigWarning( - "AnyIO auto mode has been enabled together with pytest-asyncio auto " - "mode. This may cause unexpected behavior." - ), - 1, - ) - - -@pytest.hookimpl(hookwrapper=True) -def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]: - def wrapper(anyio_backend: Any, request: SubRequest, **kwargs: Any) -> Any: - # Rebind any fixture methods to the request instance - if ( - request.instance - and ismethod(func) - and type(func.__self__) is type(request.instance) - ): - local_func = func.__func__.__get__(request.instance) - else: - local_func = func - - backend_name, backend_options = extract_backend_and_options(anyio_backend) - if has_backend_arg: - kwargs["anyio_backend"] = anyio_backend - - if has_request_arg: - kwargs["request"] = request - - with get_runner(backend_name, backend_options) as runner: - # re-entrant call into the test runner detected. this happens when an async fixture - # is dynamically requested via request.getfixturevalue() from inside a running async - # test or fixture. on asyncio this raises RuntimeError: This event loop is already - # running, on trio the runner deadlocks - the host loop blocks waiting for the - # coroutine to return, but the coroutine is waiting for the host loop. raising here - # prevents the hang and gives a consistent error across backends. - if runner.is_running(): - raise RuntimeError( - "Cannot schedule a coroutine in the test runner while another is already running; " - "likely caused by request.getfixturevalue() on an async fixture." - ) - - if isasyncgenfunction(local_func): - yield from runner.run_asyncgen_fixture(local_func, kwargs) - else: - yield runner.run_fixture(local_func, kwargs) - - # Only apply this to coroutine functions and async generator functions in requests - # that involve the anyio_backend fixture - func = fixturedef.func - if isasyncgenfunction(func) or iscoroutinefunction(func): - if "anyio_backend" in request.fixturenames: - fixturedef.func = wrapper - original_argname = fixturedef.argnames - - if not (has_backend_arg := "anyio_backend" in fixturedef.argnames): - fixturedef.argnames += ("anyio_backend",) - - if not (has_request_arg := "request" in fixturedef.argnames): - fixturedef.argnames += ("request",) - - try: - return (yield) - finally: - fixturedef.func = func - fixturedef.argnames = original_argname - - return (yield) - - -@pytest.hookimpl(tryfirst=True) -def pytest_pycollect_makeitem( - collector: pytest.Module | pytest.Class, name: str, obj: object -) -> None: - if collector.istestfunction(obj, name): - inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj - if iscoroutinefunction(inner_func): - anyio_auto_mode = collector.config.getini("anyio_mode") == "auto" - marker = collector.get_closest_marker("anyio") - own_markers = getattr(obj, "pytestmark", ()) - if ( - anyio_auto_mode - or marker - or any(marker.name == "anyio" for marker in own_markers) - ): - pytest.mark.usefixtures("anyio_backend")(obj) - - -def pytest_collection_finish(session: pytest.Session) -> None: - for i, item in reversed(list(enumerate(session.items))): - if ( - isinstance(item, pytest.Function) - and iscoroutinefunction(item.function) - and item.get_closest_marker("anyio") is not None - and "anyio_backend" not in item.fixturenames - ): - new_items = [] - try: - cs_fields = {f.name for f in dataclasses.fields(CallSpec2)} - except TypeError: - cs_fields = set() - - for param_index, backend in enumerate(get_available_backends()): - if "_arg2scope" in cs_fields: # pytest >= 8 - callspec = CallSpec2( - params={"anyio_backend": backend}, - indices={"anyio_backend": param_index}, - _arg2scope={"anyio_backend": Scope.Module}, - _idlist=[backend], - marks=[], - ) - else: # pytest 7.x - callspec = CallSpec2( # type: ignore[call-arg] - funcargs={}, - params={"anyio_backend": backend}, - indices={"anyio_backend": param_index}, - arg2scope={"anyio_backend": Scope.Module}, - idlist=[backend], - marks=[], - ) - - fi = item._fixtureinfo - new_names_closure = list(fi.names_closure) - if "anyio_backend" not in new_names_closure: - new_names_closure.append("anyio_backend") - - new_fixtureinfo = FuncFixtureInfo( - argnames=fi.argnames, - initialnames=fi.initialnames, - names_closure=new_names_closure, - name2fixturedefs=fi.name2fixturedefs, - ) - new_item = pytest.Function.from_parent( - item.parent, - name=f"{item.originalname}[{backend}]", - callspec=callspec, - callobj=item.obj, - fixtureinfo=new_fixtureinfo, - keywords=item.keywords, - originalname=item.originalname, - ) - new_items.append(new_item) - - session.items[i : i + 1] = new_items - - -@pytest.hookimpl(tryfirst=True) -def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None: - def run_with_hypothesis(**kwargs: Any) -> None: - with get_runner(backend_name, backend_options) as runner: - runner.run_test(original_func, kwargs) - - backend = pyfuncitem.funcargs.get("anyio_backend") - if backend: - backend_name, backend_options = extract_backend_and_options(backend) - - if hasattr(pyfuncitem.obj, "hypothesis"): - # Wrap the inner test function unless it's already wrapped - original_func = pyfuncitem.obj.hypothesis.inner_test - if original_func.__qualname__ != run_with_hypothesis.__qualname__: - if iscoroutinefunction(original_func): - pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis - - return None - - if iscoroutinefunction(pyfuncitem.obj): - funcargs = pyfuncitem.funcargs - testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} - with get_runner(backend_name, backend_options) as runner: - try: - runner.run_test(pyfuncitem.obj, testargs) - except ExceptionGroup as excgrp: - for exc in iterate_exceptions(excgrp): - if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)): - raise exc from excgrp - - raise - - return True - - return None - - -@pytest.fixture(scope="module", params=get_available_backends()) -def anyio_backend(request: Any) -> Any: - return request.param - - -@pytest.fixture -def anyio_backend_name(anyio_backend: Any) -> str: - if isinstance(anyio_backend, str): - return anyio_backend - else: - return anyio_backend[0] - - -@pytest.fixture -def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]: - if isinstance(anyio_backend, str): - return {} - else: - return anyio_backend[1] - - -class FreePortFactory: - """ - Manages port generation based on specified socket kind, ensuring no duplicate - ports are generated. - - This class provides functionality for generating available free ports on the - system. It is initialized with a specific socket kind and can generate ports - for given address families while avoiding reuse of previously generated ports. - - Users should not instantiate this class directly, but use the - ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple - uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead. - """ - - def __init__(self, kind: socket.SocketKind) -> None: - self._kind = kind - self._generated = set[int]() - - @property - def kind(self) -> socket.SocketKind: - """ - The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or - :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability - - """ - return self._kind - - def __call__(self, family: socket.AddressFamily | None = None) -> int: - """ - Return an unbound port for the given address family. - - :param family: if omitted, both IPv4 and IPv6 addresses will be tried - :return: a port number - - """ - if family is not None: - families = [family] - else: - families = [socket.AF_INET] - if socket.has_ipv6: - families.append(socket.AF_INET6) - - while True: - port = 0 - with ExitStack() as stack: - for family in families: - sock = stack.enter_context(socket.socket(family, self._kind)) - addr = "::1" if family == socket.AF_INET6 else "127.0.0.1" - try: - sock.bind((addr, port)) - except OSError: - break - - if not port: - port = sock.getsockname()[1] - else: - if port not in self._generated: - self._generated.add(port) - return port - - -@pytest.fixture(scope="session") -def free_tcp_port_factory() -> FreePortFactory: - return FreePortFactory(socket.SOCK_STREAM) - - -@pytest.fixture(scope="session") -def free_udp_port_factory() -> FreePortFactory: - return FreePortFactory(socket.SOCK_DGRAM) - - -@pytest.fixture -def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int: - return free_tcp_port_factory() - - -@pytest.fixture -def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int: - return free_udp_port_factory() diff --git a/server/venv/Lib/site-packages/anyio/streams/__init__.py b/server/venv/Lib/site-packages/anyio/streams/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/anyio/streams/buffered.py b/server/venv/Lib/site-packages/anyio/streams/buffered.py deleted file mode 100644 index acf312b..0000000 --- a/server/venv/Lib/site-packages/anyio/streams/buffered.py +++ /dev/null @@ -1,198 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "BufferedByteReceiveStream", - "BufferedByteStream", - "BufferedConnectable", -) - -import sys -from collections.abc import Callable, Iterable, Mapping -from dataclasses import dataclass, field -from typing import Any, SupportsIndex - -from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead -from ..abc import ( - AnyByteReceiveStream, - AnyByteStream, - AnyByteStreamConnectable, - ByteReceiveStream, - ByteStream, - ByteStreamConnectable, -) - -if sys.version_info >= (3, 12): - from typing import override -else: - from typing_extensions import override - - -@dataclass(eq=False) -class BufferedByteReceiveStream(ByteReceiveStream): - """ - Wraps any bytes-based receive stream and uses a buffer to provide sophisticated - receiving capabilities in the form of a byte stream. - """ - - receive_stream: AnyByteReceiveStream - _buffer: bytearray = field(init=False, default_factory=bytearray) - _closed: bool = field(init=False, default=False) - - async def aclose(self) -> None: - await self.receive_stream.aclose() - self._closed = True - - @property - def buffer(self) -> bytes: - """The bytes currently in the buffer.""" - return bytes(self._buffer) - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return self.receive_stream.extra_attributes - - def feed_data(self, data: Iterable[SupportsIndex], /) -> None: - """ - Append data directly into the buffer. - - Any data in the buffer will be consumed by receive operations before receiving - anything from the wrapped stream. - - :param data: the data to append to the buffer (can be bytes or anything else - that supports ``__index__()``) - - """ - self._buffer.extend(data) - - async def receive(self, max_bytes: int = 65536) -> bytes: - if self._closed: - raise ClosedResourceError - - if self._buffer: - chunk = bytes(self._buffer[:max_bytes]) - del self._buffer[:max_bytes] - return chunk - elif isinstance(self.receive_stream, ByteReceiveStream): - return await self.receive_stream.receive(max_bytes) - else: - # With a bytes-oriented object stream, we need to handle any surplus bytes - # we get from the receive() call - chunk = await self.receive_stream.receive() - if len(chunk) > max_bytes: - # Save the surplus bytes in the buffer - self._buffer.extend(chunk[max_bytes:]) - return chunk[:max_bytes] - else: - return chunk - - async def receive_exactly(self, nbytes: int) -> bytes: - """ - Read exactly the given amount of bytes from the stream. - - :param nbytes: the number of bytes to read - :return: the bytes read - :raises ~anyio.IncompleteRead: if the stream was closed before the requested - amount of bytes could be read from the stream - - """ - while True: - remaining = nbytes - len(self._buffer) - if remaining <= 0: - retval = self._buffer[:nbytes] - del self._buffer[:nbytes] - return bytes(retval) - - try: - if isinstance(self.receive_stream, ByteReceiveStream): - chunk = await self.receive_stream.receive(remaining) - else: - chunk = await self.receive_stream.receive() - except EndOfStream as exc: - raise IncompleteRead from exc - - self._buffer.extend(chunk) - - async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes: - """ - Read from the stream until the delimiter is found or max_bytes have been read. - - :param delimiter: the marker to look for in the stream - :param max_bytes: maximum number of bytes that will be read before raising - :exc:`~anyio.DelimiterNotFound` - :return: the bytes read (not including the delimiter) - :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter - was found - :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the - bytes read up to the maximum allowed - - """ - delimiter_size = len(delimiter) - offset = 0 - while True: - # Check if the delimiter can be found in the current buffer - index = self._buffer.find(delimiter, offset) - if index >= 0: - found = self._buffer[:index] - del self._buffer[: index + len(delimiter) :] - return bytes(found) - - # Check if the buffer is already at or over the limit - if len(self._buffer) >= max_bytes: - raise DelimiterNotFound(max_bytes) - - # Read more data into the buffer from the socket - try: - data = await self.receive_stream.receive() - except EndOfStream as exc: - raise IncompleteRead from exc - - # Move the offset forward and add the new data to the buffer - offset = max(len(self._buffer) - delimiter_size + 1, 0) - self._buffer.extend(data) - - -class BufferedByteStream(BufferedByteReceiveStream, ByteStream): - """ - A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed - through to the wrapped stream as-is. - """ - - def __init__(self, stream: AnyByteStream): - """ - :param stream: the stream to be wrapped - - """ - super().__init__(stream) - self._stream = stream - - @override - async def send_eof(self) -> None: - await self._stream.send_eof() - - @override - async def send(self, item: bytes) -> None: - await self._stream.send(item) - - -class BufferedConnectable(ByteStreamConnectable): - """ - Wraps a byte stream connectable to produce :class:`BufferedByteStream` connections. - - Use this when you want the streams returned by :meth:`connect` to have the buffered - receive API (e.g. :meth:`~BufferedByteReceiveStream.receive_exactly` and - :meth:`~BufferedByteReceiveStream.receive_until`). - - :param connectable: the byte stream connectable to wrap - """ - - def __init__(self, connectable: AnyByteStreamConnectable): - """ - :param connectable: the connectable to wrap - - """ - self.connectable = connectable - - @override - async def connect(self) -> BufferedByteStream: - stream = await self.connectable.connect() - return BufferedByteStream(stream) diff --git a/server/venv/Lib/site-packages/anyio/streams/file.py b/server/venv/Lib/site-packages/anyio/streams/file.py deleted file mode 100644 index 79c3d50..0000000 --- a/server/venv/Lib/site-packages/anyio/streams/file.py +++ /dev/null @@ -1,154 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "FileReadStream", - "FileStreamAttribute", - "FileWriteStream", -) - -from collections.abc import Callable, Mapping -from io import SEEK_SET, UnsupportedOperation -from os import PathLike -from pathlib import Path -from typing import IO, Any - -from .. import ( - BrokenResourceError, - ClosedResourceError, - EndOfStream, - TypedAttributeSet, - to_thread, - typed_attribute, -) -from ..abc import ByteReceiveStream, ByteSendStream - - -class FileStreamAttribute(TypedAttributeSet): - #: the open file descriptor - file: IO[bytes] = typed_attribute() - #: the path of the file on the file system, if available (file must be a real file) - path: Path = typed_attribute() - #: the file number, if available (file must be a real file or a TTY) - fileno: int = typed_attribute() - - -class _BaseFileStream: - def __init__(self, file: IO[bytes]): - self._file = file - - async def aclose(self) -> None: - await to_thread.run_sync(self._file.close) - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - attributes: dict[Any, Callable[[], Any]] = { - FileStreamAttribute.file: lambda: self._file, - } - - if hasattr(self._file, "name"): - attributes[FileStreamAttribute.path] = lambda: Path(self._file.name) - - try: - self._file.fileno() - except UnsupportedOperation: - pass - else: - attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno() - - return attributes - - -class FileReadStream(_BaseFileStream, ByteReceiveStream): - """ - A byte stream that reads from a file in the file system. - - :param file: a file that has been opened for reading in binary mode - - .. versionadded:: 3.0 - """ - - @classmethod - async def from_path(cls, path: str | PathLike[str]) -> FileReadStream: - """ - Create a file read stream by opening the given file. - - :param path: path of the file to read from - - """ - file = await to_thread.run_sync(Path(path).open, "rb") - return cls(file) - - async def receive(self, max_bytes: int = 65536) -> bytes: - try: - data = await to_thread.run_sync(self._file.read, max_bytes) - except ValueError: - raise ClosedResourceError from None - except OSError as exc: - raise BrokenResourceError from exc - - if data: - return data - else: - raise EndOfStream - - async def seek(self, position: int, whence: int = SEEK_SET) -> int: - """ - Seek the file to the given position. - - .. seealso:: :meth:`io.IOBase.seek` - - .. note:: Not all file descriptors are seekable. - - :param position: position to seek the file to - :param whence: controls how ``position`` is interpreted - :return: the new absolute position - :raises OSError: if the file is not seekable - - """ - return await to_thread.run_sync(self._file.seek, position, whence) - - async def tell(self) -> int: - """ - Return the current stream position. - - .. note:: Not all file descriptors are seekable. - - :return: the current absolute position - :raises OSError: if the file is not seekable - - """ - return await to_thread.run_sync(self._file.tell) - - -class FileWriteStream(_BaseFileStream, ByteSendStream): - """ - A byte stream that writes to a file in the file system. - - :param file: a file that has been opened for writing in binary mode - - .. versionadded:: 3.0 - """ - - @classmethod - async def from_path( - cls, path: str | PathLike[str], append: bool = False - ) -> FileWriteStream: - """ - Create a file write stream by opening the given file for writing. - - :param path: path of the file to write to - :param append: if ``True``, open the file for appending; if ``False``, any - existing file at the given path will be truncated - - """ - mode = "ab" if append else "wb" - file = await to_thread.run_sync(Path(path).open, mode) - return cls(file) - - async def send(self, item: bytes) -> None: - try: - await to_thread.run_sync(self._file.write, item) - except ValueError: - raise ClosedResourceError from None - except OSError as exc: - raise BrokenResourceError from exc diff --git a/server/venv/Lib/site-packages/anyio/streams/memory.py b/server/venv/Lib/site-packages/anyio/streams/memory.py deleted file mode 100644 index a3fa0c3..0000000 --- a/server/venv/Lib/site-packages/anyio/streams/memory.py +++ /dev/null @@ -1,325 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "MemoryObjectReceiveStream", - "MemoryObjectSendStream", - "MemoryObjectStreamStatistics", -) - -import warnings -from collections import OrderedDict, deque -from dataclasses import dataclass, field -from types import TracebackType -from typing import Generic, NamedTuple, TypeVar - -from .. import ( - BrokenResourceError, - ClosedResourceError, - EndOfStream, - WouldBlock, -) -from .._core._testing import TaskInfo, get_current_task -from ..abc import Event, ObjectReceiveStream, ObjectSendStream -from ..lowlevel import checkpoint - -T_Item = TypeVar("T_Item") -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True) - - -class MemoryObjectStreamStatistics(NamedTuple): - current_buffer_used: int #: number of items stored in the buffer - #: maximum number of items that can be stored on this stream (or :data:`math.inf`) - max_buffer_size: float - open_send_streams: int #: number of unclosed clones of the send stream - open_receive_streams: int #: number of unclosed clones of the receive stream - #: number of tasks blocked on :meth:`MemoryObjectSendStream.send` - tasks_waiting_send: int - #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive` - tasks_waiting_receive: int - - -@dataclass(eq=False) -class _MemoryObjectItemReceiver(Generic[T_Item]): - task_info: TaskInfo = field(init=False, default_factory=get_current_task) - item: T_Item = field(init=False) - - def __repr__(self) -> str: - # When item is not defined, we get following error with default __repr__: - # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item' - item = getattr(self, "item", None) - return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})" - - -@dataclass(eq=False) -class _MemoryObjectStreamState(Generic[T_Item]): - max_buffer_size: float = field() - buffer: deque[T_Item] = field(init=False, default_factory=deque) - open_send_channels: int = field(init=False, default=0) - open_receive_channels: int = field(init=False, default=0) - waiting_receivers: OrderedDict[Event, _MemoryObjectItemReceiver[T_Item]] = field( - init=False, default_factory=OrderedDict - ) - waiting_senders: OrderedDict[Event, T_Item] = field( - init=False, default_factory=OrderedDict - ) - - def statistics(self) -> MemoryObjectStreamStatistics: - return MemoryObjectStreamStatistics( - len(self.buffer), - self.max_buffer_size, - self.open_send_channels, - self.open_receive_channels, - len(self.waiting_senders), - len(self.waiting_receivers), - ) - - -@dataclass(eq=False) -class MemoryObjectReceiveStream(Generic[T_co], ObjectReceiveStream[T_co]): - _state: _MemoryObjectStreamState[T_co] - _closed: bool = field(init=False, default=False) - - def __post_init__(self) -> None: - self._state.open_receive_channels += 1 - - def receive_nowait(self) -> T_co: - """ - Receive the next item if it can be done without waiting. - - :return: the received item - :raises ~anyio.ClosedResourceError: if this send stream has been closed - :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been - closed from the sending end - :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks - waiting to send - - """ - if self._closed: - raise ClosedResourceError - - if self._state.waiting_senders: - # Get the item from the next sender - send_event, item = self._state.waiting_senders.popitem(last=False) - self._state.buffer.append(item) - send_event.set() - - if self._state.buffer: - return self._state.buffer.popleft() - elif not self._state.open_send_channels: - raise EndOfStream - - raise WouldBlock - - async def receive(self) -> T_co: - await checkpoint() - try: - return self.receive_nowait() - except WouldBlock: - # Add ourselves in the queue - receive_event = Event() - receiver = _MemoryObjectItemReceiver[T_co]() - self._state.waiting_receivers[receive_event] = receiver - - try: - await receive_event.wait() - finally: - self._state.waiting_receivers.pop(receive_event, None) - - try: - return receiver.item - except AttributeError: - raise EndOfStream from None - - def clone(self) -> MemoryObjectReceiveStream[T_co]: - """ - Create a clone of this receive stream. - - Each clone can be closed separately. Only when all clones have been closed will - the receiving end of the memory stream be considered closed by the sending ends. - - :return: the cloned stream - - """ - if self._closed: - raise ClosedResourceError - - return MemoryObjectReceiveStream(_state=self._state) - - def close(self) -> None: - """ - Close the stream. - - This works the exact same way as :meth:`aclose`, but is provided as a special - case for the benefit of synchronous callbacks. - - """ - if not self._closed: - self._closed = True - self._state.open_receive_channels -= 1 - if self._state.open_receive_channels == 0: - send_events = list(self._state.waiting_senders.keys()) - for event in send_events: - event.set() - - async def aclose(self) -> None: - self.close() - - def statistics(self) -> MemoryObjectStreamStatistics: - """ - Return statistics about the current state of this stream. - - .. versionadded:: 3.0 - """ - return self._state.statistics() - - def __enter__(self) -> MemoryObjectReceiveStream[T_co]: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.close() - - def __del__(self) -> None: - if not self._closed: - warnings.warn( - f"Unclosed <{self.__class__.__name__} at {id(self):x}>", - ResourceWarning, - stacklevel=1, - source=self, - ) - - -@dataclass(eq=False) -class MemoryObjectSendStream(Generic[T_contra], ObjectSendStream[T_contra]): - _state: _MemoryObjectStreamState[T_contra] - _closed: bool = field(init=False, default=False) - - def __post_init__(self) -> None: - self._state.open_send_channels += 1 - - def send_nowait(self, item: T_contra) -> None: - """ - Send an item immediately if it can be done without waiting. - - :param item: the item to send - :raises ~anyio.ClosedResourceError: if this send stream has been closed - :raises ~anyio.BrokenResourceError: if the stream has been closed from the - receiving end - :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting - to receive - - """ - if self._closed: - raise ClosedResourceError - if not self._state.open_receive_channels: - raise BrokenResourceError - - while self._state.waiting_receivers: - receive_event, receiver = self._state.waiting_receivers.popitem(last=False) - if not receiver.task_info.has_pending_cancellation(): - receiver.item = item - receive_event.set() - return - - if len(self._state.buffer) < self._state.max_buffer_size: - self._state.buffer.append(item) - else: - raise WouldBlock - - async def send(self, item: T_contra) -> None: - """ - Send an item to the stream. - - If the buffer is full, this method blocks until there is again room in the - buffer or the item can be sent directly to a receiver. - - :param item: the item to send - :raises ~anyio.ClosedResourceError: if this send stream has been closed - :raises ~anyio.BrokenResourceError: if the stream has been closed from the - receiving end - - """ - await checkpoint() - try: - self.send_nowait(item) - except WouldBlock: - # Wait until there's someone on the receiving end - send_event = Event() - self._state.waiting_senders[send_event] = item - try: - await send_event.wait() - except BaseException: - self._state.waiting_senders.pop(send_event, None) - raise - - if send_event in self._state.waiting_senders: - del self._state.waiting_senders[send_event] - raise BrokenResourceError from None - - def clone(self) -> MemoryObjectSendStream[T_contra]: - """ - Create a clone of this send stream. - - Each clone can be closed separately. Only when all clones have been closed will - the sending end of the memory stream be considered closed by the receiving ends. - - :return: the cloned stream - - """ - if self._closed: - raise ClosedResourceError - - return MemoryObjectSendStream(_state=self._state) - - def close(self) -> None: - """ - Close the stream. - - This works the exact same way as :meth:`aclose`, but is provided as a special - case for the benefit of synchronous callbacks. - - """ - if not self._closed: - self._closed = True - self._state.open_send_channels -= 1 - if self._state.open_send_channels == 0: - receive_events = list(self._state.waiting_receivers.keys()) - self._state.waiting_receivers.clear() - for event in receive_events: - event.set() - - async def aclose(self) -> None: - self.close() - - def statistics(self) -> MemoryObjectStreamStatistics: - """ - Return statistics about the current state of this stream. - - .. versionadded:: 3.0 - """ - return self._state.statistics() - - def __enter__(self) -> MemoryObjectSendStream[T_contra]: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> None: - self.close() - - def __del__(self) -> None: - if not self._closed: - warnings.warn( - f"Unclosed <{self.__class__.__name__} at {id(self):x}>", - ResourceWarning, - stacklevel=1, - source=self, - ) diff --git a/server/venv/Lib/site-packages/anyio/streams/stapled.py b/server/venv/Lib/site-packages/anyio/streams/stapled.py deleted file mode 100644 index 9248b68..0000000 --- a/server/venv/Lib/site-packages/anyio/streams/stapled.py +++ /dev/null @@ -1,147 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "MultiListener", - "StapledByteStream", - "StapledObjectStream", -) - -from collections.abc import Callable, Mapping, Sequence -from dataclasses import dataclass -from typing import Any, Generic, TypeVar - -from ..abc import ( - ByteReceiveStream, - ByteSendStream, - ByteStream, - Listener, - ObjectReceiveStream, - ObjectSendStream, - ObjectStream, - TaskGroup, -) - -T_Item = TypeVar("T_Item") -T_Stream = TypeVar("T_Stream") - - -@dataclass(eq=False) -class StapledByteStream(ByteStream): - """ - Combines two byte streams into a single, bidirectional byte stream. - - Extra attributes will be provided from both streams, with the receive stream - providing the values in case of a conflict. - - :param ByteSendStream send_stream: the sending byte stream - :param ByteReceiveStream receive_stream: the receiving byte stream - """ - - send_stream: ByteSendStream - receive_stream: ByteReceiveStream - - async def receive(self, max_bytes: int = 65536) -> bytes: - return await self.receive_stream.receive(max_bytes) - - async def send(self, item: bytes) -> None: - await self.send_stream.send(item) - - async def send_eof(self) -> None: - await self.send_stream.aclose() - - async def aclose(self) -> None: - await self.send_stream.aclose() - await self.receive_stream.aclose() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return { - **self.send_stream.extra_attributes, - **self.receive_stream.extra_attributes, - } - - -@dataclass(eq=False) -class StapledObjectStream(Generic[T_Item], ObjectStream[T_Item]): - """ - Combines two object streams into a single, bidirectional object stream. - - Extra attributes will be provided from both streams, with the receive stream - providing the values in case of a conflict. - - :param ObjectSendStream send_stream: the sending object stream - :param ObjectReceiveStream receive_stream: the receiving object stream - """ - - send_stream: ObjectSendStream[T_Item] - receive_stream: ObjectReceiveStream[T_Item] - - async def receive(self) -> T_Item: - return await self.receive_stream.receive() - - async def send(self, item: T_Item) -> None: - await self.send_stream.send(item) - - async def send_eof(self) -> None: - await self.send_stream.aclose() - - async def aclose(self) -> None: - await self.send_stream.aclose() - await self.receive_stream.aclose() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return { - **self.send_stream.extra_attributes, - **self.receive_stream.extra_attributes, - } - - -@dataclass(eq=False) -class MultiListener(Generic[T_Stream], Listener[T_Stream]): - """ - Combines multiple listeners into one, serving connections from all of them at once. - - Any MultiListeners in the given collection of listeners will have their listeners - moved into this one. - - Extra attributes are provided from each listener, with each successive listener - overriding any conflicting attributes from the previous one. - - :param listeners: listeners to serve - :type listeners: Sequence[Listener[T_Stream]] - """ - - listeners: Sequence[Listener[T_Stream]] - - def __post_init__(self) -> None: - listeners: list[Listener[T_Stream]] = [] - for listener in self.listeners: - if isinstance(listener, MultiListener): - listeners.extend(listener.listeners) - del listener.listeners[:] # type: ignore[attr-defined] - else: - listeners.append(listener) - - self.listeners = listeners - - async def serve( - self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None - ) -> None: - from .. import create_task_group - - async with create_task_group() as tg: - for listener in self.listeners: - tg.start_soon(listener.serve, handler, task_group) - - async def aclose(self) -> None: - for listener in self.listeners: - await listener.aclose() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - attributes: dict = {} - for listener in self.listeners: - attributes.update(listener.extra_attributes) - - return attributes diff --git a/server/venv/Lib/site-packages/anyio/streams/text.py b/server/venv/Lib/site-packages/anyio/streams/text.py deleted file mode 100644 index 296cd25..0000000 --- a/server/venv/Lib/site-packages/anyio/streams/text.py +++ /dev/null @@ -1,176 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "TextConnectable", - "TextReceiveStream", - "TextSendStream", - "TextStream", -) - -import codecs -import sys -from collections.abc import Callable, Mapping -from dataclasses import InitVar, dataclass, field -from typing import Any - -from ..abc import ( - AnyByteReceiveStream, - AnyByteSendStream, - AnyByteStream, - AnyByteStreamConnectable, - ObjectReceiveStream, - ObjectSendStream, - ObjectStream, - ObjectStreamConnectable, -) - -if sys.version_info >= (3, 12): - from typing import override -else: - from typing_extensions import override - - -@dataclass(eq=False) -class TextReceiveStream(ObjectReceiveStream[str]): - """ - Stream wrapper that decodes bytes to strings using the given encoding. - - Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any - completely received unicode characters as soon as they come in. - - :param transport_stream: any bytes-based receive stream - :param encoding: character encoding to use for decoding bytes to strings (defaults - to ``utf-8``) - :param errors: handling scheme for decoding errors (defaults to ``strict``; see the - `codecs module documentation`_ for a comprehensive list of options) - - .. _codecs module documentation: - https://docs.python.org/3/library/codecs.html#codec-objects - """ - - transport_stream: AnyByteReceiveStream - encoding: InitVar[str] = "utf-8" - errors: InitVar[str] = "strict" - _decoder: codecs.IncrementalDecoder = field(init=False) - - def __post_init__(self, encoding: str, errors: str) -> None: - decoder_class = codecs.getincrementaldecoder(encoding) - self._decoder = decoder_class(errors=errors) - - async def receive(self) -> str: - while True: - chunk = await self.transport_stream.receive() - decoded = self._decoder.decode(chunk) - if decoded: - return decoded - - async def aclose(self) -> None: - await self.transport_stream.aclose() - self._decoder.reset() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return self.transport_stream.extra_attributes - - -@dataclass(eq=False) -class TextSendStream(ObjectSendStream[str]): - """ - Sends strings to the wrapped stream as bytes using the given encoding. - - :param AnyByteSendStream transport_stream: any bytes-based send stream - :param str encoding: character encoding to use for encoding strings to bytes - (defaults to ``utf-8``) - :param str errors: handling scheme for encoding errors (defaults to ``strict``; see - the `codecs module documentation`_ for a comprehensive list of options) - - .. _codecs module documentation: - https://docs.python.org/3/library/codecs.html#codec-objects - """ - - transport_stream: AnyByteSendStream - encoding: InitVar[str] = "utf-8" - errors: str = "strict" - _encoder: Callable[..., tuple[bytes, int]] = field(init=False) - - def __post_init__(self, encoding: str) -> None: - self._encoder = codecs.getencoder(encoding) - - async def send(self, item: str) -> None: - encoded = self._encoder(item, self.errors)[0] - await self.transport_stream.send(encoded) - - async def aclose(self) -> None: - await self.transport_stream.aclose() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return self.transport_stream.extra_attributes - - -@dataclass(eq=False) -class TextStream(ObjectStream[str]): - """ - A bidirectional stream that decodes bytes to strings on receive and encodes strings - to bytes on send. - - Extra attributes will be provided from both streams, with the receive stream - providing the values in case of a conflict. - - :param AnyByteStream transport_stream: any bytes-based stream - :param str encoding: character encoding to use for encoding/decoding strings to/from - bytes (defaults to ``utf-8``) - :param str errors: handling scheme for encoding errors (defaults to ``strict``; see - the `codecs module documentation`_ for a comprehensive list of options) - - .. _codecs module documentation: - https://docs.python.org/3/library/codecs.html#codec-objects - """ - - transport_stream: AnyByteStream - encoding: InitVar[str] = "utf-8" - errors: InitVar[str] = "strict" - _receive_stream: TextReceiveStream = field(init=False) - _send_stream: TextSendStream = field(init=False) - - def __post_init__(self, encoding: str, errors: str) -> None: - self._receive_stream = TextReceiveStream( - self.transport_stream, encoding=encoding, errors=errors - ) - self._send_stream = TextSendStream( - self.transport_stream, encoding=encoding, errors=errors - ) - - async def receive(self) -> str: - return await self._receive_stream.receive() - - async def send(self, item: str) -> None: - await self._send_stream.send(item) - - async def send_eof(self) -> None: - await self.transport_stream.send_eof() - - async def aclose(self) -> None: - await self._send_stream.aclose() - await self._receive_stream.aclose() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return { - **self._send_stream.extra_attributes, - **self._receive_stream.extra_attributes, - } - - -class TextConnectable(ObjectStreamConnectable[str]): - def __init__(self, connectable: AnyByteStreamConnectable): - """ - :param connectable: the bytestream endpoint to wrap - - """ - self.connectable = connectable - - @override - async def connect(self) -> TextStream: - stream = await self.connectable.connect() - return TextStream(stream) diff --git a/server/venv/Lib/site-packages/anyio/streams/tls.py b/server/venv/Lib/site-packages/anyio/streams/tls.py deleted file mode 100644 index e2a7ca5..0000000 --- a/server/venv/Lib/site-packages/anyio/streams/tls.py +++ /dev/null @@ -1,421 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "TLSAttribute", - "TLSConnectable", - "TLSListener", - "TLSStream", -) - -import logging -import re -import ssl -import sys -from collections.abc import Callable, Mapping -from dataclasses import dataclass -from functools import wraps -from ssl import SSLContext -from typing import Any, TypeAlias, TypeVar - -from .. import ( - BrokenResourceError, - EndOfStream, - aclose_forcefully, - get_cancelled_exc_class, - to_thread, -) -from .._core._typedattr import TypedAttributeSet, typed_attribute -from ..abc import ( - AnyByteStream, - AnyByteStreamConnectable, - ByteStream, - ByteStreamConnectable, - Listener, - TaskGroup, -) - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -if sys.version_info >= (3, 12): - from typing import override -else: - from typing_extensions import override - -T_Retval = TypeVar("T_Retval") -PosArgsT = TypeVarTuple("PosArgsT") -_PCTRTT: TypeAlias = tuple[tuple[str, str], ...] -_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...] - - -class TLSAttribute(TypedAttributeSet): - """Contains Transport Layer Security related attributes.""" - - #: the selected ALPN protocol - alpn_protocol: str | None = typed_attribute() - #: the channel binding for type ``tls-unique`` - channel_binding_tls_unique: bytes = typed_attribute() - #: the selected cipher - cipher: tuple[str, str, int] = typed_attribute() - #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert` - # for more information) - peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute() - #: the peer certificate in binary form - peer_certificate_binary: bytes | None = typed_attribute() - #: ``True`` if this is the server side of the connection - server_side: bool = typed_attribute() - #: ciphers shared by the client during the TLS handshake (``None`` if this is the - #: client side) - shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute() - #: the :class:`~ssl.SSLObject` used for encryption - ssl_object: ssl.SSLObject = typed_attribute() - #: ``True`` if this stream does (and expects) a closing TLS handshake when the - #: stream is being closed - standard_compatible: bool = typed_attribute() - #: the TLS protocol version (e.g. ``TLSv1.2``) - tls_version: str = typed_attribute() - - -@dataclass(eq=False) -class TLSStream(ByteStream): - """ - A stream wrapper that encrypts all sent data and decrypts received data. - - This class has no public initializer; use :meth:`wrap` instead. - All extra attributes from :class:`~TLSAttribute` are supported. - - :var AnyByteStream transport_stream: the wrapped stream - - """ - - transport_stream: AnyByteStream - standard_compatible: bool - _ssl_object: ssl.SSLObject - _read_bio: ssl.MemoryBIO - _write_bio: ssl.MemoryBIO - - @classmethod - async def wrap( - cls, - transport_stream: AnyByteStream, - *, - server_side: bool | None = None, - hostname: str | None = None, - ssl_context: ssl.SSLContext | None = None, - standard_compatible: bool = True, - ) -> TLSStream: - """ - Wrap an existing stream with Transport Layer Security. - - This performs a TLS handshake with the peer. - - :param transport_stream: a bytes-transporting stream to wrap - :param server_side: ``True`` if this is the server side of the connection, - ``False`` if this is the client side (if omitted, will be set to ``False`` - if ``hostname`` has been provided, ``False`` otherwise). Used only to create - a default context when an explicit context has not been provided. - :param hostname: host name of the peer (if host name checking is desired) - :param ssl_context: the SSLContext object to use (if not provided, a secure - default will be created) - :param standard_compatible: if ``False``, skip the closing handshake when - closing the connection, and don't raise an exception if the peer does the - same - :raises ~ssl.SSLError: if the TLS handshake fails - - """ - if server_side is None: - server_side = not hostname - - if not ssl_context: - purpose = ( - ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH - ) - ssl_context = ssl.create_default_context(purpose) - - # Re-enable detection of unexpected EOFs if it was disabled by Python - if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"): - ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF - - bio_in = ssl.MemoryBIO() - bio_out = ssl.MemoryBIO() - - # External SSLContext implementations may do blocking I/O in wrap_bio(), - # but the standard library implementation won't - if type(ssl_context) is ssl.SSLContext: - ssl_object = ssl_context.wrap_bio( - bio_in, bio_out, server_side=server_side, server_hostname=hostname - ) - else: - ssl_object = await to_thread.run_sync( - ssl_context.wrap_bio, - bio_in, - bio_out, - server_side, - hostname, - None, - ) - - wrapper = cls( - transport_stream=transport_stream, - standard_compatible=standard_compatible, - _ssl_object=ssl_object, - _read_bio=bio_in, - _write_bio=bio_out, - ) - await wrapper._call_sslobject_method(ssl_object.do_handshake) - return wrapper - - async def _call_sslobject_method( - self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT] - ) -> T_Retval: - while True: - try: - result = func(*args) - except ssl.SSLWantReadError: - try: - # Flush any pending writes first - if self._write_bio.pending: - await self.transport_stream.send(self._write_bio.read()) - - data = await self.transport_stream.receive() - except EndOfStream: - self._read_bio.write_eof() - except OSError as exc: - self._read_bio.write_eof() - self._write_bio.write_eof() - raise BrokenResourceError from exc - else: - self._read_bio.write(data) - except ssl.SSLWantWriteError: - await self.transport_stream.send(self._write_bio.read()) - except ssl.SSLSyscallError as exc: - self._read_bio.write_eof() - self._write_bio.write_eof() - raise BrokenResourceError from exc - except ssl.SSLError as exc: - self._read_bio.write_eof() - self._write_bio.write_eof() - if isinstance(exc, ssl.SSLEOFError) or ( - exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror - ): - if self.standard_compatible: - raise BrokenResourceError from exc - else: - raise EndOfStream from None - - raise - else: - # Flush any pending writes first - if self._write_bio.pending: - await self.transport_stream.send(self._write_bio.read()) - - return result - - async def unwrap(self) -> tuple[AnyByteStream, bytes]: - """ - Does the TLS closing handshake. - - :return: a tuple of (wrapped byte stream, bytes left in the read buffer) - - """ - await self._call_sslobject_method(self._ssl_object.unwrap) - self._read_bio.write_eof() - self._write_bio.write_eof() - return self.transport_stream, self._read_bio.read() - - async def aclose(self) -> None: - if self.standard_compatible: - try: - await self.unwrap() - except BaseException: - await aclose_forcefully(self.transport_stream) - raise - - await self.transport_stream.aclose() - - async def receive(self, max_bytes: int = 65536) -> bytes: - data = await self._call_sslobject_method(self._ssl_object.read, max_bytes) - if not data: - raise EndOfStream - - return data - - async def send(self, item: bytes) -> None: - await self._call_sslobject_method(self._ssl_object.write, item) - - async def send_eof(self) -> None: - tls_version = self.extra(TLSAttribute.tls_version) - match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version) - if match: - major, minor = int(match.group(1)), int(match.group(2) or 0) - if (major, minor) < (1, 3): - raise NotImplementedError( - f"send_eof() requires at least TLSv1.3; current " - f"session uses {tls_version}" - ) - - raise NotImplementedError( - "send_eof() has not yet been implemented for TLS streams" - ) - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return { - **self.transport_stream.extra_attributes, - TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol, - TLSAttribute.channel_binding_tls_unique: ( - self._ssl_object.get_channel_binding - ), - TLSAttribute.cipher: self._ssl_object.cipher, - TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False), - TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert( - True - ), - TLSAttribute.server_side: lambda: self._ssl_object.server_side, - TLSAttribute.shared_ciphers: lambda: ( - self._ssl_object.shared_ciphers() - if self._ssl_object.server_side - else None - ), - TLSAttribute.standard_compatible: lambda: self.standard_compatible, - TLSAttribute.ssl_object: lambda: self._ssl_object, - TLSAttribute.tls_version: self._ssl_object.version, - } - - -@dataclass(eq=False) -class TLSListener(Listener[TLSStream]): - """ - A convenience listener that wraps another listener and auto-negotiates a TLS session - on every accepted connection. - - If the TLS handshake times out or raises an exception, - :meth:`handle_handshake_error` is called to do whatever post-mortem processing is - deemed necessary. - - Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute. - - :param Listener listener: the listener to wrap - :param ssl_context: the SSL context object - :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap` - :param handshake_timeout: time limit for the TLS handshake - (passed to :func:`~anyio.fail_after`) - """ - - listener: Listener[Any] - ssl_context: ssl.SSLContext - standard_compatible: bool = True - handshake_timeout: float = 30 - - @staticmethod - async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None: - """ - Handle an exception raised during the TLS handshake. - - This method does 3 things: - - #. Forcefully closes the original stream - #. Logs the exception (unless it was a cancellation exception) using the - ``anyio.streams.tls`` logger - #. Reraises the exception if it was a base exception or a cancellation exception - - :param exc: the exception - :param stream: the original stream - - """ - await aclose_forcefully(stream) - - # Log all except cancellation exceptions - if not isinstance(exc, get_cancelled_exc_class()): - # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using - # any asyncio implementation, so we explicitly pass the exception to log - # (https://github.com/python/cpython/issues/108668). Trio does not have this - # issue because it works around the CPython bug. - logging.getLogger(__name__).exception( - "Error during TLS handshake", exc_info=exc - ) - - # Only reraise base exceptions and cancellation exceptions - if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()): - raise - - async def serve( - self, - handler: Callable[[TLSStream], Any], - task_group: TaskGroup | None = None, - ) -> None: - @wraps(handler) - async def handler_wrapper(stream: AnyByteStream) -> None: - from .. import fail_after - - try: - with fail_after(self.handshake_timeout): - wrapped_stream = await TLSStream.wrap( - stream, - ssl_context=self.ssl_context, - standard_compatible=self.standard_compatible, - ) - except BaseException as exc: - await self.handle_handshake_error(exc, stream) - else: - await handler(wrapped_stream) - - await self.listener.serve(handler_wrapper, task_group) - - async def aclose(self) -> None: - await self.listener.aclose() - - @property - def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]: - return { - TLSAttribute.standard_compatible: lambda: self.standard_compatible, - } - - -class TLSConnectable(ByteStreamConnectable): - """ - Wraps another connectable and does TLS negotiation after a successful connection. - - :param connectable: the connectable to wrap - :param hostname: host name of the server (if host name checking is desired) - :param ssl_context: the SSLContext object to use (if not provided, a secure default - will be created) - :param standard_compatible: if ``False``, skip the closing handshake when closing - the connection, and don't raise an exception if the server does the same - """ - - def __init__( - self, - connectable: AnyByteStreamConnectable, - *, - hostname: str | None = None, - ssl_context: ssl.SSLContext | None = None, - standard_compatible: bool = True, - ) -> None: - self.connectable = connectable - self.ssl_context: SSLContext = ssl_context or ssl.create_default_context( - ssl.Purpose.SERVER_AUTH - ) - if not isinstance(self.ssl_context, ssl.SSLContext): - raise TypeError( - "ssl_context must be an instance of ssl.SSLContext, not " - f"{type(self.ssl_context).__name__}" - ) - self.hostname = hostname - self.standard_compatible = standard_compatible - - @override - async def connect(self) -> TLSStream: - stream = await self.connectable.connect() - try: - return await TLSStream.wrap( - stream, - hostname=self.hostname, - ssl_context=self.ssl_context, - standard_compatible=self.standard_compatible, - ) - except BaseException: - await aclose_forcefully(stream) - raise diff --git a/server/venv/Lib/site-packages/anyio/to_interpreter.py b/server/venv/Lib/site-packages/anyio/to_interpreter.py deleted file mode 100644 index 694dbe7..0000000 --- a/server/venv/Lib/site-packages/anyio/to_interpreter.py +++ /dev/null @@ -1,246 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "run_sync", - "current_default_interpreter_limiter", -) - -import atexit -import os -import sys -from collections import deque -from collections.abc import Callable -from typing import Any, Final, TypeVar - -from . import current_time, to_thread -from ._core._exceptions import BrokenWorkerInterpreter -from ._core._synchronization import CapacityLimiter -from .lowlevel import RunVar - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -if sys.version_info >= (3, 14): - from concurrent.interpreters import ExecutionFailed, create - - def _interp_call( - func: Callable[..., Any], args: tuple[Any, ...] - ) -> tuple[Any, bool]: - try: - retval = func(*args) - except BaseException as exc: - return exc, True - else: - return retval, False - - class _Worker: - last_used: float = 0 - - def __init__(self) -> None: - self._interpreter = create() - - def destroy(self) -> None: - self._interpreter.close() - - def call( - self, - func: Callable[..., T_Retval], - args: tuple[Any, ...], - ) -> T_Retval: - try: - res, is_exception = self._interpreter.call(_interp_call, func, args) - except ExecutionFailed as exc: - raise BrokenWorkerInterpreter(exc.excinfo) from exc - - if is_exception: - raise res - - return res -elif sys.version_info >= (3, 13): - import _interpqueues - import _interpreters - - UNBOUND: Final = 2 # I have no clue how this works, but it was used in the stdlib - FMT_UNPICKLED: Final = 0 - FMT_PICKLED: Final = 1 - QUEUE_PICKLE_ARGS: Final = (FMT_PICKLED, UNBOUND) - QUEUE_UNPICKLE_ARGS: Final = (FMT_UNPICKLED, UNBOUND) - - _run_func = compile( - """ -import _interpqueues -from _interpreters import NotShareableError -from pickle import loads, dumps, HIGHEST_PROTOCOL - -QUEUE_PICKLE_ARGS = (1, 2) -QUEUE_UNPICKLE_ARGS = (0, 2) - -item = _interpqueues.get(queue_id)[0] -try: - func, args = loads(item) - retval = func(*args) -except BaseException as exc: - is_exception = True - retval = exc -else: - is_exception = False - -try: - _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_UNPICKLE_ARGS) -except NotShareableError: - retval = dumps(retval, HIGHEST_PROTOCOL) - _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_PICKLE_ARGS) - """, - "", - "exec", - ) - - class _Worker: - last_used: float = 0 - - def __init__(self) -> None: - self._interpreter_id = _interpreters.create() - self._queue_id = _interpqueues.create(1, *QUEUE_UNPICKLE_ARGS) - _interpreters.set___main___attrs( - self._interpreter_id, {"queue_id": self._queue_id} - ) - - def destroy(self) -> None: - _interpqueues.destroy(self._queue_id) - _interpreters.destroy(self._interpreter_id) - - def call( - self, - func: Callable[..., T_Retval], - args: tuple[Any, ...], - ) -> T_Retval: - import pickle - - item = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL) - _interpqueues.put(self._queue_id, item, *QUEUE_PICKLE_ARGS) - exc_info = _interpreters.exec(self._interpreter_id, _run_func) - if exc_info: - raise BrokenWorkerInterpreter(exc_info) - - res = _interpqueues.get(self._queue_id) - (res, is_exception), fmt = res[:2] - if fmt == FMT_PICKLED: - res = pickle.loads(res) - - if is_exception: - raise res - - return res -else: - - class _Worker: - last_used: float = 0 - - def __init__(self) -> None: - raise RuntimeError("subinterpreters require at least Python 3.13") - - def call( - self, - func: Callable[..., T_Retval], - args: tuple[Any, ...], - ) -> T_Retval: - raise NotImplementedError - - def destroy(self) -> None: - pass - - -DEFAULT_CPU_COUNT: Final = 8 # this is just an arbitrarily selected value -MAX_WORKER_IDLE_TIME = ( - 30 # seconds a subinterpreter can be idle before becoming eligible for pruning -) - -T_Retval = TypeVar("T_Retval") -PosArgsT = TypeVarTuple("PosArgsT") - -_idle_workers = RunVar[deque[_Worker]]("_available_workers") -_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter") - - -def _stop_workers(workers: deque[_Worker]) -> None: - for worker in workers: - worker.destroy() - - workers.clear() - - -async def run_sync( - func: Callable[[Unpack[PosArgsT]], T_Retval], - *args: Unpack[PosArgsT], - limiter: CapacityLimiter | None = None, -) -> T_Retval: - """ - Call the given function with the given arguments in a subinterpreter. - - .. warning:: On Python 3.13, the :mod:`concurrent.interpreters` module was not yet - available, so the code path for that Python version relies on an undocumented, - private API. As such, it is recommended to not rely on this function for anything - mission-critical on Python 3.13. - - :param func: a callable - :param args: the positional arguments for the callable - :param limiter: capacity limiter to use to limit the total number of subinterpreters - running (if omitted, the default limiter is used) - :return: the result of the call - :raises BrokenWorkerInterpreter: if there's an internal error in a subinterpreter - - """ - if limiter is None: - limiter = current_default_interpreter_limiter() - - try: - idle_workers = _idle_workers.get() - except LookupError: - idle_workers = deque() - _idle_workers.set(idle_workers) - atexit.register(_stop_workers, idle_workers) - - async with limiter: - try: - worker = idle_workers.pop() - except IndexError: - worker = _Worker() - - try: - return await to_thread.run_sync( - worker.call, - func, - args, - limiter=limiter, - ) - finally: - # Prune workers that have been idle for too long - now = current_time() - while idle_workers: - if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME: - break - - await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter) - - worker.last_used = current_time() - idle_workers.append(worker) - - -def current_default_interpreter_limiter() -> CapacityLimiter: - """ - Return the capacity limiter used by default to limit the number of concurrently - running subinterpreters. - - Defaults to the number of CPU cores. - - :return: a capacity limiter object - - """ - try: - return _default_interpreter_limiter.get() - except LookupError: - limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT) - _default_interpreter_limiter.set(limiter) - return limiter diff --git a/server/venv/Lib/site-packages/anyio/to_process.py b/server/venv/Lib/site-packages/anyio/to_process.py deleted file mode 100644 index fd65b18..0000000 --- a/server/venv/Lib/site-packages/anyio/to_process.py +++ /dev/null @@ -1,268 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "current_default_process_limiter", - "process_worker", - "run_sync", -) - -import os -import pickle -import runpy -import subprocess -import sys -from collections import deque -from collections.abc import Callable -from types import ModuleType -from typing import TypeVar, cast - -from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class -from ._core._exceptions import BrokenWorkerProcess -from ._core._subprocesses import open_process -from ._core._synchronization import CapacityLimiter -from ._core._tasks import CancelScope, fail_after -from .abc import ByteReceiveStream, ByteSendStream, Process -from .lowlevel import RunVar, checkpoint_if_cancelled -from .streams.buffered import BufferedByteReceiveStream - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -WORKER_MAX_IDLE_TIME = 300 # 5 minutes - -T_Retval = TypeVar("T_Retval") -PosArgsT = TypeVarTuple("PosArgsT") - -_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers") -_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar( - "_process_pool_idle_workers" -) -_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter") - - -async def run_sync( # type: ignore[return] - func: Callable[[Unpack[PosArgsT]], T_Retval], - *args: Unpack[PosArgsT], - cancellable: bool = False, - limiter: CapacityLimiter | None = None, -) -> T_Retval: - """ - Call the given function with the given arguments in a worker process. - - If the ``cancellable`` option is enabled and the task waiting for its completion is - cancelled, the worker process running it will be abruptly terminated using SIGKILL - (or ``terminateProcess()`` on Windows). - - :param func: a callable - :param args: positional arguments for the callable - :param cancellable: ``True`` to allow cancellation of the operation while it's - running - :param limiter: capacity limiter to use to limit the total amount of processes - running (if omitted, the default limiter is used) - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - :return: an awaitable that yields the return value of the function. - - """ - - async def send_raw_command(pickled_cmd: bytes) -> object: - try: - await stdin.send(pickled_cmd) - response = await buffered.receive_until(b"\n", 50) - status, length = response.split(b" ") - if status not in (b"RETURN", b"EXCEPTION"): - raise RuntimeError( - f"Worker process returned unexpected response: {response!r}" - ) - - pickled_response = await buffered.receive_exactly(int(length)) - except BaseException as exc: - workers.discard(process) - try: - process.kill() - with CancelScope(shield=True): - await process.aclose() - except ProcessLookupError: - pass - - if isinstance(exc, get_cancelled_exc_class()): - raise - else: - raise BrokenWorkerProcess from exc - - retval = pickle.loads(pickled_response) - if status == b"EXCEPTION": - assert isinstance(retval, BaseException) - raise retval - else: - return retval - - # First pickle the request before trying to reserve a worker process - await checkpoint_if_cancelled() - request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL) - - # If this is the first run in this event loop thread, set up the necessary variables - try: - workers = _process_pool_workers.get() - idle_workers = _process_pool_idle_workers.get() - except LookupError: - workers = set() - idle_workers = deque() - _process_pool_workers.set(workers) - _process_pool_idle_workers.set(idle_workers) - get_async_backend().setup_process_pool_exit_at_shutdown(workers) - - async with limiter or current_default_process_limiter(): - # Pop processes from the pool (starting from the most recently used) until we - # find one that hasn't exited yet - process: Process - while idle_workers: - process, idle_since = idle_workers.pop() - if process.returncode is None: - stdin = cast(ByteSendStream, process.stdin) - buffered = BufferedByteReceiveStream( - cast(ByteReceiveStream, process.stdout) - ) - - # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME - # seconds or longer - now = current_time() - killed_processes: list[Process] = [] - while idle_workers: - if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME: - break - - process_to_kill, idle_since = idle_workers.popleft() - process_to_kill.kill() - workers.remove(process_to_kill) - killed_processes.append(process_to_kill) - - with CancelScope(shield=True): - for killed_process in killed_processes: - await killed_process.aclose() - - break - - workers.remove(process) - else: - command = [sys.executable, "-u", "-m", __name__] - process = await open_process( - command, stdin=subprocess.PIPE, stdout=subprocess.PIPE - ) - try: - stdin = cast(ByteSendStream, process.stdin) - buffered = BufferedByteReceiveStream( - cast(ByteReceiveStream, process.stdout) - ) - with fail_after(20): - message = await buffered.receive(6) - - if message != b"READY\n": - raise BrokenWorkerProcess( - f"Worker process returned unexpected response: {message!r}" - ) - - main_module_path = getattr(sys.modules["__main__"], "__file__", None) - pickled = pickle.dumps( - ("init", sys.path, main_module_path), - protocol=pickle.HIGHEST_PROTOCOL, - ) - await send_raw_command(pickled) - except (BrokenWorkerProcess, get_cancelled_exc_class()): - raise - except BaseException as exc: - process.kill() - raise BrokenWorkerProcess( - "Error during worker process initialization" - ) from exc - - workers.add(process) - - with CancelScope(shield=not cancellable): - try: - return cast(T_Retval, await send_raw_command(request)) - finally: - if process in workers: - idle_workers.append((process, current_time())) - - -def current_default_process_limiter() -> CapacityLimiter: - """ - Return the capacity limiter that is used by default to limit the number of worker - processes. - - :return: a capacity limiter object - - """ - try: - return _default_process_limiter.get() - except LookupError: - limiter = CapacityLimiter(os.cpu_count() or 2) - _default_process_limiter.set(limiter) - return limiter - - -def process_worker() -> None: - # Redirect standard streams to os.devnull so that user code won't interfere with the - # parent-worker communication - stdin = sys.stdin - stdout = sys.stdout - sys.stdin = open(os.devnull) - sys.stdout = open(os.devnull, "w") - - stdout.buffer.write(b"READY\n") - while True: - retval = exception = None - try: - command, *args = pickle.load(stdin.buffer) - except EOFError: - return - except BaseException as exc: - exception = exc - else: - if command == "run": - func, args = args - try: - retval = func(*args) - except BaseException as exc: - exception = exc - elif command == "init": - main_module_path: str | None - sys.path, main_module_path = args - del sys.modules["__main__"] - if main_module_path and os.path.isfile(main_module_path): - # Load the parent's main module but as __mp_main__ instead of - # __main__ (like multiprocessing does) to avoid infinite recursion - try: - main = ModuleType("__mp_main__") - main_content = runpy.run_path( - main_module_path, run_name="__mp_main__" - ) - main.__dict__.update(main_content) - sys.modules["__main__"] = sys.modules["__mp_main__"] = main - except BaseException as exc: - exception = exc - try: - if exception is not None: - status = b"EXCEPTION" - pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL) - else: - status = b"RETURN" - pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL) - except BaseException as exc: - exception = exc - status = b"EXCEPTION" - pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL) - - stdout.buffer.write(b"%s %d\n" % (status, len(pickled))) - stdout.buffer.write(pickled) - - # Respect SIGTERM - if isinstance(exception, SystemExit): - raise exception - - -if __name__ == "__main__": - process_worker() diff --git a/server/venv/Lib/site-packages/anyio/to_thread.py b/server/venv/Lib/site-packages/anyio/to_thread.py deleted file mode 100644 index 83c79d1..0000000 --- a/server/venv/Lib/site-packages/anyio/to_thread.py +++ /dev/null @@ -1,78 +0,0 @@ -from __future__ import annotations - -__all__ = ( - "run_sync", - "current_default_thread_limiter", -) - -import sys -from collections.abc import Callable -from typing import TypeVar -from warnings import warn - -from ._core._eventloop import get_async_backend -from .abc import CapacityLimiter - -if sys.version_info >= (3, 11): - from typing import TypeVarTuple, Unpack -else: - from typing_extensions import TypeVarTuple, Unpack - -T_Retval = TypeVar("T_Retval") -PosArgsT = TypeVarTuple("PosArgsT") - - -async def run_sync( - func: Callable[[Unpack[PosArgsT]], T_Retval], - *args: Unpack[PosArgsT], - abandon_on_cancel: bool = False, - cancellable: bool | None = None, - limiter: CapacityLimiter | None = None, -) -> T_Retval: - """ - Call the given function with the given arguments in a worker thread. - - If the ``abandon_on_cancel`` option is enabled and the task waiting for its - completion is cancelled, the thread will still run its course but its - return value (or any raised exception) will be ignored. - - :param func: a callable - :param args: positional arguments for the callable - :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run - unchecked on own) if the host task is cancelled, ``False`` to ignore - cancellations in the host task until the operation has completed in the worker - thread - :param cancellable: deprecated alias of ``abandon_on_cancel``; will override - ``abandon_on_cancel`` if both parameters are passed - :param limiter: capacity limiter to use to limit the total amount of threads running - (if omitted, the default limiter is used) - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - :return: an awaitable that yields the return value of the function. - - """ - if cancellable is not None: - abandon_on_cancel = cancellable - warn( - "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is " - "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead", - DeprecationWarning, - stacklevel=2, - ) - - return await get_async_backend().run_sync_in_worker_thread( - func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter - ) - - -def current_default_thread_limiter() -> CapacityLimiter: - """ - Return the capacity limiter that is used by default to limit the number of - concurrent threads. - - :return: a capacity limiter object - :raises NoEventLoopError: if no supported asynchronous event loop is running in the - current thread - - """ - return get_async_backend().current_default_thread_limiter() diff --git a/server/venv/Lib/site-packages/click-8.4.1.dist-info/INSTALLER b/server/venv/Lib/site-packages/click-8.4.1.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/click-8.4.1.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/click-8.4.1.dist-info/METADATA b/server/venv/Lib/site-packages/click-8.4.1.dist-info/METADATA deleted file mode 100644 index 21d7a0c..0000000 --- a/server/venv/Lib/site-packages/click-8.4.1.dist-info/METADATA +++ /dev/null @@ -1,84 +0,0 @@ -Metadata-Version: 2.4 -Name: click -Version: 8.4.1 -Summary: Composable command line interface toolkit -Maintainer-email: Pallets -Requires-Python: >=3.10 -Description-Content-Type: text/markdown -License-Expression: BSD-3-Clause -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Typing :: Typed -License-File: LICENSE.txt -Requires-Dist: colorama; platform_system == 'Windows' -Project-URL: Changes, https://click.palletsprojects.com/page/changes/ -Project-URL: Chat, https://discord.gg/pallets -Project-URL: Documentation, https://click.palletsprojects.com/ -Project-URL: Donate, https://palletsprojects.com/donate -Project-URL: Source, https://github.com/pallets/click/ - -
- -# 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 - - -## A Simple Example - -```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() -``` - -``` -$ 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 - -## Contributing - -See our [detailed contributing documentation][contrib] for many ways to -contribute, including reporting issues, requesting features, asking or answering -questions, and making PRs. - -[contrib]: https://palletsprojects.com/contributing/ - diff --git a/server/venv/Lib/site-packages/click-8.4.1.dist-info/RECORD b/server/venv/Lib/site-packages/click-8.4.1.dist-info/RECORD deleted file mode 100644 index e84fda1..0000000 --- a/server/venv/Lib/site-packages/click-8.4.1.dist-info/RECORD +++ /dev/null @@ -1,40 +0,0 @@ -click-8.4.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -click-8.4.1.dist-info/METADATA,sha256=uWALc7Y-Ixjj35al3B2qNsyXk-0tf578YgIsL1Vbn98,2621 -click-8.4.1.dist-info/RECORD,, -click-8.4.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 -click-8.4.1.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475 -click/__init__.py,sha256=FId2fXCSJB3yeWD-e2uON-mBhFa2Yc9MvXGmHu8OXG0,4634 -click/__pycache__/__init__.cpython-313.pyc,, -click/__pycache__/_compat.cpython-313.pyc,, -click/__pycache__/_termui_impl.cpython-313.pyc,, -click/__pycache__/_textwrap.cpython-313.pyc,, -click/__pycache__/_utils.cpython-313.pyc,, -click/__pycache__/_winconsole.cpython-313.pyc,, -click/__pycache__/core.cpython-313.pyc,, -click/__pycache__/decorators.cpython-313.pyc,, -click/__pycache__/exceptions.cpython-313.pyc,, -click/__pycache__/formatting.cpython-313.pyc,, -click/__pycache__/globals.cpython-313.pyc,, -click/__pycache__/parser.cpython-313.pyc,, -click/__pycache__/shell_completion.cpython-313.pyc,, -click/__pycache__/termui.cpython-313.pyc,, -click/__pycache__/testing.cpython-313.pyc,, -click/__pycache__/types.cpython-313.pyc,, -click/__pycache__/utils.cpython-313.pyc,, -click/_compat.py,sha256=XK1woqBSPRb1KNAwWwVsSMfEm8ZlfDwBCjceWzSGmEY,18910 -click/_termui_impl.py,sha256=VNyWY27pm0JUq12WK__pJNYxeUoDyfNwYEgE6vz_cnM,30411 -click/_textwrap.py,sha256=7Z0N7Vmn-66TNSTUwp6OXJbcUXRmYET9h9c2ucD8oQQ,6270 -click/_utils.py,sha256=eCZCtwJtsYD5QYkkNWJ8MY_8ABIjy8MczgMMyVY32rQ,996 -click/_winconsole.py,sha256=KSxfNbMlYRa6GOJuCLgsg2Pb3dVkgJNPqLJPae-Pa10,8543 -click/core.py,sha256=CQaJ0ALw4zyyVp6E7gP6a_G88eiuXE9mBzFnIJQ-igY,137917 -click/decorators.py,sha256=xgR8vZAClhWVkzitP22k6jETFRhKralh81zvA69Csd0,18467 -click/exceptions.py,sha256=FJitDd6MSZLj6CtKQWpkQzHnfsUw3UypxDU0t76bn0c,11294 -click/formatting.py,sha256=r6kGLpPyF0Cva6uSTwSZ_8ONRE7jp93I1C0JVRYQEqE,10370 -click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923 -click/parser.py,sha256=oJ-fU_3mvxugIuNtHaCATZ56lgEmHRggjJiSqEgYrjA,19052 -click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -click/shell_completion.py,sha256=QxeFb3RapNSP8ZwvK2BDVI2jrEKS1RbJaeuFMrHJAnU,21748 -click/termui.py,sha256=hflZ_tK7P4WUv_ef_RVVRL7n0nRPhTD0wN0pkHE7hDo,33000 -click/testing.py,sha256=0k_v5By_5ZO2McjBT7lTqQ57W8QJuW0_5Sx7d2ZLKec,25717 -click/types.py,sha256=UiVEx0yoQtrWO2wH22NgME4LfDxMNQi8j1qtJupYIpw,42783 -click/utils.py,sha256=YsTNrChEfwDXl39CTC_yWnQ-ZVkZxYjFESRCfEgClY4,20386 diff --git a/server/venv/Lib/site-packages/click-8.4.1.dist-info/WHEEL b/server/venv/Lib/site-packages/click-8.4.1.dist-info/WHEEL deleted file mode 100644 index d8b9936..0000000 --- a/server/venv/Lib/site-packages/click-8.4.1.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: flit 3.12.0 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/server/venv/Lib/site-packages/click-8.4.1.dist-info/licenses/LICENSE.txt b/server/venv/Lib/site-packages/click-8.4.1.dist-info/licenses/LICENSE.txt deleted file mode 100644 index d12a849..0000000 --- a/server/venv/Lib/site-packages/click-8.4.1.dist-info/licenses/LICENSE.txt +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2014 Pallets - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/server/venv/Lib/site-packages/click/__init__.py b/server/venv/Lib/site-packages/click/__init__.py deleted file mode 100644 index 64be7e0..0000000 --- a/server/venv/Lib/site-packages/click/__init__.py +++ /dev/null @@ -1,126 +0,0 @@ -""" -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 __future__ import annotations - -from .core import Argument as Argument -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 Option as Option -from .core import Parameter as Parameter -from .core import ParameterSource as ParameterSource -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 NoSuchCommand as NoSuchCommand -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 .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 get_pager_file as get_pager_file -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 - - -def __getattr__(name: str) -> object: - import warnings - - if name == "BaseCommand": - from .core import _BaseCommand - - warnings.warn( - "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" - " 'Command' instead.", - DeprecationWarning, - stacklevel=2, - ) - return _BaseCommand - - if name == "MultiCommand": - from .core import _MultiCommand - - warnings.warn( - "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" - " 'Group' instead.", - DeprecationWarning, - stacklevel=2, - ) - return _MultiCommand - - if name == "OptionParser": - from .parser import _OptionParser - - warnings.warn( - "'OptionParser' is deprecated and will be removed in Click 9.0. The" - " old parser is available in 'optparse'.", - DeprecationWarning, - stacklevel=2, - ) - return _OptionParser - - if name == "__version__": - import importlib.metadata - import warnings - - warnings.warn( - "The '__version__' attribute is deprecated and will be removed in" - " Click 9.1. Use feature detection or" - " 'importlib.metadata.version(\"click\")' instead.", - DeprecationWarning, - stacklevel=2, - ) - return importlib.metadata.version("click") - - raise AttributeError(name) diff --git a/server/venv/Lib/site-packages/click/_compat.py b/server/venv/Lib/site-packages/click/_compat.py deleted file mode 100644 index 36c0e53..0000000 --- a/server/venv/Lib/site-packages/click/_compat.py +++ /dev/null @@ -1,627 +0,0 @@ -from __future__ import annotations - -import codecs -import collections.abc as cabc -import io -import os -import re -import sys -import typing as t -from types import TracebackType -from weakref import WeakKeyDictionary - -CYGWIN = sys.platform.startswith("cygwin") -WIN = sys.platform.startswith("win") -MAC = sys.platform == "darwin" -auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None -_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") - - -def _make_text_stream( - stream: t.BinaryIO, - encoding: str | None, - errors: str | None, - force_readable: bool = False, - force_writable: bool = False, -) -> t.TextIO: - if encoding is None: - encoding = get_best_encoding(stream) - if errors is None: - errors = "replace" - return _NonClosingTextIOWrapper( - stream, - encoding, - errors, - line_buffering=True, - force_readable=force_readable, - force_writable=force_writable, - ) - - -def is_ascii_encoding(encoding: str) -> bool: - """Checks if a given encoding is ascii.""" - try: - return codecs.lookup(encoding).name == "ascii" - except LookupError: - return False - - -def get_best_encoding(stream: t.IO[t.Any]) -> str: - """Returns the default stream encoding if not found.""" - rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() - if is_ascii_encoding(rv): - return "utf-8" - return rv - - -class _NonClosingTextIOWrapper(io.TextIOWrapper): - def __init__( - self, - stream: t.BinaryIO, - encoding: str | None, - errors: str | None, - 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(b"") - except Exception: - try: - self._stream.write(b"") - except Exception: - return False - return True - - def seekable(self) -> bool: - x = getattr(self._stream, "seekable", None) - if x is not None: - return t.cast(bool, x()) - try: - self._stream.seek(self._stream.tell()) - except Exception: - return False - return True - - -def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool: - try: - return isinstance(stream.read(0), bytes) - except Exception: - return default - # This happens in some cases where the stream was already - # closed. In this case, we assume the default. - - -def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool: - try: - stream.write(b"") - except Exception: - try: - stream.write("") - return False - except Exception: - pass - return default - return True - - -def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None: - # We need to figure out if the given stream is already binary. - # This can happen because the official docs recommend detaching - # the streams to get binary streams. Some code might do this, so - # we need to deal with this case explicitly. - if _is_binary_reader(stream, False): - return t.cast(t.BinaryIO, stream) - - buf = getattr(stream, "buffer", None) - - # Same situation here; this time we assume that the buffer is - # actually binary in case it's closed. - if buf is not None and _is_binary_reader(buf, True): - return t.cast(t.BinaryIO, buf) - - return None - - -def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None: - # 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: str | None) -> 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: str | None, errors: str | None -) -> bool: - """Check if a stream's encoding and errors attributes are - compatible with the desired values. - """ - return _is_compat_stream_attr( - stream, "encoding", encoding - ) and _is_compat_stream_attr(stream, "errors", errors) - - -def _force_correct_text_stream( - text_stream: t.IO[t.Any], - encoding: str | None, - errors: str | None, - is_binary: t.Callable[[t.IO[t.Any], bool], bool], - find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None], - force_readable: bool = False, - force_writable: bool = False, -) -> t.TextIO: - if is_binary(text_stream, False): - binary_reader = t.cast(t.BinaryIO, text_stream) - else: - text_stream = t.cast(t.TextIO, text_stream) - # If the stream looks compatible, and won't default to a - # misconfigured ascii encoding, return it as-is. - if _is_compatible_text_stream(text_stream, encoding, errors) and not ( - encoding is None and _stream_is_misconfigured(text_stream) - ): - return text_stream - - # Otherwise, get the underlying binary reader. - possible_binary_reader = find_binary(text_stream) - - # If that's not possible, silently use the original reader - # and get mojibake instead of exceptions. - if possible_binary_reader is None: - return text_stream - - binary_reader = possible_binary_reader - - # Default errors to replace instead of strict in order to get - # something that works. - if errors is None: - errors = "replace" - - # Wrap the binary stream in a text stream with the correct - # encoding parameters. - return _make_text_stream( - binary_reader, - encoding, - errors, - force_readable=force_readable, - force_writable=force_writable, - ) - - -def _force_correct_text_reader( - text_reader: t.IO[t.Any], - encoding: str | None, - errors: str | None, - force_readable: bool = False, -) -> t.TextIO: - return _force_correct_text_stream( - text_reader, - encoding, - errors, - _is_binary_reader, - _find_binary_reader, - force_readable=force_readable, - ) - - -def _force_correct_text_writer( - text_writer: t.IO[t.Any], - encoding: str | None, - errors: str | None, - 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: str | None = None, errors: str | None = 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: str | None = None, errors: str | None = 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: str | None = None, errors: str | None = 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: str | os.PathLike[str] | int, - mode: str, - encoding: str | None, - errors: str | None, -) -> t.IO[t.Any]: - """Handles not passing ``encoding`` and ``errors`` in binary mode.""" - if "b" in mode: - return open(file, mode) - - return open(file, mode, encoding=encoding, errors=errors) - - -def open_stream( - filename: str | os.PathLike[str], - mode: str = "r", - encoding: str | None = None, - errors: str | None = "strict", - atomic: bool = False, -) -> tuple[t.IO[t.Any], bool]: - binary = "b" in mode - filename = os.fspath(filename) - - # Standard streams first. These are simple because they ignore the - # atomic flag. Use fsdecode to handle Path("-"). - if os.fsdecode(filename) == "-": - if any(m in mode for m in ["w", "a", "x"]): - if binary: - return get_binary_stdout(), False - return get_text_stdout(encoding=encoding, errors=errors), False - if binary: - return get_binary_stdin(), False - return get_text_stdin(encoding=encoding, errors=errors), False - - # Non-atomic writes directly go out through the regular open functions. - if not atomic: - return _wrap_io_open(filename, mode, encoding, errors), True - - # Some usability stuff for atomic writes - if "a" in mode: - raise ValueError( - "Appending to an existing file is not supported, because that" - " would involve an expensive `copy`-operation to a temporary" - " file. Open the file in normal `w`-mode and copy explicitly" - " if that's what you're after." - ) - if "x" in mode: - raise ValueError("Use the `overwrite`-parameter instead.") - if "w" not in mode: - raise ValueError("Atomic writes only make sense with `w`-mode.") - - # Atomic writes are more complicated. They work by opening a file - # as a proxy in the same folder and then using the fdopen - # functionality to wrap it in a Python file. Then we wrap it in an - # atomic file that moves the file over on close. - import errno - import random - - try: - perm: int | None = os.stat(filename).st_mode - except OSError: - perm = None - - flags = os.O_RDWR | os.O_CREAT | os.O_EXCL - - if binary: - flags |= getattr(os, "O_BINARY", 0) - - while True: - tmp_filename = os.path.join( - os.path.dirname(filename), - f".__atomic-write{random.randrange(1 << 32):08x}", - ) - try: - fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) - break - except OSError as e: - if e.errno == errno.EEXIST or ( - os.name == "nt" - and e.errno == errno.EACCES - and os.path.isdir(e.filename) - and os.access(e.filename, os.W_OK) - ): - continue - raise - - if perm is not None: - os.chmod(tmp_filename, perm) # in case perm includes bits in umask - - f = _wrap_io_open(fd, mode, encoding, errors) - af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) - return t.cast(t.IO[t.Any], af), True - - -class _AtomicFile: - def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None: - self._f = f - self._tmp_filename = tmp_filename - self._real_filename = real_filename - self.closed = False - - @property - def name(self) -> str: - return self._real_filename - - def close(self, delete: bool = False) -> None: - if self.closed: - return - self._f.close() - os.replace(self._tmp_filename, self._real_filename) - self.closed = True - - def __getattr__(self, name: str) -> t.Any: - return getattr(self._f, name) - - def __enter__(self) -> _AtomicFile: - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - tb: TracebackType | None, - ) -> None: - self.close(delete=exc_type is not None) - - def __repr__(self) -> str: - return repr(self._f) - - -def strip_ansi(value: str) -> str: - return _ansi_re.sub("", value) - - -def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool: - while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): - stream = stream._stream - - return stream.__class__.__module__.startswith("ipykernel.") - - -def should_strip_ansi( - stream: t.IO[t.Any] | None = None, color: bool | None = None -) -> bool: - if color is None: - if stream is None: - stream = sys.stdin - elif hasattr(stream, "color"): - # ._termui_impl.MaybeStripAnsi handles stripping ansi itself, - # so we don't need to strip it here - return False - 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: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() - - def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = 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: str) -> int: - try: - return _write(s) - except BaseException: - ansi_wrapper.reset_all() - raise - - rv.write = _safe_write # type: ignore[method-assign] - - try: - _ansi_stream_wrappers[stream] = rv - except Exception: - pass - - return rv - -else: - - def _get_argv_encoding() -> str: - return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding() - - def _get_windows_console_stream( - f: t.TextIO, encoding: str | None, errors: str | None - ) -> t.TextIO | None: - return None - - -def term_len(x: str) -> int: - return len(strip_ansi(x)) - - -def isatty(stream: t.IO[t.Any]) -> bool: - try: - return stream.isatty() - except Exception: - return False - - -def _make_cached_stream_func( - src_func: t.Callable[[], t.TextIO | None], - wrapper_func: t.Callable[[], t.TextIO], -) -> t.Callable[[], t.TextIO | None]: - cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() - - def func() -> t.TextIO | None: - stream = src_func() - - if stream is None: - return None - - try: - rv = cache.get(stream) - except Exception: - rv = None - if rv is not None: - return rv - rv = wrapper_func() - try: - cache[stream] = rv - except Exception: - pass - return rv - - return func - - -_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) -_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) -_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) - - -binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = { - "stdin": get_binary_stdin, - "stdout": get_binary_stdout, - "stderr": get_binary_stderr, -} - -text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = { - "stdin": get_text_stdin, - "stdout": get_text_stdout, - "stderr": get_text_stderr, -} diff --git a/server/venv/Lib/site-packages/click/_termui_impl.py b/server/venv/Lib/site-packages/click/_termui_impl.py deleted file mode 100644 index 76113e9..0000000 --- a/server/venv/Lib/site-packages/click/_termui_impl.py +++ /dev/null @@ -1,929 +0,0 @@ -""" -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. -""" - -from __future__ import annotations - -import collections.abc as cabc -import contextlib -import io -import math -import os -import shlex -import sys -import time -import typing as t -from gettext import gettext as _ -from io import StringIO -from pathlib import Path -from types import TracebackType - -from ._compat import _default_text_stdout -from ._compat import CYGWIN -from ._compat import get_best_encoding -from ._compat import isatty -from ._compat import strip_ansi -from ._compat import term_len -from ._compat import WIN -from .exceptions import ClickException -from .utils import echo - -V = t.TypeVar("V") - - -class _BufferedTextPagerStream(t.Protocol): - buffer: t.BinaryIO - - -def _has_binary_buffer( - stream: t.BinaryIO | t.TextIO, -) -> t.TypeGuard[_BufferedTextPagerStream]: - # TextIO is wider than TextIOWrapper; text-only streams such as StringIO - # are valid TextIO values but do not expose a binary buffer to wrap. - return getattr(stream, "buffer", None) is not None - - -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: cabc.Iterable[V] | None, - length: int | None = None, - fill_char: str = "#", - empty_char: str = " ", - bar_template: str = "%(bar)s", - info_sep: str = " ", - hidden: bool = False, - show_eta: bool = True, - show_percent: bool | None = None, - show_pos: bool = False, - item_show_func: t.Callable[[V | None], str | None] | None = None, - label: str | None = None, - file: t.TextIO | None = None, - color: bool | None = 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.hidden = hidden - self.show_eta = show_eta - self.show_percent = show_percent - self.show_pos = show_pos - self.item_show_func = item_show_func - self.label: str = label or "" - - if file is None: - file = _default_text_stdout() - - # There are no standard streams attached to write to. For example, - # pythonw on Windows. - if file is None: - file = StringIO() - - self.file = file - self.color = color - self.update_min_steps = update_min_steps - self._completed_intervals = 0 - self.width: int = width - self.autowidth: bool = width == 0 - - if length is None: - from operator import length_hint - - length = length_hint(iterable, -1) - - if length == -1: - length = None - if iterable is None: - if length is None: - raise TypeError("iterable or length is required") - iterable = t.cast("cabc.Iterable[V]", range(length)) - self.iter: cabc.Iterable[V] = iter(iterable) - self.length = length - self.pos: int = 0 - self.avg: list[float] = [] - self.last_eta: float - self.start: float - self.start = self.last_eta = time.time() - self.eta_known: bool = False - self.finished: bool = False - self.max_width: int | None = None - self.entered: bool = False - self.current_item: V | None = None - self._is_atty = isatty(self.file) - self._last_line: str | None = None - - def __enter__(self) -> ProgressBar[V]: - self.entered = True - self.render_progress() - return self - - def __exit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - tb: TracebackType | None, - ) -> None: - self.render_finish() - - def __iter__(self) -> cabc.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.hidden or not self._is_atty: - 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 "{d}{day_label} {h:02}:{m:02}:{s:02}".format( - d=t, - day_label=_("d"), - h=hours, - m=minutes, - s=seconds, - ) - 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: - if self.hidden: - return - - if not self._is_atty: - # Only output the label once if the output is not a TTY. - 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: - import shutil - - 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 and self.max_width is not None: - buf.append(BEFORE_BAR) - buf.append(" " * self.max_width) - self.max_width = new_width - self.width = new_width - - clear_width = self.width - if self.max_width is not None: - clear_width = self.max_width - - buf.append(BEFORE_BAR) - line = self.format_progress_line() - line_len = term_len(line) - if self.max_width is None or self.max_width < line_len: - self.max_width = line_len - - buf.append(line) - buf.append(" " * (clear_width - line_len)) - line = "".join(buf) - # Render the line only if it changed. - - if line != self._last_line: - self._last_line = line - echo(line, file=self.file, color=self.color, nl=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: V | None = 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) -> cabc.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 not self._is_atty: - 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() - - -class MaybeStripAnsi(io.TextIOWrapper): - def __init__(self, stream: t.IO[bytes], *, color: bool, **kwargs: t.Any): - super().__init__(stream, **kwargs) - self.color = color - - def write(self, text: str) -> int: - if not self.color: - text = strip_ansi(text) - return super().write(text) - - -def _pager_contextmanager( - color: bool | None = None, -) -> t.ContextManager[tuple[t.BinaryIO | t.TextIO, str, bool]]: - """Decide what method to use for paging through text.""" - stdout = _default_text_stdout() - - # There are no standard streams attached to write to. For example, - # pythonw on Windows. - if stdout is None: - stdout = StringIO() - - if not isatty(sys.stdin) or not isatty(stdout): - return _nullpager(stdout, color) - - # Split using POSIX mode (the default) so that quote characters are - # stripped from tokens and quoted Windows paths are preserved. - # Non-POSIX mode retains quotes in tokens, and wrapping tokens - # with shlex.quote re-introduces quoting issues on Windows. - pager_cmd_parts = shlex.split(os.environ.get("PAGER", "")) - if pager_cmd_parts: - if WIN: - return _tempfilepager(pager_cmd_parts, color) - return _pipepager(pager_cmd_parts, color) - - if os.environ.get("TERM") in ("dumb", "emacs"): - return _nullpager(stdout, color) - if WIN or sys.platform.startswith("os2"): - return _tempfilepager(["more"], color) - return _pipepager(["less"], color) - - -@contextlib.contextmanager -def get_pager_file(color: bool | None = None) -> t.Generator[t.TextIO, None, None]: - """Context manager. - Yields a writable file-like object which can be used as an output pager. - .. versionadded:: 8.4 - :param color: controls if the pager supports ANSI colors or not. The - default is autodetection. - """ - with _pager_contextmanager(color=color) as (stream, encoding, color): - # Split streams by capabilities rather than the abstract TextIO / - # BinaryIO annotations: buffered text streams can be unwrapped to bytes, - # while text-only streams are yielded as-is. - if _has_binary_buffer(stream): - # Text stream backed by a binary buffer. - stream = MaybeStripAnsi(stream.buffer, color=color, encoding=encoding) - elif isinstance(stream, t.BinaryIO): - # Binary stream - stream = MaybeStripAnsi(stream, color=color, encoding=encoding) - try: - yield stream - finally: - stream.flush() - - -@contextlib.contextmanager -def _pipepager( - cmd_parts: list[str], color: bool | None = None -) -> t.Iterator[tuple[t.BinaryIO | t.TextIO, str, bool]]: - """Page through text by feeding it to another program. - - Invokes the pager via :class:`subprocess.Popen` with an ``argv`` list - produced by :func:`shlex.split`. The command is resolved to an absolute - path with :func:`shutil.which` as recommended by the - :mod:`subprocess` docs for Windows compatibility. - - Invoking a pager through this might support colors: if piping to - ``less`` and the user hasn't decided on colors, ``LESS=-R`` is set - automatically. - """ - # Split the command into the invoked CLI and its parameters. - if not cmd_parts: - stdout = _default_text_stdout() or StringIO() - yield stdout, "utf-8", False - return - - import shutil - - cmd = cmd_parts[0] - cmd_params = cmd_parts[1:] - - cmd_filepath = shutil.which(cmd) - if not cmd_filepath: - stdout = _default_text_stdout() or StringIO() - yield stdout, "utf-8", False - return - - # Produces a normalized absolute path string. - # multi-call binaries such as busybox derive their identity from the symlink - # less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox) - cmd_path = Path(cmd_filepath).absolute() - cmd_name = cmd_path.name - - import subprocess - - # Make a local copy of the environment to not affect the global one. - env = dict(os.environ) - - # If we're piping to less and the user hasn't decided on colors, we enable - # them by default we find the -R flag in the command line arguments. - if color is None and cmd_name == "less": - less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}" - if not less_flags: - env["LESS"] = "-R" - color = True - elif "r" in less_flags or "R" in less_flags: - color = True - - if color is None: - color = False - - c = subprocess.Popen( - [str(cmd_path)] + cmd_params, - shell=False, - stdin=subprocess.PIPE, - env=env, - errors="replace", - text=True, - ) - stdin = t.cast(t.BinaryIO, c.stdin) - encoding = get_best_encoding(stdin) - try: - yield stdin, encoding, color - except BrokenPipeError: - # In case the pager exited unexpectedly, ignore the broken pipe error. - pass - except Exception as e: - # In case there is an exception we want to close the pager immediately - # and let the caller handle it. - # Otherwise the pager will keep running, and the user may not notice - # the error message, or worse yet it may leave the terminal in a broken state. - c.terminate() - raise e - finally: - # We must close stdin and wait for the pager to exit before we continue - try: - stdin.close() - # Close implies flush, so it might throw a BrokenPipeError if the pager - # process exited already. - except BrokenPipeError: - pass - - # Less doesn't respect ^C, but catches it for its own UI purposes (aborting - # search or other commands inside less). - # - # That means when the user hits ^C, the parent process (click) terminates, - # but less is still alive, paging the output and messing up the terminal. - # - # If the user wants to make the pager exit on ^C, they should set - # `LESS='-K'`. It's not our decision to make. - while True: - try: - c.wait() - except KeyboardInterrupt: - pass - else: - break - - -@contextlib.contextmanager -def _tempfilepager( - cmd_parts: list[str], color: bool | None = None -) -> t.Iterator[tuple[t.BinaryIO | t.TextIO, str, bool]]: - """Page through text by invoking a program on a temporary file. - - Used as the primary pager strategy on Windows (where piping to - ``more`` adds spurious ``\\r\\n``), and as a fallback on other - platforms. The command is resolved to an absolute path with - :func:`shutil.which`. - """ - # Split the command into the invoked CLI and its parameters. - if not cmd_parts: - stdout = _default_text_stdout() or StringIO() - yield stdout, "utf-8", False - return - - import shutil - import subprocess - - cmd = cmd_parts[0] - - cmd_filepath = shutil.which(cmd) - if not cmd_filepath: - stdout = _default_text_stdout() or StringIO() - yield stdout, "utf-8", False - return - - # Produces a normalized absolute path string. - # multi-call binaries such as busybox derive their identity from the symlink - # less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox) - cmd_path = Path(cmd_filepath).absolute() - - import tempfile - - encoding = get_best_encoding(sys.stdout) - if color is None: - color = False - # On Windows, NamedTemporaryFile cannot be opened by another process - # while Python still has it open, so we use delete=False and clean up manually - # rather than using a contextmanager here. - f = tempfile.NamedTemporaryFile(mode="wb", delete=False) - try: - yield t.cast(t.BinaryIO, f), encoding, color - f.flush() - f.close() - subprocess.call([str(cmd_path), f.name]) - finally: - os.unlink(f.name) - - -class _SkipClose: - def __init__(self, stream: t.IO[t.Any]) -> None: - self.stream = stream - - def __getattr__(self, name: str) -> t.Any: - return getattr(self.stream, name) - - @property - def buffer(self) -> t.BinaryIO: - return _SkipClose(self.stream.buffer) # type: ignore[attr-defined, return-value] - - def close(self) -> None: - pass - - -@contextlib.contextmanager -def _nullpager( - stream: t.TextIO, color: bool | None = None -) -> t.Iterator[tuple[t.TextIO, str, bool]]: - """Simply print unformatted text. This is the ultimate fallback. Don't close the - output stream in this case, since it's coming from elsewhere rather than our - internal helpers. - """ - encoding = get_best_encoding(stream) - - if color is None: - color = False - - yield _SkipClose(stream), encoding, color # type: ignore[misc] - - -class Editor: - def __init__( - self, - editor: str | None = None, - env: cabc.Mapping[str, str] | None = 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" - - from shutil import which - - for editor in "sensible-editor", "vim", "nano": - if which(editor) is not None: - return editor - return "vi" - - def edit_files(self, filenames: cabc.Iterable[str]) -> None: - """Open files in the user's editor.""" - import shlex - import subprocess - - editor = self.get_editor() - environ: dict[str, str] | None = None - - if self.env: - environ = os.environ.copy() - environ.update(self.env) - - try: - # Split in POSIX mode (the default) for the same reasons as - # in pager(): strips quotes from tokens and preserves quoted - # Windows paths. - c = subprocess.Popen( - args=shlex.split(editor) + list(filenames), - env=environ, - ) - 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 - - @t.overload - def edit(self, text: bytes | bytearray) -> bytes | None: ... - - # We cannot know whether or not the type expected is str or bytes when None - # is passed, so str is returned as that was what was done before. - @t.overload - def edit(self, text: str | None) -> str | None: ... - - def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None: - import tempfile - - if text is None: - data: bytes | bytearray = 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_files((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") - 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) - args = ["explorer", "/select,", url] - try: - return subprocess.call(args) - except OSError: - return 127 - else: - try: - os.startfile(url) # type: ignore[attr-defined] - except OSError: - return 127 - return 0 - elif CYGWIN: - if locate: - url = _unquote_file(url) - args = ["cygstart", os.path.dirname(url)] - else: - args = ["cygstart"] - if wait: - args.append("-w") - args.append(url) - try: - return subprocess.call(args) - except OSError: - # Command not found - return 127 - - 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) -> None: - 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() - - -if sys.platform == "win32": - import msvcrt - - @contextlib.contextmanager - def raw_terminal() -> cabc.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`. - - if echo: - func = t.cast(t.Callable[[], str], msvcrt.getwche) - else: - func = t.cast(t.Callable[[], str], msvcrt.getwch) - - 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 termios - import tty - - @contextlib.contextmanager - def raw_terminal() -> cabc.Iterator[int]: - f: t.TextIO | None - fd: int - - if not isatty(sys.stdin): - f = open("/dev/tty") - fd = f.fileno() - else: - fd = sys.stdin.fileno() - f = None - - try: - old_settings = termios.tcgetattr(fd) - - try: - tty.setraw(fd) - yield fd - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - sys.stdout.flush() - - if f is not None: - f.close() - except termios.error: - pass - - def getchar(echo: bool) -> str: - with raw_terminal() as fd: - ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") - - if echo and isatty(sys.stdout): - sys.stdout.write(ch) - - _translate_ch_to_exc(ch) - return ch diff --git a/server/venv/Lib/site-packages/click/_textwrap.py b/server/venv/Lib/site-packages/click/_textwrap.py deleted file mode 100644 index 82840f2..0000000 --- a/server/venv/Lib/site-packages/click/_textwrap.py +++ /dev/null @@ -1,188 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import textwrap -from contextlib import contextmanager - -from ._compat import _ansi_re -from ._compat import term_len - - -def _truncate_visible(text: str, n: int) -> str: - """Return the longest prefix of ``text`` containing at most ``n`` visible - characters. - - ANSI escape sequences inside the prefix are kept intact and do not count - toward the visible width. A cut is never placed inside an escape sequence. - """ - if n <= 0: - return "" - - visible = 0 - i = 0 - cut = 0 - end = len(text) - while i < end: - m = _ansi_re.match(text, i) - if m is not None: - i = m.end() - continue - visible += 1 - i += 1 - cut = i - if visible >= n: - break - return text[:cut] - - -class TextWrapper(textwrap.TextWrapper): - """``textwrap.TextWrapper`` variant that measures widths by visible - character count. - - ANSI escape sequences embedded in chunks, indents, or the placeholder are - excluded from the width budget. Without this, styled help text (a styled - ``Usage:`` prefix, a colorized option name, ...) would be wrapped earlier - than its visible length warrants and tokens would split mid-word. - """ - - def _handle_long_word( - self, - reversed_chunks: list[str], - cur_line: 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 = _truncate_visible(last, space_left) - res = last[len(cut) :] - cur_line.append(cut) - reversed_chunks[-1] = res - elif not cur_line: - cur_line.append(reversed_chunks.pop()) - - def _wrap_chunks(self, chunks: list[str]) -> list[str]: - """Wrap chunks counting widths in visible characters. - - Mirrors the algorithm of :meth:`textwrap.TextWrapper._wrap_chunks` - with every width measurement routed through - :func:`click._compat.term_len` instead of :func:`len`, so ANSI escape - bytes in chunks, indents, or the placeholder do not inflate the count. - - .. seealso:: - :class:`textwrap.TextWrapper` in the Python standard library documentation: - https://docs.python.org/3/library/textwrap.html#textwrap.TextWrapper - - Reference implementation in CPython: - https://github.com/python/cpython/blob/main/Lib/textwrap.py - """ - lines: list[str] = [] - if self.width <= 0: - raise ValueError(f"invalid width {self.width!r} (must be > 0)") - if self.max_lines is not None: - if self.max_lines > 1: - indent = self.subsequent_indent - else: - indent = self.initial_indent - if term_len(indent) + term_len(self.placeholder.lstrip()) > self.width: - raise ValueError("placeholder too large for max width") - - chunks.reverse() - - while chunks: - cur_line: list[str] = [] - cur_len = 0 - - if lines: - indent = self.subsequent_indent - else: - indent = self.initial_indent - - width = self.width - term_len(indent) - - if self.drop_whitespace and chunks[-1].strip() == "" and lines: - del chunks[-1] - - while chunks: - n = term_len(chunks[-1]) - - if cur_len + n <= width: - cur_line.append(chunks.pop()) - cur_len += n - - else: - break - - if chunks and term_len(chunks[-1]) > width: - self._handle_long_word(chunks, cur_line, cur_len, width) - cur_len = sum(map(term_len, cur_line)) - - if self.drop_whitespace and cur_line and cur_line[-1].strip() == "": - cur_len -= term_len(cur_line[-1]) - del cur_line[-1] - - if cur_line: - if ( - self.max_lines is None - or len(lines) + 1 < self.max_lines - or ( - not chunks - or self.drop_whitespace - and len(chunks) == 1 - and not chunks[0].strip() - ) - and cur_len <= width - ): - lines.append(indent + "".join(cur_line)) - else: - while cur_line: - if ( - cur_line[-1].strip() - and cur_len + term_len(self.placeholder) <= width - ): - cur_line.append(self.placeholder) - lines.append(indent + "".join(cur_line)) - break - cur_len -= term_len(cur_line[-1]) - del cur_line[-1] - else: - if lines: - prev_line = lines[-1].rstrip() - if ( - term_len(prev_line) + term_len(self.placeholder) - <= self.width - ): - lines[-1] = prev_line + self.placeholder - break - lines.append(indent + self.placeholder.lstrip()) - break - - return lines - - @contextmanager - def extra_indent(self, indent: str) -> cabc.Iterator[None]: - old_initial_indent = self.initial_indent - old_subsequent_indent = self.subsequent_indent - self.initial_indent += indent - self.subsequent_indent += indent - - try: - yield - finally: - self.initial_indent = old_initial_indent - self.subsequent_indent = old_subsequent_indent - - def indent_only(self, text: str) -> str: - rv = [] - - for idx, line in enumerate(text.splitlines()): - indent = self.initial_indent - - if idx > 0: - indent = self.subsequent_indent - - rv.append(f"{indent}{line}") - - return "\n".join(rv) diff --git a/server/venv/Lib/site-packages/click/_utils.py b/server/venv/Lib/site-packages/click/_utils.py deleted file mode 100644 index 05ee2e9..0000000 --- a/server/venv/Lib/site-packages/click/_utils.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -import enum -import typing as t - - -class Sentinel(enum.Enum): - """Enum used to define sentinel values. - - .. seealso:: - - `PEP 661 - Sentinel Values `_. - """ - - UNSET = object() - FLAG_NEEDS_VALUE = object() - - def __repr__(self) -> str: - return f"{self.__class__.__name__}.{self.name}" - - -UNSET: t.Literal[Sentinel.UNSET] = Sentinel.UNSET -"""Sentinel used to indicate that a value is not set.""" - -FLAG_NEEDS_VALUE: t.Literal[Sentinel.FLAG_NEEDS_VALUE] = Sentinel.FLAG_NEEDS_VALUE -"""Sentinel used to indicate 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``. -""" - -T_UNSET: t.TypeAlias = t.Literal[Sentinel.UNSET] -"""Type hint for the :data:`UNSET` sentinel value.""" - -T_FLAG_NEEDS_VALUE: t.TypeAlias = t.Literal[Sentinel.FLAG_NEEDS_VALUE] -"""Type hint for the :data:`FLAG_NEEDS_VALUE` sentinel value.""" diff --git a/server/venv/Lib/site-packages/click/_winconsole.py b/server/venv/Lib/site-packages/click/_winconsole.py deleted file mode 100644 index d25178d..0000000 --- a/server/venv/Lib/site-packages/click/_winconsole.py +++ /dev/null @@ -1,297 +0,0 @@ -# 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. -from __future__ import annotations - -import collections.abc as cabc -import io -import sys -import time -import typing as t -from ctypes import Array -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 gettext import gettext as _ - -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 - -if t.TYPE_CHECKING: - try: - # Using `typing_extensions.Buffer` instead of `collections.abc` - # on Windows for some reason does not have `Sized` implemented. - from collections.abc import Buffer # type: ignore - except ImportError: - from typing_extensions import Buffer - -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_ = [ # noqa: RUF012 - ("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: Buffer, writable: bool = False) -> Array[c_char]: - buf = Py_buffer() - flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE - PyObject_GetBuffer(py_object(obj), byref(buf), flags) - - try: - buffer_type = c_char * buf.len - out: Array[c_char] = buffer_type.from_address(buf.buf) - return out - finally: - PyBuffer_Release(byref(buf)) - - -class _WindowsConsoleRawIOBase(io.RawIOBase): - def __init__(self, handle: int | None) -> None: - self.handle = handle - - def isatty(self) -> t.Literal[True]: - super().isatty() - return True - - -class _WindowsConsoleReader(_WindowsConsoleRawIOBase): - def readable(self) -> t.Literal[True]: - return True - - def readinto(self, b: Buffer) -> int: - 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(_("Windows error: {error}").format(error=GetLastError())) - - if buffer[0] == EOF: - return 0 - return 2 * code_units_read.value - - -class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): - def writable(self) -> t.Literal[True]: - return True - - @staticmethod - def _get_error_message(errno: int) -> str: - if errno == ERROR_SUCCESS: - return "ERROR_SUCCESS" - elif errno == ERROR_NOT_ENOUGH_MEMORY: - return "ERROR_NOT_ENOUGH_MEMORY" - return _("Windows error: {error}").format(error=errno) - - def write(self, b: Buffer) -> int: - 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: cabc.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) -> str: - return f"" - - -def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: - text_stream = _NonClosingTextIOWrapper( - io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), - "utf-16-le", - "strict", - line_buffering=True, - ) - return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) - - -def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: - text_stream = _NonClosingTextIOWrapper( - io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), - "utf-16-le", - "strict", - line_buffering=True, - ) - return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) - - -def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: - text_stream = _NonClosingTextIOWrapper( - io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), - "utf-16-le", - "strict", - line_buffering=True, - ) - return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) - - -_stream_factories: cabc.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: str | None, errors: str | None -) -> t.TextIO | None: - if ( - get_buffer is None - or encoding not in {"utf-16-le", None} - or errors not in {"strict", None} - or not _is_console(f) - ): - return None - - func = _stream_factories.get(f.fileno()) - if func is None: - return None - - b = getattr(f, "buffer", None) - - if b is None: - return None - - return func(b) diff --git a/server/venv/Lib/site-packages/click/core.py b/server/venv/Lib/site-packages/click/core.py deleted file mode 100644 index bc04f64..0000000 --- a/server/venv/Lib/site-packages/click/core.py +++ /dev/null @@ -1,3542 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import enum -import errno -import inspect -import os -import sys -import typing as t -from abc import ABC -from abc import abstractmethod -from collections import abc -from collections import Counter -from contextlib import AbstractContextManager -from contextlib import contextmanager -from contextlib import ExitStack -from functools import update_wrapper -from gettext import gettext as _ -from gettext import ngettext -from itertools import repeat -from types import TracebackType - -from . import types -from ._utils import FLAG_NEEDS_VALUE -from ._utils import UNSET -from .exceptions import Abort -from .exceptions import BadParameter -from .exceptions import ClickException -from .exceptions import Exit -from .exceptions import MissingParameter -from .exceptions import NoArgsIsHelpError -from .exceptions import NoSuchCommand -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 _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: - 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 -) -> cabc.Iterator[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(Group, 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_nested_chain( - base_command: Group, cmd_name: str, cmd: Command, register: bool = False -) -> None: - if not base_command.chain or not isinstance(cmd, Group): - return - - if register: - message = ( - f"It is not possible to add the group {cmd_name!r} to another" - f" group {base_command.name!r} that is in chain mode." - ) - else: - message = ( - f"Found the group {cmd_name!r} as subcommand to another group " - f" {base_command.name!r} that is in chain mode. This is not supported." - ) - - raise RuntimeError(message) - - -def _format_deprecated_label(deprecated: bool | str) -> str: - """Return the parenthesized deprecation label shown in help text.""" - label = _("deprecated").upper() - if isinstance(deprecated, str): - return f"({label}: {deprecated})" - return f"({label})" - - -def _format_deprecated_suffix(deprecated: bool | str) -> str: - """Return the trailing reason for a ``DeprecationWarning`` message, - prefixed with a space, or an empty string when no reason was given. - """ - if isinstance(deprecated, str): - return f" {deprecated}" - return "" - - -def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]: - return list(zip(*repeat(iter(iterable), batch_size), strict=False)) - - -@contextmanager -def augment_usage_errors( - ctx: Context, param: Parameter | None = None -) -> cabc.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: cabc.Sequence[Parameter], - declaration_order: cabc.Sequence[Parameter], -) -> list[Parameter]: - """Returns all declared parameters in the order they should be processed. - - The declared parameters are re-shuffled depending on the order in which - they were invoked, as well as the eagerness of each parameters. - - The invocation order takes precedence over the declaration order. I.e. the - order in which the user provided them to the CLI is respected. - - This behavior and its effect on callback evaluation is detailed at: - https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order - """ - - def sort_key(item: Parameter) -> 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.IntEnum): - """This is an :class:`~enum.IntEnum` 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. - - Members are ordered from most explicit to least explicit source. - This allows comparison to check if a value was explicitly provided: - - .. code-block:: python - - source = ctx.get_parameter_source("port") - if source < click.ParameterSource.DEFAULT_MAP: - ... # value was explicitly set - - .. versionchanged:: 8.3.3 - Use :class:`~enum.IntEnum` and reorder members from most to - least explicit. Supports comparison operators. - - .. versionchanged:: 8.0 - Use :class:`~enum.Enum` and drop the ``validate`` method. - - .. versionchanged:: 8.0 - Added the ``PROMPT`` value. - """ - - PROMPT = enum.auto() - """Used a prompt to confirm a default or provide a 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_MAP = enum.auto() - """Used a default provided by :attr:`Context.default_map`.""" - DEFAULT = enum.auto() - """Used the default specified by the parameter.""" - - -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 that 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.2 - The ``protected_args`` attribute is deprecated and will be removed in - Click 9.0. ``args`` will contain remaining unparsed tokens. - - .. 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: type[HelpFormatter] = HelpFormatter - - def __init__( - self, - command: Command, - parent: Context | None = None, - info_name: str | None = None, - obj: t.Any | None = None, - auto_envvar_prefix: str | None = None, - default_map: cabc.MutableMapping[str, t.Any] | None = None, - terminal_width: int | None = None, - max_content_width: int | None = None, - resilient_parsing: bool = False, - allow_extra_args: bool | None = None, - allow_interspersed_args: bool | None = None, - ignore_unknown_options: bool | None = None, - help_option_names: list[str] | None = None, - token_normalize_func: t.Callable[[str], str] | None = None, - color: bool | None = None, - show_default: bool | None = 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: dict[str, t.Any] = {} - #: the leftover arguments. - self.args: 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: list[str] = [] - #: the collected prefixes of the command's options. - self._opt_prefixes: 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: 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: cabc.MutableMapping[str, t.Any] | None = 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: str | None = 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: int | None = 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: int | None = 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: 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.Callable[[str], str] | None = 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: str | None = 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: bool | None = 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: bool | None = show_default - - self._close_callbacks: list[t.Callable[[], t.Any]] = [] - self._depth = 0 - self._parameter_source: dict[str, ParameterSource] = {} - # Tracks whether the option that currently owns each parameter slot in - # :attr:`params` had its ``default`` set explicitly by the user. Used - # to tie-break feature-switch groups where multiple options share a - # parameter name and both fall back to their default value. - # Refs: https://github.com/pallets/click/issues/3403 - self._param_default_explicit: dict[str, bool] = {} - self._exit_stack = ExitStack() - - @property - def protected_args(self) -> list[str]: - import warnings - - warnings.warn( - "'protected_args' is deprecated and will be removed in Click 9.0." - " 'args' will contain remaining unparsed tokens.", - DeprecationWarning, - stacklevel=2, - ) - return self._protected_args - - def to_info_dict(self) -> 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: type[BaseException] | None, - exc_value: BaseException | None, - tb: TracebackType | None, - ) -> bool | None: - self._depth -= 1 - exit_result: bool | None = None - if self._depth == 0: - exit_result = self._close_with_exception_info(exc_type, exc_value, tb) - pop_context() - - return exit_result - - @contextmanager - def scope(self, cleanup: bool = True) -> cabc.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) -> 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: AbstractContextManager[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._close_with_exception_info(None, None, None) - - def _close_with_exception_info( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - tb: TracebackType | None, - ) -> bool | None: - """Unwind the exit stack by calling its :meth:`__exit__` providing the exception - information to allow for exception handling by the various resources registered - using :meth;`with_resource` - - :return: Whatever ``exit_stack.__exit__()`` returns. - """ - exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb) - # In case the context is reused, create a new exit stack. - self._exit_stack = ExitStack() - - return exit_result - - @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: type[V]) -> V | None: - """Finds the closest object of a given type.""" - node: Context | None = 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: 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 - - def _default_map_has(self, name: str | None) -> bool: - """Check if :attr:`default_map` contains a real value for ``name``. - - Returns ``False`` when the key is absent, the map is ``None``, - ``name`` is ``None``, or the stored value is the internal - :data:`UNSET` sentinel. - """ - return ( - name is not None - and self.default_map is not None - and name in self.default_map - and self.default_map[name] is not UNSET - ) - - @t.overload - def lookup_default( - self, name: str, call: t.Literal[True] = True - ) -> t.Any | None: ... - - @t.overload - def lookup_default( - self, name: str, call: t.Literal[False] = ... - ) -> t.Any | t.Callable[[], t.Any] | None: ... - - def lookup_default(self, name: str, call: bool = True) -> t.Any | None: - """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 not self._default_map_has(name): - return None - - # Assert to make the type checker happy. - assert self.default_map is not None - value = self.default_map[name] - - if call and callable(value): - return value() - - return value - - def fail(self, message: str) -> t.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) -> t.NoReturn: - """Aborts the script.""" - raise Abort() - - def exit(self, code: int = 0) -> t.NoReturn: - """Exits the application with a given exit code. - - .. versionchanged:: 8.2 - Callbacks and context managers registered with :meth:`call_on_close` - and :meth:`with_resource` are closed before exiting. - """ - self.close() - raise Exit(code) - - def get_usage(self) -> str: - """Helper method to get formatted usage string for the current - context and command. - """ - return self.command.get_usage(self) - - def get_help(self) -> str: - """Helper method to get formatted help page for the current - context and command. - """ - return self.command.get_help(self) - - def _make_sub_context(self, command: Command) -> Context: - """Create a new context of the same type as this context, but - for a new command. - - :meta private: - """ - return type(self)(command, info_name=command.name, parent=self) - - @t.overload - def invoke( - self, callback: t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any - ) -> V: ... - - @t.overload - def invoke(self, callback: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: ... - - def invoke( - self, callback: Command | t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any - ) -> t.Any | V: - """Invokes a command callback in exactly the way it expects. There - are two ways to invoke this method: - - 1. the first argument can be a callback and all other arguments and - keyword arguments are forwarded directly to the function. - 2. the first argument is a click command object. In that case all - arguments are forwarded as well but proper click parameters - (options and click arguments) must be keyword arguments and Click - will fill in defaults. - - .. versionchanged:: 8.0 - All ``kwargs`` are tracked in :attr:`params` so they will be - passed if :meth:`forward` is called at multiple levels. - - .. versionchanged:: 3.2 - A new context is created, and missing arguments use default values. - """ - if isinstance(callback, Command): - other_cmd = callback - - if other_cmd.callback is None: - raise TypeError( - "The given command does not have a callback that can be invoked." - ) - else: - callback = t.cast("t.Callable[..., V]", other_cmd.callback) - - ctx = self._make_sub_context(other_cmd) - - for param in other_cmd.params: - if param.name not in kwargs and param.expose_value: - default_value = param.get_default(ctx) - # We explicitly hide the :attr:`UNSET` value to the user, as we - # choose to make it an implementation detail. And because ``invoke`` - # has been designed as part of Click public API, we return ``None`` - # instead. Refs: - # https://github.com/pallets/click/issues/3066 - # https://github.com/pallets/click/issues/3065 - # https://github.com/pallets/click/pull/3068 - if default_value is UNSET: - default_value = None - kwargs[param.name] = param.type_cast_value(ctx, default_value) - - # 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) -> 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) -> ParameterSource | None: - """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 Command: - """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: If ``True`` or non-empty string, issues a message - indicating that the command is deprecated and highlights - its deprecation in --help. The message can be customized - by using a string as the value. - - .. versionchanged:: 8.2 - This is the base class for all commands, not ``BaseCommand``. - ``deprecated`` can be set to a string as well to customize the - deprecation message. - - .. 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. - """ - - #: The context class to create with :meth:`make_context`. - #: - #: .. versionadded:: 8.0 - context_class: 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: str | None, - context_settings: cabc.MutableMapping[str, t.Any] | None = None, - callback: t.Callable[..., t.Any] | None = None, - params: list[Parameter] | None = None, - help: str | None = None, - epilog: str | None = None, - short_help: str | None = None, - options_metavar: str | None = "[OPTIONS]", - add_help_option: bool = True, - no_args_is_help: bool = False, - hidden: bool = False, - deprecated: bool | str = False, - ) -> 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: cabc.MutableMapping[str, t.Any] = 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: 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._help_option = None - self.no_args_is_help = no_args_is_help - self.hidden = hidden - self.deprecated = deprecated - - def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: - return { - "name": self.name, - "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, - } - - def __repr__(self) -> str: - return f"<{self.__class__.__name__} {self.name}>" - - 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) -> list[Parameter]: - params = self.params - help_option = self.get_help_option(ctx) - - if help_option is not None: - params = [*params, help_option] - - if __debug__: - import warnings - - opts = [opt for param in params for opt in param.opts] - opts_counter = Counter(opts) - duplicate_opts = (opt for opt, count in opts_counter.items() if count > 1) - - for duplicate_opt in duplicate_opts: - warnings.warn( - ( - f"The parameter {duplicate_opt} is used more than once. " - "Remove its duplicate as parameters should be unique." - ), - stacklevel=3, - ) - - return params - - 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) -> 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) -> 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) -> Option | None: - """Returns the help option object. - - Skipped if :attr:`add_help_option` is ``False``. - - .. versionchanged:: 8.1.8 - The help option is now cached to avoid creating it multiple times. - """ - help_option_names = self.get_help_option_names(ctx) - - if not help_option_names or not self.add_help_option: - return None - - # Cache the help option object in private _help_option attribute to - # avoid creating it multiple times. Not doing this will break the - # callback ordering by iter_params_for_processing(), which relies on - # object comparison. - if self._help_option is None: - # Avoid circular import. - from .decorators import help_option - - # Apply help_option decorator and pop resulting option - help_option(*help_option_names)(self) - self._help_option = self.params.pop() # type: ignore[assignment] - - return self._help_option - - 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 = f"{_(text)} {_format_deprecated_label(self.deprecated)}" - - return text.strip() - - def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: - """Writes the help into the formatter if it exists. - - This is a low-level method called by :meth:`get_help`. - - This calls the following methods: - - - :meth:`format_usage` - - :meth:`format_help_text` - - :meth:`format_options` - - :meth:`format_epilog` - """ - self.format_usage(ctx, formatter) - self.format_help_text(ctx, formatter) - self.format_options(ctx, formatter) - self.format_epilog(ctx, formatter) - - def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: - """Writes the help text to the formatter if it exists.""" - if self.help is not None: - # truncate the help text to the first form feed - text = inspect.cleandoc(self.help).partition("\f")[0] - else: - text = "" - - if self.deprecated: - text = f"{_(text)} {_format_deprecated_label(self.deprecated)}" - - if text: - formatter.write_paragraph() - - with formatter.indentation(): - formatter.write_text(text) - - def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: - """Writes all the options into the formatter if they exist.""" - opts = [] - for param in self.get_params(ctx): - rv = param.get_help_record(ctx) - if rv is not None: - opts.append(rv) - - if opts: - with formatter.section(_("Options")): - formatter.write_dl(opts) - - def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: - """Writes the epilog into the formatter if it exists.""" - if self.epilog: - epilog = inspect.cleandoc(self.epilog) - formatter.write_paragraph() - - with formatter.indentation(): - formatter.write_text(epilog) - - def make_context( - self, - info_name: str | None, - args: list[str], - parent: Context | None = None, - **extra: t.Any, - ) -> Context: - """This function when given an info name and arguments will kick - off the parsing and create a new :class:`Context`. It does not - invoke the actual command callback though. - - To quickly customize the context class used without overriding - this method, set the :attr:`context_class` attribute. - - :param info_name: the info name for this invocation. Generally this - is the most descriptive name for the script or - command. For the toplevel script it's usually - the name of the script, for commands below it's - the name of the command. - :param args: the arguments to parse as list of strings. - :param parent: the parent context if available. - :param extra: extra keyword arguments forwarded to the context - constructor. - - .. versionchanged:: 8.0 - Added the :attr:`context_class` attribute. - """ - for key, value in self.context_settings.items(): - if key not in extra: - extra[key] = value - - ctx = self.context_class(self, info_name=info_name, parent=parent, **extra) - - with ctx.scope(cleanup=False): - self.parse_args(ctx, args) - return ctx - - def parse_args(self, ctx: Context, args: list[str]) -> list[str]: - if not args and self.no_args_is_help and not ctx.resilient_parsing: - raise NoArgsIsHelpError(ctx) - - 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)): - _, args = param.handle_parse_result(ctx, opts, args) - - # We now have all parameters' values into `ctx.params`, but the data may contain - # the `UNSET` sentinel. - # Convert `UNSET` to `None` to ensure that the user doesn't see `UNSET`. - # - # Waiting until after the initial parse to convert allows us to treat `UNSET` - # more like a missing value when multiple params use the same name. - # Refs: - # https://github.com/pallets/click/issues/3071 - # https://github.com/pallets/click/pull/3079 - for name, value in ctx.params.items(): - if value is UNSET: - ctx.params[name] = None - - 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.{extra_message}" - ).format( - name=self.name, - extra_message=_format_deprecated_suffix(self.deprecated), - ) - 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) -> list[CompletionItem]: - """Return a list of completions for the incomplete value. Looks - at the names of options and chained multi-commands. - - Any command could be part of a chained multi-command, so sibling - commands are valid at any point during command completion. - - :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: 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) - is ParameterSource.COMMANDLINE - ) - ): - continue - - results.extend( - CompletionItem(name, help=param.help) - for name in [*param.opts, *param.secondary_opts] - if name.startswith(incomplete) - ) - - while ctx.parent is not None: - ctx = ctx.parent - - if isinstance(ctx.command, Group) 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: cabc.Sequence[str] | None = None, - prog_name: str | None = None, - complete_var: str | None = None, - standalone_mode: t.Literal[True] = True, - **extra: t.Any, - ) -> t.NoReturn: ... - - @t.overload - def main( - self, - args: cabc.Sequence[str] | None = None, - prog_name: str | None = None, - complete_var: str | None = None, - standalone_mode: bool = ..., - **extra: t.Any, - ) -> t.Any: ... - - def main( - self, - args: cabc.Sequence[str] | None = None, - prog_name: str | None = None, - complete_var: str | None = None, - standalone_mode: bool = True, - windows_expand_args: bool = True, - **extra: t.Any, - ) -> t.Any: - """This is the way to invoke a script with all the bells and - whistles as a command line application. This will always terminate - the application after a call. If this is not wanted, ``SystemExit`` - needs to be caught. - - This method is also available by directly calling the instance of - a :class:`Command`. - - :param args: the arguments that should be used for parsing. If not - provided, ``sys.argv[1:]`` is used. - :param prog_name: the program name that should be used. By default - the program name is constructed by taking the file - name from ``sys.argv[0]``. - :param complete_var: the environment variable that controls the - bash completion support. The default is - ``"__COMPLETE"`` with prog_name in - uppercase. - :param standalone_mode: the default behavior is to invoke the script - in standalone mode. Click will then - handle exceptions and convert them into - error messages and the function will never - return but shut down the interpreter. If - this is set to `False` they will be - propagated to the caller and the return - value of this function is the return value - of :meth:`invoke`. - :param windows_expand_args: Expand glob patterns, user dir, and - env vars in command line args on Windows. - :param extra: extra keyword arguments are forwarded to the context - constructor. See :class:`Context` for more information. - - .. versionchanged:: 8.0.1 - Added the ``windows_expand_args`` parameter to allow - disabling command line arg expansion on Windows. - - .. versionchanged:: 8.0 - When taking arguments from ``sys.argv`` on Windows, glob - patterns, user dir, and env vars are expanded. - - .. versionchanged:: 3.0 - Added the ``standalone_mode`` parameter. - """ - if args is None: - args = sys.argv[1:] - - if os.name == "nt" and windows_expand_args: - args = _expand_args(args) - else: - args = list(args) - - if prog_name is None: - prog_name = _detect_program_name() - - # Process shell completion requests and exit early. - self._main_shell_completion(extra, prog_name, complete_var) - - try: - try: - with self.make_context(prog_name, args, **extra) as ctx: - rv = self.invoke(ctx) - if not standalone_mode: - return rv - # it's not safe to `ctx.exit(rv)` here! - # note that `rv` may actually contain data like "1" which - # has obvious effects - # more subtle case: `rv=[None, None]` can come out of - # chained commands which all returned `None` -- so it's not - # even always obvious that `rv` indicates success/failure - # by its truthiness/falsiness - ctx.exit() - except (EOFError, KeyboardInterrupt) as e: - echo(file=sys.stderr) - raise Abort() from e - except ClickException as e: - if not standalone_mode: - raise - e.show() - sys.exit(e.exit_code) - except OSError as e: - if e.errno == errno.EPIPE: - sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) - sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) - sys.exit(1) - else: - raise - except Exit as e: - if standalone_mode: - sys.exit(e.exit_code) - else: - # in non-standalone mode, return the exit code - # note that this is only reached if `self.invoke` above raises - # an Exit explicitly -- thus bypassing the check there which - # would return its result - # the results of non-standalone execution may therefore be - # somewhat ambiguous: if there are codepaths which lead to - # `ctx.exit(1)` and to `return 1`, the caller won't be able to - # tell the difference between the two - return e.exit_code - except Abort: - if not standalone_mode: - raise - echo(_("Aborted!"), file=sys.stderr) - sys.exit(1) - - def _main_shell_completion( - self, - ctx_args: cabc.MutableMapping[str, t.Any], - prog_name: str, - complete_var: str | None = None, - ) -> None: - """Check if the shell is asking for tab completion, process - that, then exit early. Called from :meth:`main` before the - program is invoked. - - :param prog_name: Name of the executable in the shell. - :param complete_var: Name of the environment variable that holds - the completion instruction. Defaults to - ``_{PROG_NAME}_COMPLETE``. - - .. versionchanged:: 8.2.0 - Dots (``.``) in ``prog_name`` are replaced with underscores (``_``). - """ - if complete_var is None: - complete_name = prog_name.replace("-", "_").replace(".", "_") - complete_var = f"_{complete_name}_COMPLETE".upper() - - instruction = os.environ.get(complete_var) - - if not instruction: - return - - from .shell_completion import shell_complete - - rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) - sys.exit(rv) - - def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: - """Alias for :meth:`main`.""" - return self.main(*args, **kwargs) - - -class _FakeSubclassCheck(type): - def __subclasscheck__(cls, subclass: type) -> bool: - return issubclass(subclass, cls.__bases__[0]) - - def __instancecheck__(cls, instance: t.Any) -> bool: - return isinstance(instance, cls.__bases__[0]) - - -class _BaseCommand(Command, metaclass=_FakeSubclassCheck): - """ - .. deprecated:: 8.2 - Will be removed in Click 9.0. Use ``Command`` instead. - """ - - -class Group(Command): - """A group is a command that nests other commands (or more groups). - - :param name: The name of the group command. - :param commands: Map names to :class:`Command` objects. Can be a list, which - will use :attr:`Command.name` as the keys. - :param invoke_without_command: Invoke the group's callback even if a - subcommand is not given. - :param no_args_is_help: If no arguments are given, show the group's help and - exit. Defaults to the opposite of ``invoke_without_command``. - :param subcommand_metavar: How to represent the subcommand argument in help. - The default will represent whether ``chain`` is set or not. - :param chain: Allow passing more than one subcommand argument. After parsing - a command's arguments, if any arguments remain another command will be - matched, and so on. - :param result_callback: A function to call after the group's and - subcommand's callbacks. The value returned by the subcommand is passed. - If ``chain`` is enabled, the value will be a list of values returned by - all the commands. If ``invoke_without_command`` is enabled, the value - will be the value returned by the group's callback, or an empty list if - ``chain`` is enabled. - :param kwargs: Other arguments passed to :class:`Command`. - - .. versionchanged:: 8.0 - The ``commands`` argument can be a list of command objects. - - .. versionchanged:: 8.2 - Merged with and replaces the ``MultiCommand`` base class. - """ - - allow_extra_args = True - allow_interspersed_args = False - - #: 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: type[Command] | None = 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: type[Group] | type[type] | None = None - # Literal[type] isn't valid, so use Type[type] - - def __init__( - self, - name: str | None = None, - commands: cabc.MutableMapping[str, Command] - | cabc.Sequence[Command] - | None = None, - invoke_without_command: bool = False, - no_args_is_help: bool | None = None, - subcommand_metavar: str | None = None, - chain: bool = False, - result_callback: t.Callable[..., t.Any] | None = None, - **kwargs: t.Any, - ) -> None: - super().__init__(name, **kwargs) - - 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: cabc.MutableMapping[str, Command] = commands - - 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( - "A group in chain mode cannot have optional arguments." - ) - - def to_info_dict(self, ctx: Context) -> 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 add_command(self, cmd: Command, name: str | None = 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_nested_chain(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.Callable[[t.Callable[..., t.Any]], Command] | Command: - """A shortcut decorator for declaring and attaching a command to - the group. This takes the same arguments as :func:`command` and - immediately registers the created command with this group by - calling :meth:`add_command`. - - To customize the command class used, set the - :attr:`command_class` attribute. - - .. versionchanged:: 8.1 - This decorator can be applied without parentheses. - - .. versionchanged:: 8.0 - Added the :attr:`command_class` attribute. - """ - from .decorators import command - - func: t.Callable[..., t.Any] | None = None - - if args and callable(args[0]): - assert len(args) == 1 and not kwargs, ( - "Use 'command(**kwargs)(callable)' to provide arguments." - ) - (func,) = args - args = () - - if self.command_class and kwargs.get("cls") is None: - kwargs["cls"] = self.command_class - - def decorator(f: t.Callable[..., t.Any]) -> Command: - cmd: Command = command(*args, **kwargs)(f) - self.add_command(cmd) - return cmd - - if func is not None: - return decorator(func) - - return decorator - - @t.overload - def group(self, __func: t.Callable[..., t.Any]) -> Group: ... - - @t.overload - def group( - self, *args: t.Any, **kwargs: t.Any - ) -> t.Callable[[t.Callable[..., t.Any]], Group]: ... - - def group( - self, *args: t.Any, **kwargs: t.Any - ) -> t.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.Callable[..., t.Any] | None = 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 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: t.Any, /, *args: t.Any, **kwargs: t.Any) -> t.Any: - inner = old_callback(value, *args, **kwargs) - return f(inner, *args, **kwargs) - - self._result_callback = rv = update_wrapper(t.cast(F, function), f) - return rv # type: ignore[return-value] - - return decorator - - def get_command(self, ctx: Context, cmd_name: str) -> Command | None: - """Given a context and a command name, this returns a :class:`Command` - object if it exists or returns ``None``. - """ - return self.commands.get(cmd_name) - - def list_commands(self, ctx: Context) -> list[str]: - """Returns a list of subcommand names in the order they should appear.""" - return sorted(self.commands) - - def collect_usage_pieces(self, ctx: Context) -> 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 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: list[str]) -> list[str]: - if not args and self.no_args_is_help and not ctx.resilient_parsing: - raise NoArgsIsHelpError(ctx) - - 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: list[str] - ) -> tuple[str | None, Command | None, list[str]]: - cmd_name = make_str(args[0]) - - # 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, args) - raise NoSuchCommand(cmd_name, possibilities=self.commands, ctx=ctx) - return cmd_name if cmd else None, cmd, args[1:] - - def shell_complete(self, ctx: Context, incomplete: str) -> 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 _MultiCommand(Group, metaclass=_FakeSubclassCheck): - """ - .. deprecated:: 8.2 - Will be removed in Click 9.0. Use ``Group`` instead. - """ - - -class CommandCollection(Group): - """A :class:`Group` that looks up subcommands on other groups. If a command - is not found on this group, each registered source is checked in order. - Parameters on a source are not added to this group, and a source's callback - is not invoked when invoking its commands. In other words, this "flattens" - commands in many groups into this one group. - - :param name: The name of the group command. - :param sources: A list of :class:`Group` objects to look up commands from. - :param kwargs: Other arguments passed to :class:`Group`. - - .. versionchanged:: 8.2 - This is a subclass of ``Group``. Commands are looked up first on this - group, then each of its sources. - """ - - def __init__( - self, - name: str | None = None, - sources: list[Group] | None = None, - **kwargs: t.Any, - ) -> None: - super().__init__(name, **kwargs) - #: The list of registered groups. - self.sources: list[Group] = sources or [] - - def add_source(self, group: Group) -> None: - """Add a group as a source of commands.""" - self.sources.append(group) - - def get_command(self, ctx: Context, cmd_name: str) -> Command | None: - rv = super().get_command(ctx, cmd_name) - - if rv is not None: - return rv - - for source in self.sources: - rv = source.get_command(ctx, cmd_name) - - if rv is not None: - if self.chain: - _check_nested_chain(self, cmd_name, rv) - - return rv - - return None - - def list_commands(self, ctx: Context) -> list[str]: - rv: set[str] = set(super().list_commands(ctx)) - - for source in self.sources: - rv.update(source.list_commands(ctx)) - - return sorted(rv) - - -def _check_iter(value: t.Any) -> cabc.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(ABC): - r"""A parameter to a command comes in two versions: they are either - :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently - not supported by design as some of the internals for parsing are - intentionally not finalized. - - Some settings are supported by both options and arguments. - - :param param_decls: the parameter declarations for this option or - argument. This is a list of flags or argument - names. - :param type: the type that should be used. Either a :class:`ParamType` - or a Python type. The latter is converted into the former - automatically if supported. - :param required: controls if this is optional or not. - :param default: the default value if omitted. This can also be a callable, - in which case it's invoked when the default is needed - without any arguments. - :param callback: A function to further process or validate the value - after type conversion. It is called as ``f(ctx, param, value)`` - and must return the value. It is called for all sources, - including prompts. - :param nargs: the number of arguments to match. If not ``1`` the return - value is a tuple instead of single value. The default for - nargs is ``1`` (except if the type is a tuple, then it's - the arity of the tuple). If ``nargs=-1``, all remaining - parameters are collected. - :param metavar: how the value is represented in the help page. - :param expose_value: if this is `True` then the value is passed onwards - to the command callback and stored on the context, - otherwise it's skipped. - :param is_eager: eager values are processed before non eager ones. This - should not be set for arguments or it will inverse the - order of processing. - :param envvar: environment variable(s) that are used to provide a default value for - this parameter. This can be a string or a sequence of strings. If a sequence is - given, only the first non-empty environment variable is used for the parameter. - :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. - :param deprecated: If ``True`` or non-empty string, issues a message - indicating that the argument is deprecated and highlights - its deprecation in --help. The message can be customized - by using a string as the value. A deprecated parameter - cannot be required, a ValueError will be raised otherwise. - - .. versionchanged:: 8.2.0 - Introduction of ``deprecated``. - - .. versionchanged:: 8.2 - Adding duplicate parameter names to a :class:`~click.core.Command` will - result in a ``UserWarning`` being shown. - - .. versionchanged:: 8.2 - Adding duplicate parameter names to a :class:`~click.core.Command` will - result in a ``UserWarning`` being shown. - - .. 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: cabc.Sequence[str] | None = None, - type: types.ParamType[t.Any] | t.Any | None = None, - required: bool = False, - # XXX The default historically embed two concepts: - # - the declaration of a Parameter object carrying the default (handy to - # arbitrage the default value of coupled Parameters sharing the same - # self.name, like flag options), - # - and the actual value of the default. - # It is confusing and is the source of many issues discussed in: - # https://github.com/pallets/click/pull/3030 - # In the future, we might think of splitting it in two, not unlike - # Option.is_flag and Option.flag_value: we could have something like - # Parameter.is_default and Parameter.default_value. - default: t.Any | t.Callable[[], t.Any] | None = UNSET, - callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None = None, - nargs: int | None = None, - multiple: bool = False, - metavar: str | None = None, - expose_value: bool = True, - is_eager: bool = False, - envvar: str | cabc.Sequence[str] | None = None, - shell_complete: t.Callable[ - [Context, Parameter, str], list[CompletionItem] | list[str] - ] - | None = None, - deprecated: bool | str = False, - ) -> None: - self.name: str - self.opts: list[str] - self.secondary_opts: list[str] - self.name, self.opts, self.secondary_opts = self._parse_decls( - param_decls or (), expose_value - ) - self.type: types.ParamType[t.Any] = 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: t.Any | t.Callable[[], t.Any] | None = default - # Whether the user passed ``default`` explicitly to the constructor. - # Captured before any auto-derived default (like ``False`` for boolean - # flags in :class:`Option`) replaces the :data:`UNSET` sentinel, so it - # remains ``False`` when the default was inferred rather than chosen. - # Refs: https://github.com/pallets/click/issues/3403 - self._default_explicit: bool = default is not UNSET - self.is_eager = is_eager - self.metavar = metavar - self.envvar = envvar - self._custom_shell_complete = shell_complete - self.deprecated = deprecated - - 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}." - ) - - if required and deprecated: - raise ValueError( - f"The {self.param_type_name} '{self.human_readable_name}' " - "is deprecated and still required. A deprecated " - f"{self.param_type_name} cannot be required." - ) - - def to_info_dict(self) -> 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. - - .. versionchanged:: 8.3.0 - Returns ``None`` for the :attr:`default` if it was not set. - - .. 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, - # We explicitly hide the :attr:`UNSET` value to the user, as we choose to - # make it an implementation detail. And because ``to_info_dict`` has been - # designed for documentation purposes, we return ``None`` instead. - "default": self.default if self.default is not UNSET else None, - "envvar": self.envvar, - } - - def __repr__(self) -> str: - return f"<{self.__class__.__name__} {self.name}>" - - @abstractmethod - def _parse_decls( - self, decls: cabc.Sequence[str], expose_value: bool - ) -> tuple[str, list[str], list[str]]: ... - - @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 - - def make_metavar(self, ctx: Context) -> str: - if self.metavar is not None: - return self.metavar - - metavar = self.type.get_metavar(param=self, ctx=ctx) - - 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: t.Literal[True] = True - ) -> t.Any | None: ... - - @t.overload - def get_default( - self, ctx: Context, call: bool = ... - ) -> t.Any | t.Callable[[], t.Any] | None: ... - - def get_default( - self, ctx: Context, call: bool = True - ) -> t.Any | t.Callable[[], t.Any] | None: - """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) - - if value is None and not ctx._default_map_has(self.name): - value = self.default - - if call and callable(value): - value = value() - - return value - - @abstractmethod - def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: ... - - def consume_value( - self, ctx: Context, opts: cabc.Mapping[str, t.Any] - ) -> tuple[t.Any, ParameterSource]: - """Returns the parameter value produced by the parser. - - If the parser did not produce a value from user input, the value is either - sourced from the environment variable, the default map, or the parameter's - default value. In that order of precedence. - - If no value is found, an internal sentinel value is returned. - - :meta private: - """ - # Collect from the parse the value passed by the user to the CLI. - value = opts.get(self.name, UNSET) - # If the value is set, it means it was sourced from the command line by the - # parser, otherwise it left unset by default. - source = ( - ParameterSource.COMMANDLINE - if value is not UNSET - else ParameterSource.DEFAULT - ) - - if value is UNSET: - envvar_value = self.value_from_envvar(ctx) - if envvar_value is not None: - value = envvar_value - source = ParameterSource.ENVIRONMENT - - if value is UNSET: - default_map_value = ctx.lookup_default(self.name) - if default_map_value is not None or ctx._default_map_has(self.name): - value = default_map_value - source = ParameterSource.DEFAULT_MAP - - # A string from default_map must be split for multi-value - # parameters, matching value_from_envvar behavior. - if isinstance(value, str) and self.nargs != 1: - value = self.type.split_envvar_value(value) - - if value is UNSET: - default_value = self.get_default(ctx) - if default_value is not UNSET: - value = default_value - 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 parameter's - :attr:`type`, :attr:`multiple`, and :attr:`nargs`. - """ - if value is None: - if self.multiple or self.nargs == -1: - return () - else: - return value - - def check_iter(value: t.Any) -> cabc.Iterator[t.Any]: - try: - return _check_iter(value) - except TypeError: - # This should only happen when passing in args manually, - # the parser should construct an iterable when parsing - # the command line. - raise BadParameter( - _("Value must be an iterable."), ctx=ctx, param=self - ) from None - - # Define the conversion function based on nargs and type. - - if self.nargs == 1 or self.type.is_composite: - - def convert(value: t.Any) -> t.Any: - return self.type(value, param=self, ctx=ctx) - - elif self.nargs == -1: - - def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] - return tuple(self.type(x, self, ctx) for x in check_iter(value)) - - else: # nargs > 1 - - def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] - value = tuple(check_iter(value)) - - if len(value) != self.nargs: - raise BadParameter( - ngettext( - "Takes {nargs} values but 1 was given.", - "Takes {nargs} values but {len} were given.", - len(value), - ).format(nargs=self.nargs, len=len(value)), - ctx=ctx, - param=self, - ) - - return tuple(self.type(x, self, ctx) for x in value) - - if self.multiple: - return tuple(convert(x) for x in check_iter(value)) - - return convert(value) - - def value_is_missing(self, value: t.Any) -> bool: - """A value is considered missing if: - - - it is :attr:`UNSET`, - - or if it is an empty sequence while the parameter is suppose to have - non-single value (i.e. :attr:`nargs` is not ``1`` or :attr:`multiple` is - set). - - :meta private: - """ - if value is UNSET: - 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: - """Process the value of this parameter: - - 1. Type cast the value using :meth:`type_cast_value`. - 2. Check if the value is missing (see: :meth:`value_is_missing`), and raise - :exc:`MissingParameter` if it is required. - 3. If a :attr:`callback` is set, call it to have the value replaced by the - result of the callback. If the value was not set, the callback receive - ``None``. This keep the legacy behavior as it was before the introduction of - the :attr:`UNSET` sentinel. - - :meta private: - """ - # shelter `type_cast_value` from ever seeing an `UNSET` value by handling the - # cases in which `UNSET` gets special treatment explicitly at this layer - # - # Refs: - # https://github.com/pallets/click/issues/3069 - if value is UNSET: - if self.multiple or self.nargs == -1: - value = () - else: - 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: - # Legacy case: UNSET is not exposed directly to the callback, but converted - # to None. - if value is UNSET: - value = None - - # Search for parameters with UNSET values in the context. - unset_keys = {k: None for k, v in ctx.params.items() if v is UNSET} - # No UNSET values, call the callback as usual. - if not unset_keys: - value = self.callback(ctx, self, value) - - # Legacy case: provide a temporarily manipulated context to the callback - # to hide UNSET values as None. - # - # Refs: - # https://github.com/pallets/click/issues/3136 - # https://github.com/pallets/click/pull/3137 - else: - # Add another layer to the context stack to clearly hint that the - # context is temporarily modified. - with ctx: - # Update the context parameters to replace UNSET with None. - ctx.params.update(unset_keys) - # Feed these fake context parameters to the callback. - value = self.callback(ctx, self, value) - # Restore the UNSET values in the context parameters. - ctx.params.update( - { - k: UNSET - for k in unset_keys - # Only restore keys that are present and still None, in case - # the callback modified other parameters. - if k in ctx.params and ctx.params[k] is None - } - ) - - return value - - def resolve_envvar_value(self, ctx: Context) -> str | None: - """Returns the value found in the environment variable(s) attached to this - parameter. - - Environment variables values are `always returned as strings - `_. - - This method returns ``None`` if: - - - the :attr:`envvar` property is not set on the :class:`Parameter`, - - the environment variable is not found in the environment, - - the variable is found in the environment but its value is empty (i.e. the - environment variable is present but has an empty string). - - If :attr:`envvar` is setup with multiple environment variables, - then only the first non-empty value is returned. - - .. caution:: - - The raw value extracted from the environment is not normalized and is - returned as-is. Any normalization or reconciliation is performed later by - the :class:`Parameter`'s :attr:`type`. - - :meta private: - """ - if not self.envvar: - 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) - - # Return the first non-empty value of the list of environment variables. - if rv: - return rv - # Else, absence of value is interpreted as an environment variable that - # is not set, so proceed to the next one. - - return None - - def value_from_envvar(self, ctx: Context) -> str | cabc.Sequence[str] | None: - """Process the raw environment variable string for this parameter. - - Returns the string as-is or splits it into a sequence of strings if the - parameter is expecting multiple values (i.e. its :attr:`nargs` property is set - to a value other than ``1``). - - :meta private: - """ - rv = self.resolve_envvar_value(ctx) - - if rv is not None and self.nargs != 1: - return self.type.split_envvar_value(rv) - - return rv - - def handle_parse_result( - self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str] - ) -> tuple[t.Any, list[str]]: - """Process the value produced by the parser from user input. - - Always process the value through the Parameter's :attr:`type`, wherever it - comes from. - - If the parameter is deprecated, this method warn the user about it. But only if - the value has been explicitly set by the user (and as such, is not coming from - a default). - - :meta private: - """ - # Capture the slot's existing state before we mutate - # ``_parameter_source`` so the write decision below can compare our - # incoming source against the source of the option that already wrote - # the slot (if any). - existing_value = ctx.params.get(self.name, UNSET) - existing_source = ctx.get_parameter_source(self.name) - existing_default_explicit = ctx._param_default_explicit.get(self.name, False) - - with augment_usage_errors(ctx, param=self): - value, source = self.consume_value(ctx, opts) - - # Record the source before processing so eager callbacks and type - # conversion can inspect it. Restored after arbitration if this - # option loses a feature-switch group. - ctx.set_parameter_source(self.name, source) - - # Display a deprecation warning if necessary. - if ( - self.deprecated - and value is not UNSET - and source < ParameterSource.DEFAULT_MAP - ): - message = _( - "DeprecationWarning: The {param_type} {name!r} is deprecated." - "{extra_message}" - ).format( - param_type=self.param_type_name, - name=self.human_readable_name, - extra_message=_format_deprecated_suffix(self.deprecated), - ) - echo(style(message, fg="red"), err=True) - - # Process the value through the parameter's type. - try: - value = self.process_value(ctx, value) - except Exception: - if not ctx.resilient_parsing: - raise - # In resilient parsing mode, we do not want to fail the command if the - # value is incompatible with the parameter type, so we reset the value - # to UNSET, which will be interpreted as a missing value. - value = UNSET - - # Arbitrate the slot when several parameters target the same variable - # name (feature-switch groups). See: https://github.com/pallets/click/issues/3403 - slot_empty = existing_value is UNSET - more_explicit = existing_source is not None and source < existing_source - same_source = existing_source is not None and source == existing_source - auto_would_downgrade_explicit = ( - same_source - and source == ParameterSource.DEFAULT - and existing_default_explicit - and not self._default_explicit - ) - is_winner = ( - slot_empty - or more_explicit - or (same_source and not auto_would_downgrade_explicit) - ) - - if is_winner: - if self.expose_value: - ctx.params[self.name] = value - ctx._param_default_explicit[self.name] = self._default_explicit - elif existing_source is not None: - # Lost arbitration; restore the winning option's source. - ctx.set_parameter_source(self.name, existing_source) - # else: ctx.params[self.name] was populated by code that bypassed - # handle_parse_result (from another option's callback for example). Keep - # the provisional source recorded before process_value so downstream - # lookups don't return ``None``. - - return value, args - - def get_help_record(self, ctx: Context) -> tuple[str, str] | None: - return None - - def get_usage_pieces(self, ctx: Context) -> list[str]: - return [] - - def get_error_hint(self, ctx: Context | None) -> str: - """Get a stringified version of the param for use in error messages to - indicate which param caused the error. - - .. versionchanged:: 8.4.0 - ``ctx`` can be ``None``. - """ - 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) -> 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[t.Any].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("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 and error messages. - 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. A deprecated option cannot be - prompted. - :param confirmation_prompt: Prompt a second time to confirm the - value if it was prompted for. Can be set to a string instead of - ``True`` to customize the message. - :param prompt_required: If set to ``False``, the user will be - prompted for input only when the option was specified as a flag - without a value. - :param hide_input: If this is ``True`` then the input on the prompt - will be hidden from the user. This is useful for password input. - :param is_flag: forces this option to act as a flag. The default is - auto detection. - :param flag_value: which value should be used for this flag if it's - enabled. This is set to a boolean automatically if - the option string contains a slash to mark two options. - :param multiple: if this is set to `True` then the argument is accepted - multiple times and recorded. This is similar to ``nargs`` - in how it works but supports arbitrary number of - arguments. - :param count: this flag makes an option increment an integer. - :param allow_from_autoenv: if this is enabled then the value of this - parameter will be pulled from an environment - variable in case a prefix is defined on the - context. - :param help: the help string. - :param hidden: hide this option from help outputs. - :param attrs: Other command arguments described in :class:`Parameter`. - - .. versionchanged:: 8.4.0 - Non-basic ``flag_value`` types (not ``str``, ``int``, ``float``, or - ``bool``) are passed through unchanged instead of being stringified. - Previously, ``type=click.UNPROCESSED`` was required to preserve them. - - .. versionchanged:: 8.2 - ``envvar`` used with ``flag_value`` will always use the ``flag_value``, - previously it would use the value of the environment variable. - - .. versionchanged:: 8.1 - Help text indentation is cleaned here instead of only in the - ``@option`` decorator. - - .. versionchanged:: 8.1 - The ``show_default`` parameter overrides - ``Context.show_default``. - - .. versionchanged:: 8.1 - 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, for basic Python - types (``str``, ``int``, ``float``, ``bool``). - """ - - param_type_name = "option" - - def __init__( - self, - param_decls: cabc.Sequence[str] | None = None, - show_default: bool | str | None = None, - prompt: bool | str = False, - confirmation_prompt: bool | str = False, - prompt_required: bool = True, - hide_input: bool = False, - is_flag: bool | None = None, - flag_value: t.Any = UNSET, - multiple: bool = False, - count: bool = False, - allow_from_autoenv: bool = True, - type: types.ParamType[t.Any] | t.Any | None = None, - help: str | None = None, - hidden: bool = False, - show_choices: bool = True, - show_envvar: bool = False, - deprecated: bool | str = False, - **attrs: t.Any, - ) -> None: - if help: - help = inspect.cleandoc(help) - - super().__init__( - param_decls, type=type, multiple=multiple, deprecated=deprecated, **attrs - ) - - if prompt is True: - if not self.name: - raise TypeError("'name' is required with 'prompt=True'.") - - prompt_text: str | None = self.name.replace("_", " ").capitalize() - elif prompt is False: - prompt_text = None - else: - prompt_text = prompt - - if deprecated: - label = _format_deprecated_label(deprecated) - help = f"{help} {label}" if help is not None else label - - self.prompt = prompt_text - self.confirmation_prompt = confirmation_prompt - self.prompt_required = prompt_required - self.hide_input = hide_input - self.hidden = hidden - - # The _flag_needs_value property tells the parser that this option is a flag - # that cannot be used standalone and needs a value. With this information, the - # parser can determine whether to consider the next user-provided argument in - # the CLI as a value for this flag or as a new option. - # If prompt is enabled but not required, then it opens the possibility for the - # option to gets its value from the user. - self._flag_needs_value = self.prompt is not None and not self.prompt_required - - # Auto-detect if this is a flag or not. - if is_flag is None: - # Implicitly a flag because flag_value was set. - if flag_value is not UNSET: - is_flag = True - # Not a flag, but when used as a flag it shows a prompt. - elif self._flag_needs_value: - is_flag = False - # Implicitly a flag because secondary options names were given. - elif self.secondary_opts: - is_flag = True - - # The option is explicitly not a flag, but to determine whether or not it needs - # value, we need to check if `flag_value` or `default` was set. Either one is - # sufficient. - # Ref: https://github.com/pallets/click/issues/3084 - elif is_flag is False and not self._flag_needs_value: - self._flag_needs_value = flag_value is not UNSET or self.default is UNSET - - if is_flag: - # Set missing default for flags if not explicitly required or prompted. - if self.default is UNSET and not self.required and not self.prompt: - if multiple: - self.default = () - - # Auto-detect the type of the flag based on the flag_value. - if type is None: - # A flag without a flag_value is a boolean flag. - if flag_value is UNSET: - self.type: types.ParamType[t.Any] = types.BoolParamType() - # If the flag value is a boolean, use BoolParamType. - elif isinstance(flag_value, bool): - self.type = types.BoolParamType() - # Otherwise, guess the type from the flag value. - else: - guessed = types.convert_type(None, flag_value) - if ( - isinstance(guessed, types.StringParamType) - and not isinstance(flag_value, str) - and flag_value is not None - ): - # The flag_value type couldn't be auto-detected - # (not str, int, float, or bool). Since flag_value - # is a programmer-provided Python object, not CLI - # input, pass it through unchanged instead of - # stringifying it. - self.type = types.UNPROCESSED - else: - self.type = guessed - - self.is_flag: bool = bool(is_flag) - self.is_bool_flag: bool = bool( - is_flag and isinstance(self.type, types.BoolParamType) - ) - self.flag_value: t.Any = flag_value - - # Set boolean flag default to False if unset and not required. - if self.is_bool_flag: - if self.default is UNSET and not self.required: - self.default = False - - # The alignment of default to the flag_value is resolved lazily in - # get_default() to prevent callable flag_values (like classes) from - # being instantiated. Refs: - # https://github.com/pallets/click/issues/3121 - # https://github.com/pallets/click/issues/3024#issuecomment-3146199461 - # https://github.com/pallets/click/pull/3030/commits/06847da - - # Set the default flag_value if it is not set. - if self.flag_value is UNSET: - if self.is_flag: - self.flag_value = True - else: - self.flag_value = None - - # Counting. - self.count = count - if count: - if type is None: - self.type = types.IntRange(min=0) - if self.default is UNSET: - 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 deprecated and prompt: - raise ValueError("`deprecated` options cannot use `prompt`.") - - if self.nargs == -1: - raise TypeError("nargs=-1 is not supported for options.") - - if not self.is_bool_flag and self.secondary_opts: - raise TypeError("Secondary flag is not valid for non-boolean flag.") - - if self.is_bool_flag and self.hide_input and self.prompt is not None: - raise TypeError( - "'prompt' with 'hide_input' is not valid for boolean flag." - ) - - if self.count: - if self.multiple: - raise TypeError("'count' is not valid with 'multiple'.") - - if self.is_flag: - raise TypeError("'count' is not valid with 'is_flag'.") - - def to_info_dict(self) -> dict[str, t.Any]: - """ - .. versionchanged:: 8.3.0 - Returns ``None`` for the :attr:`flag_value` if it was not set. - """ - info_dict = super().to_info_dict() - info_dict.update( - help=self.help, - prompt=self.prompt, - is_flag=self.is_flag, - # We explicitly hide the :attr:`UNSET` value to the user, as we choose to - # make it an implementation detail. And because ``to_info_dict`` has been - # designed for documentation purposes, we return ``None`` instead. - flag_value=self.flag_value if self.flag_value is not UNSET else None, - count=self.count, - hidden=self.hidden, - ) - return info_dict - - def get_default( - self, ctx: Context, call: bool = True - ) -> t.Any | t.Callable[[], t.Any] | None: - """Return the default value for this option. - - For non-boolean flag options, ``default=True`` is treated as a sentinel - meaning "activate this flag by default" and is resolved to - :attr:`flag_value`. For example, with ``--upper/--lower`` feature - switches where ``flag_value="upper"`` and ``default=True``, the default - resolves to ``"upper"``. - - .. caution:: - This substitution only applies to non-boolean flags - (:attr:`is_bool_flag` is ``False``). For boolean flags, ``True`` is - a legitimate Python value and ``default=True`` is returned as-is. - - .. versionchanged:: 8.3.3 - ``default=True`` is no longer substituted with ``flag_value`` for - boolean flags, fixing negative boolean flags like - ``flag_value=False, default=True``. - """ - value = super().get_default(ctx, call=False) - - # Resolve default=True to flag_value lazily (here instead of - # __init__) to prevent callable flag_values (like classes) from - # being instantiated by the callable check below. - if value is True and self.is_flag and not self.is_bool_flag: - value = self.flag_value - elif call and callable(value): - value = value() - - return value - - def get_error_hint(self, ctx: Context | None) -> str: - result = super().get_error_hint(ctx) - if self.show_envvar and self.envvar is not None: - result += f" (env var: '{self.envvar}')" - return result - - def _parse_decls( - self, decls: cabc.Sequence[str], expose_value: bool - ) -> tuple[str, list[str], list[str]]: - opts = [] - secondary_opts = [] - name = None - possible_names = [] - - for decl in decls: - if decl.isidentifier(): - if name is not None: - raise TypeError(_("Name '{name}' defined twice").format(name=name)) - 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( - _( - "Boolean option {decl!r} cannot use the" - " same flag for true/false." - ).format(decl=decl) - ) - 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 "", opts, secondary_opts - raise TypeError( - _( - "Could not determine name for option with declarations {decls!r}" - ).format(decls=decls) - ) - - if not opts and not secondary_opts: - raise TypeError( - _( - "No options defined but a name was passed ({name})." - " Did you mean to declare an argument instead? Did" - " you mean to pass '--{name}'?" - ).format(name=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) -> tuple[str, str] | None: - if self.hidden: - return None - - any_prefix_is_slash = False - - def _write_opts(opts: cabc.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(ctx=ctx)}" - - return rv - - rv = [_write_opts(self.opts)] - - if self.secondary_opts: - rv.append(_write_opts(self.secondary_opts)) - - help = self.help or "" - - extra = self.get_help_extra(ctx) - extra_items = [] - if "envvars" in extra: - extra_items.append( - _("env var: {var}").format(var=", ".join(extra["envvars"])) - ) - if "default" in extra: - extra_items.append(_("default: {default}").format(default=extra["default"])) - if "range" in extra: - extra_items.append(extra["range"]) - if "required" in extra: - extra_items.append(_(extra["required"])) - - if extra_items: - extra_str = "; ".join(extra_items) - help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" - - return ("; " if any_prefix_is_slash else " / ").join(rv), help - - def get_help_extra(self, ctx: Context) -> types.OptionHelpExtra: - extra: types.OptionHelpExtra = {} - - 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 - ): - envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" - - if envvar is not None: - if isinstance(envvar, str): - extra["envvars"] = (envvar,) - else: - extra["envvars"] = tuple(str(d) for d in envvar) - - # 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 not in (None, UNSET)) - ): - 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 isinstance(default_value, enum.Enum): - default_string = default_value.name - 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 default_value else self.secondary_opts)[0] - )[1] - elif self.is_bool_flag and not self.secondary_opts and not default_value: - default_string = "" - elif isinstance(default_value, str) and default_value == "": - default_string = '""' - else: - default_string = str(default_value) - - if default_string: - extra["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["range"] = range_str - - if self.required: - extra["required"] = "required" - - return extra - - 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 lock in the value before - # attempting any user interaction. - default = self.get_default(ctx) - - # A boolean flag can use a simplified [y/n] confirmation prompt. - if self.is_bool_flag: - # If we have no boolean default, we force the user to explicitly provide - # one. - if default in (UNSET, None): - default = None - # Nothing prevent you to declare an option that is simultaneously: - # 1) auto-detected as a boolean flag, - # 2) allowed to prompt, and - # 3) still declare a non-boolean default. - # This forced casting into a boolean is necessary to align any non-boolean - # default to the prompt, which is going to be a [y/n]-style confirmation - # because the option is still a boolean flag. That way, instead of [y/n], - # we get [Y/n] or [y/N] depending on the truthy value of the default. - # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249 - else: - default = bool(default) - return confirm(self.prompt, default) - - # If show_default is given, provide this to `prompt` as well, - # otherwise we use `prompt`'s default behavior - prompt_kwargs: t.Any = {} - if self.show_default is not None: - prompt_kwargs["show_default"] = self.show_default - - return prompt( - self.prompt, - # Use ``None`` to inform the prompt() function to reiterate until a valid - # value is provided by the user if we have no default. - default=None if default is UNSET else 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), - **prompt_kwargs, - ) - - def resolve_envvar_value(self, ctx: Context) -> str | None: - """:class:`Option` resolves its environment variable the same way as - :func:`Parameter.resolve_envvar_value`, but it also supports - :attr:`Context.auto_envvar_prefix`. If we could not find an environment from - the :attr:`envvar` property, we fallback on :attr:`Context.auto_envvar_prefix` - to build dynamiccaly the environment variable name using the - :python:`{ctx.auto_envvar_prefix}_{self.name.upper()}` template. - - :meta private: - """ - 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: - 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.Any: - """For :class:`Option`, this method processes the raw environment variable - string the same way as :func:`Parameter.value_from_envvar` does. - - But in the case of non-boolean flags, the value is analyzed to determine if the - flag is activated or not, and returns a boolean of its activation, or the - :attr:`flag_value` if the latter is set. - - This method also takes care of repeated options (i.e. options with - :attr:`multiple` set to ``True``). - - :meta private: - """ - rv = self.resolve_envvar_value(ctx) - - # Absent environment variable or an empty string is interpreted as unset. - if rv is None: - return None - - # Non-boolean flags are more liberal in what they accept. But a flag being a - # flag, its envvar value still needs to be analyzed to determine if the flag is - # activated or not. - if self.is_flag and not self.is_bool_flag: - # If the flag_value is set and match the envvar value, return it - # directly. - if self.flag_value is not UNSET and rv == self.flag_value: - return self.flag_value - # Analyze the envvar value as a boolean to know if the flag is - # activated or not. - return types.BoolParamType.str_to_bool(rv) - - # Split the envvar value if it is allowed to be repeated. - value_depth = (self.nargs != 1) + bool(self.multiple) - if value_depth > 0: - multi_rv = self.type.split_envvar_value(rv) - if self.multiple and self.nargs != 1: - multi_rv = batch(multi_rv, self.nargs) # type: ignore[assignment] - - return multi_rv - - return rv - - def consume_value( - self, ctx: Context, opts: cabc.Mapping[str, Parameter] - ) -> tuple[t.Any, ParameterSource]: - """For :class:`Option`, the value can be collected from an interactive prompt - if the option is a flag that needs a value (and the :attr:`prompt` property is - set). - - Additionally, this method handles flag option that are activated without a - value, in which case the :attr:`flag_value` is returned. - - :meta private: - """ - value, source = super().consume_value(ctx, opts) - - # The parser will emit a sentinel value if the option is allowed to as a flag - # without a value. - if value is FLAG_NEEDS_VALUE: - # If the option allows for a prompt, we start an interaction with the user. - if self.prompt is not None and not ctx.resilient_parsing: - value = self.prompt_for_value(ctx) - source = ParameterSource.PROMPT - # Else the flag takes its flag_value as value. - else: - value = self.flag_value - source = ParameterSource.COMMANDLINE - - # A flag which is activated always returns the flag value, unless the value - # comes from the explicitly sets default. - elif ( - self.is_flag - and value is True - and not self.is_bool_flag - and source < ParameterSource.DEFAULT_MAP - ): - value = self.flag_value - - # Re-interpret a multiple option which has been sent as-is by the parser. - # Here we replace each occurrence of value-less flags (marked by the - # FLAG_NEEDS_VALUE sentinel) with the flag_value. - elif ( - self.multiple - and value is not UNSET - and isinstance(value, cabc.Iterable) - and source < ParameterSource.DEFAULT_MAP - 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 for one to the user - # if prompting is enabled. - elif ( - (value is UNSET or source >= ParameterSource.DEFAULT_MAP) - 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 - - def process_value(self, ctx: Context, value: t.Any) -> t.Any: - # process_value has to be overridden on Options in order to capture - # `value == UNSET` cases before `type_cast_value()` gets called. - # - # Refs: - # https://github.com/pallets/click/issues/3069 - if self.is_flag and not self.required and self.is_bool_flag and value is UNSET: - value = False - - if self.callback is not None: - value = self.callback(ctx, self, value) - - return value - - # in the normal case, rely on Parameter.process_value - return super().process_value(ctx, value) - - -class Argument(Parameter): - """Arguments are positional parameters to a command. They generally - provide fewer features than options but can have infinite ``nargs`` - and are required by default. - - All parameters are passed onwards to the constructor of :class:`Parameter`. - """ - - param_type_name = "argument" - - def __init__( - self, - param_decls: cabc.Sequence[str], - required: bool | None = None, - **attrs: t.Any, - ) -> None: - # Auto-detect the requirement status of the argument if not explicitly set. - if required is None: - # The argument gets automatically required if it has no explicit default - # value set and is setup to match at least one value. - if attrs.get("default", UNSET) is UNSET: - required = attrs.get("nargs", 1) > 0 - # If the argument has a default value, it is not required. - else: - required = False - - if "multiple" in attrs: - raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") - - super().__init__(param_decls, required=required, **attrs) - - @property - def human_readable_name(self) -> str: - if self.metavar is not None: - return self.metavar - return self.name.upper() - - def make_metavar(self, ctx: Context) -> str: - if self.metavar is not None: - return self.metavar - var = self.type.get_metavar(param=self, ctx=ctx) - if not var: - var = self.name.upper() - if self.deprecated: - var += "!" - if not self.required: - var = f"[{var}]" - if self.nargs != 1: - var += "..." - return var - - def _parse_decls( - self, decls: cabc.Sequence[str], expose_value: bool - ) -> tuple[str, list[str], list[str]]: - if not decls: - if not expose_value: - return "", [], [] - raise TypeError("Argument is marked as exposed, but does not have a name.") - if len(decls) == 1: - name = arg = decls[0] - name = name.replace("-", "_").lower() - else: - raise TypeError( - _( - "Arguments take exactly one parameter declaration, got" - " {length}: {decls}." - ).format(length=len(decls), decls=decls) - ) - return name, [arg], [] - - def get_usage_pieces(self, ctx: Context) -> list[str]: - return [self.make_metavar(ctx)] - - def get_error_hint(self, ctx: Context | None) -> str: - if ctx is not None: - return f"'{self.make_metavar(ctx)}'" - return f"'{self.human_readable_name}'" - - def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: - parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) - - -def __getattr__(name: str) -> object: - import warnings - - if name == "BaseCommand": - warnings.warn( - "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" - " 'Command' instead.", - DeprecationWarning, - stacklevel=2, - ) - return _BaseCommand - - if name == "MultiCommand": - warnings.warn( - "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" - " 'Group' instead.", - DeprecationWarning, - stacklevel=2, - ) - return _MultiCommand - - raise AttributeError(name) diff --git a/server/venv/Lib/site-packages/click/decorators.py b/server/venv/Lib/site-packages/click/decorators.py deleted file mode 100644 index 14aee42..0000000 --- a/server/venv/Lib/site-packages/click/decorators.py +++ /dev/null @@ -1,551 +0,0 @@ -from __future__ import annotations - -import inspect -import typing as t -from functools import update_wrapper -from gettext import gettext as _ - -from .core import Argument -from .core import Command -from .core import Context -from .core import Group -from .core import Option -from .core import Parameter -from .globals import get_current_context -from .utils import echo - -if t.TYPE_CHECKING: - import typing_extensions as te - - P = te.ParamSpec("P") - -R = t.TypeVar("R") -T = t.TypeVar("T") -_AnyCallable = t.Callable[..., t.Any] -FC = t.TypeVar("FC", bound="_AnyCallable | Command") - - -def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]: - """Marks a callback as wanting to receive the current context - object as first argument. - """ - - def new_func(*args: P.args, **kwargs: P.kwargs) -> R: - return f(get_current_context(), *args, **kwargs) - - return update_wrapper(new_func, f) - - -def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: - """Similar to :func:`pass_context`, but only pass the object on the - context onwards (:attr:`Context.obj`). This is useful if that object - represents the state of a nested system. - """ - - def new_func(*args: P.args, **kwargs: P.kwargs) -> R: - return f(get_current_context().obj, *args, **kwargs) - - return update_wrapper(new_func, f) - - -def make_pass_decorator( - object_type: type[T], ensure: bool = False -) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: - """Given an object type this creates a decorator that will work - similar to :func:`pass_obj` but instead of passing the object of the - current context, it will find the innermost context of type - :func:`object_type`. - - This generates a decorator that works roughly like this:: - - from functools import update_wrapper - - def decorator(f): - @pass_context - def new_func(ctx, *args, **kwargs): - obj = ctx.find_object(object_type) - return ctx.invoke(f, obj, *args, **kwargs) - return update_wrapper(new_func, f) - return decorator - - :param object_type: the type of the object to pass. - :param ensure: if set to `True`, a new object will be created and - remembered on the context if it's not there yet. - """ - - def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: - def new_func(*args: P.args, **kwargs: P.kwargs) -> R: - ctx = get_current_context() - - obj: T | None - if ensure: - obj = ctx.ensure_object(object_type) - else: - obj = ctx.find_object(object_type) - - if obj is None: - raise RuntimeError( - "Managed to invoke callback without a context" - f" object of type {object_type.__name__!r}" - " existing." - ) - - return ctx.invoke(f, obj, *args, **kwargs) - - return update_wrapper(new_func, f) - - return decorator - - -def pass_meta_key( - key: str, *, doc_description: str | None = None -) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: - """Create a decorator that passes a key from - :attr:`click.Context.meta` as the first argument to the decorated - function. - - :param key: Key in ``Context.meta`` to pass. - :param doc_description: Description of the object being passed, - inserted into the decorator's docstring. Defaults to "the 'key' - key from Context.meta". - - .. versionadded:: 8.0 - """ - - def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: - def new_func(*args: P.args, **kwargs: P.kwargs) -> R: - ctx = get_current_context() - obj = ctx.meta[key] - return ctx.invoke(f, obj, *args, **kwargs) - - return update_wrapper(new_func, f) - - if doc_description is None: - doc_description = f"the {key!r} key from :attr:`click.Context.meta`" - - decorator.__doc__ = ( - f"Decorator that passes {doc_description} as the first argument" - " to the decorated function." - ) - return decorator - - -CmdType = t.TypeVar("CmdType", bound=Command) - - -# variant: no call, directly as decorator for a function. -@t.overload -def command(name: _AnyCallable) -> Command: ... - - -# variant: with positional name and with positional or keyword cls argument: -# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) -@t.overload -def command( - name: str | None, - cls: type[CmdType], - **attrs: t.Any, -) -> t.Callable[[_AnyCallable], CmdType]: ... - - -# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) -@t.overload -def command( - name: None = None, - *, - cls: type[CmdType], - **attrs: t.Any, -) -> t.Callable[[_AnyCallable], CmdType]: ... - - -# variant: with optional string name, no cls argument provided. -@t.overload -def command( - name: str | None = ..., cls: None = None, **attrs: t.Any -) -> t.Callable[[_AnyCallable], Command]: ... - - -def command( - name: str | _AnyCallable | None = None, - cls: type[CmdType] | None = None, - **attrs: t.Any, -) -> Command | t.Callable[[_AnyCallable], Command | CmdType]: - r"""Creates a new :class:`Command` and uses the decorated function as - callback. This will also automatically attach all decorated - :func:`option`\s and :func:`argument`\s as parameters to the command. - - The name of the command defaults to the name of the function, converted to - lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes - ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example, - ``init_data_command`` becomes ``init-data``. - - 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. Defaults to modifying the function's - name as described above. - :param cls: The command class to create. Defaults to :class:`Command`. - - .. versionchanged:: 8.2 - The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are - removed when generating the name. - - .. 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.Callable[[_AnyCallable], t.Any] | None = None - - if callable(name): - func = name - name = None - assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." - assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." - - if cls is None: - cls = t.cast("type[CmdType]", Command) - - def decorator(f: _AnyCallable) -> CmdType: - if isinstance(f, Command): - raise TypeError("Attempted to convert a callback into a command twice.") - - attr_params = attrs.pop("params", None) - params = attr_params if attr_params is not None else [] - - try: - decorator_params = f.__click_params__ # type: ignore - except AttributeError: - pass - else: - del f.__click_params__ # type: ignore - params.extend(reversed(decorator_params)) - - if attrs.get("help") is None: - attrs["help"] = f.__doc__ - - if t.TYPE_CHECKING: - assert cls is not None - assert not callable(name) - - if name is not None: - cmd_name = name - else: - cmd_name = f.__name__.lower().replace("_", "-") - cmd_left, sep, suffix = cmd_name.rpartition("-") - - if sep and suffix in {"command", "cmd", "group", "grp"}: - cmd_name = cmd_left - - cmd = cls(name=cmd_name, callback=f, params=params, **attrs) - cmd.__doc__ = f.__doc__ - return cmd - - if func is not None: - return decorator(func) - - return decorator - - -GrpType = t.TypeVar("GrpType", bound=Group) - - -# variant: no call, directly as decorator for a function. -@t.overload -def group(name: _AnyCallable) -> Group: ... - - -# variant: with positional name and with positional or keyword cls argument: -# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) -@t.overload -def group( - name: str | None, - cls: type[GrpType], - **attrs: t.Any, -) -> t.Callable[[_AnyCallable], GrpType]: ... - - -# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) -@t.overload -def group( - name: None = None, - *, - cls: type[GrpType], - **attrs: t.Any, -) -> t.Callable[[_AnyCallable], GrpType]: ... - - -# variant: with optional string name, no cls argument provided. -@t.overload -def group( - name: str | None = ..., cls: None = None, **attrs: t.Any -) -> t.Callable[[_AnyCallable], Group]: ... - - -def group( - name: str | _AnyCallable | None = None, - cls: type[GrpType] | None = None, - **attrs: t.Any, -) -> Group | t.Callable[[_AnyCallable], Group | GrpType]: - """Creates a new :class:`Group` with a function as callback. This - works otherwise the same as :func:`command` just that the `cls` - parameter is set to :class:`Group`. - - .. versionchanged:: 8.1 - This decorator can be applied without parentheses. - """ - if cls is None: - cls = t.cast("type[GrpType]", Group) - - if callable(name): - return command(cls=cls, **attrs)(name) - - return command(name, cls, **attrs) - - -def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None: - if isinstance(f, Command): - f.params.append(param) - else: - if not hasattr(f, "__click_params__"): - f.__click_params__ = [] # type: ignore - - f.__click_params__.append(param) # type: ignore - - -def argument( - *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any -) -> t.Callable[[FC], FC]: - """Attaches an argument to the command. All positional arguments are - passed as parameter declarations to :class:`Argument`; all keyword - arguments are forwarded unchanged (except ``cls``). - This is equivalent to creating an :class:`Argument` instance manually - and attaching it to the :attr:`Command.params` list. - - For the default argument class, refer to :class:`Argument` and - :class:`Parameter` for descriptions of parameters. - - :param cls: the argument class to instantiate. This defaults to - :class:`Argument`. - :param param_decls: Passed as positional arguments to the constructor of - ``cls``. - :param attrs: Passed as keyword arguments to the constructor of ``cls``. - """ - if cls is None: - cls = Argument - - def decorator(f: FC) -> FC: - _param_memo(f, cls(param_decls, **attrs)) - return f - - return decorator - - -def option( - *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any -) -> t.Callable[[FC], FC]: - """Attaches an option to the command. All positional arguments are - passed as parameter declarations to :class:`Option`; all keyword - arguments are forwarded unchanged (except ``cls``). - This is equivalent to creating an :class:`Option` instance manually - and attaching it to the :attr:`Command.params` list. - - For the default option class, refer to :class:`Option` and - :class:`Parameter` for descriptions of parameters. - - :param cls: the option class to instantiate. This defaults to - :class:`Option`. - :param param_decls: Passed as positional arguments to the constructor of - ``cls``. - :param attrs: Passed as keyword arguments to the constructor of ``cls``. - """ - if cls is None: - cls = Option - - def decorator(f: FC) -> FC: - _param_memo(f, cls(param_decls, **attrs)) - return f - - return decorator - - -def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: - """Add a ``--yes`` option which shows a prompt before continuing if - not passed. If the prompt is declined, the program will exit. - - :param param_decls: One or more option names. Defaults to the single - value ``"--yes"``. - :param kwargs: Extra arguments are passed to :func:`option`. - """ - - def callback(ctx: Context, param: Parameter, value: bool) -> None: - if not value: - ctx.abort() - - if not param_decls: - param_decls = ("--yes",) - - kwargs.setdefault("is_flag", True) - kwargs.setdefault("callback", callback) - kwargs.setdefault("expose_value", False) - kwargs.setdefault("prompt", _("Do you want to continue?")) - kwargs.setdefault("help", _("Confirm the action without prompting.")) - return option(*param_decls, **kwargs) - - -def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: - """Add a ``--password`` option which prompts for a password, hiding - input and asking to enter the value again for confirmation. - - :param param_decls: One or more option names. Defaults to the single - value ``"--password"``. - :param kwargs: Extra arguments are passed to :func:`option`. - """ - if not param_decls: - param_decls = ("--password",) - - kwargs.setdefault("prompt", True) - kwargs.setdefault("confirmation_prompt", True) - kwargs.setdefault("hide_input", True) - return option(*param_decls, **kwargs) - - -def version_option( - version: str | None = None, - *param_decls: str, - package_name: str | None = None, - prog_name: str | None = None, - message: str | None = 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``. - - 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: - import importlib.metadata - - try: - version = importlib.metadata.version(package_name) - except importlib.metadata.PackageNotFoundError: - raise RuntimeError( - f"{package_name!r} is not installed. Try passing" - " 'package_name' instead." - ) from None - - if version is None: - raise RuntimeError( - f"Could not determine the version for {package_name!r} automatically." - ) - - echo( - message % {"prog": prog_name, "package": package_name, "version": version}, - color=ctx.color, - ) - ctx.exit() - - if not param_decls: - param_decls = ("--version",) - - kwargs.setdefault("is_flag", True) - kwargs.setdefault("expose_value", False) - kwargs.setdefault("is_eager", True) - kwargs.setdefault("help", _("Show the version and exit.")) - kwargs["callback"] = callback - return option(*param_decls, **kwargs) - - -def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: - """Pre-configured ``--help`` option which immediately prints the help page - and exits the program. - - :param param_decls: One or more option names. Defaults to the single - value ``"--help"``. - :param kwargs: Extra arguments are passed to :func:`option`. - """ - - def show_help(ctx: Context, param: Parameter, value: bool) -> None: - """Callback that print the help page on ```` and exits.""" - if value and not ctx.resilient_parsing: - 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.setdefault("callback", show_help) - - return option(*param_decls, **kwargs) diff --git a/server/venv/Lib/site-packages/click/exceptions.py b/server/venv/Lib/site-packages/click/exceptions.py deleted file mode 100644 index 68c2806..0000000 --- a/server/venv/Lib/site-packages/click/exceptions.py +++ /dev/null @@ -1,347 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import typing as t -from gettext import gettext as _ -from gettext import ngettext - -from ._compat import get_text_stderr -from .globals import resolve_color_default -from .utils import echo -from .utils import format_filename - -if t.TYPE_CHECKING: - from .core import Command - from .core import Context - from .core import Parameter - - -def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None: - if param_hint is not None and not isinstance(param_hint, str): - return " / ".join(repr(x) for x in param_hint) - - return param_hint - - -def _format_possibilities(possibilities: list[str]) -> str: - possibility_str = ", ".join(repr(p) for p in sorted(possibilities)) - return ngettext( - "Did you mean {possibility}?", - "(Did you mean one of: {possibilities}?)", - len(possibilities), - ).format(possibility=possibility_str, possibilities=possibility_str) - - -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) - # The context will be removed by the time we print the message, so cache - # the color settings here to be used later on (in `show`) - self.show_color: bool | None = resolve_color_default() - self.message = message - - def format_message(self) -> str: - return self.message - - def __str__(self) -> str: - return self.message - - def show(self, file: t.IO[t.Any] | None = None) -> None: - if file is None: - file = get_text_stderr() - - echo( - _("Error: {message}").format(message=self.format_message()), - file=file, - color=self.show_color, - ) - - -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: Context | None = None) -> None: - super().__init__(message) - self.ctx = ctx - self.cmd: Command | None = self.ctx.command if self.ctx else None - - def show(self, file: t.IO[t.Any] | None = 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 - ): - help_names = self.ctx.command.get_help_option_names(self.ctx) - # Pick the longest name (like ``--help`` over ``-h``) for - # readability in error messages. - hint = _("Try '{command} {option}' for help.").format( - command=self.ctx.command_path, - option=max(help_names, key=len), - ) - 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: Context | None = None, - param: Parameter | None = None, - param_hint: cabc.Sequence[str] | str | None = 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) - 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: str | None = None, - ctx: Context | None = None, - param: Parameter | None = None, - param_hint: cabc.Sequence[str] | str | None = None, - param_type: str | None = 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: cabc.Sequence[str] | str | None = self.param_hint - elif self.param is not None: - param_hint = self.param.get_error_hint(self.ctx) - 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( - param=self.param, ctx=self.ctx - ) - 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: str | None = None, - possibilities: cabc.Iterable[str] | None = None, - ctx: Context | None = None, - ) -> None: - if message is None: - message = _("No such option {name!r}.").format(name=option_name) - - super().__init__(message, ctx) - self.option_name = option_name - self.possibilities: list[str] | None = None - if possibilities: - from difflib import get_close_matches - - self.possibilities = get_close_matches(option_name, possibilities) - - def format_message(self) -> str: - if not self.possibilities: - return self.message - return f"{self.message} {_format_possibilities(self.possibilities)}" - - -class NoSuchCommand(UsageError): - """Raised if Click attempted to handle a command that does not exist. - - .. versionadded:: 8.4.0 - """ - - def __init__( - self, - command_name: str, - message: str | None = None, - possibilities: cabc.Iterable[str] | None = None, - ctx: Context | None = None, - ) -> None: - if message is None: - message = _("No such command {name!r}.").format(name=command_name) - - super().__init__(message, ctx) - self.command_name = command_name - self.possibilities: list[str] | None = None - if possibilities: - from difflib import get_close_matches - - self.possibilities = get_close_matches(command_name, possibilities) - - def format_message(self) -> str: - if not self.possibilities: - return self.message - return f"{self.message} {_format_possibilities(self.possibilities)}" - - -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: Context | None = 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 NoArgsIsHelpError(UsageError): - def __init__(self, ctx: Context) -> None: - self.ctx: Context - super().__init__(ctx.get_help(), ctx=ctx) - - def show(self, file: t.IO[t.Any] | None = None) -> None: - echo(self.format_message(), file=file, err=True, color=self.ctx.color) - - -class FileError(ClickException): - """Raised if a file cannot be opened.""" - - def __init__(self, filename: str, hint: str | None = None) -> None: - if hint is None: - hint = _("unknown error") - - super().__init__(hint) - self.ui_filename: str = format_filename(filename) - self.filename = filename - - def format_message(self) -> str: - return _("Could not open file {filename!r}: {message}").format( - filename=self.ui_filename, message=self.message - ) - - -class Abort(RuntimeError): - """An internal signalling exception that signals Click to abort.""" - - -class Exit(RuntimeError): - """An exception that indicates that the application should exit with some - status code. - - :param code: the status code to exit with. - """ - - __slots__ = ("exit_code",) - - def __init__(self, code: int = 0) -> None: - self.exit_code: int = code diff --git a/server/venv/Lib/site-packages/click/formatting.py b/server/venv/Lib/site-packages/click/formatting.py deleted file mode 100644 index 886a4ad..0000000 --- a/server/venv/Lib/site-packages/click/formatting.py +++ /dev/null @@ -1,315 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -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: int | None = None - - -def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]: - widths: 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: cabc.Iterable[tuple[str, str]], col_count: int -) -> cabc.Iterator[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. - - .. versionchanged:: 8.4.0 - Width is measured in visible characters. ANSI escape sequences in - ``text``, ``initial_indent``, or ``subsequent_indent`` no longer - count toward the width budget, so styled input wraps based on what - the user sees instead of raw byte length. - """ - 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: list[tuple[int, bool, str]] = [] - buf: 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: int | None = None, - max_width: int | None = None, - ) -> None: - self.indent_increment = indent_increment - if max_width is None: - max_width = 80 - if width is None: - import shutil - - 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: int = 0 - self.buffer: 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: str | None = 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 = "{usage} ".format(usage=_("Usage:")) - - usage_prefix = f"{prefix:>{self.current_indent}}{prog} " - text_width = self.width - self.current_indent - - if not args: - # Without args, the prefix's trailing space and the wrap_text - # call that would normally place args on the line are both - # unnecessary. Emit just the prefix line. - self.write(usage_prefix.rstrip(" ")) - self.write("\n") - return - - 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: cabc.Sequence[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) -> cabc.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) -> cabc.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: cabc.Sequence[str]) -> tuple[str, bool]: - """Given a list of option strings this joins them in the most appropriate - way and returns them in the form ``(formatted_string, - any_prefix_is_slash)`` where the second item in the tuple is a flag that - indicates if any of the option prefixes was a slash. - """ - rv = [] - any_prefix_is_slash = False - - for opt in options: - prefix = _split_opt(opt)[0] - - if prefix == "/": - any_prefix_is_slash = True - - rv.append((len(prefix), opt)) - - rv.sort(key=lambda x: x[0]) - return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/server/venv/Lib/site-packages/click/globals.py b/server/venv/Lib/site-packages/click/globals.py deleted file mode 100644 index a2f9172..0000000 --- a/server/venv/Lib/site-packages/click/globals.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import annotations - -import typing as t -from threading import local - -if t.TYPE_CHECKING: - from .core import Context - -_local = local() - - -@t.overload -def get_current_context(silent: t.Literal[False] = False) -> Context: ... - - -@t.overload -def get_current_context(silent: bool = ...) -> Context | None: ... - - -def get_current_context(silent: bool = False) -> Context | None: - """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: bool | None = None) -> bool | None: - """Internal helper to get the default value of the color flag. If a - value is passed it's returned unchanged, otherwise it's looked up from - the current context. - """ - if color is not None: - return color - - ctx = get_current_context(silent=True) - - if ctx is not None: - return ctx.color - - return None diff --git a/server/venv/Lib/site-packages/click/parser.py b/server/venv/Lib/site-packages/click/parser.py deleted file mode 100644 index 4fcbf7c..0000000 --- a/server/venv/Lib/site-packages/click/parser.py +++ /dev/null @@ -1,533 +0,0 @@ -""" -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 -from __future__ import annotations - -import collections.abc as cabc -import typing as t -from collections import deque -from gettext import gettext as _ -from gettext import ngettext - -from ._utils import FLAG_NEEDS_VALUE -from ._utils import UNSET -from .exceptions import BadArgumentUsage -from .exceptions import BadOptionUsage -from .exceptions import NoSuchOption -from .exceptions import UsageError - -if t.TYPE_CHECKING: - from ._utils import T_FLAG_NEEDS_VALUE - from ._utils import T_UNSET - 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") - - -def _unpack_args( - args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int] -) -> tuple[cabc.Sequence[str | cabc.Sequence[str | T_UNSET] | T_UNSET], 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 ``UNSET``. - """ - args = deque(args) - nargs_spec = deque(nargs_spec) - rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = [] - spos: int | None = None - - def _fetch(c: deque[str]) -> str | T_UNSET: - try: - if spos is None: - return c.popleft() - else: - return c.pop() - except IndexError: - return UNSET - - while nargs_spec: - if spos is None: - nargs = nargs_spec.popleft() - else: - nargs = nargs_spec.pop() - - if nargs == 1: - rv.append(_fetch(args)) - elif nargs > 1: - x: list[str | T_UNSET] = [_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(UNSET) - - # 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) -> 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: Context | None) -> 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)}" - - -class _Option: - def __init__( - self, - obj: CoreOption, - opts: cabc.Sequence[str], - dest: str | None, - action: str | None = None, - nargs: int = 1, - const: t.Any | None = None, - ): - self._short_opts = [] - self._long_opts = [] - self.prefixes: set[str] = set() - - for opt in opts: - prefix, value = _split_opt(opt) - if not prefix: - raise ValueError( - _("Invalid start character for option ({option})").format( - option=opt - ) - ) - self.prefixes.add(prefix[0]) - if len(prefix) == 1 and len(value) == 1: - self._short_opts.append(opt) - else: - self._long_opts.append(opt) - self.prefixes.add(prefix) - - if action is None: - action = "store" - - self.dest = dest - self.action = action - self.nargs = nargs - self.const = const - self.obj = obj - - @property - def takes_value(self) -> bool: - return self.action in ("store", "append") - - def process(self, value: t.Any, state: _ParsingState) -> None: - if self.action == "store": - state.opts[self.dest] = value # type: ignore - elif self.action == "store_const": - state.opts[self.dest] = self.const # type: ignore - elif self.action == "append": - state.opts.setdefault(self.dest, []).append(value) # type: ignore - elif self.action == "append_const": - state.opts.setdefault(self.dest, []).append(self.const) # type: ignore - elif self.action == "count": - state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore - else: - raise ValueError(f"unknown action '{self.action}'") - state.order.append(self.obj) - - -class _Argument: - def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1): - self.dest = dest - self.nargs = nargs - self.obj = obj - - def process( - self, - value: str | cabc.Sequence[str | T_UNSET] | T_UNSET, - state: _ParsingState, - ) -> None: - if self.nargs > 1: - assert isinstance(value, cabc.Sequence) - holes = sum(x is UNSET for x in value) - if holes == len(value): - value = UNSET - elif holes != 0: - raise BadArgumentUsage( - _("Argument {name!r} takes {nargs} values.").format( - name=self.dest, nargs=self.nargs - ) - ) - - # We failed to collect any argument value so we consider the argument as unset. - if value == (): - value = UNSET - - state.opts[self.dest] = value # type: ignore - state.order.append(self.obj) - - -class _ParsingState: - def __init__(self, rargs: list[str]) -> None: - self.opts: dict[str, t.Any] = {} - self.largs: list[str] = [] - self.rargs = rargs - self.order: 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. - - .. deprecated:: 8.2 - Will be removed in Click 9.0. - """ - - def __init__(self, ctx: Context | None = None) -> None: - #: The :class:`~click.Context` for this parser. This might be - #: `None` for some advanced use cases. - self.ctx = ctx - #: This controls how the parser deals with interspersed arguments. - #: If this is set to `False`, the parser will stop on the first - #: non-option. Click uses this to implement nested subcommands - #: safely. - self.allow_interspersed_args: bool = True - #: This tells the parser how to deal with unknown options. By - #: default it will error out (which is sensible), but there is a - #: second mode where it will ignore it and continue processing - #: after shifting all the unknown options into the resulting args. - self.ignore_unknown_options: bool = False - - if ctx is not None: - self.allow_interspersed_args = ctx.allow_interspersed_args - self.ignore_unknown_options = ctx.ignore_unknown_options - - self._short_opt: dict[str, _Option] = {} - self._long_opt: dict[str, _Option] = {} - self._opt_prefixes = {"-", "--"} - self._args: list[_Argument] = [] - - def add_option( - self, - obj: CoreOption, - opts: cabc.Sequence[str], - dest: str | None, - action: str | None = None, - nargs: int = 1, - const: t.Any | None = 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: str | None, 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: list[str] - ) -> tuple[dict[str, t.Any], list[str], 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: str | None, state: _ParsingState - ) -> None: - if opt not in self._long_opt: - raise NoSuchOption(opt, possibilities=self._long_opt, 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 = UNSET - - 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 = UNSET - - option.process(value, state) - - if stop: - break - - # If we got any unknown options we recombine the string of the - # remaining options and re-attach the prefix, then report that - # to the state as new large. 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 - ) -> str | cabc.Sequence[str] | T_UNSET | T_FLAG_NEEDS_VALUE: - nargs = option.nargs - - value: str | cabc.Sequence[str] | T_UNSET | T_FLAG_NEEDS_VALUE - - 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) - - -def __getattr__(name: str) -> object: - import warnings - - if name in { - "OptionParser", - "Argument", - "Option", - "split_opt", - "normalize_opt", - "ParsingState", - }: - warnings.warn( - f"'parser.{name}' is deprecated and will be removed in Click 9.0." - " The old parser is available in 'optparse'.", - DeprecationWarning, - stacklevel=2, - ) - return globals()[f"_{name}"] - - if name == "split_arg_string": - from .shell_completion import split_arg_string - - warnings.warn( - "Importing 'parser.split_arg_string' is deprecated, it will only be" - " available in 'shell_completion' in Click 9.0.", - DeprecationWarning, - stacklevel=2, - ) - return split_arg_string - - raise AttributeError(name) diff --git a/server/venv/Lib/site-packages/click/py.typed b/server/venv/Lib/site-packages/click/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/click/shell_completion.py b/server/venv/Lib/site-packages/click/shell_completion.py deleted file mode 100644 index b01e290..0000000 --- a/server/venv/Lib/site-packages/click/shell_completion.py +++ /dev/null @@ -1,680 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import os -import re -import typing as t -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 .core import ParameterSource -from .utils import echo - - -def shell_complete( - cli: Command, - ctx_args: cabc.MutableMapping[str, t.Any], - prog_name: str, - complete_var: str, - instruction: str, -) -> int: - """Perform shell completion for the given CLI program. - - :param cli: Command being called. - :param ctx_args: Extra arguments to pass to - ``cli.make_context``. - :param prog_name: Name of the executable in the shell. - :param complete_var: Name of the environment variable that holds - the completion instruction. - :param instruction: Value of ``complete_var`` with the completion - instruction and shell, in the form ``instruction_shell``. - :return: Status code to exit with. - """ - shell, _, instruction = instruction.partition("_") - comp_cls = get_completion_class(shell) - - if comp_cls is None: - return 1 - - comp = comp_cls(cli, ctx_args, prog_name, complete_var) - - # Write bytes, otherwise Windows text stdout translates LF to CRLF and breaks. - if instruction == "source": - echo(comp.source().encode(), nl=False) - return 0 - - if instruction == "complete": - echo(comp.complete().encode()) - 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: str | None = None, - **kwargs: t.Any, - ) -> None: - self.value: t.Any = value - self.type: str = type - self.help: str | None = 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; -""" - -# See ZshComplete.format_completion below, and issue #2703, before -# changing this script. -# -# (TL;DR: _describe is picky about the format, but this Zsh script snippet -# is already widely deployed. So freeze this script, and use clever-ish -# handling of colons in ZshComplet.format_completion.) -_SOURCE_ZSH = """\ -#compdef %(prog_name)s - -%(complete_func)s() { - local -a completions - local -a completions_with_descriptions - local -a response - (( ! $+commands[%(prog_name)s] )) && return 1 - - response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ -%(complete_var)s=zsh_complete %(prog_name)s)}") - - for type key descr in ${response}; do - if [[ "$type" == "plain" ]]; then - if [[ "$descr" == "_" ]]; then - completions+=("$key") - else - completions_with_descriptions+=("$key":"$descr") - fi - elif [[ "$type" == "dir" ]]; then - _path_files -/ - elif [[ "$type" == "file" ]]; then - _path_files -f - fi - done - - if [ -n "$completions_with_descriptions" ]; then - _describe -V unsorted completions_with_descriptions -U - fi - - if [ -n "$completions" ]; then - compadd -U -V unsorted -a completions - fi -} - -if [[ $zsh_eval_context[-1] == loadautofunc ]]; then - # autoload from fpath, call function directly - %(complete_func)s "$@" -else - # eval/source/. command, register function for later - compdef %(complete_func)s %(prog_name)s -fi -""" - -_SOURCE_FISH = """\ -function %(complete_func)s; - set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ -COMP_CWORD=(commandline -t) %(prog_name)s); - - for completion in $response; - set -l metadata (string split \n $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"; - if test $metadata[3] != "_"; - echo $metadata[2]\t$metadata[3]; - else; - echo $metadata[2]; - end; - 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: Command, - ctx_args: cabc.MutableMapping[str, t.Any], - prog_name: str, - complete_var: str, - ) -> None: - self.cli = cli - self.ctx_args = ctx_args - self.prog_name = prog_name - self.complete_var = complete_var - - @property - def func_name(self) -> str: - """The name of the shell function defined by the completion - script. - """ - safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) - return f"_{safe_name}_completion" - - def source_vars(self) -> 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) -> tuple[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: list[str], incomplete: str) -> list[CompletionItem]: - """Determine the context and last complete command or parameter - from the complete args. Call that object's ``shell_complete`` - method to get the completions for the incomplete value. - - :param args: List of complete args before the incomplete value. - :param incomplete: Value being completed. May be empty. - """ - ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) - obj, incomplete = _resolve_incomplete(ctx, args, incomplete) - return obj.shell_complete(ctx, incomplete) - - def format_completion(self, item: CompletionItem) -> str: - """Format a completion item into the form recognized by the - shell script. This must be implemented by subclasses. - - :param item: Completion item to format. - """ - raise NotImplementedError - - def complete(self) -> str: - """Produce the completion data to send back to the shell. - - By default this calls :meth:`get_completion_args`, gets the - completions, then calls :meth:`format_completion` for each - completion. - """ - args, incomplete = self.get_completion_args() - completions = self.get_completions(args, incomplete) - out = [self.format_completion(item) for item in completions] - return "\n".join(out) - - -class BashComplete(ShellComplete): - """Shell completion for Bash.""" - - name = "bash" - source_template = _SOURCE_BASH - - @staticmethod - def _check_version() -> None: - import shutil - import subprocess - - bash_exe = shutil.which("bash") - - if bash_exe is None: - match = None - else: - output = subprocess.run( - [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'], - stdout=subprocess.PIPE, - ) - match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) - - if match is not None: - major, minor = match.groups() - - if major < "4" or major == "4" and minor < "4": - echo( - _( - "Shell completion is not supported for Bash" - " versions older than 4.4." - ), - err=True, - ) - else: - echo( - _("Couldn't detect Bash version, shell completion is not supported."), - err=True, - ) - - def source(self) -> str: - self._check_version() - return super().source() - - def get_completion_args(self) -> tuple[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) -> tuple[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: - help_ = item.help or "_" - # The zsh completion script uses `_describe` on items with help - # texts (which splits the item help from the item value at the - # first unescaped colon) and `compadd` on items without help - # text (which uses the item value as-is and does not support - # colon escaping). So escape colons in the item value if and - # only if the item help is not the sentinel "_" value, as used - # by the completion script. - # - # (The zsh completion script is potentially widely deployed, and - # thus harder to fix than this method.) - # - # See issue #1812 and issue #2703 for further context. - value = item.value.replace(":", r"\:") if help_ != "_" else item.value - return f"{item.type}\n{value}\n{help_}" - - -class FishComplete(ShellComplete): - """Shell completion for Fish.""" - - name = "fish" - source_template = _SOURCE_FISH - - def get_completion_args(self) -> tuple[list[str], str]: - cwords = split_arg_string(os.environ["COMP_WORDS"]) - incomplete = os.environ["COMP_CWORD"] - if incomplete: - incomplete = split_arg_string(incomplete)[0] - 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: - """ - .. versionchanged:: 8.4.0 - Escape newlines in value and help to fix completion errors with - multi-line help strings. - """ - # The fish completion script splits each response line on literal - # newlines, so any newline in the value or help would corrupt the - # frame. Replace them with the two-character escape "\n" so the text - # round-trips through fish without breaking the format. The "_" - # sentinel for missing help mirrors :class:`ZshComplete`. - help_ = item.help or "_" - value = item.value.replace("\n", r"\n") - help_escaped = help_.replace("\n", r"\n") - return f"{item.type}\n{value}\n{help_escaped}" - - -ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]") - - -_available_shells: dict[str, type[ShellComplete]] = { - "bash": BashComplete, - "fish": FishComplete, - "zsh": ZshComplete, -} - - -def add_completion_class( - cls: ShellCompleteType, name: str | None = None -) -> ShellCompleteType: - """Register a :class:`ShellComplete` subclass under the given name. - The name will be provided by the completion instruction environment - variable during completion. - - :param cls: The completion class that will handle completion for the - shell. - :param name: Name to register the class under. Defaults to the - class's ``name`` attribute. - """ - if name is None: - name = cls.name - - _available_shells[name] = cls - - return cls - - -def get_completion_class(shell: str) -> type[ShellComplete] | None: - """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 split_arg_string(string: str) -> 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. - - .. versionchanged:: 8.2 - Moved to ``shell_completion`` from ``parser``. - """ - 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 - - -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 - - value = ctx.params.get(param.name) - return ( - param.nargs == -1 - or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE - or ( - param.nargs > 1 - and isinstance(value, (tuple, list)) - and len(value) < param.nargs - ) - ) - - -def _start_of_option(ctx: Context, value: str) -> bool: - """Check if the value looks like the start of an option.""" - if not value: - return False - - c = value[0] - return c in ctx._opt_prefixes - - -def _is_incomplete_option(ctx: Context, args: 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 - break - - return last_option is not None and last_option in param.opts - - -def _resolve_context( - cli: Command, - ctx_args: cabc.MutableMapping[str, t.Any], - prog_name: str, - args: 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 - with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx: - args = ctx._protected_args + ctx.args - - while args: - command = ctx.command - - if isinstance(command, Group): - if not command.chain: - name, cmd, args = command.resolve_command(ctx, args) - - if cmd is None: - return ctx - - with cmd.make_context( - name, args, parent=ctx, resilient_parsing=True - ) as sub_ctx: - ctx = sub_ctx - args = ctx._protected_args + ctx.args - else: - sub_ctx = ctx - - while args: - name, cmd, args = command.resolve_command(ctx, args) - - if cmd is None: - return ctx - - with cmd.make_context( - name, - args, - parent=ctx, - allow_extra_args=True, - allow_interspersed_args=False, - resilient_parsing=True, - ) as sub_sub_ctx: - sub_ctx = sub_sub_ctx - 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: list[str], incomplete: str -) -> tuple[Command | Parameter, str]: - """Find the Click object that will handle the completion of the - incomplete value. Return the object and the incomplete value. - - :param ctx: Invocation context for the command represented by - the parsed complete args. - :param args: List of complete args before the incomplete value. - :param incomplete: Value being completed. May be empty. - """ - # Different shells treat an "=" between a long option name and - # value differently. Might keep the value joined, return the "=" - # as a separate item, or return the split name and value. Always - # split and discard the "=" to make completion easier. - if incomplete == "=": - incomplete = "" - elif "=" in incomplete and _start_of_option(ctx, incomplete): - name, _, incomplete = incomplete.partition("=") - args.append(name) - - # The "--" marker tells Click to stop treating values as options - # even if they start with the option character. If it hasn't been - # given and the incomplete arg looks like an option, the current - # command will provide option name completions. - if "--" not in args and _start_of_option(ctx, incomplete): - return ctx.command, incomplete - - params = ctx.command.get_params(ctx) - - # If the last complete arg is an option name with an incomplete - # value, the option will provide value completions. - for param in params: - if _is_incomplete_option(ctx, args, param): - return param, incomplete - - # It's not an option name or value. The first argument without a - # parsed value will provide value completions. - for param in params: - if _is_incomplete_argument(ctx, param): - return param, incomplete - - # There were no unparsed arguments, the command may be a group that - # will provide command name completions. - return ctx.command, incomplete diff --git a/server/venv/Lib/site-packages/click/termui.py b/server/venv/Lib/site-packages/click/termui.py deleted file mode 100644 index 91ef6e1..0000000 --- a/server/venv/Lib/site-packages/click/termui.py +++ /dev/null @@ -1,941 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import inspect -import io -import itertools -import re -import sys -import typing as t -from contextlib import AbstractContextManager -from contextlib import redirect_stdout -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" - - -_HIDDEN_INPUT_MASK = "'***'" - - -def _mask_hidden_input(message: str, value: str) -> str: - """Replace occurrences of ``value`` in ``message`` with a fixed mask. - - Both ``repr(value)`` (the form built-in :class:`ParamType` errors use - via ``{value!r}``) and the raw value are masked. The raw-value pass - uses word-boundary lookarounds so a substring like ``"1"`` does not - match inside ``"10"``, and ``"ent"`` does not match inside - ``"Authentication"``. The empty string is skipped to avoid matching - at every boundary. - """ - message = message.replace(repr(value), _HIDDEN_INPUT_MASK) - if value: - message = re.sub( - rf"(? str: - import getpass - - return getpass.getpass(prompt) - - -def _readline_prompt(func: t.Callable[[str], str], text: str, err: bool) -> str: - """Call a prompt function, passing the full prompt on non-Windows so - readline can handle line editing and cursor positioning correctly. - - On Windows the prompt is written separately via :func:`echo` for - colorama support, with only the last character passed to *func*. - """ - if WIN: - # Write the prompt separately so that we get nice coloring - # through colorama on Windows. - echo(text[:-1], nl=False, err=err) - # Echo the last character to stdout to work around an issue - # where readline causes backspace to clear the whole line. - return func(text[-1:]) - if err: - with redirect_stdout(sys.stderr): - return func(text) - return func(text) - - -def _build_prompt( - text: str, - suffix: str, - show_default: bool | str = False, - default: t.Any | None = None, - show_choices: bool = True, - type: ParamType[t.Any] | None = None, -) -> str: - prompt = text - if type is not None and show_choices and isinstance(type, Choice): - prompt += f" ({', '.join(map(str, type.choices))})" - if isinstance(show_default, str): - default = f"({show_default})" - if default is not None and show_default: - prompt = f"{prompt} [{_format_default(default)}]" - return f"{prompt}{suffix}" - - -def _format_default(default: t.Any) -> t.Any: - if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): - return default.name - - return default - - -def prompt( - text: str, - default: t.Any | None = None, - hide_input: bool = False, - confirmation_prompt: bool | str = False, - type: ParamType[t.Any] | t.Any | None = None, - value_proc: t.Callable[[str], t.Any] | None = None, - prompt_suffix: str = ": ", - show_default: bool | str = 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. - If this value is a string, it shows that string - in parentheses instead of the actual value. - :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): ". - - .. versionchanged:: 8.3.3 - ``show_default`` can be a string to show a custom value instead - of the actual default, matching the help text behavior. - - .. versionchanged:: 8.3.1 - A space is no longer appended to the prompt. - - .. 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: - return _readline_prompt(f, text, err) - 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: - message = _mask_hidden_input(e.message, value) if hide_input else e.message - echo(_("Error: {message}").format(message=message), err=err) - 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: bool | None = 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.3.1 - A space is no longer appended to the prompt. - - .. 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: - value = _readline_prompt(visible_prompt_func, prompt, err).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 get_pager_file( - color: bool | None = None, -) -> t.ContextManager[t.TextIO]: - """Context manager. - - Yields a writable file-like object which can be used as an output pager. - - .. versionadded:: 8.4.0 - - :param color: controls if the pager supports ANSI colors or not. The - default is autodetection. - """ - from ._termui_impl import get_pager_file - - color = resolve_color_default(color) - - return get_pager_file(color=color) - - -def echo_via_pager( - text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str, - color: bool | None = 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. - """ - - if inspect.isgeneratorfunction(text_or_generator): - i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)() - elif isinstance(text_or_generator, str): - i = [text_or_generator] - else: - i = iter(t.cast("cabc.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) - - with get_pager_file(color=color) as pager: - for text in itertools.chain(text_generator, "\n"): - pager.write(text) - - -@t.overload -def progressbar( - *, - length: int, - label: str | None = None, - hidden: bool = False, - show_eta: bool = True, - show_percent: bool | None = None, - show_pos: bool = False, - fill_char: str = "#", - empty_char: str = "-", - bar_template: str = "%(label)s [%(bar)s] %(info)s", - info_sep: str = " ", - width: int = 36, - file: t.TextIO | None = None, - color: bool | None = None, - update_min_steps: int = 1, -) -> ProgressBar[int]: ... - - -@t.overload -def progressbar( - iterable: cabc.Iterable[V] | None = None, - length: int | None = None, - label: str | None = None, - hidden: bool = False, - show_eta: bool = True, - show_percent: bool | None = None, - show_pos: bool = False, - item_show_func: t.Callable[[V | None], str | None] | None = None, - fill_char: str = "#", - empty_char: str = "-", - bar_template: str = "%(label)s [%(bar)s] %(info)s", - info_sep: str = " ", - width: int = 36, - file: t.TextIO | None = None, - color: bool | None = None, - update_min_steps: int = 1, -) -> ProgressBar[V]: ... - - -def progressbar( - iterable: cabc.Iterable[V] | None = None, - length: int | None = None, - label: str | None = None, - hidden: bool = False, - show_eta: bool = True, - show_percent: bool | None = None, - show_pos: bool = False, - item_show_func: t.Callable[[V | None], str | None] | None = None, - fill_char: str = "#", - empty_char: str = "-", - bar_template: str = "%(label)s [%(bar)s] %(info)s", - info_sep: str = " ", - width: int = 36, - file: t.TextIO | None = None, - color: bool | None = 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 hidden: hide the progressbar. Defaults to ``False``. When no tty is - detected, it will only print the progressbar label. Setting this to - ``False`` also disables that. - :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. - - .. versionadded:: 8.2 - The ``hidden`` argument. - - .. 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 - The ``update_min_steps`` parameter. - - .. versionadded:: 4.0 - The ``color`` parameter and ``update`` method. - - .. versionadded:: 2.0 - """ - from ._termui_impl import ProgressBar - - color = resolve_color_default(color) - return ProgressBar( - iterable=iterable, - length=length, - hidden=hidden, - show_eta=show_eta, - show_percent=show_percent, - show_pos=show_pos, - item_show_func=item_show_func, - fill_char=fill_char, - empty_char=empty_char, - bar_template=bar_template, - info_sep=info_sep, - file=file, - label=label, - width=width, - color=color, - update_min_steps=update_min_steps, - ) - - -def clear() -> None: - """Clears the terminal screen. This will have the effect of clearing - the whole visible space of the terminal and moving the cursor to the - top left. This does not do anything if not connected to a terminal. - - .. versionadded:: 2.0 - """ - if not isatty(sys.stdout): - return - - # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor - echo("\033[2J\033[1;1H", nl=False) - - -def _interpret_color(color: int | 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: int | tuple[int, int, int] | str | None = None, - bg: int | tuple[int, int, int] | str | None = None, - bold: bool | None = None, - dim: bool | None = None, - underline: bool | None = None, - overline: bool | None = None, - italic: bool | None = None, - blink: bool | None = None, - reverse: bool | None = None, - strikethrough: bool | None = 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(_("Unknown color {colour!r}").format(colour=fg)) from None - - if bg: - try: - bits.append(f"\033[{_interpret_color(bg, 10)}m") - except KeyError: - raise TypeError(_("Unknown color {colour!r}").format(colour=bg)) 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.Any | None = None, - file: t.IO[t.AnyStr] | None = None, - nl: bool = True, - err: bool = False, - color: bool | None = 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) - - -@t.overload -def edit( - text: bytes | bytearray, - editor: str | None = None, - env: cabc.Mapping[str, str] | None = None, - require_save: bool = False, - extension: str = ".txt", -) -> bytes | None: ... - - -@t.overload -def edit( - text: str, - editor: str | None = None, - env: cabc.Mapping[str, str] | None = None, - require_save: bool = True, - extension: str = ".txt", -) -> str | None: ... - - -@t.overload -def edit( - text: None = None, - editor: str | None = None, - env: cabc.Mapping[str, str] | None = None, - require_save: bool = True, - extension: str = ".txt", - filename: str | cabc.Iterable[str] | None = None, -) -> None: ... - - -def edit( - text: str | bytes | bytearray | None = None, - editor: str | None = None, - env: cabc.Mapping[str, str] | None = None, - require_save: bool = True, - extension: str = ".txt", - filename: str | cabc.Iterable[str] | None = None, -) -> str | bytes | bytearray | None: - r"""Edits the given text in the defined editor. If an editor is given - (should be the full path to the executable but the regular operating - system search path is used for finding the executable) it overrides - the detected editor. Optionally, some environment variables can be - used. If the editor is closed without changes, `None` is returned. In - case a file is edited directly the return value is always `None` and - `require_save` and `extension` are ignored. - - If the editor cannot be opened a :exc:`UsageError` is raised. - - Note for Windows: to simplify cross-platform usage, the newlines are - automatically converted from POSIX to Windows and vice versa. As such, - the message here will have ``\n`` as newline markers. - - :param text: the text to edit. - :param editor: optionally the editor to use. Defaults to automatic - detection. - :param env: environment variables to forward to the editor. - :param require_save: if this is true, then not saving in the editor - will make the return value become `None`. - :param extension: the extension to tell the editor about. This defaults - to `.txt` but changing this might change syntax - highlighting. - :param filename: if provided it will edit this file instead of the - provided text contents. It will not use a temporary - file as an indirection in that case. If the editor supports - editing multiple files at once, a sequence of files may be - passed as well. Invoke `click.file` once per file instead - if multiple files cannot be managed at once or editing the - files serially is desired. - - .. versionchanged:: 8.2.0 - ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str`` - if the ``editor`` supports editing multiple files at once. - - """ - 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) - - if isinstance(filename, str): - filename = (filename,) - - ed.edit_files(filenames=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.Callable[[bool], str] | None = 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() -> AbstractContextManager[int]: - from ._termui_impl import raw_terminal as f - - return f() - - -def pause(info: str | None = None, err: bool = False) -> None: - """This command stops execution and waits for the user to press any - key to continue. This is similar to the Windows batch "pause" - command. If the program is not run through a terminal, this command - will instead do nothing. - - .. versionadded:: 2.0 - - .. versionadded:: 4.0 - Added the `err` parameter. - - :param info: The message to print before pausing. Defaults to - ``"Press any key to continue..."``. - :param err: if set to message goes to ``stderr`` instead of - ``stdout``, the same as with echo. - """ - if not isatty(sys.stdin) or not isatty(sys.stdout): - return - - if info is None: - info = _("Press any key to continue...") - - try: - if info: - echo(info, nl=False, err=err) - try: - getchar() - except (KeyboardInterrupt, EOFError): - pass - finally: - if info: - echo(err=err) diff --git a/server/venv/Lib/site-packages/click/testing.py b/server/venv/Lib/site-packages/click/testing.py deleted file mode 100644 index 1eb81d2..0000000 --- a/server/venv/Lib/site-packages/click/testing.py +++ /dev/null @@ -1,736 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import contextlib -import io -import os -import pdb -import shlex -import sys -import tempfile -import typing as t -from types import TracebackType - -from . import _compat -from . import formatting -from . import termui -from . import utils -from ._compat import _find_binary_reader - -if t.TYPE_CHECKING: - from _typeshed import ReadableBuffer - - from .core import Command - -CaptureMode = t.Literal["sys", "fd"] - - -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) -> list[bytes]: - return [self._echo(x) for x in self._input.readlines()] - - def __iter__(self) -> cabc.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: EchoingStdin | None) -> cabc.Iterator[None]: - if stream is None: - yield - else: - stream._paused = True - yield - stream._paused = False - - -class _FDCapture: - """Redirect a file descriptor to a temporary file for capture. - - Saves the current target of *targetfd* via :func:`os.dup`, then - redirects it to a temporary file via :func:`os.dup2`. On - :meth:`stop`, restores the original ``fd`` and returns the captured - bytes. Inspired by Pytest's ``FDCapture``. - - .. versionadded:: 8.4.0 - """ - - def __init__(self, targetfd: int) -> None: - self._targetfd = targetfd - self.saved_fd: int = -1 - self._tmpfile: t.BinaryIO | None = None - - def start(self) -> None: - self.saved_fd = os.dup(self._targetfd) - self._tmpfile = tempfile.TemporaryFile(buffering=0) - os.dup2(self._tmpfile.fileno(), self._targetfd) - - def stop(self) -> bytes: - assert self._tmpfile is not None, "_FDCapture.start() was not called" - os.dup2(self.saved_fd, self._targetfd) - os.close(self.saved_fd) - self.saved_fd = -1 - self._tmpfile.seek(0) - data = self._tmpfile.read() - self._tmpfile.close() - self._tmpfile = None - return data - - -class BytesIOCopy(io.BytesIO): - """Patch ``io.BytesIO`` to let the written stream be copied to another. - - .. versionadded:: 8.2 - """ - - def __init__(self, copy_to: io.BytesIO) -> None: - super().__init__() - self.copy_to = copy_to - - def flush(self) -> None: - super().flush() - self.copy_to.flush() - - def write(self, b: ReadableBuffer) -> int: - self.copy_to.write(b) - return super().write(b) - - -class StreamMixer: - """Mixes `` and `` streams. - - The result is available in the ``output`` attribute. - - .. versionadded:: 8.2 - """ - - def __init__(self) -> None: - self.output: io.BytesIO = io.BytesIO() - self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output) - self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output) - - -class _NamedTextIOWrapper(io.TextIOWrapper): - """A :class:`~io.TextIOWrapper` with custom ``name`` and ``mode`` - that does not close its underlying buffer. - - When ``CliRunner`` runs in ``fd`` mode, ``_original_fd`` is patched to - point at the saved (pre-redirection) ``fd``, so C-level consumers that call - :meth:`fileno` (like ``faulthandler`` or ``subprocess``) keep working. In - the default ``sys`` mode ``_original_fd`` stays at ``-1`` and - :meth:`fileno` raises :exc:`io.UnsupportedOperation`, matching the - pre-``8.3.3`` behavior. - """ - - def __init__( - self, - buffer: t.BinaryIO, - name: str, - mode: str, - **kwargs: t.Any, - ) -> None: - super().__init__(buffer, **kwargs) - self._name = name - self._mode = mode - self._original_fd: int = -1 - - def close(self) -> None: - """The buffer this object contains belongs to some other object, - so prevent the default ``__del__`` implementation from closing - that buffer. - - .. versionadded:: 8.3.2 - """ - - def fileno(self) -> int: - """Return the file descriptor of the saved original stream when - ``CliRunner`` runs in ``fd`` mode. Otherwise delegate to - :class:`~io.TextIOWrapper`, which raises - :exc:`io.UnsupportedOperation` for a ``BytesIO``-backed buffer. - """ - if self._original_fd >= 0: - return self._original_fd - return super().fileno() - - @property - def name(self) -> str: - return self._name - - @property - def mode(self) -> str: - return self._mode - - -def make_input_stream( - input: str | bytes | t.IO[t.Any] | None, charset: str -) -> t.BinaryIO: - # Is already an input stream. - if hasattr(input, "read"): - rv = _find_binary_reader(t.cast("t.IO[t.Any]", input)) - - if rv is not None: - return rv - - raise TypeError("Could not find binary reader for input stream.") - - if input is None: - input = b"" - elif isinstance(input, str): - input = input.encode(charset) - - return io.BytesIO(input) - - -class Result: - """Holds the captured result of an invoked CLI script. - - :param runner: The runner that created the result - :param stdout_bytes: The standard output as bytes. - :param stderr_bytes: The standard error as bytes. - :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the - user would see it in its terminal. - :param return_value: The value returned from the invoked command. - :param exit_code: The exit code as integer. - :param exception: The exception that happened if one did. - :param exc_info: Exception information (exception type, exception instance, - traceback type). - - .. versionchanged:: 8.2 - ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and - ``mix_stderr`` has been removed. - - .. versionadded:: 8.0 - Added ``return_value``. - """ - - def __init__( - self, - runner: CliRunner, - stdout_bytes: bytes, - stderr_bytes: bytes, - output_bytes: bytes, - return_value: t.Any, - exit_code: int, - exception: BaseException | None, - exc_info: tuple[type[BaseException], BaseException, TracebackType] - | None = None, - ): - self.runner = runner - self.stdout_bytes = stdout_bytes - self.stderr_bytes = stderr_bytes - self.output_bytes = output_bytes - self.return_value = return_value - self.exit_code = exit_code - self.exception = exception - self.exc_info = exc_info - - @property - def output(self) -> str: - """The terminal output as unicode string, as the user would see it. - - .. versionchanged:: 8.2 - No longer a proxy for ``self.stdout``. Now has its own independent stream - that is mixing `` and ``, in the order they were written. - """ - return self.output_bytes.decode(self.runner.charset, "replace").replace( - "\r\n", "\n" - ) - - @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. - - .. versionchanged:: 8.2 - No longer raise an exception, always returns the `` string. - """ - 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 `` writes - to ``. This is useful for showing examples in - some circumstances. Note that regular prompts - will automatically echo the input. - :param catch_exceptions: Whether to catch any exceptions other than - ``SystemExit`` when running :meth:`~CliRunner.invoke`. - :param capture: Selects the output capture strategy. ``sys`` (default) - captures Python-level writes only and leaves - :meth:`sys.stdout.fileno` raising :exc:`io.UnsupportedOperation`, so - user code that calls :func:`os.dup2` on ``sys.stdout.fileno()`` cannot - clobber the host runner's stdout. ``fd`` redirects file descriptors - ``1`` and ``2`` via :func:`os.dup2` to a temporary file, also catching - output from stale stream references, C extensions, and subprocesses. - ``fd`` is not supported on Windows. - - .. versionchanged:: 8.4.0 - Added the ``capture`` parameter. The default ``sys`` mode no longer - exposes the original fd through :meth:`fileno`, reverting the change - introduced in ``8.3.3`` that broke Pytest's ``fd``-level capture - teardown. Use ``capture="fd"`` to restore that behavior with proper - isolation. :issue:`3384` - - .. versionchanged:: 8.2 - Added the ``catch_exceptions`` parameter. - - .. versionchanged:: 8.2 - ``mix_stderr`` parameter has been removed. - """ - - def __init__( - self, - charset: str = "utf-8", - env: cabc.Mapping[str, str | None] | None = None, - echo_stdin: bool = False, - catch_exceptions: bool = True, - capture: CaptureMode = "sys", - ) -> None: - if capture not in {"sys", "fd"}: - raise ValueError( - f"capture={capture!r} is not valid. Choose from 'sys' or 'fd'." - ) - if capture == "fd" and sys.platform == "win32": - raise ValueError( - f"capture={capture!r} is not supported on Windows. Use 'sys'." - ) - self.charset = charset - self.env: cabc.Mapping[str, str | None] = env or {} - self.echo_stdin = echo_stdin - self.catch_exceptions = catch_exceptions - self.capture: CaptureMode = capture - - def get_default_prog_name(self, cli: Command) -> 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: cabc.Mapping[str, str | None] | None = None - ) -> cabc.Mapping[str, str | None]: - """Returns the environment overrides for invoking a script.""" - rv = dict(self.env) - if overrides: - rv.update(overrides) - return rv - - @contextlib.contextmanager - def isolation( - self, - input: str | bytes | t.IO[t.Any] | None = None, - env: cabc.Mapping[str, str | None] | None = None, - color: bool = False, - ) -> cabc.Iterator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]: - """A context manager that sets up the isolation for invoking of a - command line tool. This sets up `` 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. - - .. versionadded:: 8.2 - An additional output stream is returned, which is a mix of - `` and `` streams. - - .. versionchanged:: 8.2 - Always returns the `` stream. - - .. versionchanged:: 8.0 - `` 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) - - stream_mixer = StreamMixer() - - if self.echo_stdin: - bytes_input = echo_input = t.cast( - t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout) - ) - - sys.stdin = text_input = _NamedTextIOWrapper( - bytes_input, encoding=self.charset, name="", mode="r" - ) - - if self.echo_stdin: - # Force unbuffered reads, otherwise TextIOWrapper reads a - # large chunk which is echoed early. - text_input._CHUNK_SIZE = 1 # type: ignore - - sys.stdout = _NamedTextIOWrapper( - stream_mixer.stdout, - encoding=self.charset, - name="", - mode="w", - ) - - sys.stderr = _NamedTextIOWrapper( - stream_mixer.stderr, - encoding=self.charset, - name="", - mode="w", - errors="backslashreplace", - ) - - @_pause_echo(echo_input) # type: ignore - def visible_input(prompt: str | None = None) -> str: - sys.stdout.write(prompt or "") - try: - val = next(text_input).rstrip("\r\n") - except StopIteration as e: - raise EOFError() from e - sys.stdout.write(f"{val}\n") - sys.stdout.flush() - return val - - @_pause_echo(echo_input) # type: ignore - def hidden_input(prompt: str | None = None) -> str: - sys.stdout.write(f"{prompt or ''}\n") - sys.stdout.flush() - try: - return next(text_input).rstrip("\r\n") - except StopIteration as e: - raise EOFError() from e - - @_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.IO[t.Any] | None = None, color: bool | None = 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 - old__compat_should_strip_ansi = _compat.should_strip_ansi - old_pdb_init = pdb.Pdb.__init__ - termui.visible_prompt_func = visible_input - termui.hidden_prompt_func = hidden_input - termui._getchar = _getchar - utils.should_strip_ansi = should_strip_ansi # type: ignore - _compat.should_strip_ansi = should_strip_ansi - - def _patched_pdb_init( - self: pdb.Pdb, - completekey: str = "tab", - stdin: t.IO[str] | None = None, - stdout: t.IO[str] | None = None, - **kwargs: t.Any, - ) -> None: - """Default ``pdb.Pdb`` to real terminal streams during - ``CliRunner`` isolation. - - Without this patch, ``pdb.Pdb.__init__`` inherits from - ``cmd.Cmd`` which falls back to ``sys.stdin``/``sys.stdout`` - when no explicit streams are provided. During isolation - those are ``BytesIO``-backed wrappers, so the debugger - reads from an empty buffer and writes to captured output, - making interactive debugging impossible. - - By defaulting to ``sys.__stdin__``/``sys.__stdout__`` (the - original terminal streams Python preserves regardless of - redirection), debuggers can interact with the user while - ``click.echo`` output is still captured normally. - - This covers ``pdb.set_trace()``, ``breakpoint()``, - ``pdb.post_mortem()``, and debuggers that subclass - ``pdb.Pdb`` (ipdb, pdbpp). Explicit ``stdin``/``stdout`` - arguments are honored and not overridden. Debuggers that - do not subclass ``pdb.Pdb`` (pudb, debugpy) are not - covered. - """ - if stdin is None: - stdin = sys.__stdin__ - if stdout is None: - stdout = sys.__stdout__ - old_pdb_init( - self, completekey=completekey, stdin=stdin, stdout=stdout, **kwargs - ) - - pdb.Pdb.__init__ = _patched_pdb_init # type: ignore[assignment] - - 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 (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output) - 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 - _compat.should_strip_ansi = old__compat_should_strip_ansi - formatting.FORCED_WIDTH = old_forced_width - pdb.Pdb.__init__ = old_pdb_init # type: ignore[method-assign] - - def invoke( - self, - cli: Command, - args: str | cabc.Sequence[str] | None = None, - input: str | bytes | t.IO[t.Any] | None = None, - env: cabc.Mapping[str, str | None] | None = None, - catch_exceptions: bool | None = None, - 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``. If :data:`None`, the value - from :class:`CliRunner` is used. - :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. - - .. versionadded:: 8.2 - The result object has the ``output_bytes`` attribute with - the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would - see it in its terminal. - - .. versionchanged:: 8.2 - The result object always returns the ``stderr_bytes`` stream. - - .. 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 - if catch_exceptions is None: - catch_exceptions = self.catch_exceptions - - # Set up fd capture before isolation replaces sys.stdout and sys.stderr. - cap_out: _FDCapture | None = None - cap_err: _FDCapture | None = None - - if self.capture == "fd": - cap_out = _FDCapture(1) - cap_err = _FDCapture(2) - try: - cap_out.start() - cap_err.start() - except OSError: - cap_out = cap_err = None - - with self.isolation(input=input, env=env, color=color) as outstreams: - # Point the captured streams' fileno() at the saved (original) - # fd so that C-level consumers like faulthandler keep working - # while fd 1/2 are redirected to the capture tmpfile. - if cap_out is not None and cap_err is not None: - sys.stdout._original_fd = cap_out.saved_fd # type: ignore[union-attr] - sys.stderr._original_fd = cap_err.saved_fd # type: ignore[union-attr] - - return_value = None - exception: BaseException | None = 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("int | t.Any | None", 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() - sys.stderr.flush() - - # Stop fd capture and merge the captured bytes into - # the stdout/stderr BytesIO streams. BytesIOCopy mirrors - # those writes into outstreams[2] automatically. - if cap_out is not None and cap_err is not None: - fd_out = cap_out.stop() - fd_err = cap_err.stop() - if fd_out: - outstreams[0].write(fd_out) - if fd_err: - outstreams[1].write(fd_err) - - stdout = outstreams[0].getvalue() - stderr = outstreams[1].getvalue() - output = outstreams[2].getvalue() - - return Result( - runner=self, - stdout_bytes=stdout, - stderr_bytes=stderr, - output_bytes=output, - return_value=return_value, - exit_code=exit_code, - exception=exception, - exc_info=exc_info, # type: ignore - ) - - @contextlib.contextmanager - def isolated_filesystem( - self, temp_dir: str | os.PathLike[str] | None = None - ) -> cabc.Iterator[str]: - """A context manager that creates a temporary directory and - changes the current working directory to it. This isolates tests - that affect the contents of the CWD to prevent them from - interfering with each other. - - :param temp_dir: Create the temporary directory under this - directory. If given, the created directory is not removed - when exiting. - - .. versionchanged:: 8.0 - Added the ``temp_dir`` parameter. - """ - cwd = os.getcwd() - dt = tempfile.mkdtemp(dir=temp_dir) - os.chdir(dt) - - try: - yield dt - finally: - os.chdir(cwd) - - if temp_dir is None: - import shutil - - try: - shutil.rmtree(dt) - except OSError: - pass diff --git a/server/venv/Lib/site-packages/click/types.py b/server/venv/Lib/site-packages/click/types.py deleted file mode 100644 index 98c2d61..0000000 --- a/server/venv/Lib/site-packages/click/types.py +++ /dev/null @@ -1,1309 +0,0 @@ -from __future__ import annotations - -import abc -import collections.abc as cabc -import enum -import os -import stat -import sys -import typing as t -import uuid -from datetime import datetime -from gettext import gettext as _ -from gettext import ngettext - -from ._compat import _get_argv_encoding -from ._compat import open_stream -from .exceptions import BadParameter -from .utils import format_filename -from .utils import LazyFile -from .utils import safecall - -if t.TYPE_CHECKING: - import typing_extensions as te - - from .core import Context - from .core import Parameter - from .shell_completion import CompletionItem - -ParamTypeValue = t.TypeVar("ParamTypeValue") - - -class ParamTypeInfoDict(t.TypedDict): - param_type: str - name: str - - -class ParamType(t.Generic[ParamTypeValue], abc.ABC): - """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. - - .. versionchanged:: 8.4.0 - Now a generic abstract base class. Parameterize with the - converted value type (``ParamType[int]`` for an integer-returning - type) so that :meth:`convert` and downstream consumers carry the - narrowed return type. - """ - - 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[str | None] = None - - def to_info_dict(self) -> ParamTypeInfoDict: - """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: Parameter | None = None, - ctx: Context | None = None, - ) -> ParamTypeValue | None: - if value is not None: - return self.convert(value, param, ctx) - return None - - def get_metavar(self, param: Parameter, ctx: Context) -> str | None: - """Returns the metavar default for this param if it provides one.""" - - def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None: - """Optionally might return extra information about a missing - parameter. - - .. versionadded:: 2.0 - """ - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> ParamTypeValue: - """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``. - """ - # The default returns the value as-is so subclasses that only customize - # metadata are not forced to redeclare ``convert``. - return t.cast("ParamTypeValue", value) - - def split_envvar_value(self, rv: str) -> cabc.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: Parameter | None = None, - ctx: Context | None = 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 - ) -> 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[ParamTypeValue]): - is_composite = True - - @property - @abc.abstractmethod - def arity(self) -> int: ... # type: ignore[override] - - -class FuncParamTypeInfoDict(ParamTypeInfoDict): - func: t.Callable[[t.Any], t.Any] - - -class FuncParamType(ParamType[ParamTypeValue]): - def __init__(self, func: t.Callable[[t.Any], ParamTypeValue]) -> None: - self.name: str = func.__name__ - self.func = func - - def to_info_dict(self) -> FuncParamTypeInfoDict: - return {"func": self.func, **super().to_info_dict()} - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> ParamTypeValue: - try: - return self.func(value) - except ValueError as exc: - message = str(exc) - - if not message: - try: - message = str(value) - except UnicodeError: - message = value.decode("utf-8", "replace") - - self.fail(message, param, ctx) - - -class UnprocessedParamType(ParamType[t.Any]): - name = "text" - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> t.Any: - return value - - def __repr__(self) -> str: - return "UNPROCESSED" - - -class StringParamType(ParamType[str]): - name = "text" - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> str: - if isinstance(value, bytes): - enc = _get_argv_encoding() - try: - value = value.decode(enc) - except UnicodeError: - fs_enc = sys.getfilesystemencoding() - if fs_enc != enc: - try: - value = value.decode(fs_enc) - except UnicodeError: - value = value.decode("utf-8", "replace") - else: - value = value.decode("utf-8", "replace") - return value # type: ignore[no-any-return] - return str(value) - - def __repr__(self) -> str: - return "STRING" - - -class ChoiceInfoDict(ParamTypeInfoDict): - choices: cabc.Sequence[t.Any] - case_sensitive: bool - - -class Choice(ParamType[ParamTypeValue], t.Generic[ParamTypeValue]): - """The choice type allows a value to be checked against a fixed set - of supported values. - - You may pass any iterable value which will be converted to a tuple - and thus will only be iterated once. - - The resulting value will always be one of the originally passed choices. - See :meth:`normalize_choice` for more info on the mapping of strings - to choices. See :ref:`choice-opts` for an example. - - :param case_sensitive: Set to false to make choices case - insensitive. Defaults to true. - - .. versionchanged:: 8.4.0 - Now generic in the choice value type. Parameterize with the type of - the choice values (``Choice[HashType]`` for an enum, ``Choice[str]`` - for plain strings) to enable type-checked consumers. - - .. versionchanged:: 8.2.0 - Non-``str`` ``choices`` are now supported. It can additionally be any - iterable. Before you were not recommended to pass anything but a list or - tuple. - - .. versionadded:: 8.2.0 - Choice normalization can be overridden via :meth:`normalize_choice`. - """ - - name = "choice" - - def __init__( - self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True - ) -> None: - self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices) - self.case_sensitive = case_sensitive - - def to_info_dict(self) -> ChoiceInfoDict: - return { - "choices": self.choices, - "case_sensitive": self.case_sensitive, - **super().to_info_dict(), - } - - def _normalized_mapping( - self, ctx: Context | None = None - ) -> cabc.Mapping[ParamTypeValue, str]: - """ - Returns mapping where keys are the original choices and the values are - the normalized values that are accepted via the command line. - - This is a simple wrapper around :meth:`normalize_choice`, use that - instead which is supported. - """ - return { - choice: self.normalize_choice( - choice=choice, - ctx=ctx, - ) - for choice in self.choices - } - - def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str: - """ - Normalize a choice value, used to map a passed string to a choice. - Each choice must have a unique normalized value. - - By default uses :meth:`Context.token_normalize_func` and if not case - sensitive, convert it to a casefolded value. - - .. versionadded:: 8.2.0 - """ - normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice) - - if ctx is not None and ctx.token_normalize_func is not None: - normed_value = ctx.token_normalize_func(normed_value) - - if not self.case_sensitive: - normed_value = normed_value.casefold() - - return normed_value - - def get_metavar(self, param: Parameter, ctx: Context) -> str | None: - if param.param_type_name == "option" and not param.show_choices: # type: ignore - choice_metavars = [ - convert_type(type(choice)).name.upper() for choice in self.choices - ] - choices_str = "|".join([*dict.fromkeys(choice_metavars)]) - else: - choices_str = "|".join( - [str(i) for i in self._normalized_mapping(ctx=ctx).values()] - ) - - # 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, ctx: Context | None) -> str: - """ - Message shown when no choice is passed. - - .. versionchanged:: 8.2.0 Added ``ctx`` argument. - """ - return _("Choose from:\n\t{choices}").format( - choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values()) - ) - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> ParamTypeValue: - """ - For a given value from the parser, normalize it and find its - matching normalized value in the list of choices. Then return the - matched "original" choice. - """ - normed_value = self.normalize_choice(choice=value, ctx=ctx) - normalized_mapping = self._normalized_mapping(ctx=ctx) - - try: - return next( - original - for original, normalized in normalized_mapping.items() - if normalized == normed_value - ) - except StopIteration: - self.fail( - self.get_invalid_choice_message(value=value, ctx=ctx), - param=param, - ctx=ctx, - ) - - def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str: - """Get the error message when the given choice is invalid. - - :param value: The invalid value. - - .. versionadded:: 8.2 - """ - choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values())) - return 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) - - def __repr__(self) -> str: - return _("Choice({choices})").format(choices=list(self.choices)) - - def shell_complete( - self, ctx: Context, param: Parameter, incomplete: str - ) -> 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 = [self.normalize_choice(choice, ctx) for choice in 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 DateTimeInfoDict(ParamTypeInfoDict): - formats: cabc.Sequence[str] - - -class DateTime(ParamType[datetime]): - """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: cabc.Sequence[str] | None = None): - self.formats: cabc.Sequence[str] = formats or [ - "%Y-%m-%d", - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%d %H:%M:%S", - ] - - def to_info_dict(self) -> DateTimeInfoDict: - return {"formats": self.formats, **super().to_info_dict()} - - def get_metavar(self, param: Parameter, ctx: Context) -> str | None: - return f"[{'|'.join(self.formats)}]" - - def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None: - try: - return datetime.strptime(value, format) - except ValueError: - return None - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> datetime: - 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[ParamTypeValue]): - _number_class: t.Callable[[t.Any], ParamTypeValue] - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> ParamTypeValue: - 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 NumberRangeInfoDict(ParamTypeInfoDict): - min: float | None - max: float | None - min_open: bool - max_open: bool - clamp: bool - - -class _NumberRangeBase(_NumberParamTypeBase[ParamTypeValue]): - def __init__( - self, - min: float | None = None, - max: float | None = 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) -> NumberRangeInfoDict: - return { - "min": self.min, - "max": self.max, - "min_open": self.min_open, - "max_open": self.max_open, - "clamp": self.clamp, - **super().to_info_dict(), - } - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> ParamTypeValue: - import operator - - rv = super().convert(value, param, ctx) - min = self.min - max = self.max - lt_min: bool = min is not None and ( - operator.le if self.min_open else operator.lt - )(rv, min) # type: ignore[arg-type] - gt_max: bool = max is not None and ( - operator.ge if self.max_open else operator.gt - )(rv, max) # type: ignore[arg-type] - - if self.clamp: - if min is not None and lt_min: - return self._clamp(min, 1, self.min_open) # type: ignore[arg-type] - - if max is not None and gt_max: - return self._clamp(max, -1, self.max_open) # type: ignore[arg-type] - - 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 - - @abc.abstractmethod - def _clamp( - self, bound: ParamTypeValue, dir: t.Literal[1, -1], open: bool - ) -> ParamTypeValue: - """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. - """ - ... - - 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[int]): - name = "integer" - _number_class = int - - def __repr__(self) -> str: - return "INT" - - -class IntRange(_NumberRangeBase[int], 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(self, bound: int, dir: t.Literal[1, -1], open: bool) -> int: - if not open: - return bound - - return bound + dir - - -class FloatParamType(_NumberParamTypeBase[float]): - name = "float" - _number_class = float - - def __repr__(self) -> str: - return "FLOAT" - - -class FloatRange(_NumberRangeBase[float], 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: float | None = None, - max: float | None = 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: t.Literal[1, -1], open: bool) -> float: - if not open: - return bound - - # Could use 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[bool]): - name = "boolean" - - bool_states: dict[str, bool] = { - "1": True, - "0": False, - "yes": True, - "no": False, - "true": True, - "false": False, - "on": True, - "off": False, - "t": True, - "f": False, - "y": True, - "n": False, - # Absence of value is considered False. - "": False, - } - """A mapping of string values to boolean states. - - Mapping is inspired by :py:attr:`configparser.ConfigParser.BOOLEAN_STATES` - and extends it. - - .. caution:: - String values are lower-cased, as the ``str_to_bool`` comparison function - below is case-insensitive. - - .. warning:: - The mapping is not exhaustive, and does not cover all possible boolean strings - representations. It will remains as it is to avoid endless bikeshedding. - - Future work my be considered to make this mapping user-configurable from public - API. - """ - - @staticmethod - def str_to_bool(value: str | bool) -> bool | None: - """Convert a string to a boolean value. - - If the value is already a boolean, it is returned as-is. If the value is a - string, it is stripped of whitespaces and lower-cased, then checked against - the known boolean states pre-defined in the `BoolParamType.bool_states` mapping - above. - - Returns `None` if the value does not match any known boolean state. - """ - if isinstance(value, bool): - return value - return BoolParamType.bool_states.get(value.strip().lower()) - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> bool: - normalized = self.str_to_bool(value) - if normalized is None: - self.fail( - _( - "{value!r} is not a valid boolean. Recognized values: {states}" - ).format(value=value, states=", ".join(sorted(self.bool_states))), - param, - ctx, - ) - return normalized - - def __repr__(self) -> str: - return "BOOL" - - -class UUIDParameterType(ParamType[uuid.UUID]): - name = "uuid" - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> uuid.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 FileInfoDict(ParamTypeInfoDict): - mode: str - encoding: str | None - - -class File(ParamType[t.IO[t.Any]]): - """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. - - 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. - - .. versionchanged:: 2.0 - Added the ``atomic`` parameter. - """ - - name = "filename" - envvar_list_splitter: t.ClassVar[str] = os.path.pathsep - - def __init__( - self, - mode: str = "r", - encoding: str | None = None, - errors: str | None = "strict", - lazy: bool | None = 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) -> FileInfoDict: - return { - "mode": self.mode, - "encoding": self.encoding, - **super().to_info_dict(), - } - - def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool: - if self.lazy is not None: - return self.lazy - if os.fspath(value) == "-": - return False - elif "w" in self.mode: - return True - return False - - def convert( - self, - value: str | os.PathLike[str] | t.IO[t.Any], - param: Parameter | None, - ctx: Context | None, - ) -> t.IO[t.Any]: - if _is_file_like(value): - return value - - value = t.cast("str | os.PathLike[str]", value) - - try: - lazy = self.resolve_lazy_flag(value) - - if lazy: - lf = LazyFile( - value, self.mode, self.encoding, self.errors, atomic=self.atomic - ) - - if ctx is not None: - ctx.call_on_close(lf.close_intelligently) - - return t.cast("t.IO[t.Any]", lf) - - f, should_close = open_stream( - value, self.mode, self.encoding, self.errors, atomic=self.atomic - ) - - # If a context is provided, we automatically close the file - # at the end of the context execution (or flush out). If a - # context does not exist, it's the caller's responsibility to - # properly close the file. This for instance happens when the - # type is used with prompts. - if ctx is not None: - if should_close: - ctx.call_on_close(safecall(f.close)) - else: - ctx.call_on_close(safecall(f.flush)) - - return f - except OSError as e: - self.fail( - f"'{format_filename(value)}': {e.strerror}", - param, - ctx, - ) - - def shell_complete( - self, ctx: Context, param: Parameter, incomplete: str - ) -> list[CompletionItem]: - """Return a special completion marker that tells the completion - system to use the shell to provide file path completions. - - :param ctx: Invocation context for this command. - :param param: The parameter that is requesting completion. - :param incomplete: Value being completed. May be empty. - - .. versionadded:: 8.0 - """ - from click.shell_completion import CompletionItem - - return [CompletionItem(incomplete, type="file")] - - -def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]: - return hasattr(value, "read") or hasattr(value, "write") - - -class PathInfoDict(ParamTypeInfoDict): - exists: bool - file_okay: bool - dir_okay: bool - writable: bool - readable: bool - allow_dash: bool - - -class Path(ParamType[str | bytes | os.PathLike[str]]): - """The ``Path`` type is similar to the :class:`File` type, but - returns the filename instead of an open file. Various checks can be - enabled to validate the type of file and permissions. - - :param exists: The file or directory needs to exist for the value to - be valid. If this is not set to ``True``, and the file does not - exist, then all further checks are silently skipped. - :param file_okay: Allow a file as a value. - :param dir_okay: Allow a directory as a value. - :param readable: if true, a readable check is performed. - :param writable: if true, a writable check is performed. - :param executable: if true, an executable check is performed. - :param resolve_path: Make the value absolute and resolve any - symlinks. A ``~`` is not expanded, as this is supposed to be - done by the shell only. - :param allow_dash: Allow a single dash as a value, which indicates - a standard stream (but does not open it). Use - :func:`~click.open_file` to handle opening this value. - :param path_type: Convert the incoming path value to this type. If - ``None``, keep Python's default, which is ``str``. Useful to - convert to :class:`pathlib.Path`. - - .. versionchanged:: 8.1 - Added the ``executable`` parameter. - - .. versionchanged:: 8.0 - Allow passing ``path_type=pathlib.Path``. - - .. versionchanged:: 6.0 - Added the ``allow_dash`` parameter. - """ - - envvar_list_splitter: t.ClassVar[str] = os.path.pathsep - - def __init__( - self, - exists: bool = False, - file_okay: bool = True, - dir_okay: bool = True, - writable: bool = False, - readable: bool = True, - resolve_path: bool = False, - allow_dash: bool = False, - path_type: type[t.Any] | None = None, - executable: bool = False, - ): - self.exists = exists - self.file_okay = file_okay - self.dir_okay = dir_okay - self.readable = readable - self.writable = writable - self.executable = executable - self.resolve_path = resolve_path - self.allow_dash = allow_dash - self.type = path_type - - if self.file_okay and not self.dir_okay: - self.name: str = _("file") - elif self.dir_okay and not self.file_okay: - self.name = _("directory") - else: - self.name = _("path") - - def to_info_dict(self) -> PathInfoDict: - return { - "exists": self.exists, - "file_okay": self.file_okay, - "dir_okay": self.dir_okay, - "writable": self.writable, - "readable": self.readable, - "allow_dash": self.allow_dash, - **super().to_info_dict(), - } - - def coerce_path_result( - self, value: str | os.PathLike[str] - ) -> str | bytes | os.PathLike[str]: - if self.type is not None and not isinstance(value, self.type): - if self.type is str: - return os.fsdecode(value) - elif self.type is bytes: - return os.fsencode(value) - else: - return t.cast("os.PathLike[str]", self.type(value)) - - return value - - def convert( - self, - value: str | os.PathLike[str], - param: Parameter | None, - ctx: Context | None, - ) -> str | bytes | os.PathLike[str]: - rv = value - - is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") - - if not is_dash: - if self.resolve_path: - rv = os.path.realpath(rv) - - try: - st = os.stat(rv) - except OSError: - if not self.exists: - return self.coerce_path_result(rv) - self.fail( - _("{name} {filename!r} does not exist.").format( - name=self.name.title(), filename=format_filename(value) - ), - param, - ctx, - ) - - if not self.file_okay and stat.S_ISREG(st.st_mode): - self.fail( - _("{name} {filename!r} is a file.").format( - name=self.name.title(), filename=format_filename(value) - ), - param, - ctx, - ) - if not self.dir_okay and stat.S_ISDIR(st.st_mode): - self.fail( - _("{name} {filename!r} is a directory.").format( - name=self.name.title(), filename=format_filename(value) - ), - param, - ctx, - ) - - if self.readable and not os.access(rv, os.R_OK): - self.fail( - _("{name} {filename!r} is not readable.").format( - name=self.name.title(), filename=format_filename(value) - ), - param, - ctx, - ) - - if self.writable and not os.access(rv, os.W_OK): - self.fail( - _("{name} {filename!r} is not writable.").format( - name=self.name.title(), filename=format_filename(value) - ), - param, - ctx, - ) - - if self.executable and not os.access(value, os.X_OK): - self.fail( - _("{name} {filename!r} is not executable.").format( - name=self.name.title(), filename=format_filename(value) - ), - param, - ctx, - ) - - return self.coerce_path_result(rv) - - def shell_complete( - self, ctx: Context, param: Parameter, incomplete: str - ) -> 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 TupleInfoDict(ParamTypeInfoDict): - types: cabc.Sequence[ParamTypeInfoDict] - - -class Tuple(CompositeParamType[tuple[t.Any, ...]]): - """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: cabc.Sequence[type[t.Any] | ParamType[t.Any]]) -> None: - self.types: cabc.Sequence[ParamType[t.Any]] = [convert_type(ty) for ty in types] - - def to_info_dict(self) -> TupleInfoDict: - return { - "types": [ty.to_info_dict() for ty in self.types], - **super().to_info_dict(), - } - - @property - def name(self) -> str: # type: ignore[override] - return f"<{' '.join(ty.name for ty in self.types)}>" - - @property - def arity(self) -> int: # type: ignore[override] - return len(self.types) - - def convert( - self, value: t.Any, param: Parameter | None, ctx: Context | None - ) -> tuple[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, strict=False) - ) - - -def _guess_type( - ty: type[t.Any] | ParamType[t.Any] | None, - default: t.Any | None, -) -> type[t.Any] | tuple[type[t.Any], ...] | ParamType[t.Any] | None: - """Infer a type from *ty* or *default*. - - Returns *ty* unchanged when it is not ``None``. Otherwise inspects - *default* to produce a ``type``, a ``tuple`` of types (for tuple - defaults), or ``None``. - """ - if ty is not None: - return ty - - if default is None: - return None - - if not isinstance(default, (tuple, list)): - return type(default) - - # If the default is empty, return None so convert_type falls - # through to STRING. - if not default: - return None - - item = default[0] - - # A sequence of iterables needs to detect the inner types. - # Can't call convert_type recursively because that would - # incorrectly unwind the tuple to a single type. - if isinstance(item, (tuple, list)): - return tuple(map(type, item)) - - return type(item) - - -@t.overload -def convert_type(ty: None, default: None = None) -> StringParamType: ... - - -@t.overload -def convert_type( - ty: type[t.Any] | ParamType[t.Any], default: t.Any | None = None -) -> ParamType[t.Any]: ... - - -@t.overload -def convert_type( - ty: t.Any | None, default: t.Any | None = None -) -> ParamType[t.Any]: ... - - -def convert_type( - ty: t.Any | None = None, default: t.Any | None = None -) -> ParamType[t.Any]: - """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 = _guess_type(ty, default) - is_guessed = guessed is not ty - - if isinstance(guessed, tuple): - return Tuple(guessed) - - if isinstance(guessed, ParamType): - return guessed - - if guessed is str or guessed is None: - return STRING - - if guessed is int: - return INT - - if guessed is float: - return FLOAT - - if guessed is bool: - return BOOL - - if is_guessed: - return STRING - - if __debug__: - try: - if issubclass(guessed, ParamType): - raise AssertionError( - f"Attempted to use an uninstantiated parameter type ({guessed})." - ) - except TypeError: - # guessed is an instance (correct), so issubclass fails. - pass - - return FuncParamType(guessed) - - -#: 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 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() - - -class OptionHelpExtra(t.TypedDict, total=False): - envvars: tuple[str, ...] - default: str - range: str - required: str diff --git a/server/venv/Lib/site-packages/click/utils.py b/server/venv/Lib/site-packages/click/utils.py deleted file mode 100644 index 67922b8..0000000 --- a/server/venv/Lib/site-packages/click/utils.py +++ /dev/null @@ -1,641 +0,0 @@ -from __future__ import annotations - -import collections.abc as cabc -import os -import re -import sys -import typing as t -from functools import update_wrapper -from gettext import gettext as _ -from types import ModuleType -from types import TracebackType - -from ._compat import _default_text_stderr -from ._compat import _default_text_stdout -from ._compat import _find_binary_writer -from ._compat import auto_wrap_for_ansi -from ._compat import binary_streams -from ._compat import open_stream -from ._compat import should_strip_ansi -from ._compat import strip_ansi -from ._compat import text_streams -from ._compat import WIN -from .globals import resolve_color_default - -if t.TYPE_CHECKING: - import typing_extensions as te - - P = te.ParamSpec("P") - -R = t.TypeVar("R") - - -def _posixify(name: str) -> str: - return "-".join(name.split()).lower() - - -def safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]: - """Wraps a function so that it swallows exceptions.""" - - def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None: - try: - return func(*args, **kwargs) - except Exception: - pass - return None - - return update_wrapper(wrapper, func) - - -def make_str(value: t.Any) -> str: - """Converts a value into a valid string.""" - if isinstance(value, bytes): - try: - return value.decode(sys.getfilesystemencoding()) - except UnicodeError: - return value.decode("utf-8", "replace") - return str(value) - - -def make_default_short_help(help: str, max_length: int = 45) -> str: - """Returns a condensed version of help string. - - :meta private: - """ - # 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. - """ - - name: str - mode: str - encoding: str | None - errors: str | None - atomic: bool - _f: t.IO[t.Any] | None - should_close: bool - - def __init__( - self, - filename: str | os.PathLike[str], - mode: str = "r", - encoding: str | None = None, - errors: str | None = "strict", - atomic: bool = False, - ) -> None: - self.name = os.fspath(filename) - self.mode = mode - self.encoding = encoding - self.errors = errors - self.atomic = atomic - - if self.name == "-": - self._f, self.should_close = open_stream(filename, mode, encoding, errors) - else: - if "r" in mode: - # Open and close the file in case we're opening it for - # reading so that we can catch at least some errors in - # some cases early. - open(filename, mode).close() - self._f = None - self.should_close = True - - def __getattr__(self, name: str) -> t.Any: - return getattr(self.open(), name) - - def __repr__(self) -> str: - if self._f is not None: - return repr(self._f) - return f"" - - def open(self) -> t.IO[t.Any]: - """Opens the file if it's not yet open. This call might fail with - a :exc:`FileError`. Not handling this error will produce an error - that Click shows. - """ - if self._f is not None: - return self._f - try: - rv, self.should_close = open_stream( - self.name, self.mode, self.encoding, self.errors, atomic=self.atomic - ) - except OSError as e: - 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: type[BaseException] | None, - exc_value: BaseException | None, - tb: TracebackType | None, - ) -> None: - self.close_intelligently() - - def __iter__(self) -> cabc.Iterator[t.AnyStr]: - self.open() - return iter(self._f) # type: ignore - - -class KeepOpenFile: - _file: t.IO[t.Any] - - def __init__(self, file: t.IO[t.Any]) -> 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: type[BaseException] | None, - exc_value: BaseException | None, - tb: TracebackType | None, - ) -> None: - pass - - def __repr__(self) -> str: - return repr(self._file) - - def __iter__(self) -> cabc.Iterator[t.AnyStr]: - return iter(self._file) - - -def echo( - message: object = None, - file: t.IO[t.Any] | None = None, - nl: bool = True, - err: bool = False, - color: bool | None = None, -) -> None: - """Print a message and newline to stdout or a file. This should be - used instead of :func:`print` because it provides better support - for different data, files, and environments. - - Compared to :func:`print`, this does the following: - - - Ensures that the output encoding is not misconfigured on Linux. - - Supports Unicode in the Windows console. - - Supports writing to binary outputs, and supports writing bytes - to text outputs. - - Supports colors and styles on Windows. - - Removes ANSI color and style codes if the output does not look - like an interactive terminal. - - Always flushes the output. - - :param message: The string or bytes to output. Other objects are - converted to strings. - :param file: The file to write to. Defaults to ``stdout``. - :param err: Write to ``stderr`` instead of ``stdout``. - :param nl: Print a newline after the message. Enabled by default. - :param color: Force showing or hiding colors and other styles. By - default Click will remove color if the output does not look like - an interactive terminal. - - .. versionchanged:: 6.0 - Support Unicode output on the Windows console. Click does not - modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` - will still not support Unicode. - - .. versionchanged:: 4.0 - Added the ``color`` parameter. - - .. versionadded:: 3.0 - Added the ``err`` parameter. - - .. versionchanged:: 2.0 - Support colors on Windows if colorama is installed. - """ - if file is None: - if err: - file = _default_text_stderr() - else: - file = _default_text_stdout() - - # There are no standard streams attached to write to. For example, - # pythonw on Windows. - if file is None: - return - - match message: - case str() | bytes() | bytearray(): - out = message - case None: - out = "" - case _: - out = str(message) - - if nl: - 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, color) # type: ignore - elif not color: - out = strip_ansi(out) - - file.write(out) # type: ignore - file.flush() - - -def get_binary_stream(name: t.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(_("Unknown standard stream '{name}'").format(name=name)) - return opener() - - -def get_text_stream( - name: t.Literal["stdin", "stdout", "stderr"], - encoding: str | None = None, - errors: str | None = "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(_("Unknown standard stream '{name}'").format(name=name)) - return opener(encoding, errors) - - -def open_file( - filename: str | os.PathLike[str], - mode: str = "r", - encoding: str | None = None, - errors: str | None = "strict", - lazy: bool = False, - atomic: bool = False, -) -> t.IO[t.Any]: - """Open a file, with extra behavior to handle ``'-'`` to indicate - a standard stream, lazy open on write, and atomic write. Similar to - the behavior of the :class:`~click.File` param type. - - If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is - wrapped so that using it in a context manager will not close it. - This makes it possible to use the function without accidentally - closing a standard stream: - - .. code-block:: python - - with open_file(filename) as f: - ... - - :param filename: The name or Path of the file to open, or ``'-'`` for - ``stdin``/``stdout``. - :param mode: The mode in which to open the file. - :param encoding: The encoding to decode or encode a file opened in - text mode. - :param errors: The error handling mode. - :param lazy: Wait to open the file until it is accessed. For read - mode, the file is temporarily opened to raise access errors - early, then closed until it is read again. - :param atomic: Write to a temporary file and replace the given file - on close. - - .. versionadded:: 3.0 - """ - if lazy: - return t.cast( - "t.IO[t.Any]", LazyFile(filename, mode, encoding, errors, atomic=atomic) - ) - - f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) - - if not should_close: - f = t.cast("t.IO[t.Any]", KeepOpenFile(f)) - - return f - - -def format_filename( - filename: str | bytes | os.PathLike[str] | os.PathLike[bytes], - shorten: bool = False, -) -> str: - """Format a filename as a string for display. Ensures the filename can be - displayed by replacing any invalid bytes or surrogate escapes in the name - with the replacement character ``�``. - - Invalid bytes or surrogate escapes will raise an error when written to a - stream with ``errors="strict"``. This will typically happen with ``stdout`` - when the locale is something like ``en_GB.UTF-8``. - - Many scenarios *are* safe to write surrogates though, due to PEP 538 and - PEP 540, including: - - - Writing to ``stderr``, which uses ``errors="backslashreplace"``. - - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens - stdout and stderr with ``errors="surrogateescape"``. - - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. - - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. - Python opens stdout and stderr with ``errors="surrogateescape"``. - - :param filename: formats a filename for UI display. This will also convert - the filename into unicode without failing. - :param shorten: this optionally shortens the filename to strip of the - path that leads up to it. - """ - if shorten: - filename = os.path.basename(filename) - else: - filename = os.fspath(filename) - - if isinstance(filename, bytes): - filename = filename.decode(sys.getfilesystemencoding(), "replace") - else: - filename = filename.encode("utf-8", "surrogateescape").decode( - "utf-8", "replace" - ) - - return filename - - -def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: - r"""Returns the config folder for the application. The default behavior - is to return whatever is most appropriate for the operating system. - - To give you an idea, for an app called ``"Foo Bar"``, something like - the following folders could be returned: - - Mac OS X: - ``~/Library/Application Support/Foo Bar`` - Mac OS X (POSIX): - ``~/.foo-bar`` - Unix: - ``~/.config/foo-bar`` - Unix (POSIX): - ``~/.foo-bar`` - Windows (roaming): - ``C:\Users\\AppData\Roaming\Foo Bar`` - Windows (not roaming): - ``C:\Users\\AppData\Local\Foo Bar`` - - .. versionadded:: 2.0 - - :param app_name: the application name. This should be properly capitalized - and can contain whitespace. - :param roaming: controls if the folder should be roaming or not on Windows. - Has no effect otherwise. - :param force_posix: if this is set to `True` then on any POSIX system the - folder will be stored in the home folder with a leading - dot instead of the XDG config home or darwin's - application support folder. - """ - if WIN: - key = "APPDATA" if roaming else "LOCALAPPDATA" - folder = os.environ.get(key) - if folder is None: - folder = os.path.expanduser("~") - return os.path.join(folder, app_name) - if force_posix: - return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) - if sys.platform == "darwin": - return os.path.join( - os.path.expanduser("~/Library/Application Support"), app_name - ) - return os.path.join( - os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), - _posixify(app_name), - ) - - -class PacifyFlushWrapper: - """This wrapper is used to catch and suppress BrokenPipeErrors resulting - from ``.flush()`` being called on broken pipe during the shutdown/final-GC - of the Python interpreter. Notably ``.flush()`` is always called on - ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any - other cleanup code, and the case where the underlying file is not a broken - pipe, all calls and attributes are proxied. - """ - - wrapped: t.IO[t.Any] - - def __init__(self, wrapped: t.IO[t.Any]) -> None: - self.wrapped = wrapped - - def flush(self) -> None: - try: - self.wrapped.flush() - except OSError as e: - import errno - - if e.errno != errno.EPIPE: - raise - - def __getattr__(self, attr: str) -> t.Any: - return getattr(self.wrapped, attr) - - -def _detect_program_name( - path: str | None = None, _main: ModuleType | None = None -) -> str: - """Determine the command used to run the program, for use in help - text. If a file or entry point was executed, the file name is - returned. If ``python -m`` was used to execute a module or package, - ``python -m name`` is returned. - - This doesn't try to be too precise, the goal is to give a concise - name for help text. Files are only shown as their name without the - path. ``python`` is only shown for modules, and the full path to - ``sys.executable`` is not shown. - - :param path: The Python file being executed. Python puts this in - ``sys.argv[0]``, which is used by default. - :param _main: The ``__main__`` module. This should only be passed - during internal testing. - - .. versionadded:: 8.0 - Based on command args detection in the Werkzeug reloader. - - :meta private: - """ - if _main is None: - _main = sys.modules["__main__"] - - if not path: - path = sys.argv[0] - - # The value of __package__ indicates how Python was called. It may - # not exist if a setuptools script is installed as an egg. It may be - # set incorrectly for entry points created with pip on Windows. - # It is set to "" inside a Shiv or PEX zipapp. - if getattr(_main, "__package__", None) in {None, ""} or ( - os.name == "nt" - and _main.__package__ == "" - and not os.path.exists(path) - and os.path.exists(f"{path}.exe") - ): - # Executed a file, like "python app.py". - return os.path.basename(path) - - # Executed a module, like "python -m example". - # Rewritten by Python from "-m script" to "/path/to/script.py". - # Need to look at main module to determine how it was executed. - py_module = t.cast(str, _main.__package__) - name = os.path.splitext(os.path.basename(path))[0] - - # A submodule like "example.cli". - if name != "__main__": - py_module = f"{py_module}.{name}" - - return f"python -m {py_module.lstrip('.')}" - - -def _expand_args( - args: cabc.Iterable[str], - *, - user: bool = True, - env: bool = True, - glob_recursive: bool = True, -) -> list[str]: - """Simulate Unix shell expansion with Python functions. - - See :func:`glob.glob`, :func:`os.path.expanduser`, and - :func:`os.path.expandvars`. - - This is intended for use on Windows, where the shell does not do any - expansion. It may not exactly match what a Unix shell would do. - - :param args: List of command line arguments to expand. - :param user: Expand user home directory. - :param env: Expand environment variables. - :param glob_recursive: ``**`` matches directories recursively. - - .. versionchanged:: 8.1 - Invalid glob patterns are treated as empty expansions rather - than raising an error. - - .. versionadded:: 8.0 - - :meta private: - """ - from glob import glob - - out = [] - - for arg in args: - if user: - arg = os.path.expanduser(arg) - - if env: - arg = os.path.expandvars(arg) - - try: - matches = glob(arg, recursive=glob_recursive) - except re.error: - matches = [] - - if not matches: - out.append(arg) - else: - out.extend(matches) - - return out diff --git a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER b/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA b/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA deleted file mode 100644 index a1b5c57..0000000 --- a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/METADATA +++ /dev/null @@ -1,441 +0,0 @@ -Metadata-Version: 2.1 -Name: colorama -Version: 0.4.6 -Summary: Cross-platform colored terminal text. -Project-URL: Homepage, https://github.com/tartley/colorama -Author-email: Jonathan Hartley -License-File: LICENSE.txt -Keywords: ansi,color,colour,crossplatform,terminal,text,windows,xplatform -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Console -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Terminals -Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 -Description-Content-Type: text/x-rst - -.. image:: https://img.shields.io/pypi/v/colorama.svg - :target: https://pypi.org/project/colorama/ - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/pyversions/colorama.svg - :target: https://pypi.org/project/colorama/ - :alt: Supported Python versions - -.. image:: https://github.com/tartley/colorama/actions/workflows/test.yml/badge.svg - :target: https://github.com/tartley/colorama/actions/workflows/test.yml - :alt: Build Status - -Colorama -======== - -Makes ANSI escape character sequences (for producing colored terminal text and -cursor positioning) work under MS Windows. - -.. |donate| image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif - :target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2MZ9D2GMLYCUJ&item_name=Colorama¤cy_code=USD - :alt: Donate with Paypal - -`PyPI for releases `_ | -`Github for source `_ | -`Colorama for enterprise on Tidelift `_ - -If you find Colorama useful, please |donate| to the authors. Thank you! - -Installation ------------- - -Tested on CPython 2.7, 3.7, 3.8, 3.9 and 3.10 and Pypy 2.7 and 3.8. - -No requirements other than the standard library. - -.. code-block:: bash - - pip install colorama - # or - conda install -c anaconda colorama - -Description ------------ - -ANSI escape character sequences have long been used to produce colored terminal -text and cursor positioning on Unix and Macs. Colorama makes this work on -Windows, too, by wrapping ``stdout``, stripping ANSI sequences it finds (which -would appear as gobbledygook in the output), and converting them into the -appropriate win32 calls to modify the state of the terminal. On other platforms, -Colorama does nothing. - -This has the upshot of providing a simple cross-platform API for printing -colored terminal text from Python, and has the happy side-effect that existing -applications or libraries which use ANSI sequences to produce colored output on -Linux or Macs can now also work on Windows, simply by calling -``colorama.just_fix_windows_console()`` (since v0.4.6) or ``colorama.init()`` -(all versions, but may have other side-effects – see below). - -An alternative approach is to install ``ansi.sys`` on Windows machines, which -provides the same behaviour for all applications running in terminals. Colorama -is intended for situations where that isn't easy (e.g., maybe your app doesn't -have an installer.) - -Demo scripts in the source code repository print some colored text using -ANSI sequences. Compare their output under Gnome-terminal's built in ANSI -handling, versus on Windows Command-Prompt using Colorama: - -.. image:: https://github.com/tartley/colorama/raw/master/screenshots/ubuntu-demo.png - :width: 661 - :height: 357 - :alt: ANSI sequences on Ubuntu under gnome-terminal. - -.. image:: https://github.com/tartley/colorama/raw/master/screenshots/windows-demo.png - :width: 668 - :height: 325 - :alt: Same ANSI sequences on Windows, using Colorama. - -These screenshots show that, on Windows, Colorama does not support ANSI 'dim -text'; it looks the same as 'normal text'. - -Usage ------ - -Initialisation -.............. - -If the only thing you want from Colorama is to get ANSI escapes to work on -Windows, then run: - -.. code-block:: python - - from colorama import just_fix_windows_console - just_fix_windows_console() - -If you're on a recent version of Windows 10 or better, and your stdout/stderr -are pointing to a Windows console, then this will flip the magic configuration -switch to enable Windows' built-in ANSI support. - -If you're on an older version of Windows, and your stdout/stderr are pointing to -a Windows console, then this will wrap ``sys.stdout`` and/or ``sys.stderr`` in a -magic file object that intercepts ANSI escape sequences and issues the -appropriate Win32 calls to emulate them. - -In all other circumstances, it does nothing whatsoever. Basically the idea is -that this makes Windows act like Unix with respect to ANSI escape handling. - -It's safe to call this function multiple times. It's safe to call this function -on non-Windows platforms, but it won't do anything. It's safe to call this -function when one or both of your stdout/stderr are redirected to a file – it -won't do anything to those streams. - -Alternatively, you can use the older interface with more features (but also more -potential footguns): - -.. code-block:: python - - from colorama import init - init() - -This does the same thing as ``just_fix_windows_console``, except for the -following differences: - -- It's not safe to call ``init`` multiple times; you can end up with multiple - layers of wrapping and broken ANSI support. - -- Colorama will apply a heuristic to guess whether stdout/stderr support ANSI, - and if it thinks they don't, then it will wrap ``sys.stdout`` and - ``sys.stderr`` in a magic file object that strips out ANSI escape sequences - before printing them. This happens on all platforms, and can be convenient if - you want to write your code to emit ANSI escape sequences unconditionally, and - let Colorama decide whether they should actually be output. But note that - Colorama's heuristic is not particularly clever. - -- ``init`` also accepts explicit keyword args to enable/disable various - functionality – see below. - -To stop using Colorama before your program exits, simply call ``deinit()``. -This will restore ``stdout`` and ``stderr`` to their original values, so that -Colorama is disabled. To resume using Colorama again, call ``reinit()``; it is -cheaper than calling ``init()`` again (but does the same thing). - -Most users should depend on ``colorama >= 0.4.6``, and use -``just_fix_windows_console``. The old ``init`` interface will be supported -indefinitely for backwards compatibility, but we don't plan to fix any issues -with it, also for backwards compatibility. - -Colored Output -.............. - -Cross-platform printing of colored text can then be done using Colorama's -constant shorthand for ANSI escape sequences. These are deliberately -rudimentary, see below. - -.. code-block:: python - - from colorama import Fore, Back, Style - print(Fore.RED + 'some red text') - print(Back.GREEN + 'and with a green background') - print(Style.DIM + 'and in dim text') - print(Style.RESET_ALL) - print('back to normal now') - -...or simply by manually printing ANSI sequences from your own code: - -.. code-block:: python - - print('\033[31m' + 'some red text') - print('\033[39m') # and reset to default color - -...or, Colorama can be used in conjunction with existing ANSI libraries -such as the venerable `Termcolor `_ -the fabulous `Blessings `_, -or the incredible `_Rich `_. - -If you wish Colorama's Fore, Back and Style constants were more capable, -then consider using one of the above highly capable libraries to generate -colors, etc, and use Colorama just for its primary purpose: to convert -those ANSI sequences to also work on Windows: - -SIMILARLY, do not send PRs adding the generation of new ANSI types to Colorama. -We are only interested in converting ANSI codes to win32 API calls, not -shortcuts like the above to generate ANSI characters. - -.. code-block:: python - - from colorama import just_fix_windows_console - from termcolor import colored - - # use Colorama to make Termcolor work on Windows too - just_fix_windows_console() - - # then use Termcolor for all colored text output - print(colored('Hello, World!', 'green', 'on_red')) - -Available formatting constants are:: - - Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. - Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. - Style: DIM, NORMAL, BRIGHT, RESET_ALL - -``Style.RESET_ALL`` resets foreground, background, and brightness. Colorama will -perform this reset automatically on program exit. - -These are fairly well supported, but not part of the standard:: - - Fore: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX - Back: LIGHTBLACK_EX, LIGHTRED_EX, LIGHTGREEN_EX, LIGHTYELLOW_EX, LIGHTBLUE_EX, LIGHTMAGENTA_EX, LIGHTCYAN_EX, LIGHTWHITE_EX - -Cursor Positioning -.................. - -ANSI codes to reposition the cursor are supported. See ``demos/demo06.py`` for -an example of how to generate them. - -Init Keyword Args -................. - -``init()`` accepts some ``**kwargs`` to override default behaviour. - -init(autoreset=False): - If you find yourself repeatedly sending reset sequences to turn off color - changes at the end of every print, then ``init(autoreset=True)`` will - automate that: - - .. code-block:: python - - from colorama import init - init(autoreset=True) - print(Fore.RED + 'some red text') - print('automatically back to default color again') - -init(strip=None): - Pass ``True`` or ``False`` to override whether ANSI codes should be - stripped from the output. The default behaviour is to strip if on Windows - or if output is redirected (not a tty). - -init(convert=None): - Pass ``True`` or ``False`` to override whether to convert ANSI codes in the - output into win32 calls. The default behaviour is to convert if on Windows - and output is to a tty (terminal). - -init(wrap=True): - On Windows, Colorama works by replacing ``sys.stdout`` and ``sys.stderr`` - with proxy objects, which override the ``.write()`` method to do their work. - If this wrapping causes you problems, then this can be disabled by passing - ``init(wrap=False)``. The default behaviour is to wrap if ``autoreset`` or - ``strip`` or ``convert`` are True. - - When wrapping is disabled, colored printing on non-Windows platforms will - continue to work as normal. To do cross-platform colored output, you can - use Colorama's ``AnsiToWin32`` proxy directly: - - .. code-block:: python - - import sys - from colorama import init, AnsiToWin32 - init(wrap=False) - stream = AnsiToWin32(sys.stderr).stream - - # Python 2 - print >>stream, Fore.BLUE + 'blue text on stderr' - - # Python 3 - print(Fore.BLUE + 'blue text on stderr', file=stream) - -Recognised ANSI Sequences -......................... - -ANSI sequences generally take the form:: - - ESC [ ; ... - -Where ```` is an integer, and ```` is a single letter. Zero or -more params are passed to a ````. If no params are passed, it is -generally synonymous with passing a single zero. No spaces exist in the -sequence; they have been inserted here simply to read more easily. - -The only ANSI sequences that Colorama converts into win32 calls are:: - - ESC [ 0 m # reset all (colors and brightness) - ESC [ 1 m # bright - ESC [ 2 m # dim (looks same as normal brightness) - ESC [ 22 m # normal brightness - - # FOREGROUND: - ESC [ 30 m # black - ESC [ 31 m # red - ESC [ 32 m # green - ESC [ 33 m # yellow - ESC [ 34 m # blue - ESC [ 35 m # magenta - ESC [ 36 m # cyan - ESC [ 37 m # white - ESC [ 39 m # reset - - # BACKGROUND - ESC [ 40 m # black - ESC [ 41 m # red - ESC [ 42 m # green - ESC [ 43 m # yellow - ESC [ 44 m # blue - ESC [ 45 m # magenta - ESC [ 46 m # cyan - ESC [ 47 m # white - ESC [ 49 m # reset - - # cursor positioning - ESC [ y;x H # position cursor at x across, y down - ESC [ y;x f # position cursor at x across, y down - ESC [ n A # move cursor n lines up - ESC [ n B # move cursor n lines down - ESC [ n C # move cursor n characters forward - ESC [ n D # move cursor n characters backward - - # clear the screen - ESC [ mode J # clear the screen - - # clear the line - ESC [ mode K # clear the line - -Multiple numeric params to the ``'m'`` command can be combined into a single -sequence:: - - ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background - -All other ANSI sequences of the form ``ESC [ ; ... `` -are silently stripped from the output on Windows. - -Any other form of ANSI sequence, such as single-character codes or alternative -initial characters, are not recognised or stripped. It would be cool to add -them though. Let me know if it would be useful for you, via the Issues on -GitHub. - -Status & Known Problems ------------------------ - -I've personally only tested it on Windows XP (CMD, Console2), Ubuntu -(gnome-terminal, xterm), and OS X. - -Some valid ANSI sequences aren't recognised. - -If you're hacking on the code, see `README-hacking.md`_. ESPECIALLY, see the -explanation there of why we do not want PRs that allow Colorama to generate new -types of ANSI codes. - -See outstanding issues and wish-list: -https://github.com/tartley/colorama/issues - -If anything doesn't work for you, or doesn't do what you expected or hoped for, -I'd love to hear about it on that issues list, would be delighted by patches, -and would be happy to grant commit access to anyone who submits a working patch -or two. - -.. _README-hacking.md: README-hacking.md - -License -------- - -Copyright Jonathan Hartley & Arnon Yaari, 2013-2020. BSD 3-Clause license; see -LICENSE file. - -Professional support --------------------- - -.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png - :alt: Tidelift - :target: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme - -.. list-table:: - :widths: 10 100 - - * - |tideliftlogo| - - Professional support for colorama is available as part of the - `Tidelift Subscription`_. - Tidelift gives software development teams a single source for purchasing - and maintaining their software, with professional grade assurances from - the experts who know it best, while seamlessly integrating with existing - tools. - -.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-colorama?utm_source=pypi-colorama&utm_medium=referral&utm_campaign=readme - -Thanks ------- - -See the CHANGELOG for more thanks! - -* Marc Schlaich (schlamar) for a ``setup.py`` fix for Python2.5. -* Marc Abramowitz, reported & fixed a crash on exit with closed ``stdout``, - providing a solution to issue #7's setuptools/distutils debate, - and other fixes. -* User 'eryksun', for guidance on correctly instantiating ``ctypes.windll``. -* Matthew McCormick for politely pointing out a longstanding crash on non-Win. -* Ben Hoyt, for a magnificent fix under 64-bit Windows. -* Jesse at Empty Square for submitting a fix for examples in the README. -* User 'jamessp', an observant documentation fix for cursor positioning. -* User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 - fix. -* Julien Stuyck, for wisely suggesting Python3 compatible updates to README. -* Daniel Griffith for multiple fabulous patches. -* Oscar Lesta for a valuable fix to stop ANSI chars being sent to non-tty - output. -* Roger Binns, for many suggestions, valuable feedback, & bug reports. -* Tim Golden for thought and much appreciated feedback on the initial idea. -* User 'Zearin' for updates to the README file. -* John Szakmeister for adding support for light colors -* Charles Merriam for adding documentation to demos -* Jurko for a fix on 64-bit Windows CPython2.5 w/o ctypes -* Florian Bruhin for a fix when stdout or stderr are None -* Thomas Weininger for fixing ValueError on Windows -* Remi Rampin for better Github integration and fixes to the README file -* Simeon Visser for closing a file handle using 'with' and updating classifiers - to include Python 3.3 and 3.4 -* Andy Neff for fixing RESET of LIGHT_EX colors. -* Jonathan Hartley for the initial idea and implementation. diff --git a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD b/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD deleted file mode 100644 index b85b5b7..0000000 --- a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/RECORD +++ /dev/null @@ -1,31 +0,0 @@ -colorama-0.4.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -colorama-0.4.6.dist-info/METADATA,sha256=e67SnrUMOym9sz_4TjF3vxvAV4T3aF7NyqRHHH3YEMw,17158 -colorama-0.4.6.dist-info/RECORD,, -colorama-0.4.6.dist-info/WHEEL,sha256=cdcF4Fbd0FPtw2EMIOwH-3rSOTUdTCeOSXRMD1iLUb8,105 -colorama-0.4.6.dist-info/licenses/LICENSE.txt,sha256=ysNcAmhuXQSlpxQL-zs25zrtSWZW6JEQLkKIhteTAxg,1491 -colorama/__init__.py,sha256=wePQA4U20tKgYARySLEC047ucNX-g8pRLpYBuiHlLb8,266 -colorama/__pycache__/__init__.cpython-313.pyc,, -colorama/__pycache__/ansi.cpython-313.pyc,, -colorama/__pycache__/ansitowin32.cpython-313.pyc,, -colorama/__pycache__/initialise.cpython-313.pyc,, -colorama/__pycache__/win32.cpython-313.pyc,, -colorama/__pycache__/winterm.cpython-313.pyc,, -colorama/ansi.py,sha256=Top4EeEuaQdBWdteKMEcGOTeKeF19Q-Wo_6_Cj5kOzQ,2522 -colorama/ansitowin32.py,sha256=vPNYa3OZbxjbuFyaVo0Tmhmy1FZ1lKMWCnT7odXpItk,11128 -colorama/initialise.py,sha256=-hIny86ClXo39ixh5iSCfUIa2f_h_bgKRDW7gqs-KLU,3325 -colorama/tests/__init__.py,sha256=MkgPAEzGQd-Rq0w0PZXSX2LadRWhUECcisJY8lSrm4Q,75 -colorama/tests/__pycache__/__init__.cpython-313.pyc,, -colorama/tests/__pycache__/ansi_test.cpython-313.pyc,, -colorama/tests/__pycache__/ansitowin32_test.cpython-313.pyc,, -colorama/tests/__pycache__/initialise_test.cpython-313.pyc,, -colorama/tests/__pycache__/isatty_test.cpython-313.pyc,, -colorama/tests/__pycache__/utils.cpython-313.pyc,, -colorama/tests/__pycache__/winterm_test.cpython-313.pyc,, -colorama/tests/ansi_test.py,sha256=FeViDrUINIZcr505PAxvU4AjXz1asEiALs9GXMhwRaE,2839 -colorama/tests/ansitowin32_test.py,sha256=RN7AIhMJ5EqDsYaCjVo-o4u8JzDD4ukJbmevWKS70rY,10678 -colorama/tests/initialise_test.py,sha256=BbPy-XfyHwJ6zKozuQOvNvQZzsx9vdb_0bYXn7hsBTc,6741 -colorama/tests/isatty_test.py,sha256=Pg26LRpv0yQDB5Ac-sxgVXG7hsA1NYvapFgApZfYzZg,1866 -colorama/tests/utils.py,sha256=1IIRylG39z5-dzq09R_ngufxyPZxgldNbrxKxUGwGKE,1079 -colorama/tests/winterm_test.py,sha256=qoWFPEjym5gm2RuMwpf3pOis3a5r_PJZFCzK254JL8A,3709 -colorama/win32.py,sha256=YQOKwMTwtGBbsY4dL5HYTvwTeP9wIQra5MvPNddpxZs,6181 -colorama/winterm.py,sha256=XCQFDHjPi6AHYNdZwy0tA02H-Jh48Jp-HvCjeLeLp3U,7134 diff --git a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL b/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL deleted file mode 100644 index d79189f..0000000 --- a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: hatchling 1.11.1 -Root-Is-Purelib: true -Tag: py2-none-any -Tag: py3-none-any diff --git a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt b/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt deleted file mode 100644 index 3105888..0000000 --- a/server/venv/Lib/site-packages/colorama-0.4.6.dist-info/licenses/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2010 Jonathan Hartley -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of the copyright holders, nor those of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/server/venv/Lib/site-packages/colorama/__init__.py b/server/venv/Lib/site-packages/colorama/__init__.py deleted file mode 100644 index 383101c..0000000 --- a/server/venv/Lib/site-packages/colorama/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -from .initialise import init, deinit, reinit, colorama_text, just_fix_windows_console -from .ansi import Fore, Back, Style, Cursor -from .ansitowin32 import AnsiToWin32 - -__version__ = '0.4.6' - diff --git a/server/venv/Lib/site-packages/colorama/ansi.py b/server/venv/Lib/site-packages/colorama/ansi.py deleted file mode 100644 index 11ec695..0000000 --- a/server/venv/Lib/site-packages/colorama/ansi.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -''' -This module generates ANSI character codes to printing colors to terminals. -See: http://en.wikipedia.org/wiki/ANSI_escape_code -''' - -CSI = '\033[' -OSC = '\033]' -BEL = '\a' - - -def code_to_chars(code): - return CSI + str(code) + 'm' - -def set_title(title): - return OSC + '2;' + title + BEL - -def clear_screen(mode=2): - return CSI + str(mode) + 'J' - -def clear_line(mode=2): - return CSI + str(mode) + 'K' - - -class AnsiCodes(object): - def __init__(self): - # the subclasses declare class attributes which are numbers. - # Upon instantiation we define instance attributes, which are the same - # as the class attributes but wrapped with the ANSI escape sequence - for name in dir(self): - if not name.startswith('_'): - value = getattr(self, name) - setattr(self, name, code_to_chars(value)) - - -class AnsiCursor(object): - def UP(self, n=1): - return CSI + str(n) + 'A' - def DOWN(self, n=1): - return CSI + str(n) + 'B' - def FORWARD(self, n=1): - return CSI + str(n) + 'C' - def BACK(self, n=1): - return CSI + str(n) + 'D' - def POS(self, x=1, y=1): - return CSI + str(y) + ';' + str(x) + 'H' - - -class AnsiFore(AnsiCodes): - BLACK = 30 - RED = 31 - GREEN = 32 - YELLOW = 33 - BLUE = 34 - MAGENTA = 35 - CYAN = 36 - WHITE = 37 - RESET = 39 - - # These are fairly well supported, but not part of the standard. - LIGHTBLACK_EX = 90 - LIGHTRED_EX = 91 - LIGHTGREEN_EX = 92 - LIGHTYELLOW_EX = 93 - LIGHTBLUE_EX = 94 - LIGHTMAGENTA_EX = 95 - LIGHTCYAN_EX = 96 - LIGHTWHITE_EX = 97 - - -class AnsiBack(AnsiCodes): - BLACK = 40 - RED = 41 - GREEN = 42 - YELLOW = 43 - BLUE = 44 - MAGENTA = 45 - CYAN = 46 - WHITE = 47 - RESET = 49 - - # These are fairly well supported, but not part of the standard. - LIGHTBLACK_EX = 100 - LIGHTRED_EX = 101 - LIGHTGREEN_EX = 102 - LIGHTYELLOW_EX = 103 - LIGHTBLUE_EX = 104 - LIGHTMAGENTA_EX = 105 - LIGHTCYAN_EX = 106 - LIGHTWHITE_EX = 107 - - -class AnsiStyle(AnsiCodes): - BRIGHT = 1 - DIM = 2 - NORMAL = 22 - RESET_ALL = 0 - -Fore = AnsiFore() -Back = AnsiBack() -Style = AnsiStyle() -Cursor = AnsiCursor() diff --git a/server/venv/Lib/site-packages/colorama/ansitowin32.py b/server/venv/Lib/site-packages/colorama/ansitowin32.py deleted file mode 100644 index abf209e..0000000 --- a/server/venv/Lib/site-packages/colorama/ansitowin32.py +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -import re -import sys -import os - -from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style, BEL -from .winterm import enable_vt_processing, WinTerm, WinColor, WinStyle -from .win32 import windll, winapi_test - - -winterm = None -if windll is not None: - winterm = WinTerm() - - -class StreamWrapper(object): - ''' - Wraps a stream (such as stdout), acting as a transparent proxy for all - attribute access apart from method 'write()', which is delegated to our - Converter instance. - ''' - def __init__(self, wrapped, converter): - # double-underscore everything to prevent clashes with names of - # attributes on the wrapped stream object. - self.__wrapped = wrapped - self.__convertor = converter - - def __getattr__(self, name): - return getattr(self.__wrapped, name) - - def __enter__(self, *args, **kwargs): - # special method lookup bypasses __getattr__/__getattribute__, see - # https://stackoverflow.com/questions/12632894/why-doesnt-getattr-work-with-exit - # thus, contextlib magic methods are not proxied via __getattr__ - return self.__wrapped.__enter__(*args, **kwargs) - - def __exit__(self, *args, **kwargs): - return self.__wrapped.__exit__(*args, **kwargs) - - def __setstate__(self, state): - self.__dict__ = state - - def __getstate__(self): - return self.__dict__ - - def write(self, text): - self.__convertor.write(text) - - def isatty(self): - stream = self.__wrapped - if 'PYCHARM_HOSTED' in os.environ: - if stream is not None and (stream is sys.__stdout__ or stream is sys.__stderr__): - return True - try: - stream_isatty = stream.isatty - except AttributeError: - return False - else: - return stream_isatty() - - @property - def closed(self): - stream = self.__wrapped - try: - return stream.closed - # AttributeError in the case that the stream doesn't support being closed - # ValueError for the case that the stream has already been detached when atexit runs - except (AttributeError, ValueError): - return True - - -class AnsiToWin32(object): - ''' - Implements a 'write()' method which, on Windows, will strip ANSI character - sequences from the text, and if outputting to a tty, will convert them into - win32 function calls. - ''' - ANSI_CSI_RE = re.compile('\001?\033\\[((?:\\d|;)*)([a-zA-Z])\002?') # Control Sequence Introducer - ANSI_OSC_RE = re.compile('\001?\033\\]([^\a]*)(\a)\002?') # Operating System Command - - def __init__(self, wrapped, convert=None, strip=None, autoreset=False): - # The wrapped stream (normally sys.stdout or sys.stderr) - self.wrapped = wrapped - - # should we reset colors to defaults after every .write() - self.autoreset = autoreset - - # create the proxy wrapping our output stream - self.stream = StreamWrapper(wrapped, self) - - on_windows = os.name == 'nt' - # We test if the WinAPI works, because even if we are on Windows - # we may be using a terminal that doesn't support the WinAPI - # (e.g. Cygwin Terminal). In this case it's up to the terminal - # to support the ANSI codes. - conversion_supported = on_windows and winapi_test() - try: - fd = wrapped.fileno() - except Exception: - fd = -1 - system_has_native_ansi = not on_windows or enable_vt_processing(fd) - have_tty = not self.stream.closed and self.stream.isatty() - need_conversion = conversion_supported and not system_has_native_ansi - - # should we strip ANSI sequences from our output? - if strip is None: - strip = need_conversion or not have_tty - self.strip = strip - - # should we should convert ANSI sequences into win32 calls? - if convert is None: - convert = need_conversion and have_tty - self.convert = convert - - # dict of ansi codes to win32 functions and parameters - self.win32_calls = self.get_win32_calls() - - # are we wrapping stderr? - self.on_stderr = self.wrapped is sys.stderr - - def should_wrap(self): - ''' - True if this class is actually needed. If false, then the output - stream will not be affected, nor will win32 calls be issued, so - wrapping stdout is not actually required. This will generally be - False on non-Windows platforms, unless optional functionality like - autoreset has been requested using kwargs to init() - ''' - return self.convert or self.strip or self.autoreset - - def get_win32_calls(self): - if self.convert and winterm: - return { - AnsiStyle.RESET_ALL: (winterm.reset_all, ), - AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), - AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), - AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), - AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), - AnsiFore.RED: (winterm.fore, WinColor.RED), - AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), - AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), - AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), - AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), - AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), - AnsiFore.WHITE: (winterm.fore, WinColor.GREY), - AnsiFore.RESET: (winterm.fore, ), - AnsiFore.LIGHTBLACK_EX: (winterm.fore, WinColor.BLACK, True), - AnsiFore.LIGHTRED_EX: (winterm.fore, WinColor.RED, True), - AnsiFore.LIGHTGREEN_EX: (winterm.fore, WinColor.GREEN, True), - AnsiFore.LIGHTYELLOW_EX: (winterm.fore, WinColor.YELLOW, True), - AnsiFore.LIGHTBLUE_EX: (winterm.fore, WinColor.BLUE, True), - AnsiFore.LIGHTMAGENTA_EX: (winterm.fore, WinColor.MAGENTA, True), - AnsiFore.LIGHTCYAN_EX: (winterm.fore, WinColor.CYAN, True), - AnsiFore.LIGHTWHITE_EX: (winterm.fore, WinColor.GREY, True), - AnsiBack.BLACK: (winterm.back, WinColor.BLACK), - AnsiBack.RED: (winterm.back, WinColor.RED), - AnsiBack.GREEN: (winterm.back, WinColor.GREEN), - AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), - AnsiBack.BLUE: (winterm.back, WinColor.BLUE), - AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), - AnsiBack.CYAN: (winterm.back, WinColor.CYAN), - AnsiBack.WHITE: (winterm.back, WinColor.GREY), - AnsiBack.RESET: (winterm.back, ), - AnsiBack.LIGHTBLACK_EX: (winterm.back, WinColor.BLACK, True), - AnsiBack.LIGHTRED_EX: (winterm.back, WinColor.RED, True), - AnsiBack.LIGHTGREEN_EX: (winterm.back, WinColor.GREEN, True), - AnsiBack.LIGHTYELLOW_EX: (winterm.back, WinColor.YELLOW, True), - AnsiBack.LIGHTBLUE_EX: (winterm.back, WinColor.BLUE, True), - AnsiBack.LIGHTMAGENTA_EX: (winterm.back, WinColor.MAGENTA, True), - AnsiBack.LIGHTCYAN_EX: (winterm.back, WinColor.CYAN, True), - AnsiBack.LIGHTWHITE_EX: (winterm.back, WinColor.GREY, True), - } - return dict() - - def write(self, text): - if self.strip or self.convert: - self.write_and_convert(text) - else: - self.wrapped.write(text) - self.wrapped.flush() - if self.autoreset: - self.reset_all() - - - def reset_all(self): - if self.convert: - self.call_win32('m', (0,)) - elif not self.strip and not self.stream.closed: - self.wrapped.write(Style.RESET_ALL) - - - def write_and_convert(self, text): - ''' - Write the given text to our wrapped stream, stripping any ANSI - sequences from the text, and optionally converting them into win32 - calls. - ''' - cursor = 0 - text = self.convert_osc(text) - for match in self.ANSI_CSI_RE.finditer(text): - start, end = match.span() - self.write_plain_text(text, cursor, start) - self.convert_ansi(*match.groups()) - cursor = end - self.write_plain_text(text, cursor, len(text)) - - - def write_plain_text(self, text, start, end): - if start < end: - self.wrapped.write(text[start:end]) - self.wrapped.flush() - - - def convert_ansi(self, paramstring, command): - if self.convert: - params = self.extract_params(command, paramstring) - self.call_win32(command, params) - - - def extract_params(self, command, paramstring): - if command in 'Hf': - params = tuple(int(p) if len(p) != 0 else 1 for p in paramstring.split(';')) - while len(params) < 2: - # defaults: - params = params + (1,) - else: - params = tuple(int(p) for p in paramstring.split(';') if len(p) != 0) - if len(params) == 0: - # defaults: - if command in 'JKm': - params = (0,) - elif command in 'ABCD': - params = (1,) - - return params - - - def call_win32(self, command, params): - if command == 'm': - for param in params: - if param in self.win32_calls: - func_args = self.win32_calls[param] - func = func_args[0] - args = func_args[1:] - kwargs = dict(on_stderr=self.on_stderr) - func(*args, **kwargs) - elif command in 'J': - winterm.erase_screen(params[0], on_stderr=self.on_stderr) - elif command in 'K': - winterm.erase_line(params[0], on_stderr=self.on_stderr) - elif command in 'Hf': # cursor position - absolute - winterm.set_cursor_position(params, on_stderr=self.on_stderr) - elif command in 'ABCD': # cursor position - relative - n = params[0] - # A - up, B - down, C - forward, D - back - x, y = {'A': (0, -n), 'B': (0, n), 'C': (n, 0), 'D': (-n, 0)}[command] - winterm.cursor_adjust(x, y, on_stderr=self.on_stderr) - - - def convert_osc(self, text): - for match in self.ANSI_OSC_RE.finditer(text): - start, end = match.span() - text = text[:start] + text[end:] - paramstring, command = match.groups() - if command == BEL: - if paramstring.count(";") == 1: - params = paramstring.split(";") - # 0 - change title and icon (we will only change title) - # 1 - change icon (we don't support this) - # 2 - change title - if params[0] in '02': - winterm.set_title(params[1]) - return text - - - def flush(self): - self.wrapped.flush() diff --git a/server/venv/Lib/site-packages/colorama/initialise.py b/server/venv/Lib/site-packages/colorama/initialise.py deleted file mode 100644 index d5fd4b7..0000000 --- a/server/venv/Lib/site-packages/colorama/initialise.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -import atexit -import contextlib -import sys - -from .ansitowin32 import AnsiToWin32 - - -def _wipe_internal_state_for_tests(): - global orig_stdout, orig_stderr - orig_stdout = None - orig_stderr = None - - global wrapped_stdout, wrapped_stderr - wrapped_stdout = None - wrapped_stderr = None - - global atexit_done - atexit_done = False - - global fixed_windows_console - fixed_windows_console = False - - try: - # no-op if it wasn't registered - atexit.unregister(reset_all) - except AttributeError: - # python 2: no atexit.unregister. Oh well, we did our best. - pass - - -def reset_all(): - if AnsiToWin32 is not None: # Issue #74: objects might become None at exit - AnsiToWin32(orig_stdout).reset_all() - - -def init(autoreset=False, convert=None, strip=None, wrap=True): - - if not wrap and any([autoreset, convert, strip]): - raise ValueError('wrap=False conflicts with any other arg=True') - - global wrapped_stdout, wrapped_stderr - global orig_stdout, orig_stderr - - orig_stdout = sys.stdout - orig_stderr = sys.stderr - - if sys.stdout is None: - wrapped_stdout = None - else: - sys.stdout = wrapped_stdout = \ - wrap_stream(orig_stdout, convert, strip, autoreset, wrap) - if sys.stderr is None: - wrapped_stderr = None - else: - sys.stderr = wrapped_stderr = \ - wrap_stream(orig_stderr, convert, strip, autoreset, wrap) - - global atexit_done - if not atexit_done: - atexit.register(reset_all) - atexit_done = True - - -def deinit(): - if orig_stdout is not None: - sys.stdout = orig_stdout - if orig_stderr is not None: - sys.stderr = orig_stderr - - -def just_fix_windows_console(): - global fixed_windows_console - - if sys.platform != "win32": - return - if fixed_windows_console: - return - if wrapped_stdout is not None or wrapped_stderr is not None: - # Someone already ran init() and it did stuff, so we won't second-guess them - return - - # On newer versions of Windows, AnsiToWin32.__init__ will implicitly enable the - # native ANSI support in the console as a side-effect. We only need to actually - # replace sys.stdout/stderr if we're in the old-style conversion mode. - new_stdout = AnsiToWin32(sys.stdout, convert=None, strip=None, autoreset=False) - if new_stdout.convert: - sys.stdout = new_stdout - new_stderr = AnsiToWin32(sys.stderr, convert=None, strip=None, autoreset=False) - if new_stderr.convert: - sys.stderr = new_stderr - - fixed_windows_console = True - -@contextlib.contextmanager -def colorama_text(*args, **kwargs): - init(*args, **kwargs) - try: - yield - finally: - deinit() - - -def reinit(): - if wrapped_stdout is not None: - sys.stdout = wrapped_stdout - if wrapped_stderr is not None: - sys.stderr = wrapped_stderr - - -def wrap_stream(stream, convert, strip, autoreset, wrap): - if wrap: - wrapper = AnsiToWin32(stream, - convert=convert, strip=strip, autoreset=autoreset) - if wrapper.should_wrap(): - stream = wrapper.stream - return stream - - -# Use this for initial setup as well, to reduce code duplication -_wipe_internal_state_for_tests() diff --git a/server/venv/Lib/site-packages/colorama/tests/__init__.py b/server/venv/Lib/site-packages/colorama/tests/__init__.py deleted file mode 100644 index 8c5661e..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. diff --git a/server/venv/Lib/site-packages/colorama/tests/ansi_test.py b/server/venv/Lib/site-packages/colorama/tests/ansi_test.py deleted file mode 100644 index 0a20c80..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/ansi_test.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -import sys -from unittest import TestCase, main - -from ..ansi import Back, Fore, Style -from ..ansitowin32 import AnsiToWin32 - -stdout_orig = sys.stdout -stderr_orig = sys.stderr - - -class AnsiTest(TestCase): - - def setUp(self): - # sanity check: stdout should be a file or StringIO object. - # It will only be AnsiToWin32 if init() has previously wrapped it - self.assertNotEqual(type(sys.stdout), AnsiToWin32) - self.assertNotEqual(type(sys.stderr), AnsiToWin32) - - def tearDown(self): - sys.stdout = stdout_orig - sys.stderr = stderr_orig - - - def testForeAttributes(self): - self.assertEqual(Fore.BLACK, '\033[30m') - self.assertEqual(Fore.RED, '\033[31m') - self.assertEqual(Fore.GREEN, '\033[32m') - self.assertEqual(Fore.YELLOW, '\033[33m') - self.assertEqual(Fore.BLUE, '\033[34m') - self.assertEqual(Fore.MAGENTA, '\033[35m') - self.assertEqual(Fore.CYAN, '\033[36m') - self.assertEqual(Fore.WHITE, '\033[37m') - self.assertEqual(Fore.RESET, '\033[39m') - - # Check the light, extended versions. - self.assertEqual(Fore.LIGHTBLACK_EX, '\033[90m') - self.assertEqual(Fore.LIGHTRED_EX, '\033[91m') - self.assertEqual(Fore.LIGHTGREEN_EX, '\033[92m') - self.assertEqual(Fore.LIGHTYELLOW_EX, '\033[93m') - self.assertEqual(Fore.LIGHTBLUE_EX, '\033[94m') - self.assertEqual(Fore.LIGHTMAGENTA_EX, '\033[95m') - self.assertEqual(Fore.LIGHTCYAN_EX, '\033[96m') - self.assertEqual(Fore.LIGHTWHITE_EX, '\033[97m') - - - def testBackAttributes(self): - self.assertEqual(Back.BLACK, '\033[40m') - self.assertEqual(Back.RED, '\033[41m') - self.assertEqual(Back.GREEN, '\033[42m') - self.assertEqual(Back.YELLOW, '\033[43m') - self.assertEqual(Back.BLUE, '\033[44m') - self.assertEqual(Back.MAGENTA, '\033[45m') - self.assertEqual(Back.CYAN, '\033[46m') - self.assertEqual(Back.WHITE, '\033[47m') - self.assertEqual(Back.RESET, '\033[49m') - - # Check the light, extended versions. - self.assertEqual(Back.LIGHTBLACK_EX, '\033[100m') - self.assertEqual(Back.LIGHTRED_EX, '\033[101m') - self.assertEqual(Back.LIGHTGREEN_EX, '\033[102m') - self.assertEqual(Back.LIGHTYELLOW_EX, '\033[103m') - self.assertEqual(Back.LIGHTBLUE_EX, '\033[104m') - self.assertEqual(Back.LIGHTMAGENTA_EX, '\033[105m') - self.assertEqual(Back.LIGHTCYAN_EX, '\033[106m') - self.assertEqual(Back.LIGHTWHITE_EX, '\033[107m') - - - def testStyleAttributes(self): - self.assertEqual(Style.DIM, '\033[2m') - self.assertEqual(Style.NORMAL, '\033[22m') - self.assertEqual(Style.BRIGHT, '\033[1m') - - -if __name__ == '__main__': - main() diff --git a/server/venv/Lib/site-packages/colorama/tests/ansitowin32_test.py b/server/venv/Lib/site-packages/colorama/tests/ansitowin32_test.py deleted file mode 100644 index 91ca551..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/ansitowin32_test.py +++ /dev/null @@ -1,294 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -from io import StringIO, TextIOWrapper -from unittest import TestCase, main -try: - from contextlib import ExitStack -except ImportError: - # python 2 - from contextlib2 import ExitStack - -try: - from unittest.mock import MagicMock, Mock, patch -except ImportError: - from mock import MagicMock, Mock, patch - -from ..ansitowin32 import AnsiToWin32, StreamWrapper -from ..win32 import ENABLE_VIRTUAL_TERMINAL_PROCESSING -from .utils import osname - - -class StreamWrapperTest(TestCase): - - def testIsAProxy(self): - mockStream = Mock() - wrapper = StreamWrapper(mockStream, None) - self.assertTrue( wrapper.random_attr is mockStream.random_attr ) - - def testDelegatesWrite(self): - mockStream = Mock() - mockConverter = Mock() - wrapper = StreamWrapper(mockStream, mockConverter) - wrapper.write('hello') - self.assertTrue(mockConverter.write.call_args, (('hello',), {})) - - def testDelegatesContext(self): - mockConverter = Mock() - s = StringIO() - with StreamWrapper(s, mockConverter) as fp: - fp.write(u'hello') - self.assertTrue(s.closed) - - def testProxyNoContextManager(self): - mockStream = MagicMock() - mockStream.__enter__.side_effect = AttributeError() - mockConverter = Mock() - with self.assertRaises(AttributeError) as excinfo: - with StreamWrapper(mockStream, mockConverter) as wrapper: - wrapper.write('hello') - - def test_closed_shouldnt_raise_on_closed_stream(self): - stream = StringIO() - stream.close() - wrapper = StreamWrapper(stream, None) - self.assertEqual(wrapper.closed, True) - - def test_closed_shouldnt_raise_on_detached_stream(self): - stream = TextIOWrapper(StringIO()) - stream.detach() - wrapper = StreamWrapper(stream, None) - self.assertEqual(wrapper.closed, True) - -class AnsiToWin32Test(TestCase): - - def testInit(self): - mockStdout = Mock() - auto = Mock() - stream = AnsiToWin32(mockStdout, autoreset=auto) - self.assertEqual(stream.wrapped, mockStdout) - self.assertEqual(stream.autoreset, auto) - - @patch('colorama.ansitowin32.winterm', None) - @patch('colorama.ansitowin32.winapi_test', lambda *_: True) - def testStripIsTrueOnWindows(self): - with osname('nt'): - mockStdout = Mock() - stream = AnsiToWin32(mockStdout) - self.assertTrue(stream.strip) - - def testStripIsFalseOffWindows(self): - with osname('posix'): - mockStdout = Mock(closed=False) - stream = AnsiToWin32(mockStdout) - self.assertFalse(stream.strip) - - def testWriteStripsAnsi(self): - mockStdout = Mock() - stream = AnsiToWin32(mockStdout) - stream.wrapped = Mock() - stream.write_and_convert = Mock() - stream.strip = True - - stream.write('abc') - - self.assertFalse(stream.wrapped.write.called) - self.assertEqual(stream.write_and_convert.call_args, (('abc',), {})) - - def testWriteDoesNotStripAnsi(self): - mockStdout = Mock() - stream = AnsiToWin32(mockStdout) - stream.wrapped = Mock() - stream.write_and_convert = Mock() - stream.strip = False - stream.convert = False - - stream.write('abc') - - self.assertFalse(stream.write_and_convert.called) - self.assertEqual(stream.wrapped.write.call_args, (('abc',), {})) - - def assert_autoresets(self, convert, autoreset=True): - stream = AnsiToWin32(Mock()) - stream.convert = convert - stream.reset_all = Mock() - stream.autoreset = autoreset - stream.winterm = Mock() - - stream.write('abc') - - self.assertEqual(stream.reset_all.called, autoreset) - - def testWriteAutoresets(self): - self.assert_autoresets(convert=True) - self.assert_autoresets(convert=False) - self.assert_autoresets(convert=True, autoreset=False) - self.assert_autoresets(convert=False, autoreset=False) - - def testWriteAndConvertWritesPlainText(self): - stream = AnsiToWin32(Mock()) - stream.write_and_convert( 'abc' ) - self.assertEqual( stream.wrapped.write.call_args, (('abc',), {}) ) - - def testWriteAndConvertStripsAllValidAnsi(self): - stream = AnsiToWin32(Mock()) - stream.call_win32 = Mock() - data = [ - 'abc\033[mdef', - 'abc\033[0mdef', - 'abc\033[2mdef', - 'abc\033[02mdef', - 'abc\033[002mdef', - 'abc\033[40mdef', - 'abc\033[040mdef', - 'abc\033[0;1mdef', - 'abc\033[40;50mdef', - 'abc\033[50;30;40mdef', - 'abc\033[Adef', - 'abc\033[0Gdef', - 'abc\033[1;20;128Hdef', - ] - for datum in data: - stream.wrapped.write.reset_mock() - stream.write_and_convert( datum ) - self.assertEqual( - [args[0] for args in stream.wrapped.write.call_args_list], - [ ('abc',), ('def',) ] - ) - - def testWriteAndConvertSkipsEmptySnippets(self): - stream = AnsiToWin32(Mock()) - stream.call_win32 = Mock() - stream.write_and_convert( '\033[40m\033[41m' ) - self.assertFalse( stream.wrapped.write.called ) - - def testWriteAndConvertCallsWin32WithParamsAndCommand(self): - stream = AnsiToWin32(Mock()) - stream.convert = True - stream.call_win32 = Mock() - stream.extract_params = Mock(return_value='params') - data = { - 'abc\033[adef': ('a', 'params'), - 'abc\033[;;bdef': ('b', 'params'), - 'abc\033[0cdef': ('c', 'params'), - 'abc\033[;;0;;Gdef': ('G', 'params'), - 'abc\033[1;20;128Hdef': ('H', 'params'), - } - for datum, expected in data.items(): - stream.call_win32.reset_mock() - stream.write_and_convert( datum ) - self.assertEqual( stream.call_win32.call_args[0], expected ) - - def test_reset_all_shouldnt_raise_on_closed_orig_stdout(self): - stream = StringIO() - converter = AnsiToWin32(stream) - stream.close() - - converter.reset_all() - - def test_wrap_shouldnt_raise_on_closed_orig_stdout(self): - stream = StringIO() - stream.close() - with \ - patch("colorama.ansitowin32.os.name", "nt"), \ - patch("colorama.ansitowin32.winapi_test", lambda: True): - converter = AnsiToWin32(stream) - self.assertTrue(converter.strip) - self.assertFalse(converter.convert) - - def test_wrap_shouldnt_raise_on_missing_closed_attr(self): - with \ - patch("colorama.ansitowin32.os.name", "nt"), \ - patch("colorama.ansitowin32.winapi_test", lambda: True): - converter = AnsiToWin32(object()) - self.assertTrue(converter.strip) - self.assertFalse(converter.convert) - - def testExtractParams(self): - stream = AnsiToWin32(Mock()) - data = { - '': (0,), - ';;': (0,), - '2': (2,), - ';;002;;': (2,), - '0;1': (0, 1), - ';;003;;456;;': (3, 456), - '11;22;33;44;55': (11, 22, 33, 44, 55), - } - for datum, expected in data.items(): - self.assertEqual(stream.extract_params('m', datum), expected) - - def testCallWin32UsesLookup(self): - listener = Mock() - stream = AnsiToWin32(listener) - stream.win32_calls = { - 1: (lambda *_, **__: listener(11),), - 2: (lambda *_, **__: listener(22),), - 3: (lambda *_, **__: listener(33),), - } - stream.call_win32('m', (3, 1, 99, 2)) - self.assertEqual( - [a[0][0] for a in listener.call_args_list], - [33, 11, 22] ) - - def test_osc_codes(self): - mockStdout = Mock() - stream = AnsiToWin32(mockStdout, convert=True) - with patch('colorama.ansitowin32.winterm') as winterm: - data = [ - '\033]0\x07', # missing arguments - '\033]0;foo\x08', # wrong OSC command - '\033]0;colorama_test_title\x07', # should work - '\033]1;colorama_test_title\x07', # wrong set command - '\033]2;colorama_test_title\x07', # should work - '\033]' + ';' * 64 + '\x08', # see issue #247 - ] - for code in data: - stream.write(code) - self.assertEqual(winterm.set_title.call_count, 2) - - def test_native_windows_ansi(self): - with ExitStack() as stack: - def p(a, b): - stack.enter_context(patch(a, b, create=True)) - # Pretend to be on Windows - p("colorama.ansitowin32.os.name", "nt") - p("colorama.ansitowin32.winapi_test", lambda: True) - p("colorama.win32.winapi_test", lambda: True) - p("colorama.winterm.win32.windll", "non-None") - p("colorama.winterm.get_osfhandle", lambda _: 1234) - - # Pretend that our mock stream has native ANSI support - p( - "colorama.winterm.win32.GetConsoleMode", - lambda _: ENABLE_VIRTUAL_TERMINAL_PROCESSING, - ) - SetConsoleMode = Mock() - p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode) - - stdout = Mock() - stdout.closed = False - stdout.isatty.return_value = True - stdout.fileno.return_value = 1 - - # Our fake console says it has native vt support, so AnsiToWin32 should - # enable that support and do nothing else. - stream = AnsiToWin32(stdout) - SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING) - self.assertFalse(stream.strip) - self.assertFalse(stream.convert) - self.assertFalse(stream.should_wrap()) - - # Now let's pretend we're on an old Windows console, that doesn't have - # native ANSI support. - p("colorama.winterm.win32.GetConsoleMode", lambda _: 0) - SetConsoleMode = Mock() - p("colorama.winterm.win32.SetConsoleMode", SetConsoleMode) - - stream = AnsiToWin32(stdout) - SetConsoleMode.assert_called_with(1234, ENABLE_VIRTUAL_TERMINAL_PROCESSING) - self.assertTrue(stream.strip) - self.assertTrue(stream.convert) - self.assertTrue(stream.should_wrap()) - - -if __name__ == '__main__': - main() diff --git a/server/venv/Lib/site-packages/colorama/tests/initialise_test.py b/server/venv/Lib/site-packages/colorama/tests/initialise_test.py deleted file mode 100644 index 89f9b07..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/initialise_test.py +++ /dev/null @@ -1,189 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -import sys -from unittest import TestCase, main, skipUnless - -try: - from unittest.mock import patch, Mock -except ImportError: - from mock import patch, Mock - -from ..ansitowin32 import StreamWrapper -from ..initialise import init, just_fix_windows_console, _wipe_internal_state_for_tests -from .utils import osname, replace_by - -orig_stdout = sys.stdout -orig_stderr = sys.stderr - - -class InitTest(TestCase): - - @skipUnless(sys.stdout.isatty(), "sys.stdout is not a tty") - def setUp(self): - # sanity check - self.assertNotWrapped() - - def tearDown(self): - _wipe_internal_state_for_tests() - sys.stdout = orig_stdout - sys.stderr = orig_stderr - - def assertWrapped(self): - self.assertIsNot(sys.stdout, orig_stdout, 'stdout should be wrapped') - self.assertIsNot(sys.stderr, orig_stderr, 'stderr should be wrapped') - self.assertTrue(isinstance(sys.stdout, StreamWrapper), - 'bad stdout wrapper') - self.assertTrue(isinstance(sys.stderr, StreamWrapper), - 'bad stderr wrapper') - - def assertNotWrapped(self): - self.assertIs(sys.stdout, orig_stdout, 'stdout should not be wrapped') - self.assertIs(sys.stderr, orig_stderr, 'stderr should not be wrapped') - - @patch('colorama.initialise.reset_all') - @patch('colorama.ansitowin32.winapi_test', lambda *_: True) - @patch('colorama.ansitowin32.enable_vt_processing', lambda *_: False) - def testInitWrapsOnWindows(self, _): - with osname("nt"): - init() - self.assertWrapped() - - @patch('colorama.initialise.reset_all') - @patch('colorama.ansitowin32.winapi_test', lambda *_: False) - def testInitDoesntWrapOnEmulatedWindows(self, _): - with osname("nt"): - init() - self.assertNotWrapped() - - def testInitDoesntWrapOnNonWindows(self): - with osname("posix"): - init() - self.assertNotWrapped() - - def testInitDoesntWrapIfNone(self): - with replace_by(None): - init() - # We can't use assertNotWrapped here because replace_by(None) - # changes stdout/stderr already. - self.assertIsNone(sys.stdout) - self.assertIsNone(sys.stderr) - - def testInitAutoresetOnWrapsOnAllPlatforms(self): - with osname("posix"): - init(autoreset=True) - self.assertWrapped() - - def testInitWrapOffDoesntWrapOnWindows(self): - with osname("nt"): - init(wrap=False) - self.assertNotWrapped() - - def testInitWrapOffIncompatibleWithAutoresetOn(self): - self.assertRaises(ValueError, lambda: init(autoreset=True, wrap=False)) - - @patch('colorama.win32.SetConsoleTextAttribute') - @patch('colorama.initialise.AnsiToWin32') - def testAutoResetPassedOn(self, mockATW32, _): - with osname("nt"): - init(autoreset=True) - self.assertEqual(len(mockATW32.call_args_list), 2) - self.assertEqual(mockATW32.call_args_list[1][1]['autoreset'], True) - self.assertEqual(mockATW32.call_args_list[0][1]['autoreset'], True) - - @patch('colorama.initialise.AnsiToWin32') - def testAutoResetChangeable(self, mockATW32): - with osname("nt"): - init() - - init(autoreset=True) - self.assertEqual(len(mockATW32.call_args_list), 4) - self.assertEqual(mockATW32.call_args_list[2][1]['autoreset'], True) - self.assertEqual(mockATW32.call_args_list[3][1]['autoreset'], True) - - init() - self.assertEqual(len(mockATW32.call_args_list), 6) - self.assertEqual( - mockATW32.call_args_list[4][1]['autoreset'], False) - self.assertEqual( - mockATW32.call_args_list[5][1]['autoreset'], False) - - - @patch('colorama.initialise.atexit.register') - def testAtexitRegisteredOnlyOnce(self, mockRegister): - init() - self.assertTrue(mockRegister.called) - mockRegister.reset_mock() - init() - self.assertFalse(mockRegister.called) - - -class JustFixWindowsConsoleTest(TestCase): - def _reset(self): - _wipe_internal_state_for_tests() - sys.stdout = orig_stdout - sys.stderr = orig_stderr - - def tearDown(self): - self._reset() - - @patch("colorama.ansitowin32.winapi_test", lambda: True) - def testJustFixWindowsConsole(self): - if sys.platform != "win32": - # just_fix_windows_console should be a no-op - just_fix_windows_console() - self.assertIs(sys.stdout, orig_stdout) - self.assertIs(sys.stderr, orig_stderr) - else: - def fake_std(): - # Emulate stdout=not a tty, stderr=tty - # to check that we handle both cases correctly - stdout = Mock() - stdout.closed = False - stdout.isatty.return_value = False - stdout.fileno.return_value = 1 - sys.stdout = stdout - - stderr = Mock() - stderr.closed = False - stderr.isatty.return_value = True - stderr.fileno.return_value = 2 - sys.stderr = stderr - - for native_ansi in [False, True]: - with patch( - 'colorama.ansitowin32.enable_vt_processing', - lambda *_: native_ansi - ): - self._reset() - fake_std() - - # Regular single-call test - prev_stdout = sys.stdout - prev_stderr = sys.stderr - just_fix_windows_console() - self.assertIs(sys.stdout, prev_stdout) - if native_ansi: - self.assertIs(sys.stderr, prev_stderr) - else: - self.assertIsNot(sys.stderr, prev_stderr) - - # second call without resetting is always a no-op - prev_stdout = sys.stdout - prev_stderr = sys.stderr - just_fix_windows_console() - self.assertIs(sys.stdout, prev_stdout) - self.assertIs(sys.stderr, prev_stderr) - - self._reset() - fake_std() - - # If init() runs first, just_fix_windows_console should be a no-op - init() - prev_stdout = sys.stdout - prev_stderr = sys.stderr - just_fix_windows_console() - self.assertIs(prev_stdout, sys.stdout) - self.assertIs(prev_stderr, sys.stderr) - - -if __name__ == '__main__': - main() diff --git a/server/venv/Lib/site-packages/colorama/tests/isatty_test.py b/server/venv/Lib/site-packages/colorama/tests/isatty_test.py deleted file mode 100644 index 0f84e4b..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/isatty_test.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -import sys -from unittest import TestCase, main - -from ..ansitowin32 import StreamWrapper, AnsiToWin32 -from .utils import pycharm, replace_by, replace_original_by, StreamTTY, StreamNonTTY - - -def is_a_tty(stream): - return StreamWrapper(stream, None).isatty() - -class IsattyTest(TestCase): - - def test_TTY(self): - tty = StreamTTY() - self.assertTrue(is_a_tty(tty)) - with pycharm(): - self.assertTrue(is_a_tty(tty)) - - def test_nonTTY(self): - non_tty = StreamNonTTY() - self.assertFalse(is_a_tty(non_tty)) - with pycharm(): - self.assertFalse(is_a_tty(non_tty)) - - def test_withPycharm(self): - with pycharm(): - self.assertTrue(is_a_tty(sys.stderr)) - self.assertTrue(is_a_tty(sys.stdout)) - - def test_withPycharmTTYOverride(self): - tty = StreamTTY() - with pycharm(), replace_by(tty): - self.assertTrue(is_a_tty(tty)) - - def test_withPycharmNonTTYOverride(self): - non_tty = StreamNonTTY() - with pycharm(), replace_by(non_tty): - self.assertFalse(is_a_tty(non_tty)) - - def test_withPycharmNoneOverride(self): - with pycharm(): - with replace_by(None), replace_original_by(None): - self.assertFalse(is_a_tty(None)) - self.assertFalse(is_a_tty(StreamNonTTY())) - self.assertTrue(is_a_tty(StreamTTY())) - - def test_withPycharmStreamWrapped(self): - with pycharm(): - self.assertTrue(AnsiToWin32(StreamTTY()).stream.isatty()) - self.assertFalse(AnsiToWin32(StreamNonTTY()).stream.isatty()) - self.assertTrue(AnsiToWin32(sys.stdout).stream.isatty()) - self.assertTrue(AnsiToWin32(sys.stderr).stream.isatty()) - - -if __name__ == '__main__': - main() diff --git a/server/venv/Lib/site-packages/colorama/tests/utils.py b/server/venv/Lib/site-packages/colorama/tests/utils.py deleted file mode 100644 index 472fafb..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/utils.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -from contextlib import contextmanager -from io import StringIO -import sys -import os - - -class StreamTTY(StringIO): - def isatty(self): - return True - -class StreamNonTTY(StringIO): - def isatty(self): - return False - -@contextmanager -def osname(name): - orig = os.name - os.name = name - yield - os.name = orig - -@contextmanager -def replace_by(stream): - orig_stdout = sys.stdout - orig_stderr = sys.stderr - sys.stdout = stream - sys.stderr = stream - yield - sys.stdout = orig_stdout - sys.stderr = orig_stderr - -@contextmanager -def replace_original_by(stream): - orig_stdout = sys.__stdout__ - orig_stderr = sys.__stderr__ - sys.__stdout__ = stream - sys.__stderr__ = stream - yield - sys.__stdout__ = orig_stdout - sys.__stderr__ = orig_stderr - -@contextmanager -def pycharm(): - os.environ["PYCHARM_HOSTED"] = "1" - non_tty = StreamNonTTY() - with replace_by(non_tty), replace_original_by(non_tty): - yield - del os.environ["PYCHARM_HOSTED"] diff --git a/server/venv/Lib/site-packages/colorama/tests/winterm_test.py b/server/venv/Lib/site-packages/colorama/tests/winterm_test.py deleted file mode 100644 index d0955f9..0000000 --- a/server/venv/Lib/site-packages/colorama/tests/winterm_test.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -import sys -from unittest import TestCase, main, skipUnless - -try: - from unittest.mock import Mock, patch -except ImportError: - from mock import Mock, patch - -from ..winterm import WinColor, WinStyle, WinTerm - - -class WinTermTest(TestCase): - - @patch('colorama.winterm.win32') - def testInit(self, mockWin32): - mockAttr = Mock() - mockAttr.wAttributes = 7 + 6 * 16 + 8 - mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr - term = WinTerm() - self.assertEqual(term._fore, 7) - self.assertEqual(term._back, 6) - self.assertEqual(term._style, 8) - - @skipUnless(sys.platform.startswith("win"), "requires Windows") - def testGetAttrs(self): - term = WinTerm() - - term._fore = 0 - term._back = 0 - term._style = 0 - self.assertEqual(term.get_attrs(), 0) - - term._fore = WinColor.YELLOW - self.assertEqual(term.get_attrs(), WinColor.YELLOW) - - term._back = WinColor.MAGENTA - self.assertEqual( - term.get_attrs(), - WinColor.YELLOW + WinColor.MAGENTA * 16) - - term._style = WinStyle.BRIGHT - self.assertEqual( - term.get_attrs(), - WinColor.YELLOW + WinColor.MAGENTA * 16 + WinStyle.BRIGHT) - - @patch('colorama.winterm.win32') - def testResetAll(self, mockWin32): - mockAttr = Mock() - mockAttr.wAttributes = 1 + 2 * 16 + 8 - mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr - term = WinTerm() - - term.set_console = Mock() - term._fore = -1 - term._back = -1 - term._style = -1 - - term.reset_all() - - self.assertEqual(term._fore, 1) - self.assertEqual(term._back, 2) - self.assertEqual(term._style, 8) - self.assertEqual(term.set_console.called, True) - - @skipUnless(sys.platform.startswith("win"), "requires Windows") - def testFore(self): - term = WinTerm() - term.set_console = Mock() - term._fore = 0 - - term.fore(5) - - self.assertEqual(term._fore, 5) - self.assertEqual(term.set_console.called, True) - - @skipUnless(sys.platform.startswith("win"), "requires Windows") - def testBack(self): - term = WinTerm() - term.set_console = Mock() - term._back = 0 - - term.back(5) - - self.assertEqual(term._back, 5) - self.assertEqual(term.set_console.called, True) - - @skipUnless(sys.platform.startswith("win"), "requires Windows") - def testStyle(self): - term = WinTerm() - term.set_console = Mock() - term._style = 0 - - term.style(22) - - self.assertEqual(term._style, 22) - self.assertEqual(term.set_console.called, True) - - @patch('colorama.winterm.win32') - def testSetConsole(self, mockWin32): - mockAttr = Mock() - mockAttr.wAttributes = 0 - mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr - term = WinTerm() - term.windll = Mock() - - term.set_console() - - self.assertEqual( - mockWin32.SetConsoleTextAttribute.call_args, - ((mockWin32.STDOUT, term.get_attrs()), {}) - ) - - @patch('colorama.winterm.win32') - def testSetConsoleOnStderr(self, mockWin32): - mockAttr = Mock() - mockAttr.wAttributes = 0 - mockWin32.GetConsoleScreenBufferInfo.return_value = mockAttr - term = WinTerm() - term.windll = Mock() - - term.set_console(on_stderr=True) - - self.assertEqual( - mockWin32.SetConsoleTextAttribute.call_args, - ((mockWin32.STDERR, term.get_attrs()), {}) - ) - - -if __name__ == '__main__': - main() diff --git a/server/venv/Lib/site-packages/colorama/win32.py b/server/venv/Lib/site-packages/colorama/win32.py deleted file mode 100644 index 841b0e2..0000000 --- a/server/venv/Lib/site-packages/colorama/win32.py +++ /dev/null @@ -1,180 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. - -# from winbase.h -STDOUT = -11 -STDERR = -12 - -ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - -try: - import ctypes - from ctypes import LibraryLoader - windll = LibraryLoader(ctypes.WinDLL) - from ctypes import wintypes -except (AttributeError, ImportError): - windll = None - SetConsoleTextAttribute = lambda *_: None - winapi_test = lambda *_: None -else: - from ctypes import byref, Structure, c_char, POINTER - - COORD = wintypes._COORD - - class CONSOLE_SCREEN_BUFFER_INFO(Structure): - """struct in wincon.h.""" - _fields_ = [ - ("dwSize", COORD), - ("dwCursorPosition", COORD), - ("wAttributes", wintypes.WORD), - ("srWindow", wintypes.SMALL_RECT), - ("dwMaximumWindowSize", COORD), - ] - def __str__(self): - return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( - self.dwSize.Y, self.dwSize.X - , self.dwCursorPosition.Y, self.dwCursorPosition.X - , self.wAttributes - , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right - , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X - ) - - _GetStdHandle = windll.kernel32.GetStdHandle - _GetStdHandle.argtypes = [ - wintypes.DWORD, - ] - _GetStdHandle.restype = wintypes.HANDLE - - _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo - _GetConsoleScreenBufferInfo.argtypes = [ - wintypes.HANDLE, - POINTER(CONSOLE_SCREEN_BUFFER_INFO), - ] - _GetConsoleScreenBufferInfo.restype = wintypes.BOOL - - _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute - _SetConsoleTextAttribute.argtypes = [ - wintypes.HANDLE, - wintypes.WORD, - ] - _SetConsoleTextAttribute.restype = wintypes.BOOL - - _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition - _SetConsoleCursorPosition.argtypes = [ - wintypes.HANDLE, - COORD, - ] - _SetConsoleCursorPosition.restype = wintypes.BOOL - - _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA - _FillConsoleOutputCharacterA.argtypes = [ - wintypes.HANDLE, - c_char, - wintypes.DWORD, - COORD, - POINTER(wintypes.DWORD), - ] - _FillConsoleOutputCharacterA.restype = wintypes.BOOL - - _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute - _FillConsoleOutputAttribute.argtypes = [ - wintypes.HANDLE, - wintypes.WORD, - wintypes.DWORD, - COORD, - POINTER(wintypes.DWORD), - ] - _FillConsoleOutputAttribute.restype = wintypes.BOOL - - _SetConsoleTitleW = windll.kernel32.SetConsoleTitleW - _SetConsoleTitleW.argtypes = [ - wintypes.LPCWSTR - ] - _SetConsoleTitleW.restype = wintypes.BOOL - - _GetConsoleMode = windll.kernel32.GetConsoleMode - _GetConsoleMode.argtypes = [ - wintypes.HANDLE, - POINTER(wintypes.DWORD) - ] - _GetConsoleMode.restype = wintypes.BOOL - - _SetConsoleMode = windll.kernel32.SetConsoleMode - _SetConsoleMode.argtypes = [ - wintypes.HANDLE, - wintypes.DWORD - ] - _SetConsoleMode.restype = wintypes.BOOL - - def _winapi_test(handle): - csbi = CONSOLE_SCREEN_BUFFER_INFO() - success = _GetConsoleScreenBufferInfo( - handle, byref(csbi)) - return bool(success) - - def winapi_test(): - return any(_winapi_test(h) for h in - (_GetStdHandle(STDOUT), _GetStdHandle(STDERR))) - - def GetConsoleScreenBufferInfo(stream_id=STDOUT): - handle = _GetStdHandle(stream_id) - csbi = CONSOLE_SCREEN_BUFFER_INFO() - success = _GetConsoleScreenBufferInfo( - handle, byref(csbi)) - return csbi - - def SetConsoleTextAttribute(stream_id, attrs): - handle = _GetStdHandle(stream_id) - return _SetConsoleTextAttribute(handle, attrs) - - def SetConsoleCursorPosition(stream_id, position, adjust=True): - position = COORD(*position) - # If the position is out of range, do nothing. - if position.Y <= 0 or position.X <= 0: - return - # Adjust for Windows' SetConsoleCursorPosition: - # 1. being 0-based, while ANSI is 1-based. - # 2. expecting (x,y), while ANSI uses (y,x). - adjusted_position = COORD(position.Y - 1, position.X - 1) - if adjust: - # Adjust for viewport's scroll position - sr = GetConsoleScreenBufferInfo(STDOUT).srWindow - adjusted_position.Y += sr.Top - adjusted_position.X += sr.Left - # Resume normal processing - handle = _GetStdHandle(stream_id) - return _SetConsoleCursorPosition(handle, adjusted_position) - - def FillConsoleOutputCharacter(stream_id, char, length, start): - handle = _GetStdHandle(stream_id) - char = c_char(char.encode()) - length = wintypes.DWORD(length) - num_written = wintypes.DWORD(0) - # Note that this is hard-coded for ANSI (vs wide) bytes. - success = _FillConsoleOutputCharacterA( - handle, char, length, start, byref(num_written)) - return num_written.value - - def FillConsoleOutputAttribute(stream_id, attr, length, start): - ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' - handle = _GetStdHandle(stream_id) - attribute = wintypes.WORD(attr) - length = wintypes.DWORD(length) - num_written = wintypes.DWORD(0) - # Note that this is hard-coded for ANSI (vs wide) bytes. - return _FillConsoleOutputAttribute( - handle, attribute, length, start, byref(num_written)) - - def SetConsoleTitle(title): - return _SetConsoleTitleW(title) - - def GetConsoleMode(handle): - mode = wintypes.DWORD() - success = _GetConsoleMode(handle, byref(mode)) - if not success: - raise ctypes.WinError() - return mode.value - - def SetConsoleMode(handle, mode): - success = _SetConsoleMode(handle, mode) - if not success: - raise ctypes.WinError() diff --git a/server/venv/Lib/site-packages/colorama/winterm.py b/server/venv/Lib/site-packages/colorama/winterm.py deleted file mode 100644 index aad867e..0000000 --- a/server/venv/Lib/site-packages/colorama/winterm.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. -try: - from msvcrt import get_osfhandle -except ImportError: - def get_osfhandle(_): - raise OSError("This isn't windows!") - - -from . import win32 - -# from wincon.h -class WinColor(object): - BLACK = 0 - BLUE = 1 - GREEN = 2 - CYAN = 3 - RED = 4 - MAGENTA = 5 - YELLOW = 6 - GREY = 7 - -# from wincon.h -class WinStyle(object): - NORMAL = 0x00 # dim text, dim background - BRIGHT = 0x08 # bright text, dim background - BRIGHT_BACKGROUND = 0x80 # dim text, bright background - -class WinTerm(object): - - def __init__(self): - self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes - self.set_attrs(self._default) - self._default_fore = self._fore - self._default_back = self._back - self._default_style = self._style - # In order to emulate LIGHT_EX in windows, we borrow the BRIGHT style. - # So that LIGHT_EX colors and BRIGHT style do not clobber each other, - # we track them separately, since LIGHT_EX is overwritten by Fore/Back - # and BRIGHT is overwritten by Style codes. - self._light = 0 - - def get_attrs(self): - return self._fore + self._back * 16 + (self._style | self._light) - - def set_attrs(self, value): - self._fore = value & 7 - self._back = (value >> 4) & 7 - self._style = value & (WinStyle.BRIGHT | WinStyle.BRIGHT_BACKGROUND) - - def reset_all(self, on_stderr=None): - self.set_attrs(self._default) - self.set_console(attrs=self._default) - self._light = 0 - - def fore(self, fore=None, light=False, on_stderr=False): - if fore is None: - fore = self._default_fore - self._fore = fore - # Emulate LIGHT_EX with BRIGHT Style - if light: - self._light |= WinStyle.BRIGHT - else: - self._light &= ~WinStyle.BRIGHT - self.set_console(on_stderr=on_stderr) - - def back(self, back=None, light=False, on_stderr=False): - if back is None: - back = self._default_back - self._back = back - # Emulate LIGHT_EX with BRIGHT_BACKGROUND Style - if light: - self._light |= WinStyle.BRIGHT_BACKGROUND - else: - self._light &= ~WinStyle.BRIGHT_BACKGROUND - self.set_console(on_stderr=on_stderr) - - def style(self, style=None, on_stderr=False): - if style is None: - style = self._default_style - self._style = style - self.set_console(on_stderr=on_stderr) - - def set_console(self, attrs=None, on_stderr=False): - if attrs is None: - attrs = self.get_attrs() - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - win32.SetConsoleTextAttribute(handle, attrs) - - def get_position(self, handle): - position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition - # Because Windows coordinates are 0-based, - # and win32.SetConsoleCursorPosition expects 1-based. - position.X += 1 - position.Y += 1 - return position - - def set_cursor_position(self, position=None, on_stderr=False): - if position is None: - # I'm not currently tracking the position, so there is no default. - # position = self.get_position() - return - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - win32.SetConsoleCursorPosition(handle, position) - - def cursor_adjust(self, x, y, on_stderr=False): - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - position = self.get_position(handle) - adjusted_position = (position.Y + y, position.X + x) - win32.SetConsoleCursorPosition(handle, adjusted_position, adjust=False) - - def erase_screen(self, mode=0, on_stderr=False): - # 0 should clear from the cursor to the end of the screen. - # 1 should clear from the cursor to the beginning of the screen. - # 2 should clear the entire screen, and move cursor to (1,1) - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - csbi = win32.GetConsoleScreenBufferInfo(handle) - # get the number of character cells in the current buffer - cells_in_screen = csbi.dwSize.X * csbi.dwSize.Y - # get number of character cells before current cursor position - cells_before_cursor = csbi.dwSize.X * csbi.dwCursorPosition.Y + csbi.dwCursorPosition.X - if mode == 0: - from_coord = csbi.dwCursorPosition - cells_to_erase = cells_in_screen - cells_before_cursor - elif mode == 1: - from_coord = win32.COORD(0, 0) - cells_to_erase = cells_before_cursor - elif mode == 2: - from_coord = win32.COORD(0, 0) - cells_to_erase = cells_in_screen - else: - # invalid mode - return - # fill the entire screen with blanks - win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) - # now set the buffer's attributes accordingly - win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) - if mode == 2: - # put the cursor where needed - win32.SetConsoleCursorPosition(handle, (1, 1)) - - def erase_line(self, mode=0, on_stderr=False): - # 0 should clear from the cursor to the end of the line. - # 1 should clear from the cursor to the beginning of the line. - # 2 should clear the entire line. - handle = win32.STDOUT - if on_stderr: - handle = win32.STDERR - csbi = win32.GetConsoleScreenBufferInfo(handle) - if mode == 0: - from_coord = csbi.dwCursorPosition - cells_to_erase = csbi.dwSize.X - csbi.dwCursorPosition.X - elif mode == 1: - from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) - cells_to_erase = csbi.dwCursorPosition.X - elif mode == 2: - from_coord = win32.COORD(0, csbi.dwCursorPosition.Y) - cells_to_erase = csbi.dwSize.X - else: - # invalid mode - return - # fill the entire screen with blanks - win32.FillConsoleOutputCharacter(handle, ' ', cells_to_erase, from_coord) - # now set the buffer's attributes accordingly - win32.FillConsoleOutputAttribute(handle, self.get_attrs(), cells_to_erase, from_coord) - - def set_title(self, title): - win32.SetConsoleTitle(title) - - -def enable_vt_processing(fd): - if win32.windll is None or not win32.winapi_test(): - return False - - try: - handle = get_osfhandle(fd) - mode = win32.GetConsoleMode(handle) - win32.SetConsoleMode( - handle, - mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING, - ) - - mode = win32.GetConsoleMode(handle) - if mode & win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING: - return True - # Can get TypeError in testsuite where 'fd' is a Mock() - except (OSError, TypeError): - return False diff --git a/server/venv/Lib/site-packages/comerr64.dll b/server/venv/Lib/site-packages/comerr64.dll deleted file mode 100644 index a067ea5..0000000 Binary files a/server/venv/Lib/site-packages/comerr64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/dotenv/__init__.py b/server/venv/Lib/site-packages/dotenv/__init__.py deleted file mode 100644 index dde24a0..0000000 --- a/server/venv/Lib/site-packages/dotenv/__init__.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import Any, Optional - -from .main import dotenv_values, find_dotenv, get_key, load_dotenv, set_key, unset_key - - -def load_ipython_extension(ipython: Any) -> None: - from .ipython import load_ipython_extension - - load_ipython_extension(ipython) - - -def get_cli_string( - path: Optional[str] = None, - action: Optional[str] = None, - key: Optional[str] = None, - value: Optional[str] = None, - quote: Optional[str] = None, -): - """Returns a string suitable for running as a shell script. - - Useful for converting a arguments passed to a fabric task - to be passed to a `local` or `run` command. - """ - command = ["dotenv"] - if quote: - command.append(f"-q {quote}") - if path: - command.append(f"-f {path}") - if action: - command.append(action) - if key: - command.append(key) - if value: - if " " in value: - command.append(f'"{value}"') - else: - command.append(value) - - return " ".join(command).strip() - - -__all__ = [ - "get_cli_string", - "load_dotenv", - "dotenv_values", - "get_key", - "set_key", - "unset_key", - "find_dotenv", - "load_ipython_extension", -] diff --git a/server/venv/Lib/site-packages/dotenv/__main__.py b/server/venv/Lib/site-packages/dotenv/__main__.py deleted file mode 100644 index 3977f55..0000000 --- a/server/venv/Lib/site-packages/dotenv/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Entry point for cli, enables execution with `python -m dotenv`""" - -from .cli import cli - -if __name__ == "__main__": - cli() diff --git a/server/venv/Lib/site-packages/dotenv/cli.py b/server/venv/Lib/site-packages/dotenv/cli.py deleted file mode 100644 index 47eec04..0000000 --- a/server/venv/Lib/site-packages/dotenv/cli.py +++ /dev/null @@ -1,236 +0,0 @@ -import json -import os -import shlex -import sys -from contextlib import contextmanager -from typing import IO, Any, Dict, Iterator, List, Optional - -if sys.platform == "win32": - from subprocess import Popen - -try: - import click -except ImportError: - sys.stderr.write( - "It seems python-dotenv is not installed with cli option. \n" - 'Run pip install "python-dotenv[cli]" to fix this.' - ) - sys.exit(1) - -from .main import dotenv_values, set_key, unset_key -from .version import __version__ - - -def enumerate_env() -> Optional[str]: - """ - Return a path for the ${pwd}/.env file. - - If pwd does not exist, return None. - """ - try: - cwd = os.getcwd() - except FileNotFoundError: - return None - path = os.path.join(cwd, ".env") - return path - - -@click.group() -@click.option( - "-f", - "--file", - default=enumerate_env(), - type=click.Path(file_okay=True), - help="Location of the .env file, defaults to .env file in current working directory.", -) -@click.option( - "-q", - "--quote", - default="always", - type=click.Choice(["always", "never", "auto"]), - help="Whether to quote or not the variable values. Default mode is always. This does not affect parsing.", -) -@click.option( - "-e", - "--export", - default=False, - type=click.BOOL, - help="Whether to write the dot file as an executable bash script.", -) -@click.version_option(version=__version__) -@click.pass_context -def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None: - """This script is used to set, get or unset values from a .env file.""" - ctx.obj = {"QUOTE": quote, "EXPORT": export, "FILE": file} - - -@contextmanager -def stream_file(path: os.PathLike) -> Iterator[IO[str]]: - """ - Open a file and yield the corresponding (decoded) stream. - - Exits with error code 2 if the file cannot be opened. - """ - - try: - with open(path) as stream: - yield stream - except OSError as exc: - print(f"Error opening env file: {exc}", file=sys.stderr) - sys.exit(2) - - -@cli.command(name="list") -@click.pass_context -@click.option( - "--format", - "output_format", - default="simple", - type=click.Choice(["simple", "json", "shell", "export"]), - help="The format in which to display the list. Default format is simple, " - "which displays name=value without quotes.", -) -def list_values(ctx: click.Context, output_format: str) -> None: - """Display all the stored key/value.""" - file = ctx.obj["FILE"] - - with stream_file(file) as stream: - values = dotenv_values(stream=stream) - - if output_format == "json": - click.echo(json.dumps(values, indent=2, sort_keys=True)) - else: - prefix = "export " if output_format == "export" else "" - for k in sorted(values): - v = values[k] - if v is not None: - if output_format in ("export", "shell"): - v = shlex.quote(v) - click.echo(f"{prefix}{k}={v}") - - -@cli.command(name="set") -@click.pass_context -@click.argument("key", required=True) -@click.argument("value", required=True) -def set_value(ctx: click.Context, key: Any, value: Any) -> None: - """ - Store the given key/value. - - This doesn't follow symlinks, to avoid accidentally modifying a file at a - potentially untrusted path. - """ - - file = ctx.obj["FILE"] - quote = ctx.obj["QUOTE"] - export = ctx.obj["EXPORT"] - success, key, value = set_key(file, key, value, quote, export) - if success: - click.echo(f"{key}={value}") - else: - sys.exit(1) - - -@cli.command() -@click.pass_context -@click.argument("key", required=True) -def get(ctx: click.Context, key: Any) -> None: - """Retrieve the value for the given key.""" - file = ctx.obj["FILE"] - - with stream_file(file) as stream: - values = dotenv_values(stream=stream) - - stored_value = values.get(key) - if stored_value: - click.echo(stored_value) - else: - sys.exit(1) - - -@cli.command() -@click.pass_context -@click.argument("key", required=True) -def unset(ctx: click.Context, key: Any) -> None: - """ - Removes the given key. - - This doesn't follow symlinks, to avoid accidentally modifying a file at a - potentially untrusted path. - """ - file = ctx.obj["FILE"] - quote = ctx.obj["QUOTE"] - success, key = unset_key(file, key, quote) - if success: - click.echo(f"Successfully removed {key}") - else: - sys.exit(1) - - -@cli.command( - context_settings={ - "allow_extra_args": True, - "allow_interspersed_args": False, - "ignore_unknown_options": True, - } -) -@click.pass_context -@click.option( - "--override/--no-override", - default=True, - help="Override variables from the environment file with those from the .env file.", -) -@click.argument("commandline", nargs=-1, type=click.UNPROCESSED) -def run(ctx: click.Context, override: bool, commandline: tuple[str, ...]) -> None: - """Run command with environment variables present.""" - file = ctx.obj["FILE"] - if not os.path.isfile(file): - raise click.BadParameter( - f"Invalid value for '-f' \"{file}\" does not exist.", ctx=ctx - ) - dotenv_as_dict = { - k: v - for (k, v) in dotenv_values(file).items() - if v is not None and (override or k not in os.environ) - } - - if not commandline: - click.echo("No command given.") - sys.exit(1) - - run_command([*commandline, *ctx.args], dotenv_as_dict) - - -def run_command(command: List[str], env: Dict[str, str]) -> None: - """Replace the current process with the specified command. - - Replaces the current process with the specified command and the variables from `env` - added in the current environment variables. - - Parameters - ---------- - command: List[str] - The command and it's parameters - env: Dict - The additional environment variables - - Returns - ------- - None - This function does not return any value. It replaces the current process with the new one. - - """ - # copy the current environment variables and add the vales from - # `env` - cmd_env = os.environ.copy() - cmd_env.update(env) - - if sys.platform == "win32": - # execvpe on Windows returns control immediately - # rather than once the command has finished. - p = Popen(command, universal_newlines=True, bufsize=0, shell=False, env=cmd_env) - _, _ = p.communicate() - - sys.exit(p.returncode) - else: - os.execvpe(command[0], args=command, env=cmd_env) diff --git a/server/venv/Lib/site-packages/dotenv/ipython.py b/server/venv/Lib/site-packages/dotenv/ipython.py deleted file mode 100644 index 4e7edbb..0000000 --- a/server/venv/Lib/site-packages/dotenv/ipython.py +++ /dev/null @@ -1,50 +0,0 @@ -from IPython.core.magic import Magics, line_magic, magics_class # type: ignore -from IPython.core.magic_arguments import ( - argument, - magic_arguments, - parse_argstring, -) # type: ignore - -from .main import find_dotenv, load_dotenv - - -@magics_class -class IPythonDotEnv(Magics): - @magic_arguments() - @argument( - "-o", - "--override", - action="store_true", - help="Indicate to override existing variables", - ) - @argument( - "-v", - "--verbose", - action="store_true", - help="Indicate function calls to be verbose", - ) - @argument( - "dotenv_path", - nargs="?", - type=str, - default=".env", - help="Search in increasingly higher folders for the `dotenv_path`", - ) - @line_magic - def dotenv(self, line): - args = parse_argstring(self.dotenv, line) - # Locate the .env file - dotenv_path = args.dotenv_path - try: - dotenv_path = find_dotenv(dotenv_path, True, True) - except IOError: - print("cannot find .env file") - return - - # Load the .env file - load_dotenv(dotenv_path, verbose=args.verbose, override=args.override) - - -def load_ipython_extension(ipython): - """Register the %dotenv magic.""" - ipython.register_magics(IPythonDotEnv) diff --git a/server/venv/Lib/site-packages/dotenv/main.py b/server/venv/Lib/site-packages/dotenv/main.py deleted file mode 100644 index 48e5245..0000000 --- a/server/venv/Lib/site-packages/dotenv/main.py +++ /dev/null @@ -1,480 +0,0 @@ -import io -import logging -import os -import pathlib -import stat -import sys -import tempfile -from collections import OrderedDict -from contextlib import contextmanager -from typing import IO, Dict, Iterable, Iterator, Mapping, Optional, Tuple, Union - -from .parser import Binding, parse_stream -from .variables import parse_variables - -# A type alias for a string path to be used for the paths in this file. -# These paths may flow to `open()` and `os.replace()`. -StrPath = Union[str, "os.PathLike[str]"] - -logger = logging.getLogger(__name__) - - -def _load_dotenv_disabled() -> bool: - """ - Determine if dotenv loading has been disabled. - """ - if "PYTHON_DOTENV_DISABLED" not in os.environ: - return False - value = os.environ["PYTHON_DOTENV_DISABLED"].casefold() - return value in {"1", "true", "t", "yes", "y"} - - -def with_warn_for_invalid_lines(mappings: Iterator[Binding]) -> Iterator[Binding]: - for mapping in mappings: - if mapping.error: - logger.warning( - "python-dotenv could not parse statement starting at line %s", - mapping.original.line, - ) - yield mapping - - -class DotEnv: - def __init__( - self, - dotenv_path: Optional[StrPath], - stream: Optional[IO[str]] = None, - verbose: bool = False, - encoding: Optional[str] = None, - interpolate: bool = True, - override: bool = True, - ) -> None: - self.dotenv_path: Optional[StrPath] = dotenv_path - self.stream: Optional[IO[str]] = stream - self._dict: Optional[Dict[str, Optional[str]]] = None - self.verbose: bool = verbose - self.encoding: Optional[str] = encoding - self.interpolate: bool = interpolate - self.override: bool = override - - @contextmanager - def _get_stream(self) -> Iterator[IO[str]]: - if self.dotenv_path and _is_file_or_fifo(self.dotenv_path): - with open(self.dotenv_path, encoding=self.encoding) as stream: - yield stream - elif self.stream is not None: - yield self.stream - else: - if self.verbose: - logger.info( - "python-dotenv could not find configuration file %s.", - self.dotenv_path or ".env", - ) - yield io.StringIO("") - - def dict(self) -> Dict[str, Optional[str]]: - """Return dotenv as dict""" - if self._dict: - return self._dict - - raw_values = self.parse() - - if self.interpolate: - self._dict = OrderedDict( - resolve_variables(raw_values, override=self.override) - ) - else: - self._dict = OrderedDict(raw_values) - - return self._dict - - def parse(self) -> Iterator[Tuple[str, Optional[str]]]: - with self._get_stream() as stream: - for mapping in with_warn_for_invalid_lines(parse_stream(stream)): - if mapping.key is not None: - yield mapping.key, mapping.value - - def set_as_environment_variables(self) -> bool: - """ - Load the current dotenv as system environment variable. - """ - if not self.dict(): - return False - - for k, v in self.dict().items(): - if k in os.environ and not self.override: - continue - if v is not None: - os.environ[k] = v - - return True - - def get(self, key: str) -> Optional[str]: - """ """ - data = self.dict() - - if key in data: - return data[key] - - if self.verbose: - logger.warning("Key %s not found in %s.", key, self.dotenv_path) - - return None - - -def get_key( - dotenv_path: StrPath, - key_to_get: str, - encoding: Optional[str] = "utf-8", -) -> Optional[str]: - """ - Get the value of a given key from the given .env. - - Returns `None` if the key isn't found or doesn't have a value. - """ - return DotEnv(dotenv_path, verbose=True, encoding=encoding).get(key_to_get) - - -@contextmanager -def rewrite( - path: StrPath, - encoding: Optional[str], - follow_symlinks: bool = False, -) -> Iterator[Tuple[IO[str], IO[str]]]: - if follow_symlinks: - path = os.path.realpath(path) - - try: - source: IO[str] = open(path, encoding=encoding) - try: - path_stat = os.lstat(path) - original_mode: Optional[int] = ( - stat.S_IMODE(path_stat.st_mode) - if stat.S_ISREG(path_stat.st_mode) - else None - ) - except BaseException: - source.close() - raise - except FileNotFoundError: - source = io.StringIO("") - original_mode = None - - with tempfile.NamedTemporaryFile( - mode="w", - encoding=encoding, - delete=False, - prefix=".tmp_", - dir=os.path.dirname(os.path.abspath(path)), - ) as dest: - dest_path = pathlib.Path(dest.name) - error = None - - try: - with source: - yield (source, dest) - except BaseException as err: - error = err - - if error is None: - try: - if original_mode is not None: - os.chmod(dest_path, original_mode) - - os.replace(dest_path, path) - except BaseException: - dest_path.unlink(missing_ok=True) - raise - else: - dest_path.unlink(missing_ok=True) - raise error from None - - -def set_key( - dotenv_path: StrPath, - key_to_set: str, - value_to_set: str, - quote_mode: str = "always", - export: bool = False, - encoding: Optional[str] = "utf-8", - follow_symlinks: bool = False, -) -> Tuple[Optional[bool], str, str]: - """ - Adds or Updates a key/value to the given .env - - The target .env file is created if it doesn't exist. - - This function doesn't follow symlinks by default, to avoid accidentally - modifying a file at a potentially untrusted path. If you don't need this - protection and need symlinks to be followed, use `follow_symlinks`. - """ - if quote_mode not in ("always", "auto", "never"): - raise ValueError(f"Unknown quote_mode: {quote_mode}") - - quote = quote_mode == "always" or ( - quote_mode == "auto" and not value_to_set.isalnum() - ) - - if quote: - value_out = "'{}'".format(value_to_set.replace("'", "\\'")) - else: - value_out = value_to_set - if export: - line_out = f"export {key_to_set}={value_out}\n" - else: - line_out = f"{key_to_set}={value_out}\n" - - with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as ( - source, - dest, - ): - replaced = False - missing_newline = False - for mapping in with_warn_for_invalid_lines(parse_stream(source)): - if mapping.key == key_to_set: - dest.write(line_out) - replaced = True - else: - dest.write(mapping.original.string) - missing_newline = not mapping.original.string.endswith("\n") - if not replaced: - if missing_newline: - dest.write("\n") - dest.write(line_out) - - return True, key_to_set, value_to_set - - -def unset_key( - dotenv_path: StrPath, - key_to_unset: str, - quote_mode: str = "always", - encoding: Optional[str] = "utf-8", - follow_symlinks: bool = False, -) -> Tuple[Optional[bool], str]: - """ - Removes a given key from the given `.env` file. - - If the .env path given doesn't exist, fails. - If the given key doesn't exist in the .env, fails. - - This function doesn't follow symlinks by default, to avoid accidentally - modifying a file at a potentially untrusted path. If you don't need this - protection and need symlinks to be followed, use `follow_symlinks`. - """ - if not os.path.exists(dotenv_path): - logger.warning("Can't delete from %s - it doesn't exist.", dotenv_path) - return None, key_to_unset - - removed = False - with rewrite(dotenv_path, encoding=encoding, follow_symlinks=follow_symlinks) as ( - source, - dest, - ): - for mapping in with_warn_for_invalid_lines(parse_stream(source)): - if mapping.key == key_to_unset: - removed = True - else: - dest.write(mapping.original.string) - - if not removed: - logger.warning( - "Key %s not removed from %s - key doesn't exist.", key_to_unset, dotenv_path - ) - return None, key_to_unset - - return removed, key_to_unset - - -def resolve_variables( - values: Iterable[Tuple[str, Optional[str]]], - override: bool, -) -> Mapping[str, Optional[str]]: - new_values: Dict[str, Optional[str]] = {} - - for name, value in values: - if value is None: - result = None - else: - atoms = parse_variables(value) - env: Dict[str, Optional[str]] = {} - if override: - env.update(os.environ) # type: ignore - env.update(new_values) - else: - env.update(new_values) - env.update(os.environ) # type: ignore - result = "".join(atom.resolve(env) for atom in atoms) - - new_values[name] = result - - return new_values - - -def _walk_to_root(path: str) -> Iterator[str]: - """ - Yield directories starting from the given directory up to the root - """ - if not os.path.exists(path): - raise IOError("Starting path not found") - - if os.path.isfile(path): - path = os.path.dirname(path) - - last_dir = None - current_dir = os.path.abspath(path) - while last_dir != current_dir: - yield current_dir - parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir)) - last_dir, current_dir = current_dir, parent_dir - - -def find_dotenv( - filename: str = ".env", - raise_error_if_not_found: bool = False, - usecwd: bool = False, -) -> str: - """ - Search in increasingly higher folders for the given file - - Returns path to the file if found, or an empty string otherwise - """ - - def _is_interactive(): - """Decide whether this is running in a REPL or IPython notebook""" - if hasattr(sys, "ps1") or hasattr(sys, "ps2"): - return True - try: - main = __import__("__main__", None, None, fromlist=["__file__"]) - except ModuleNotFoundError: - return False - return not hasattr(main, "__file__") - - def _is_debugger(): - return sys.gettrace() is not None - - if usecwd or _is_interactive() or _is_debugger() or getattr(sys, "frozen", False): - # Should work without __file__, e.g. in REPL or IPython notebook. - path = os.getcwd() - else: - # will work for .py files - frame = sys._getframe() - current_file = __file__ - - while frame.f_code.co_filename == current_file or not os.path.exists( - frame.f_code.co_filename - ): - assert frame.f_back is not None - frame = frame.f_back - frame_filename = frame.f_code.co_filename - path = os.path.dirname(os.path.abspath(frame_filename)) - - for dirname in _walk_to_root(path): - check_path = os.path.join(dirname, filename) - if _is_file_or_fifo(check_path): - return check_path - - if raise_error_if_not_found: - raise IOError("File not found") - - return "" - - -def load_dotenv( - dotenv_path: Optional[StrPath] = None, - stream: Optional[IO[str]] = None, - verbose: bool = False, - override: bool = False, - interpolate: bool = True, - encoding: Optional[str] = "utf-8", -) -> bool: - """Parse a .env file and then load all the variables found as environment variables. - - Parameters: - dotenv_path: Absolute or relative path to .env file. - stream: Text stream (such as `io.StringIO`) with .env content, used if - `dotenv_path` is `None`. - verbose: Whether to output a warning the .env file is missing. - override: Whether to override the system environment variables with the variables - from the `.env` file. - encoding: Encoding to be used to read the file. - Returns: - Bool: True if at least one environment variable is set else False - - If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the - .env file with it's default parameters. If you need to change the default parameters - of `find_dotenv()`, you can explicitly call `find_dotenv()` and pass the result - to this function as `dotenv_path`. - - If the environment variable `PYTHON_DOTENV_DISABLED` is set to a truthy value, - .env loading is disabled. - """ - if _load_dotenv_disabled(): - logger.debug( - "python-dotenv: .env loading disabled by PYTHON_DOTENV_DISABLED environment variable" - ) - return False - - if dotenv_path is None and stream is None: - dotenv_path = find_dotenv() - - dotenv = DotEnv( - dotenv_path=dotenv_path, - stream=stream, - verbose=verbose, - interpolate=interpolate, - override=override, - encoding=encoding, - ) - return dotenv.set_as_environment_variables() - - -def dotenv_values( - dotenv_path: Optional[StrPath] = None, - stream: Optional[IO[str]] = None, - verbose: bool = False, - interpolate: bool = True, - encoding: Optional[str] = "utf-8", -) -> Dict[str, Optional[str]]: - """ - Parse a .env file and return its content as a dict. - - The returned dict will have `None` values for keys without values in the .env file. - For example, `foo=bar` results in `{"foo": "bar"}` whereas `foo` alone results in - `{"foo": None}` - - Parameters: - dotenv_path: Absolute or relative path to the .env file. - stream: `StringIO` object with .env content, used if `dotenv_path` is `None`. - verbose: Whether to output a warning if the .env file is missing. - encoding: Encoding to be used to read the file. - - If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the - .env file. - """ - if dotenv_path is None and stream is None: - dotenv_path = find_dotenv() - - return DotEnv( - dotenv_path=dotenv_path, - stream=stream, - verbose=verbose, - interpolate=interpolate, - override=True, - encoding=encoding, - ).dict() - - -def _is_file_or_fifo(path: StrPath) -> bool: - """ - Return True if `path` exists and is either a regular file or a FIFO. - """ - if os.path.isfile(path): - return True - - try: - st = os.stat(path) - except (FileNotFoundError, OSError): - return False - - return stat.S_ISFIFO(st.st_mode) diff --git a/server/venv/Lib/site-packages/dotenv/parser.py b/server/venv/Lib/site-packages/dotenv/parser.py deleted file mode 100644 index eb100b4..0000000 --- a/server/venv/Lib/site-packages/dotenv/parser.py +++ /dev/null @@ -1,182 +0,0 @@ -import codecs -import re -from typing import ( - IO, - Iterator, - Match, - NamedTuple, - Optional, - Pattern, - Sequence, -) - - -def make_regex(string: str, extra_flags: int = 0) -> Pattern[str]: - return re.compile(string, re.UNICODE | extra_flags) - - -_newline = make_regex(r"(\r\n|\n|\r)") -_multiline_whitespace = make_regex(r"\s*", extra_flags=re.MULTILINE) -_whitespace = make_regex(r"[^\S\r\n]*") -_export = make_regex(r"(?:export[^\S\r\n]+)?") -_single_quoted_key = make_regex(r"'([^']+)'") -_unquoted_key = make_regex(r"([^=\#\s]+)") -_equal_sign = make_regex(r"(=[^\S\r\n]*)") -_single_quoted_value = make_regex(r"'((?:\\'|[^'])*)'") -_double_quoted_value = make_regex(r'"((?:\\"|[^"])*)"') -_unquoted_value = make_regex(r"([^\r\n]*)") -_comment = make_regex(r"(?:[^\S\r\n]*#[^\r\n]*)?") -_end_of_line = make_regex(r"[^\S\r\n]*(?:\r\n|\n|\r|$)") -_rest_of_line = make_regex(r"[^\r\n]*(?:\r|\n|\r\n)?") -_double_quote_escapes = make_regex(r"\\[\\'\"abfnrtv]") -_single_quote_escapes = make_regex(r"\\[\\']") - - -class Original(NamedTuple): - string: str - line: int - - -class Binding(NamedTuple): - key: Optional[str] - value: Optional[str] - original: Original - error: bool - - -class Position: - def __init__(self, chars: int, line: int) -> None: - self.chars = chars - self.line = line - - @classmethod - def start(cls) -> "Position": - return cls(chars=0, line=1) - - def set(self, other: "Position") -> None: - self.chars = other.chars - self.line = other.line - - def advance(self, string: str) -> None: - self.chars += len(string) - self.line += len(re.findall(_newline, string)) - - -class Error(Exception): - pass - - -class Reader: - def __init__(self, stream: IO[str]) -> None: - self.string = stream.read() - self.position = Position.start() - self.mark = Position.start() - - def has_next(self) -> bool: - return self.position.chars < len(self.string) - - def set_mark(self) -> None: - self.mark.set(self.position) - - def get_marked(self) -> Original: - return Original( - string=self.string[self.mark.chars : self.position.chars], - line=self.mark.line, - ) - - def peek(self, count: int) -> str: - return self.string[self.position.chars : self.position.chars + count] - - def read(self, count: int) -> str: - result = self.string[self.position.chars : self.position.chars + count] - if len(result) < count: - raise Error("read: End of string") - self.position.advance(result) - return result - - def read_regex(self, regex: Pattern[str]) -> Sequence[str]: - match = regex.match(self.string, self.position.chars) - if match is None: - raise Error("read_regex: Pattern not found") - self.position.advance(self.string[match.start() : match.end()]) - return match.groups() - - -def decode_escapes(regex: Pattern[str], string: str) -> str: - def decode_match(match: Match[str]) -> str: - return codecs.decode(match.group(0), "unicode-escape") # type: ignore - - return regex.sub(decode_match, string) - - -def parse_key(reader: Reader) -> Optional[str]: - char = reader.peek(1) - if char == "#": - return None - elif char == "'": - (key,) = reader.read_regex(_single_quoted_key) - else: - (key,) = reader.read_regex(_unquoted_key) - return key - - -def parse_unquoted_value(reader: Reader) -> str: - (part,) = reader.read_regex(_unquoted_value) - return re.sub(r"\s+#.*", "", part).rstrip() - - -def parse_value(reader: Reader) -> str: - char = reader.peek(1) - if char == "'": - (value,) = reader.read_regex(_single_quoted_value) - return decode_escapes(_single_quote_escapes, value) - elif char == '"': - (value,) = reader.read_regex(_double_quoted_value) - return decode_escapes(_double_quote_escapes, value) - elif char in ("", "\n", "\r"): - return "" - else: - return parse_unquoted_value(reader) - - -def parse_binding(reader: Reader) -> Binding: - reader.set_mark() - try: - reader.read_regex(_multiline_whitespace) - if not reader.has_next(): - return Binding( - key=None, - value=None, - original=reader.get_marked(), - error=False, - ) - reader.read_regex(_export) - key = parse_key(reader) - reader.read_regex(_whitespace) - if reader.peek(1) == "=": - reader.read_regex(_equal_sign) - value: Optional[str] = parse_value(reader) - else: - value = None - reader.read_regex(_comment) - reader.read_regex(_end_of_line) - return Binding( - key=key, - value=value, - original=reader.get_marked(), - error=False, - ) - except Error: - reader.read_regex(_rest_of_line) - return Binding( - key=None, - value=None, - original=reader.get_marked(), - error=True, - ) - - -def parse_stream(stream: IO[str]) -> Iterator[Binding]: - reader = Reader(stream) - while reader.has_next(): - yield parse_binding(reader) diff --git a/server/venv/Lib/site-packages/dotenv/py.typed b/server/venv/Lib/site-packages/dotenv/py.typed deleted file mode 100644 index 7632ecf..0000000 --- a/server/venv/Lib/site-packages/dotenv/py.typed +++ /dev/null @@ -1 +0,0 @@ -# Marker file for PEP 561 diff --git a/server/venv/Lib/site-packages/dotenv/variables.py b/server/venv/Lib/site-packages/dotenv/variables.py deleted file mode 100644 index 667f2f2..0000000 --- a/server/venv/Lib/site-packages/dotenv/variables.py +++ /dev/null @@ -1,86 +0,0 @@ -import re -from abc import ABCMeta, abstractmethod -from typing import Iterator, Mapping, Optional, Pattern - -_posix_variable: Pattern[str] = re.compile( - r""" - \$\{ - (?P[^\}:]*) - (?::- - (?P[^\}]*) - )? - \} - """, - re.VERBOSE, -) - - -class Atom(metaclass=ABCMeta): - def __ne__(self, other: object) -> bool: - result = self.__eq__(other) - if result is NotImplemented: - return NotImplemented - return not result - - @abstractmethod - def resolve(self, env: Mapping[str, Optional[str]]) -> str: ... - - -class Literal(Atom): - def __init__(self, value: str) -> None: - self.value = value - - def __repr__(self) -> str: - return f"Literal(value={self.value})" - - def __eq__(self, other: object) -> bool: - if not isinstance(other, self.__class__): - return NotImplemented - return self.value == other.value - - def __hash__(self) -> int: - return hash((self.__class__, self.value)) - - def resolve(self, env: Mapping[str, Optional[str]]) -> str: - return self.value - - -class Variable(Atom): - def __init__(self, name: str, default: Optional[str]) -> None: - self.name = name - self.default = default - - def __repr__(self) -> str: - return f"Variable(name={self.name}, default={self.default})" - - def __eq__(self, other: object) -> bool: - if not isinstance(other, self.__class__): - return NotImplemented - return (self.name, self.default) == (other.name, other.default) - - def __hash__(self) -> int: - return hash((self.__class__, self.name, self.default)) - - def resolve(self, env: Mapping[str, Optional[str]]) -> str: - default = self.default if self.default is not None else "" - result = env.get(self.name, default) - return result if result is not None else "" - - -def parse_variables(value: str) -> Iterator[Atom]: - cursor = 0 - - for match in _posix_variable.finditer(value): - (start, end) = match.span() - name = match["name"] - default = match["default"] - - if start > cursor: - yield Literal(value=value[cursor:start]) - - yield Variable(name=name, default=default) - cursor = end - - length = len(value) - if cursor < length: - yield Literal(value=value[cursor:length]) diff --git a/server/venv/Lib/site-packages/dotenv/version.py b/server/venv/Lib/site-packages/dotenv/version.py deleted file mode 100644 index bc86c94..0000000 --- a/server/venv/Lib/site-packages/dotenv/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "1.2.2" diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/INSTALLER b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/METADATA b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/METADATA deleted file mode 100644 index bf3259f..0000000 --- a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/METADATA +++ /dev/null @@ -1,616 +0,0 @@ -Metadata-Version: 2.4 -Name: fastapi -Version: 0.138.0 -Summary: FastAPI framework, high performance, easy to learn, fast to code, ready for production -Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= -License-Expression: MIT -License-File: LICENSE -Classifier: Intended Audience :: Information Technology -Classifier: Intended Audience :: System Administrators -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python -Classifier: Topic :: Internet -Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Software Development -Classifier: Typing :: Typed -Classifier: Development Status :: 4 - Beta -Classifier: Environment :: Web Environment -Classifier: Framework :: AsyncIO -Classifier: Framework :: FastAPI -Classifier: Framework :: Pydantic -Classifier: Framework :: Pydantic :: 2 -Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 -Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers -Classifier: Topic :: Internet :: WWW/HTTP -Project-URL: Homepage, https://github.com/fastapi/fastapi -Project-URL: Documentation, https://fastapi.tiangolo.com/ -Project-URL: Repository, https://github.com/fastapi/fastapi -Project-URL: Issues, https://github.com/fastapi/fastapi/issues -Project-URL: Changelog, https://fastapi.tiangolo.com/release-notes/ -Requires-Python: >=3.10 -Requires-Dist: starlette>=0.46.0 -Requires-Dist: pydantic>=2.9.0 -Requires-Dist: typing-extensions>=4.8.0 -Requires-Dist: typing-inspection>=0.4.2 -Requires-Dist: annotated-doc>=0.0.2 -Provides-Extra: standard -Requires-Dist: fastapi-cli[standard]>=0.0.8; extra == "standard" -Requires-Dist: fastar>=0.9.0; extra == "standard" -Requires-Dist: httpx<1.0.0,>=0.23.0; extra == "standard" -Requires-Dist: jinja2>=3.1.5; extra == "standard" -Requires-Dist: python-multipart>=0.0.18; extra == "standard" -Requires-Dist: email-validator>=2.0.0; extra == "standard" -Requires-Dist: uvicorn[standard]>=0.12.0; extra == "standard" -Requires-Dist: pydantic-settings>=2.0.0; extra == "standard" -Requires-Dist: pydantic-extra-types>=2.0.0; extra == "standard" -Provides-Extra: standard-no-fastapi-cloud-cli -Requires-Dist: fastapi-cli[standard-no-fastapi-cloud-cli]>=0.0.8; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: httpx<1.0.0,>=0.23.0; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: jinja2>=3.1.5; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: python-multipart>=0.0.18; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: email-validator>=2.0.0; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: uvicorn[standard]>=0.12.0; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: pydantic-settings>=2.0.0; extra == "standard-no-fastapi-cloud-cli" -Requires-Dist: pydantic-extra-types>=2.0.0; extra == "standard-no-fastapi-cloud-cli" -Provides-Extra: all -Requires-Dist: fastapi-cli[standard]>=0.0.8; extra == "all" -Requires-Dist: httpx<1.0.0,>=0.23.0; extra == "all" -Requires-Dist: jinja2>=3.1.5; extra == "all" -Requires-Dist: python-multipart>=0.0.18; extra == "all" -Requires-Dist: itsdangerous>=1.1.0; extra == "all" -Requires-Dist: pyyaml>=5.3.1; extra == "all" -Requires-Dist: email-validator>=2.0.0; extra == "all" -Requires-Dist: uvicorn[standard]>=0.12.0; extra == "all" -Requires-Dist: pydantic-settings>=2.0.0; extra == "all" -Requires-Dist: pydantic-extra-types>=2.0.0; extra == "all" -Description-Content-Type: text/markdown - -

- FastAPI -

-

- FastAPI framework, high performance, easy to learn, fast to code, ready for production -

-

- - Test - - - Coverage - - - Package version - - - Supported Python versions - -

- ---- - -**Documentation**: [https://fastapi.tiangolo.com](https://fastapi.tiangolo.com) - -**Source Code**: [https://github.com/fastapi/fastapi](https://github.com/fastapi/fastapi) - ---- - -FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. - -The key features are: - -* **Fast**: Very high performance, on par with **NodeJS** and **Go** (thanks to Starlette and Pydantic). [One of the fastest Python frameworks available](#performance). -* **Fast to code**: Increase the speed to develop features by about 200% to 300%. * -* **Fewer bugs**: Reduce about 40% of human (developer) induced errors. * -* **Intuitive**: Great editor support. Completion everywhere. Less time debugging. -* **Easy**: Designed to be easy to use and learn. Less time reading docs. -* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. -* **Robust**: Get production-ready code. With automatic interactive documentation. -* **Standards-based**: Based on (and fully compatible with) the open standards for APIs: [OpenAPI](https://github.com/OAI/OpenAPI-Specification) (previously known as Swagger) and [JSON Schema](https://json-schema.org/). - -* estimation based on tests conducted by an internal development team, building production applications. - -## Sponsors - - -### Keystone Sponsor - - - -### Gold Sponsors - - - - - - - - - - -### Silver Sponsors - - - - - - - - - - - -[Other sponsors](https://fastapi.tiangolo.com/fastapi-people/#sponsors) - -## Opinions - - - -
- -"_[...] I'm using **FastAPI** a ton these days. [...] I'm actually planning to use it for all of my team's **ML services at Microsoft**. Some of them are getting integrated into the core **Windows** product and some **Office** products._" - -
Kabir Khan - Microsoft (ref)
- ---- - -"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_" - -
Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - Uber (ref)
- ---- - -"_**Netflix** is pleased to announce the open-source release of our **crisis management** orchestration framework: **Dispatch**! [built with **FastAPI**]_" - -
Kevin Glisson, Marc Vilanova, Forest Monsen - Netflix (ref)
- ---- - -"_If anyone is looking to build a production Python API, I would highly recommend **FastAPI**. It is **beautifully designed**, **simple to use** and **highly scalable**, it has become a **key component** in our API first development strategy and is driving many automations and services such as our Virtual TAC Engineer._" - -
Deon Pillsbury - Cisco (ref)
- ---- - -
- -## FastAPI Conf - -[**FastAPI Conf '26**](https://fastapiconf.com) is happening on **October 28, 2026** in **Amsterdam, NL**. All about FastAPI, right from the source. 🎤 - -FastAPI Conf '26 - October 28, 2026 - Amsterdam, NL - -## FastAPI mini documentary - -There's a [FastAPI mini documentary](https://www.youtube.com/watch?v=mpR8ngthqiE) released at the end of 2025, you can watch it online: - -FastAPI Mini Documentary - -## **Typer**, the FastAPI of CLIs - - - -If you are building a CLI app to be used in the terminal instead of a web API, check out [**Typer**](https://typer.tiangolo.com/). - -**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀 - -## Requirements - -FastAPI stands on the shoulders of giants: - -* [Starlette](https://www.starlette.dev/) for the web parts. -* [Pydantic](https://docs.pydantic.dev/) for the data parts. - -## Installation - -Create and activate a [virtual environment](https://fastapi.tiangolo.com/virtual-environments/) and then install FastAPI: - -
- -```console -$ pip install "fastapi[standard]" - ----> 100% -``` - -
- -**Note**: Make sure you put `"fastapi[standard]"` in quotes to ensure it works in all terminals. - -## Example - -### Create it - -Create a file `main.py` with: - -```Python -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: str | None = None): - return {"item_id": item_id, "q": q} -``` - -
-Or use async def... - -If your code uses `async` / `await`, use `async def`: - -```Python hl_lines="7 12" -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/") -async def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -async def read_item(item_id: int, q: str | None = None): - return {"item_id": item_id, "q": q} -``` - -**Note**: - -If you don't know, check the _"In a hurry?"_ section about [`async` and `await` in the docs](https://fastapi.tiangolo.com/async/#in-a-hurry). - -
- -### Run it - -Run the server with: - -
- -```console -$ fastapi dev - - ╭────────── FastAPI CLI - Development mode ───────────╮ - │ │ - │ Serving at: http://127.0.0.1:8000 │ - │ │ - │ API docs: http://127.0.0.1:8000/docs │ - │ │ - │ Running in development mode, for production use: │ - │ │ - │ fastapi run │ - │ │ - ╰─────────────────────────────────────────────────────╯ - -INFO: Will watch for changes in these directories: ['/home/user/code/awesomeapp'] -INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) -INFO: Started reloader process [2248755] using WatchFiles -INFO: Started server process [2248757] -INFO: Waiting for application startup. -INFO: Application startup complete. -``` - -
- -
-About the command fastapi dev... - -The command `fastapi dev` reads your `main.py` file automatically, detects the **FastAPI** app in it, and starts a server using [Uvicorn](https://www.uvicorn.dev). - -By default, `fastapi dev` will start with auto-reload enabled for local development. - -You can read more about it in the [FastAPI CLI docs](https://fastapi.tiangolo.com/fastapi-cli/). - -
- -### Check it - -Open your browser at [http://127.0.0.1:8000/items/5?q=somequery](http://127.0.0.1:8000/items/5?q=somequery). - -You will see the JSON response as: - -```JSON -{"item_id": 5, "q": "somequery"} -``` - -You already created an API that: - -* Receives HTTP requests in the _paths_ `/` and `/items/{item_id}`. -* Both _paths_ take `GET` operations (also known as HTTP _methods_). -* The _path_ `/items/{item_id}` has a _path parameter_ `item_id` that should be an `int`. -* The _path_ `/items/{item_id}` has an optional `str` _query parameter_ `q`. - -### Interactive API docs - -Now go to [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). - -You will see the automatic interactive API documentation (provided by [Swagger UI](https://github.com/swagger-api/swagger-ui)): - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) - -### Alternative API docs - -And now, go to [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc). - -You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Rebilly/ReDoc)): - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) - -## Example upgrade - -Now modify the file `main.py` to receive a body from a `PUT` request. - -Declare the body using standard Python types, thanks to Pydantic. - -```Python hl_lines="2 7-10 23-25" -from fastapi import FastAPI -from pydantic import BaseModel - -app = FastAPI() - - -class Item(BaseModel): - name: str - price: float - is_offer: bool | None = None - - -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: str | None = None): - return {"item_id": item_id, "q": q} - - -@app.put("/items/{item_id}") -def update_item(item_id: int, item: Item): - return {"item_name": item.name, "item_id": item_id} -``` - -The `fastapi dev` server should reload automatically. - -### Interactive API docs upgrade - -Now go to [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). - -* The interactive API documentation will be automatically updated, including the new body: - -![Swagger UI](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png) - -* Click on the button "Try it out", it allows you to fill the parameters and directly interact with the API: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-04-swagger-03.png) - -* Then click on the "Execute" button, the user interface will communicate with your API, send the parameters, get the results and show them on the screen: - -![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-05-swagger-04.png) - -### Alternative API docs upgrade - -And now, go to [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc). - -* The alternative documentation will also reflect the new query parameter and body: - -![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png) - -### Recap - -In summary, you declare **once** the types of parameters, body, etc. as function parameters. - -You do that with standard modern Python types. - -You don't have to learn a new syntax, the methods or classes of a specific library, etc. - -Just standard **Python**. - -For example, for an `int`: - -```Python -item_id: int -``` - -or for a more complex `Item` model: - -```Python -item: Item -``` - -...and with that single declaration you get: - -* Editor support, including: - * Completion. - * Type checks. -* Validation of data: - * Automatic and clear errors when the data is invalid. - * Validation even for deeply nested JSON objects. -* Conversion of input data: coming from the network to Python data and types. Reading from: - * JSON. - * Path parameters. - * Query parameters. - * Cookies. - * Headers. - * Forms. - * Files. -* Conversion of output data: converting from Python data and types to network data (as JSON): - * Convert Python types (`str`, `int`, `float`, `bool`, `list`, etc). - * `datetime` objects. - * `UUID` objects. - * Database models. - * ...and many more. -* Automatic interactive API documentation, including 2 alternative user interfaces: - * Swagger UI. - * ReDoc. - ---- - -Coming back to the previous code example, **FastAPI** will: - -* Validate that there is an `item_id` in the path for `GET` and `PUT` requests. -* Validate that the `item_id` is of type `int` for `GET` and `PUT` requests. - * If it is not, the client will see a useful, clear error. -* Check if there is an optional query parameter named `q` (as in `http://127.0.0.1:8000/items/foo?q=somequery`) for `GET` requests. - * As the `q` parameter is declared with `= None`, it is optional. - * Without the `None` it would be required (as is the body in the case with `PUT`). -* For `PUT` requests to `/items/{item_id}`, read the body as JSON: - * Check that it has a required attribute `name` that should be a `str`. - * Check that it has a required attribute `price` that has to be a `float`. - * Check that it has an optional attribute `is_offer`, that should be a `bool`, if present. - * All this would also work for deeply nested JSON objects. -* Convert from and to JSON automatically. -* Document everything with OpenAPI, that can be used by: - * Interactive documentation systems. - * Automatic client code generation systems, for many languages. -* Provide 2 interactive documentation web interfaces directly. - ---- - -We just scratched the surface, but you already get the idea of how it all works. - -Try changing the line with: - -```Python - return {"item_name": item.name, "item_id": item_id} -``` - -...from: - -```Python - ... "item_name": item.name ... -``` - -...to: - -```Python - ... "item_price": item.price ... -``` - -...and see how your editor will auto-complete the attributes and know their types: - -![editor support](https://fastapi.tiangolo.com/img/vscode-completion.png) - -For a more complete example including more features, see the Tutorial - User Guide. - -**Spoiler alert**: the tutorial - user guide includes: - -* Declaration of **parameters** from other different places such as: **headers**, **cookies**, **form fields** and **files**. -* How to set **validation constraints** such as `maximum_length` or `regex`. -* A very powerful and easy to use **Dependency Injection** system. -* Security and authentication, including support for **OAuth2** with **JWT tokens** and **HTTP Basic** auth. -* More advanced (but equally easy) techniques for declaring **deeply nested JSON models** (thanks to Pydantic). -* **GraphQL** integration with [Strawberry](https://strawberry.rocks) and other libraries. -* Many extra features (thanks to Starlette) such as: - * **WebSockets** - * extremely easy tests based on HTTPX and `pytest` - * **CORS** - * **Cookie Sessions** - * ...and more. - -### Deploy your app (optional) - -You can optionally deploy your FastAPI app to [FastAPI Cloud](https://fastapicloud.com) with a single command. 🚀 - -
- -```console -$ fastapi deploy - -Deploying to FastAPI Cloud... - -✅ Deployment successful! - -🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev -``` - -
- -The CLI will automatically detect your FastAPI application and deploy it to the cloud. If you are not logged in, your browser will open to complete the authentication process. - -That's it! Now you can access your app at that URL. ✨ - -#### About FastAPI Cloud - -**[FastAPI Cloud](https://fastapicloud.com)** is built by the same author and team behind **FastAPI**. - -It streamlines the process of **building**, **deploying**, and **accessing** an API with minimal effort. - -It brings the same **developer experience** of building apps with FastAPI to **deploying** them to the cloud. 🎉 - -FastAPI Cloud is the primary sponsor and funding provider for the *FastAPI and friends* open source projects. ✨ - -#### Deploy to other cloud providers - -FastAPI is open source and based on standards. You can deploy FastAPI apps to any cloud provider you choose. - -Follow your cloud provider's guides to deploy FastAPI apps with them. 🤓 - -## Performance - -Independent TechEmpower benchmarks show **FastAPI** applications running under Uvicorn as [one of the fastest Python frameworks available](https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7), only below Starlette and Uvicorn themselves (used internally by FastAPI). (*) - -To understand more about it, see the section [Benchmarks](https://fastapi.tiangolo.com/benchmarks/). - -## Dependencies - -FastAPI depends on Pydantic and Starlette. - -### `standard` Dependencies - -When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies: - -Used by Pydantic: - -* [`email-validator`](https://github.com/JoshData/python-email-validator) - for email validation. - -Used by Starlette: - -* [`httpx`](https://www.python-httpx.org) - Required if you want to use the `TestClient`. -* [`jinja2`](https://jinja.palletsprojects.com) - Required if you want to use the default template configuration. -* [`python-multipart`](https://github.com/Kludex/python-multipart) - Required if you want to support form "parsing", with `request.form()`. - -Used by FastAPI: - -* [`uvicorn`](https://www.uvicorn.dev) - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving. -* `fastapi-cli[standard]` - to provide the `fastapi` command. - * This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to [FastAPI Cloud](https://fastapicloud.com). - -### Without `standard` Dependencies - -If you don't want to include the `standard` optional dependencies, you can install with `pip install fastapi` instead of `pip install "fastapi[standard]"`. - -### Without `fastapi-cloud-cli` - -If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `pip install "fastapi[standard-no-fastapi-cloud-cli]"`. - -### Additional Optional Dependencies - -There are some additional dependencies you might want to install. - -Additional optional Pydantic dependencies: - -* [`pydantic-settings`](https://docs.pydantic.dev/latest/usage/pydantic_settings/) - for settings management. -* [`pydantic-extra-types`](https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/) - for extra types to be used with Pydantic. - -Additional optional FastAPI dependencies: - -* [`orjson`](https://github.com/ijl/orjson) - Required if you want to use `ORJSONResponse`. -* [`ujson`](https://github.com/esnme/ultrajson) - Required if you want to use `UJSONResponse`. - -## License - -This project is licensed under the terms of the MIT license. diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/RECORD b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/RECORD deleted file mode 100644 index ef7e49e..0000000 --- a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/RECORD +++ /dev/null @@ -1,109 +0,0 @@ -../../Scripts/fastapi.exe,sha256=5j15NkpabsAZ8L1Pep_hF7JB3hy_Dy0jiCVi5MDNunU,108459 -fastapi-0.138.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -fastapi-0.138.0.dist-info/METADATA,sha256=Ljq5wAm9GugY3jIBZNrFULS04Z8bKjJr1a9G5OVUPtY,27208 -fastapi-0.138.0.dist-info/RECORD,, -fastapi-0.138.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -fastapi-0.138.0.dist-info/WHEEL,sha256=VP-D4TPS230sME9Z3vb3INXvo1yt0924YRm5AOsk_dE,90 -fastapi-0.138.0.dist-info/entry_points.txt,sha256=GCf-WbIZxyGT4MUmrPGj1cOHYZoGsNPHAvNkT6hnGeA,61 -fastapi-0.138.0.dist-info/licenses/LICENSE,sha256=Tsif_IFIW5f-xYSy1KlhAy7v_oNEU4lP2cEnSQbMdE4,1086 -fastapi/.agents/skills/fastapi/SKILL.md,sha256=sJ7wiSY8WOd9YhhMd9nd2iGJWtIjox_JpaYxHV1HB1M,11240 -fastapi/.agents/skills/fastapi/references/dependencies.md,sha256=i2txoD-hRoCQWoH1PxiDuQwqt6xl7vp3wmZMLQON5Gk,3268 -fastapi/.agents/skills/fastapi/references/other-tools.md,sha256=oskZlYkCdGgezS9mIfqW5kImCGtOsgPUa46Sd81H-P0,1527 -fastapi/.agents/skills/fastapi/references/streaming.md,sha256=bHaIKnwbTkd7TUVQm_uxapUnAlEG4rdvALV9koD5ypI,2581 -fastapi/__init__.py,sha256=Q2cJRNvOpEZdE7ZHIgqtAS1bEvOw6pDaE8d3lC3EGXs,1081 -fastapi/__main__.py,sha256=bKePXLdO4SsVSM6r9SVoLickJDcR2c0cTOxZRKq26YQ,37 -fastapi/__pycache__/__init__.cpython-313.pyc,, -fastapi/__pycache__/__main__.cpython-313.pyc,, -fastapi/__pycache__/applications.cpython-313.pyc,, -fastapi/__pycache__/background.cpython-313.pyc,, -fastapi/__pycache__/cli.cpython-313.pyc,, -fastapi/__pycache__/concurrency.cpython-313.pyc,, -fastapi/__pycache__/datastructures.cpython-313.pyc,, -fastapi/__pycache__/encoders.cpython-313.pyc,, -fastapi/__pycache__/exception_handlers.cpython-313.pyc,, -fastapi/__pycache__/exceptions.cpython-313.pyc,, -fastapi/__pycache__/logger.cpython-313.pyc,, -fastapi/__pycache__/param_functions.cpython-313.pyc,, -fastapi/__pycache__/params.cpython-313.pyc,, -fastapi/__pycache__/requests.cpython-313.pyc,, -fastapi/__pycache__/responses.cpython-313.pyc,, -fastapi/__pycache__/routing.cpython-313.pyc,, -fastapi/__pycache__/sse.cpython-313.pyc,, -fastapi/__pycache__/staticfiles.cpython-313.pyc,, -fastapi/__pycache__/templating.cpython-313.pyc,, -fastapi/__pycache__/testclient.cpython-313.pyc,, -fastapi/__pycache__/types.cpython-313.pyc,, -fastapi/__pycache__/utils.cpython-313.pyc,, -fastapi/__pycache__/websockets.cpython-313.pyc,, -fastapi/_compat/__init__.py,sha256=PYOR-8vJ5va4Qjl810FcQmJbqmpyyeQMoZ9R86CeE2U,2095 -fastapi/_compat/__pycache__/__init__.cpython-313.pyc,, -fastapi/_compat/__pycache__/shared.cpython-313.pyc,, -fastapi/_compat/__pycache__/v2.cpython-313.pyc,, -fastapi/_compat/shared.py,sha256=XJl8cVYOTSgfK-1fiJHLeHHeSGLfC00H7MrtpyYpQgU,7118 -fastapi/_compat/v2.py,sha256=sDGyi1iKSFW9LuJ7n4B61-1yrQI1NHRSwsFRRLuFJ8k,17601 -fastapi/applications.py,sha256=hf4vGBWMU5nEkHmeqQutYBIK3AsnhEwcVF2wIvegekc,183452 -fastapi/background.py,sha256=TADzAethOAaqpVvckYuTT3c4O9N1HaFQysFCPt0MsgU,1820 -fastapi/cli.py,sha256=OYhZb0NR_deuT5ofyPF2NoNBzZDNOP8Salef2nk-HqA,418 -fastapi/concurrency.py,sha256=xHGDEOQAA6cvFEDX46oq3r2t1Zd4sVvreaRgdIE4juM,1489 -fastapi/datastructures.py,sha256=XPugnojHc4N07eF5Xp1TTndP2gMv1fG4k-0L4Vdr7Kc,5321 -fastapi/dependencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -fastapi/dependencies/__pycache__/__init__.cpython-313.pyc,, -fastapi/dependencies/__pycache__/models.cpython-313.pyc,, -fastapi/dependencies/__pycache__/utils.cpython-313.pyc,, -fastapi/dependencies/models.py,sha256=XPIkEaukHaTaFz9LoeI9f_T-3otOAVkoOC113TpFbWI,7250 -fastapi/dependencies/utils.py,sha256=XIpRML7ttx5EhmchgWz6R5CMsBuepx7KHdmi38F_e_s,39728 -fastapi/encoders.py,sha256=m2OGnq9k94Alcd6UQM8r7iwn-cjvJdpOKRVJuQR68T8,11603 -fastapi/exception_handlers.py,sha256=YVcT8Zy021VYYeecgdyh5YEUjEIHKcLspbkSf4OfbJI,1275 -fastapi/exceptions.py,sha256=fUNOBRdIsULU0TnO8aNBg3HoJeyJxXp9UV6D8wYu9lI,7453 -fastapi/logger.py,sha256=I9NNi3ov8AcqbsbC9wl1X-hdItKgYt2XTrx1f99Zpl4,54 -fastapi/middleware/__init__.py,sha256=oQDxiFVcc1fYJUOIFvphnK7pTT5kktmfL32QXpBFvvo,58 -fastapi/middleware/__pycache__/__init__.cpython-313.pyc,, -fastapi/middleware/__pycache__/asyncexitstack.cpython-313.pyc,, -fastapi/middleware/__pycache__/cors.cpython-313.pyc,, -fastapi/middleware/__pycache__/gzip.cpython-313.pyc,, -fastapi/middleware/__pycache__/httpsredirect.cpython-313.pyc,, -fastapi/middleware/__pycache__/trustedhost.cpython-313.pyc,, -fastapi/middleware/__pycache__/wsgi.cpython-313.pyc,, -fastapi/middleware/asyncexitstack.py,sha256=RKGlQpGzg3GLosqVhrxBy_NCZ9qJS7zQeNHt5Y3x-00,637 -fastapi/middleware/cors.py,sha256=ynwjWQZoc_vbhzZ3_ZXceoaSrslHFHPdoM52rXr0WUU,79 -fastapi/middleware/gzip.py,sha256=xM5PcsH8QlAimZw4VDvcmTnqQamslThsfe3CVN2voa0,79 -fastapi/middleware/httpsredirect.py,sha256=rL8eXMnmLijwVkH7_400zHri1AekfeBd6D6qs8ix950,115 -fastapi/middleware/trustedhost.py,sha256=eE5XGRxGa7c5zPnMJDGp3BxaL25k5iVQlhnv-Pk0Pss,109 -fastapi/middleware/wsgi.py,sha256=a_FMDoeTwcdig9wdAGumIH82oDFfuj4pxtQxLbAw2Ns,107 -fastapi/openapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -fastapi/openapi/__pycache__/__init__.cpython-313.pyc,, -fastapi/openapi/__pycache__/constants.cpython-313.pyc,, -fastapi/openapi/__pycache__/docs.cpython-313.pyc,, -fastapi/openapi/__pycache__/models.cpython-313.pyc,, -fastapi/openapi/__pycache__/utils.cpython-313.pyc,, -fastapi/openapi/constants.py,sha256=adGzmis1L1HJRTE3kJ5fmHS_Noq6tIY6pWv_SFzoFDU,153 -fastapi/openapi/docs.py,sha256=PcjH0Sn-yp97O0exXincG8epsY7ATyJC5662bK1DRao,12425 -fastapi/openapi/models.py,sha256=twjJWGf6lcKRJZgaZCcnQ0Ten1hDhcyCLgbr7tXIP30,14608 -fastapi/openapi/utils.py,sha256=-Haf16aXydAE-IVwloo5_MVBp0l0fi_F9er5wKeKbZ8,26916 -fastapi/param_functions.py,sha256=4fTCVlvEDbAQr7gvWYkOX8hktdl2Iwk-kv4FaTz7P8w,69596 -fastapi/params.py,sha256=1fNNSK5J7PM5Fw-F5_9uOysA2NgC-61mb3LlrBWpqJM,26205 -fastapi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -fastapi/requests.py,sha256=zayepKFcienBllv3snmWI20Gk0oHNVLU4DDhqXBb4LU,142 -fastapi/responses.py,sha256=BM7JtiZ_G4j2Z7QvHUzV1KtZPnjOoCfX35RqYXlZDh0,4144 -fastapi/routing.py,sha256=OgQMs5UPY3b1wG8P1BcVa1MrX_ZzUcWV4tEbd12nAKY,246593 -fastapi/security/__init__.py,sha256=bO8pNmxqVRXUjfl2mOKiVZLn0FpBQ61VUYVjmppnbJw,881 -fastapi/security/__pycache__/__init__.cpython-313.pyc,, -fastapi/security/__pycache__/api_key.cpython-313.pyc,, -fastapi/security/__pycache__/base.cpython-313.pyc,, -fastapi/security/__pycache__/http.cpython-313.pyc,, -fastapi/security/__pycache__/oauth2.cpython-313.pyc,, -fastapi/security/__pycache__/open_id_connect_url.cpython-313.pyc,, -fastapi/security/__pycache__/utils.cpython-313.pyc,, -fastapi/security/api_key.py,sha256=4CNLNVAStOsMhytH9C5EOUEOZrtLg_IpMQS_HcRDP4M,9793 -fastapi/security/base.py,sha256=dl4pvbC-RxjfbWgPtCWd8MVU-7CB2SZ22rJDXVCXO6c,141 -fastapi/security/http.py,sha256=Z0xALDqwgJZRAaDs40Sa68rAnjFzEL99UEmO5PJzTKA,13410 -fastapi/security/oauth2.py,sha256=sSqW4tbvoHaWNld46TYatta5rgFfjod4LhxmedXzkI8,24178 -fastapi/security/open_id_connect_url.py,sha256=V8WLPEsEq_WJlIjPwJO2vCoJWGLu-VTt1-N2H7aV-D4,3136 -fastapi/security/utils.py,sha256=E9YIoez-H2k1oBLEdxqJEi8sV1umunJYKjQHvZG3FRY,261 -fastapi/sse.py,sha256=adtwP03ePndMQOuRCNRHrTfFQxMpRwrSUoRMHyB8OvA,6848 -fastapi/staticfiles.py,sha256=iirGIt3sdY2QZXd36ijs3Cj-T0FuGFda3cd90kM9Ikw,69 -fastapi/templating.py,sha256=4zsuTWgcjcEainMJFAlW6-gnslm6AgOS1SiiDWfmQxk,76 -fastapi/testclient.py,sha256=nBvaAmX66YldReJNZXPOk1sfuo2Q6hs8bOvIaCep6LQ,66 -fastapi/types.py,sha256=g2tD842BUHC2C3_P8P06albQ4MhCb9RybrSmp5rODgU,438 -fastapi/utils.py,sha256=DX0VrnMwfVsZxRz8IitQ42c2---fDzmFZkeRMZR2UMo,4341 -fastapi/websockets.py,sha256=419uncYObEKZG0YcrXscfQQYLSWoE10jqxVMetGdR98,222 diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/REQUESTED b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/REQUESTED deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/WHEEL b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/WHEEL deleted file mode 100644 index e651d8e..0000000 --- a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: pdm-backend (2.4.9) -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/entry_points.txt b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/entry_points.txt deleted file mode 100644 index b81849e..0000000 --- a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/entry_points.txt +++ /dev/null @@ -1,5 +0,0 @@ -[console_scripts] -fastapi = fastapi.cli:main - -[gui_scripts] - diff --git a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/licenses/LICENSE b/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/licenses/LICENSE deleted file mode 100644 index 3e92463..0000000 --- a/server/venv/Lib/site-packages/fastapi-0.138.0.dist-info/licenses/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Sebastián Ramírez - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/fastapi/__init__.py b/server/venv/Lib/site-packages/fastapi/__init__.py deleted file mode 100644 index d7b99b5..0000000 --- a/server/venv/Lib/site-packages/fastapi/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -"""FastAPI framework, high performance, easy to learn, fast to code, ready for production""" - -__version__ = "0.138.0" - -from starlette import status as status - -from .applications import FastAPI as FastAPI -from .background import BackgroundTasks as BackgroundTasks -from .datastructures import UploadFile as UploadFile -from .exceptions import HTTPException as HTTPException -from .exceptions import WebSocketException as WebSocketException -from .param_functions import Body as Body -from .param_functions import Cookie as Cookie -from .param_functions import Depends as Depends -from .param_functions import File as File -from .param_functions import Form as Form -from .param_functions import Header as Header -from .param_functions import Path as Path -from .param_functions import Query as Query -from .param_functions import Security as Security -from .requests import Request as Request -from .responses import Response as Response -from .routing import APIRouter as APIRouter -from .websockets import WebSocket as WebSocket -from .websockets import WebSocketDisconnect as WebSocketDisconnect diff --git a/server/venv/Lib/site-packages/fastapi/__main__.py b/server/venv/Lib/site-packages/fastapi/__main__.py deleted file mode 100644 index fc36465..0000000 --- a/server/venv/Lib/site-packages/fastapi/__main__.py +++ /dev/null @@ -1,3 +0,0 @@ -from fastapi.cli import main - -main() diff --git a/server/venv/Lib/site-packages/fastapi/_compat/__init__.py b/server/venv/Lib/site-packages/fastapi/_compat/__init__.py deleted file mode 100644 index 4581c38..0000000 --- a/server/venv/Lib/site-packages/fastapi/_compat/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from .shared import PYDANTIC_VERSION_MINOR_TUPLE as PYDANTIC_VERSION_MINOR_TUPLE -from .shared import annotation_is_pydantic_v1 as annotation_is_pydantic_v1 -from .shared import field_annotation_is_scalar as field_annotation_is_scalar -from .shared import ( - field_annotation_is_scalar_sequence as field_annotation_is_scalar_sequence, -) -from .shared import field_annotation_is_sequence as field_annotation_is_sequence -from .shared import ( - is_bytes_or_nonable_bytes_annotation as is_bytes_or_nonable_bytes_annotation, -) -from .shared import is_bytes_sequence_annotation as is_bytes_sequence_annotation -from .shared import is_pydantic_v1_model_instance as is_pydantic_v1_model_instance -from .shared import ( - is_uploadfile_or_nonable_uploadfile_annotation as is_uploadfile_or_nonable_uploadfile_annotation, -) -from .shared import ( - is_uploadfile_sequence_annotation as is_uploadfile_sequence_annotation, -) -from .shared import lenient_issubclass as lenient_issubclass -from .shared import sequence_types as sequence_types -from .shared import value_is_sequence as value_is_sequence -from .v2 import ModelField as ModelField -from .v2 import PydanticSchemaGenerationError as PydanticSchemaGenerationError -from .v2 import RequiredParam as RequiredParam -from .v2 import Undefined as Undefined -from .v2 import Url as Url -from .v2 import copy_field_info as copy_field_info -from .v2 import create_body_model as create_body_model -from .v2 import evaluate_forwardref as evaluate_forwardref -from .v2 import get_cached_model_fields as get_cached_model_fields -from .v2 import get_definitions as get_definitions -from .v2 import get_flat_models_from_fields as get_flat_models_from_fields -from .v2 import get_missing_field_error as get_missing_field_error -from .v2 import get_model_name_map as get_model_name_map -from .v2 import get_schema_from_model_field as get_schema_from_model_field -from .v2 import is_scalar_field as is_scalar_field -from .v2 import serialize_sequence_value as serialize_sequence_value -from .v2 import ( - with_info_plain_validator_function as with_info_plain_validator_function, -) diff --git a/server/venv/Lib/site-packages/fastapi/_compat/shared.py b/server/venv/Lib/site-packages/fastapi/_compat/shared.py deleted file mode 100644 index bd38c55..0000000 --- a/server/venv/Lib/site-packages/fastapi/_compat/shared.py +++ /dev/null @@ -1,214 +0,0 @@ -import types -import typing -import warnings -from collections import deque -from collections.abc import Mapping, Sequence -from dataclasses import is_dataclass -from typing import ( - Annotated, - Any, - TypeGuard, - TypeVar, - Union, - get_args, - get_origin, -) - -from fastapi.types import UnionType -from pydantic import BaseModel -from pydantic.version import VERSION as PYDANTIC_VERSION -from starlette.datastructures import UploadFile - -_T = TypeVar("_T") - -# Copy from Pydantic: pydantic/_internal/_typing_extra.py -WithArgsTypes: tuple[Any, ...] = ( - typing._GenericAlias, # type: ignore[attr-defined] # ty: ignore[unresolved-attribute] - types.GenericAlias, - types.UnionType, -) # pyright: ignore[reportAttributeAccessIssue] - -PYDANTIC_VERSION_MINOR_TUPLE = tuple(int(x) for x in PYDANTIC_VERSION.split(".")[:2]) - - -sequence_annotation_to_type = { - Sequence: list, - list: list, - tuple: tuple, - set: set, - frozenset: frozenset, - deque: deque, -} - -sequence_types: tuple[type[Any], ...] = tuple(sequence_annotation_to_type.keys()) - - -# Copy of Pydantic: pydantic/_internal/_utils.py with added TypeGuard -def lenient_issubclass( - cls: Any, class_or_tuple: type[_T] | tuple[type[_T], ...] | None -) -> TypeGuard[type[_T]]: - try: - return isinstance(cls, type) and issubclass(cls, class_or_tuple) # type: ignore[arg-type] # ty: ignore[invalid-argument-type] - except TypeError: # pragma: no cover - if isinstance(cls, WithArgsTypes): - return False - raise # pragma: no cover - - -def _annotation_is_sequence(annotation: type[Any] | None) -> bool: - if lenient_issubclass(annotation, (str, bytes)): - return False - return lenient_issubclass(annotation, sequence_types) - - -def field_annotation_is_sequence(annotation: type[Any] | None) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - for arg in get_args(annotation): - if field_annotation_is_sequence(arg): - return True - return False - return _annotation_is_sequence(annotation) or _annotation_is_sequence( - get_origin(annotation) - ) - - -def value_is_sequence(value: Any) -> bool: - return isinstance(value, sequence_types) and not isinstance(value, (str, bytes)) - - -def _annotation_is_complex(annotation: type[Any] | None) -> bool: - return ( - lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile)) - or _annotation_is_sequence(annotation) - or is_dataclass(annotation) - ) - - -def field_annotation_is_complex(annotation: type[Any] | None) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - return any(field_annotation_is_complex(arg) for arg in get_args(annotation)) - - if origin is Annotated: - return field_annotation_is_complex(get_args(annotation)[0]) - - return ( - _annotation_is_complex(annotation) - or _annotation_is_complex(origin) - or hasattr(origin, "__pydantic_core_schema__") - or hasattr(origin, "__get_pydantic_core_schema__") - ) - - -def field_annotation_is_scalar(annotation: Any) -> bool: - # handle Ellipsis here to make tuple[int, ...] work nicely - return annotation is Ellipsis or not field_annotation_is_complex(annotation) - - -def field_annotation_is_scalar_sequence(annotation: type[Any] | None) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - at_least_one_scalar_sequence = False - for arg in get_args(annotation): - if field_annotation_is_scalar_sequence(arg): - at_least_one_scalar_sequence = True - continue - elif not field_annotation_is_scalar(arg): - return False - return at_least_one_scalar_sequence - return field_annotation_is_sequence(annotation) and all( - field_annotation_is_scalar(sub_annotation) - for sub_annotation in get_args(annotation) - ) - - -def is_bytes_or_nonable_bytes_annotation(annotation: Any) -> bool: - if lenient_issubclass(annotation, bytes): - return True - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - for arg in get_args(annotation): - if lenient_issubclass(arg, bytes): - return True - return False - - -def is_uploadfile_or_nonable_uploadfile_annotation(annotation: Any) -> bool: - if lenient_issubclass(annotation, UploadFile): - return True - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - for arg in get_args(annotation): - if lenient_issubclass(arg, UploadFile): - return True - return False - - -def is_bytes_sequence_annotation(annotation: Any) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - at_least_one = False - for arg in get_args(annotation): - if is_bytes_sequence_annotation(arg): - at_least_one = True - continue - return at_least_one - return field_annotation_is_sequence(annotation) and all( - is_bytes_or_nonable_bytes_annotation(sub_annotation) - for sub_annotation in get_args(annotation) - ) - - -def is_uploadfile_sequence_annotation(annotation: Any) -> bool: - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - at_least_one = False - for arg in get_args(annotation): - if is_uploadfile_sequence_annotation(arg): - at_least_one = True - continue - return at_least_one - return field_annotation_is_sequence(annotation) and all( - is_uploadfile_or_nonable_uploadfile_annotation(sub_annotation) - for sub_annotation in get_args(annotation) - ) - - -def is_pydantic_v1_model_instance(obj: Any) -> bool: - # TODO: remove this function once the required version of Pydantic fully - # removes pydantic.v1 - try: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", UserWarning) - from pydantic import v1 - except ImportError: # pragma: no cover - return False - return isinstance(obj, v1.BaseModel) - - -def is_pydantic_v1_model_class(cls: Any) -> bool: - # TODO: remove this function once the required version of Pydantic fully - # removes pydantic.v1 - try: - with warnings.catch_warnings(): - warnings.simplefilter("ignore", UserWarning) - from pydantic import v1 - except ImportError: # pragma: no cover - return False - return lenient_issubclass(cls, v1.BaseModel) - - -def annotation_is_pydantic_v1(annotation: Any) -> bool: - if is_pydantic_v1_model_class(annotation): - return True - origin = get_origin(annotation) - if origin is Union or origin is UnionType: - for arg in get_args(annotation): - if is_pydantic_v1_model_class(arg): - return True - if field_annotation_is_sequence(annotation): - for sub_annotation in get_args(annotation): - if annotation_is_pydantic_v1(sub_annotation): - return True - return False diff --git a/server/venv/Lib/site-packages/fastapi/_compat/v2.py b/server/venv/Lib/site-packages/fastapi/_compat/v2.py deleted file mode 100644 index 7be686d..0000000 --- a/server/venv/Lib/site-packages/fastapi/_compat/v2.py +++ /dev/null @@ -1,493 +0,0 @@ -import re -import warnings -from collections.abc import Sequence -from copy import copy -from dataclasses import dataclass, is_dataclass -from enum import Enum -from functools import lru_cache -from typing import ( - Annotated, - Any, - Literal, - Union, - cast, - get_args, - get_origin, -) - -from fastapi._compat import lenient_issubclass, shared -from fastapi.openapi.constants import REF_TEMPLATE -from fastapi.types import IncEx, ModelNameMap, UnionType -from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, create_model -from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError -from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation -from pydantic import ValidationError as ValidationError -from pydantic._internal import _typing_extra as _pydantic_typing_extra -from pydantic._internal._schema_generation_shared import ( # type: ignore[attr-defined] - GetJsonSchemaHandler as GetJsonSchemaHandler, -) -from pydantic.fields import FieldInfo as FieldInfo -from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema -from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue -from pydantic_core import CoreSchema as CoreSchema -from pydantic_core import PydanticUndefined -from pydantic_core import Url as Url -from pydantic_core.core_schema import ( - with_info_plain_validator_function as with_info_plain_validator_function, -) - -RequiredParam = PydanticUndefined -Undefined = PydanticUndefined - - -def evaluate_forwardref( - value: Any, - globalns: dict[str, Any] | None = None, - localns: dict[str, Any] | None = None, -) -> Any: - # eval_type_lenient has been deprecated since Pydantic v2.10.0b1 (PR #10530) - try_eval_type = getattr(_pydantic_typing_extra, "try_eval_type", None) - if try_eval_type is not None: - return try_eval_type(value, globalns, localns)[0] - return _pydantic_typing_extra.eval_type_lenient( # ty: ignore[deprecated] - value, globalns, localns - ) - - -class GenerateJsonSchema(_GenerateJsonSchema): - # TODO: remove when this is merged (or equivalent): https://github.com/pydantic/pydantic/pull/12841 - # and dropping support for any version of Pydantic before that one (so, in a very long time) - def bytes_schema(self, schema: CoreSchema) -> JsonSchemaValue: - json_schema = {"type": "string", "contentMediaType": "application/octet-stream"} - bytes_mode = ( - self._config.ser_json_bytes - if self.mode == "serialization" - else self._config.val_json_bytes - ) - if bytes_mode == "base64": - json_schema["contentEncoding"] = "base64" - self.update_with_validations(json_schema, schema, self.ValidationsMapping.bytes) - return json_schema - - -# TODO: remove when dropping support for Pydantic < v2.12.3 -_Attrs = { - "default": ..., - "default_factory": None, - "alias": None, - "alias_priority": None, - "validation_alias": None, - "serialization_alias": None, - "title": None, - "field_title_generator": None, - "description": None, - "examples": None, - "exclude": None, - "exclude_if": None, - "discriminator": None, - "deprecated": None, - "json_schema_extra": None, - "frozen": None, - "validate_default": None, - "repr": True, - "init": None, - "init_var": None, - "kw_only": None, -} - - -# TODO: remove when dropping support for Pydantic < v2.12.3 -def asdict(field_info: FieldInfo) -> dict[str, Any]: - attributes = {} - for attr in _Attrs: - value = getattr(field_info, attr, Undefined) - if value is not Undefined: - attributes[attr] = value - return { - "annotation": field_info.annotation, - "metadata": field_info.metadata, - "attributes": attributes, - } - - -@dataclass -class ModelField: - field_info: FieldInfo - name: str - mode: Literal["validation", "serialization"] = "validation" - config: ConfigDict | None = None - - @property - def alias(self) -> str: - a = self.field_info.alias - return a if a is not None else self.name - - @property - def validation_alias(self) -> str | None: - va = self.field_info.validation_alias - if isinstance(va, str) and va: - return va - return None - - @property - def serialization_alias(self) -> str | None: - sa = self.field_info.serialization_alias - return sa or None - - @property - def default(self) -> Any: - return self.get_default() - - def __post_init__(self) -> None: - with warnings.catch_warnings(): - # Pydantic >= 2.12.0 warns about field specific metadata that is unused - # (e.g. `TypeAdapter(Annotated[int, Field(alias='b')])`). In some cases, we - # end up building the type adapter from a model field annotation so we - # need to ignore the warning: - if shared.PYDANTIC_VERSION_MINOR_TUPLE >= (2, 12): - from pydantic.warnings import UnsupportedFieldAttributeWarning - - warnings.simplefilter( - "ignore", category=UnsupportedFieldAttributeWarning - ) - # TODO: remove after setting the min Pydantic to v2.12.3 - # that adds asdict(), and use self.field_info.asdict() instead - field_dict = asdict(self.field_info) - annotated_args = ( - field_dict["annotation"], - *field_dict["metadata"], - # this FieldInfo needs to be created again so that it doesn't include - # the old field info metadata and only the rest of the attributes - Field(**field_dict["attributes"]), - ) - self._type_adapter: TypeAdapter[Any] = TypeAdapter( - Annotated[annotated_args], # ty: ignore[invalid-type-form] - config=self.config, - ) - - def get_default(self) -> Any: - if self.field_info.is_required(): - return Undefined - return self.field_info.get_default(call_default_factory=True) - - def validate( - self, - value: Any, - values: dict[str, Any] = {}, # noqa: B006 - *, - loc: tuple[int | str, ...] = (), - ) -> tuple[Any, list[dict[str, Any]]]: - try: - return ( - self._type_adapter.validate_python(value, from_attributes=True), - [], - ) - except ValidationError as exc: - return None, _regenerate_error_with_loc( - errors=exc.errors(include_url=False), loc_prefix=loc - ) - - def serialize( - self, - value: Any, - *, - mode: Literal["json", "python"] = "json", - include: IncEx | None = None, - exclude: IncEx | None = None, - by_alias: bool = True, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) -> Any: - # What calls this code passes a value that already called - # self._type_adapter.validate_python(value) - return self._type_adapter.dump_python( - value, - mode=mode, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - def serialize_json( - self, - value: Any, - *, - include: IncEx | None = None, - exclude: IncEx | None = None, - by_alias: bool = True, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - ) -> bytes: - # What calls this code passes a value that already called - # self._type_adapter.validate_python(value) - # This uses Pydantic's dump_json() which serializes directly to JSON - # bytes in one pass (via Rust), avoiding the intermediate Python dict - # step of dump_python(mode="json") + json.dumps(). - return self._type_adapter.dump_json( - value, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - def __hash__(self) -> int: - # Each ModelField is unique for our purposes, to allow making a dict from - # ModelField to its JSON Schema. - return id(self) - - -def _has_computed_fields(field: ModelField) -> bool: - computed_fields = field._type_adapter.core_schema.get("schema", {}).get( - "computed_fields", [] - ) - return len(computed_fields) > 0 - - -def get_schema_from_model_field( - *, - field: ModelField, - model_name_map: ModelNameMap, - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue - ], - separate_input_output_schemas: bool = True, -) -> dict[str, Any]: - override_mode: Literal["validation"] | None = ( - None - if (separate_input_output_schemas or _has_computed_fields(field)) - else "validation" - ) - field_alias = ( - (field.validation_alias or field.alias) - if field.mode == "validation" - else (field.serialization_alias or field.alias) - ) - - # This expects that GenerateJsonSchema was already used to generate the definitions - json_schema = field_mapping[(field, override_mode or field.mode)] - if "$ref" not in json_schema: - # TODO remove when deprecating Pydantic v1 - # Ref: https://github.com/pydantic/pydantic/blob/d61792cc42c80b13b23e3ffa74bc37ec7c77f7d1/pydantic/schema.py#L207 - json_schema["title"] = field.field_info.title or field_alias.title().replace( - "_", " " - ) - return json_schema - - -def get_definitions( - *, - fields: Sequence[ModelField], - model_name_map: ModelNameMap, - separate_input_output_schemas: bool = True, -) -> tuple[ - dict[tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue], - dict[str, dict[str, Any]], -]: - schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE) - validation_fields = [field for field in fields if field.mode == "validation"] - serialization_fields = [field for field in fields if field.mode == "serialization"] - flat_validation_models = get_flat_models_from_fields( - validation_fields, known_models=set() - ) - flat_serialization_models = get_flat_models_from_fields( - serialization_fields, known_models=set() - ) - flat_validation_model_fields = [ - ModelField( - field_info=FieldInfo(annotation=model), - name=model.__name__, - mode="validation", - ) - for model in flat_validation_models - ] - flat_serialization_model_fields = [ - ModelField( - field_info=FieldInfo(annotation=model), - name=model.__name__, - mode="serialization", - ) - for model in flat_serialization_models - ] - flat_model_fields = flat_validation_model_fields + flat_serialization_model_fields - input_types = {f.field_info.annotation for f in fields} - unique_flat_model_fields = { - f for f in flat_model_fields if f.field_info.annotation not in input_types - } - inputs = [ - ( - field, - ( - field.mode - if (separate_input_output_schemas or _has_computed_fields(field)) - else "validation" - ), - field._type_adapter.core_schema, - ) - for field in list(fields) + list(unique_flat_model_fields) - ] - field_mapping, definitions = schema_generator.generate_definitions(inputs=inputs) - for item_def in cast(dict[str, dict[str, Any]], definitions).values(): - if "description" in item_def: - item_description = cast(str, item_def["description"]).split("\f")[0] - item_def["description"] = item_description - # definitions: dict[DefsRef, dict[str, Any]] - # but mypy complains about general str in other places that are not declared as - # DefsRef, although DefsRef is just str: - # DefsRef = NewType('DefsRef', str) - # So, a cast to simplify the types here - return field_mapping, cast(dict[str, dict[str, Any]], definitions) - - -def is_scalar_field(field: ModelField) -> bool: - from fastapi import params - - return shared.field_annotation_is_scalar( - field.field_info.annotation - ) and not isinstance(field.field_info, params.Body) - - -def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo: - cls = type(field_info) - merged_field_info = cls.from_annotation(annotation) - new_field_info = copy(field_info) - new_field_info.metadata = merged_field_info.metadata - new_field_info.annotation = merged_field_info.annotation - return new_field_info - - -def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]: - origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation - if origin_type is Union or origin_type is UnionType: # Handle optional sequences - union_args = get_args(field.field_info.annotation) - for union_arg in union_args: - if union_arg is type(None): - continue - origin_type = get_origin(union_arg) or union_arg - break - assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type] # ty: ignore[invalid-argument-type] - return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return,index] # ty: ignore[invalid-return-type] - - -def get_missing_field_error(loc: tuple[int | str, ...]) -> dict[str, Any]: - error = ValidationError.from_exception_data( - "Field required", [{"type": "missing", "loc": loc, "input": {}}] - ).errors(include_url=False)[0] - error["input"] = None - return error # type: ignore[return-value] # ty: ignore[invalid-return-type] - - -def create_body_model( - *, fields: Sequence[ModelField], model_name: str -) -> type[BaseModel]: - field_params = {f.name: (f.field_info.annotation, f.field_info) for f in fields} - BodyModel: type[BaseModel] = create_model(model_name, **field_params) # type: ignore[call-overload] # ty: ignore[no-matching-overload] - return BodyModel - - -def get_model_fields(model: type[BaseModel]) -> list[ModelField]: - model_fields: list[ModelField] = [] - for name, field_info in model.model_fields.items(): - type_ = field_info.annotation - if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_): - model_config = None - else: - model_config = model.model_config - model_fields.append( - ModelField( - field_info=field_info, - name=name, - config=model_config, - ) - ) - return model_fields - - -@lru_cache -def get_cached_model_fields(model: type[BaseModel]) -> list[ModelField]: - return get_model_fields(model) - - -# Duplicate of several schema functions from Pydantic v1 to make them compatible with -# Pydantic v2 and allow mixing the models - -TypeModelOrEnum = type["BaseModel"] | type[Enum] -TypeModelSet = set[TypeModelOrEnum] - - -def normalize_name(name: str) -> str: - return re.sub(r"[^a-zA-Z0-9.\-_]", "_", name) - - -def get_model_name_map(unique_models: TypeModelSet) -> dict[TypeModelOrEnum, str]: - name_model_map = {} - for model in unique_models: - model_name = normalize_name(model.__name__) - name_model_map[model_name] = model - return {v: k for k, v in name_model_map.items()} - - -def get_flat_models_from_model( - model: type["BaseModel"], known_models: TypeModelSet | None = None -) -> TypeModelSet: - known_models = known_models or set() - fields = get_model_fields(model) - get_flat_models_from_fields(fields, known_models=known_models) - return known_models - - -def get_flat_models_from_annotation( - annotation: Any, known_models: TypeModelSet -) -> TypeModelSet: - origin = get_origin(annotation) - if origin is not None: - for arg in get_args(annotation): - if lenient_issubclass(arg, (BaseModel, Enum)): - if arg not in known_models: - known_models.add(arg) # type: ignore[arg-type] - if lenient_issubclass(arg, BaseModel): - get_flat_models_from_model(arg, known_models=known_models) - else: - get_flat_models_from_annotation(arg, known_models=known_models) - return known_models - - -def get_flat_models_from_field( - field: ModelField, known_models: TypeModelSet -) -> TypeModelSet: - field_type = field.field_info.annotation - if lenient_issubclass(field_type, BaseModel): - if field_type in known_models: - return known_models - known_models.add(field_type) - get_flat_models_from_model(field_type, known_models=known_models) - elif lenient_issubclass(field_type, Enum): - known_models.add(field_type) - else: - get_flat_models_from_annotation(field_type, known_models=known_models) - return known_models - - -def get_flat_models_from_fields( - fields: Sequence[ModelField], known_models: TypeModelSet -) -> TypeModelSet: - for field in fields: - get_flat_models_from_field(field, known_models=known_models) - return known_models - - -def _regenerate_error_with_loc( - *, errors: Sequence[Any], loc_prefix: tuple[str | int, ...] -) -> list[dict[str, Any]]: - updated_loc_errors: list[Any] = [ - {**err, "loc": loc_prefix + err.get("loc", ())} for err in errors - ] - - return updated_loc_errors diff --git a/server/venv/Lib/site-packages/fastapi/applications.py b/server/venv/Lib/site-packages/fastapi/applications.py deleted file mode 100644 index 56e1a3e..0000000 --- a/server/venv/Lib/site-packages/fastapi/applications.py +++ /dev/null @@ -1,4768 +0,0 @@ -import os -from collections.abc import Awaitable, Callable, Coroutine, Sequence -from enum import Enum -from typing import Annotated, Any, Literal, TypeVar - -from annotated_doc import Doc -from fastapi import routing -from fastapi.datastructures import Default, DefaultPlaceholder -from fastapi.exception_handlers import ( - http_exception_handler, - request_validation_exception_handler, - websocket_request_validation_exception_handler, -) -from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError -from fastapi.logger import logger -from fastapi.middleware.asyncexitstack import AsyncExitStackMiddleware -from fastapi.openapi.docs import ( - get_redoc_html, - get_swagger_ui_html, - get_swagger_ui_oauth2_redirect_html, -) -from fastapi.openapi.utils import get_openapi -from fastapi.params import Depends -from fastapi.types import DecoratedCallable, IncEx -from fastapi.utils import generate_unique_id -from starlette.applications import Starlette -from starlette.datastructures import State -from starlette.exceptions import HTTPException -from starlette.middleware import Middleware -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.middleware.errors import ServerErrorMiddleware -from starlette.middleware.exceptions import ExceptionMiddleware -from starlette.requests import Request -from starlette.responses import HTMLResponse, JSONResponse, Response -from starlette.routing import BaseRoute -from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send -from typing_extensions import deprecated - -AppType = TypeVar("AppType", bound="FastAPI") - - -class FastAPI(Starlette): - """ - `FastAPI` app class, the main entrypoint to use FastAPI. - - Read more in the - [FastAPI docs for First Steps](https://fastapi.tiangolo.com/tutorial/first-steps/). - - ## Example - - ```python - from fastapi import FastAPI - - app = FastAPI() - ``` - """ - - def __init__( - self: AppType, - *, - debug: Annotated[ - bool, - Doc( - """ - Boolean indicating if debug tracebacks should be returned on server - errors. - - Read more in the - [Starlette docs for Applications](https://www.starlette.dev/applications/#instantiating-the-application). - """ - ), - ] = False, - routes: Annotated[ - list[BaseRoute] | None, - Doc( - """ - **Note**: you probably shouldn't use this parameter, it is inherited - from Starlette and supported for compatibility. - - --- - - A list of routes to serve incoming HTTP and WebSocket requests. - """ - ), - deprecated( - """ - You normally wouldn't use this parameter with FastAPI, it is inherited - from Starlette and supported for compatibility. - - In FastAPI, you normally would use the *path operation methods*, - like `app.get()`, `app.post()`, etc. - """ - ), - ] = None, - title: Annotated[ - str, - Doc( - """ - The title of the API. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(title="ChimichangApp") - ``` - """ - ), - ] = "FastAPI", - summary: Annotated[ - str | None, - Doc( - """ - A short summary of the API. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(summary="Deadpond's favorite app. Nuff said.") - ``` - """ - ), - ] = None, - description: Annotated[ - str, - Doc( - ''' - A description of the API. Supports Markdown (using - [CommonMark syntax](https://commonmark.org/)). - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI( - description=""" - ChimichangApp API helps you do awesome stuff. 🚀 - - ## Items - - You can **read items**. - - ## Users - - You will be able to: - - * **Create users** (_not implemented_). - * **Read users** (_not implemented_). - - """ - ) - ``` - ''' - ), - ] = "", - version: Annotated[ - str, - Doc( - """ - The version of the API. - - **Note** This is the version of your application, not the version of - the OpenAPI specification nor the version of FastAPI being used. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(version="0.0.1") - ``` - """ - ), - ] = "0.1.0", - openapi_url: Annotated[ - str | None, - Doc( - """ - The URL where the OpenAPI schema will be served from. - - If you set it to `None`, no OpenAPI schema will be served publicly, and - the default automatic endpoints `/docs` and `/redoc` will also be - disabled. - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#openapi-url). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(openapi_url="/api/v1/openapi.json") - ``` - """ - ), - ] = "/openapi.json", - openapi_tags: Annotated[ - list[dict[str, Any]] | None, - Doc( - """ - A list of tags used by OpenAPI, these are the same `tags` you can set - in the *path operations*, like: - - * `@app.get("/users/", tags=["users"])` - * `@app.get("/items/", tags=["items"])` - - The order of the tags can be used to specify the order shown in - tools like Swagger UI, used in the automatic path `/docs`. - - It's not required to specify all the tags used. - - The tags that are not declared MAY be organized randomly or based - on the tools' logic. Each tag name in the list MUST be unique. - - The value of each item is a `dict` containing: - - * `name`: The name of the tag. - * `description`: A short description of the tag. - [CommonMark syntax](https://commonmark.org/) MAY be used for rich - text representation. - * `externalDocs`: Additional external documentation for this tag. If - provided, it would contain a `dict` with: - * `description`: A short description of the target documentation. - [CommonMark syntax](https://commonmark.org/) MAY be used for - rich text representation. - * `url`: The URL for the target documentation. Value MUST be in - the form of a URL. - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-tags). - - **Example** - - ```python - from fastapi import FastAPI - - tags_metadata = [ - { - "name": "users", - "description": "Operations with users. The **login** logic is also here.", - }, - { - "name": "items", - "description": "Manage items. So _fancy_ they have their own docs.", - "externalDocs": { - "description": "Items external docs", - "url": "https://fastapi.tiangolo.com/", - }, - }, - ] - - app = FastAPI(openapi_tags=tags_metadata) - ``` - """ - ), - ] = None, - servers: Annotated[ - list[dict[str, str | Any]] | None, - Doc( - """ - A `list` of `dict`s with connectivity information to a target server. - - You would use it, for example, if your application is served from - different domains and you want to use the same Swagger UI in the - browser to interact with each of them (instead of having multiple - browser tabs open). Or if you want to leave fixed the possible URLs. - - If the servers `list` is not provided, or is an empty `list`, the - `servers` property in the generated OpenAPI will be: - - * a `dict` with a `url` value of the application's mounting point - (`root_path`) if it's different from `/`. - * otherwise, the `servers` property will be omitted from the OpenAPI - schema. - - Each item in the `list` is a `dict` containing: - - * `url`: A URL to the target host. This URL supports Server Variables - and MAY be relative, to indicate that the host location is relative - to the location where the OpenAPI document is being served. Variable - substitutions will be made when a variable is named in `{`brackets`}`. - * `description`: An optional string describing the host designated by - the URL. [CommonMark syntax](https://commonmark.org/) MAY be used for - rich text representation. - * `variables`: A `dict` between a variable name and its value. The value - is used for substitution in the server's URL template. - - Read more in the - [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#additional-servers). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI( - servers=[ - {"url": "https://stag.example.com", "description": "Staging environment"}, - {"url": "https://prod.example.com", "description": "Production environment"}, - ] - ) - ``` - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of global dependencies, they will be applied to each - *path operation*, including in sub-routers. - - Read more about it in the - [FastAPI docs for Global Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/global-dependencies/). - - **Example** - - ```python - from fastapi import Depends, FastAPI - - from .dependencies import func_dep_1, func_dep_2 - - app = FastAPI(dependencies=[Depends(func_dep_1), Depends(func_dep_2)]) - ``` - """ - ), - ] = None, - default_response_class: Annotated[ - type[Response], - Doc( - """ - The default response class to be used. - - Read more in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). - - **Example** - - ```python - from fastapi import FastAPI - from fastapi.responses import ORJSONResponse - - app = FastAPI(default_response_class=ORJSONResponse) - ``` - """ - ), - ] = Default(JSONResponse), - redirect_slashes: Annotated[ - bool, - Doc( - """ - Whether to detect and redirect slashes in URLs when the client doesn't - use the same format. - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(redirect_slashes=True) # the default - - @app.get("/items/") - async def read_items(): - return [{"item_id": "Foo"}] - ``` - - With this app, if a client goes to `/items` (without a trailing slash), - they will be automatically redirected with an HTTP status code of 307 - to `/items/`. - """ - ), - ] = True, - docs_url: Annotated[ - str | None, - Doc( - """ - The path to the automatic interactive API documentation. - It is handled in the browser by Swagger UI. - - The default URL is `/docs`. You can disable it by setting it to `None`. - - If `openapi_url` is set to `None`, this will be automatically disabled. - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(docs_url="/documentation", redoc_url=None) - ``` - """ - ), - ] = "/docs", - redoc_url: Annotated[ - str | None, - Doc( - """ - The path to the alternative automatic interactive API documentation - provided by ReDoc. - - The default URL is `/redoc`. You can disable it by setting it to `None`. - - If `openapi_url` is set to `None`, this will be automatically disabled. - - Read more in the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#docs-urls). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(docs_url="/documentation", redoc_url="redocumentation") - ``` - """ - ), - ] = "/redoc", - swagger_ui_oauth2_redirect_url: Annotated[ - str | None, - Doc( - """ - The OAuth2 redirect endpoint for the Swagger UI. - - By default it is `/docs/oauth2-redirect`. - - This is only used if you use OAuth2 (with the "Authorize" button) - with Swagger UI. - """ - ), - ] = "/docs/oauth2-redirect", - swagger_ui_init_oauth: Annotated[ - dict[str, Any] | None, - Doc( - """ - OAuth2 configuration for the Swagger UI, by default shown at `/docs`. - - Read more about the available configuration options in the - [Swagger UI docs](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/). - """ - ), - ] = None, - middleware: Annotated[ - Sequence[Middleware] | None, - Doc( - """ - List of middleware to be added when creating the application. - - In FastAPI you would normally do this with `app.add_middleware()` - instead. - - Read more in the - [FastAPI docs for Middleware](https://fastapi.tiangolo.com/tutorial/middleware/). - """ - ), - ] = None, - exception_handlers: Annotated[ - dict[ - int | type[Exception], - Callable[[Request, Any], Coroutine[Any, Any, Response]], - ] - | None, - Doc( - """ - A dictionary with handlers for exceptions. - - In FastAPI, you would normally use the decorator - `@app.exception_handler()`. - - Read more in the - [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/). - """ - ), - ] = None, - on_startup: Annotated[ - Sequence[Callable[[], Any]] | None, - Doc( - """ - A list of startup event handler functions. - - You should instead use the `lifespan` handlers. - - Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). - """ - ), - ] = None, - on_shutdown: Annotated[ - Sequence[Callable[[], Any]] | None, - Doc( - """ - A list of shutdown event handler functions. - - You should instead use the `lifespan` handlers. - - Read more in the - [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). - """ - ), - ] = None, - lifespan: Annotated[ - Lifespan[AppType] | None, - Doc( - """ - A `Lifespan` context manager handler. This replaces `startup` and - `shutdown` functions with a single context manager. - - Read more in the - [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). - """ - ), - ] = None, - terms_of_service: Annotated[ - str | None, - Doc( - """ - A URL to the Terms of Service for your API. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more at the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - app = FastAPI(terms_of_service="http://example.com/terms/") - ``` - """ - ), - ] = None, - contact: Annotated[ - dict[str, str | Any] | None, - Doc( - """ - A dictionary with the contact information for the exposed API. - - It can contain several fields. - - * `name`: (`str`) The name of the contact person/organization. - * `url`: (`str`) A URL pointing to the contact information. MUST be in - the format of a URL. - * `email`: (`str`) The email address of the contact person/organization. - MUST be in the format of an email address. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more at the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - app = FastAPI( - contact={ - "name": "Deadpoolio the Amazing", - "url": "http://x-force.example.com/contact/", - "email": "dp@x-force.example.com", - } - ) - ``` - """ - ), - ] = None, - license_info: Annotated[ - dict[str, str | Any] | None, - Doc( - """ - A dictionary with the license information for the exposed API. - - It can contain several fields. - - * `name`: (`str`) **REQUIRED** (if a `license_info` is set). The - license name used for the API. - * `identifier`: (`str`) An [SPDX](https://spdx.dev/) license expression - for the API. The `identifier` field is mutually exclusive of the `url` - field. Available since OpenAPI 3.1.0, FastAPI 0.99.0. - * `url`: (`str`) A URL to the license used for the API. This MUST be - the format of a URL. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more at the - [FastAPI docs for Metadata and Docs URLs](https://fastapi.tiangolo.com/tutorial/metadata/#metadata-for-api). - - **Example** - - ```python - app = FastAPI( - license_info={ - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html", - } - ) - ``` - """ - ), - ] = None, - openapi_prefix: Annotated[ - str, - Doc( - """ - A URL prefix for the OpenAPI URL. - """ - ), - deprecated( - """ - "openapi_prefix" has been deprecated in favor of "root_path", which - follows more closely the ASGI standard, is simpler, and more - automatic. - """ - ), - ] = "", - root_path: Annotated[ - str, - Doc( - """ - A path prefix handled by a proxy that is not seen by the application - but is seen by external clients, which affects things like Swagger UI. - - Read more about it at the - [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(root_path="/api/v1") - ``` - """ - ), - ] = "", - root_path_in_servers: Annotated[ - bool, - Doc( - """ - To disable automatically generating the URLs in the `servers` field - in the autogenerated OpenAPI using the `root_path`. - - Read more about it in the - [FastAPI docs for Behind a Proxy](https://fastapi.tiangolo.com/advanced/behind-a-proxy/#disable-automatic-server-from-root-path). - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI(root_path_in_servers=False) - ``` - """ - ), - ] = True, - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses to be shown in OpenAPI. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). - - And in the - [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - OpenAPI callbacks that should apply to all *path operations*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - webhooks: Annotated[ - routing.APIRouter | None, - Doc( - """ - Add OpenAPI webhooks. This is similar to `callbacks` but it doesn't - depend on specific *path operations*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - **Note**: This is available since OpenAPI 3.1.0, FastAPI 0.99.0. - - Read more about it in the - [FastAPI docs for OpenAPI Webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark all *path operations* as deprecated. You probably don't need it, - but it's available. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#deprecate-a-path-operation). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) all the *path operations* in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - swagger_ui_parameters: Annotated[ - dict[str, Any] | None, - Doc( - """ - Parameters to configure Swagger UI, the autogenerated interactive API - documentation (by default at `/docs`). - - Read more about it in the - [FastAPI docs about how to Configure Swagger UI](https://fastapi.tiangolo.com/how-to/configure-swagger-ui/). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - separate_input_output_schemas: Annotated[ - bool, - Doc( - """ - Whether to generate separate OpenAPI schemas for request body and - response body when the results would be more precise. - - This is particularly useful when automatically generating clients. - - For example, if you have a model like: - - ```python - from pydantic import BaseModel - - class Item(BaseModel): - name: str - tags: list[str] = [] - ``` - - When `Item` is used for input, a request body, `tags` is not required, - the client doesn't have to provide it. - - But when using `Item` for output, for a response body, `tags` is always - available because it has a default value, even if it's just an empty - list. So, the client should be able to always expect it. - - In this case, there would be two different schemas, one for input and - another one for output. - - Read more about it in the - [FastAPI docs about how to separate schemas for input and output](https://fastapi.tiangolo.com/how-to/separate-openapi-schemas) - """ - ), - ] = True, - openapi_external_docs: Annotated[ - dict[str, Any] | None, - Doc( - """ - This field allows you to provide additional external documentation links. - If provided, it must be a dictionary containing: - - * `description`: A brief description of the external documentation. - * `url`: The URL pointing to the external documentation. The value **MUST** - be a valid URL format. - - **Example**: - - ```python - from fastapi import FastAPI - - external_docs = { - "description": "Detailed API Reference", - "url": "https://example.com/api-docs", - } - - app = FastAPI(openapi_external_docs=external_docs) - ``` - """ - ), - ] = None, - strict_content_type: Annotated[ - bool, - Doc( - """ - Enable strict checking for request Content-Type headers. - - When `True` (the default), requests with a body that do not include - a `Content-Type` header will **not** be parsed as JSON. - - This prevents potential cross-site request forgery (CSRF) attacks - that exploit the browser's ability to send requests without a - Content-Type header, bypassing CORS preflight checks. In particular - applicable for apps that need to be run locally (in localhost). - - When `False`, requests without a `Content-Type` header will have - their body parsed as JSON, which maintains compatibility with - certain clients that don't send `Content-Type` headers. - - Read more about it in the - [FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/). - """ - ), - ] = True, - **extra: Annotated[ - Any, - Doc( - """ - Extra keyword arguments to be stored in the app, not used by FastAPI - anywhere. - """ - ), - ], - ) -> None: - self.debug = debug - self.title = title - self.summary = summary - self.description = description - self.version = version - self.terms_of_service = terms_of_service - self.contact = contact - self.license_info = license_info - self.openapi_url = openapi_url - self.openapi_tags = openapi_tags - self.root_path_in_servers = root_path_in_servers - self.docs_url = docs_url - self.redoc_url = redoc_url - self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url - self.swagger_ui_init_oauth = swagger_ui_init_oauth - self.swagger_ui_parameters = swagger_ui_parameters - self.servers = servers or [] - self.separate_input_output_schemas = separate_input_output_schemas - self.openapi_external_docs = openapi_external_docs - self.extra = extra - self.openapi_version: Annotated[ - str, - Doc( - """ - The version string of OpenAPI. - - FastAPI will generate OpenAPI version 3.1.0, and will output that as - the OpenAPI version. But some tools, even though they might be - compatible with OpenAPI 3.1.0, might not recognize it as a valid. - - So you could override this value to trick those tools into using - the generated OpenAPI. Have in mind that this is a hack. But if you - avoid using features added in OpenAPI 3.1.0, it might work for your - use case. - - This is not passed as a parameter to the `FastAPI` class to avoid - giving the false idea that FastAPI would generate a different OpenAPI - schema. It is only available as an attribute. - - **Example** - - ```python - from fastapi import FastAPI - - app = FastAPI() - - app.openapi_version = "3.0.2" - ``` - """ - ), - ] = "3.1.0" - self.openapi_schema: dict[str, Any] | None = None - self._openapi_routes_version: int | None = None - if self.openapi_url: - assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'" - assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'" - # TODO: remove when discarding the openapi_prefix parameter - if openapi_prefix: - logger.warning( - '"openapi_prefix" has been deprecated in favor of "root_path", which ' - "follows more closely the ASGI standard, is simpler, and more " - "automatic. Check the docs at " - "https://fastapi.tiangolo.com/advanced/sub-applications/" - ) - self.webhooks: Annotated[ - routing.APIRouter, - Doc( - """ - The `app.webhooks` attribute is an `APIRouter` with the *path - operations* that will be used just for documentation of webhooks. - - Read more about it in the - [FastAPI docs for OpenAPI Webhooks](https://fastapi.tiangolo.com/advanced/openapi-webhooks/). - """ - ), - ] = webhooks or routing.APIRouter() - self.root_path = root_path or openapi_prefix - self.state: Annotated[ - State, - Doc( - """ - A state object for the application. This is the same object for the - entire application, it doesn't change from request to request. - - You normally wouldn't use this in FastAPI, for most of the cases you - would instead use FastAPI dependencies. - - This is simply inherited from Starlette. - - Read more about it in the - [Starlette docs for Applications](https://www.starlette.dev/applications/#storing-state-on-the-app-instance). - """ - ), - ] = State() - self.dependency_overrides: Annotated[ - dict[Callable[..., Any], Callable[..., Any]], - Doc( - """ - A dictionary with overrides for the dependencies. - - Each key is the original dependency callable, and the value is the - actual dependency that should be called. - - This is for testing, to replace expensive dependencies with testing - versions. - - Read more about it in the - [FastAPI docs for Testing Dependencies with Overrides](https://fastapi.tiangolo.com/advanced/testing-dependencies/). - """ - ), - ] = {} - self.router: routing.APIRouter = routing.APIRouter( - routes=routes, - redirect_slashes=redirect_slashes, - dependency_overrides_provider=self, - on_startup=on_startup, - on_shutdown=on_shutdown, - lifespan=lifespan, - default_response_class=default_response_class, - dependencies=dependencies, - callbacks=callbacks, - deprecated=deprecated, - include_in_schema=include_in_schema, - responses=responses, - generate_unique_id_function=generate_unique_id_function, - strict_content_type=strict_content_type, - ) - self.exception_handlers: dict[ - Any, Callable[[Request, Any], Response | Awaitable[Response]] - ] = {} if exception_handlers is None else dict(exception_handlers) - self.exception_handlers.setdefault(HTTPException, http_exception_handler) - self.exception_handlers.setdefault( - RequestValidationError, request_validation_exception_handler - ) - - # Starlette still has incorrect type specification for the handlers - self.exception_handlers.setdefault( - WebSocketRequestValidationError, - websocket_request_validation_exception_handler, # type: ignore[arg-type] - ) # ty: ignore[no-matching-overload] - - self.user_middleware: list[Middleware] = ( - [] if middleware is None else list(middleware) - ) - self.middleware_stack: ASGIApp | None = None - self.setup() - - def build_middleware_stack(self) -> ASGIApp: - # Duplicate/override from Starlette to add AsyncExitStackMiddleware - # inside of ExceptionMiddleware, inside of custom user middlewares - debug = self.debug - error_handler = None - exception_handlers: dict[Any, ExceptionHandler] = {} - - for key, value in self.exception_handlers.items(): - if key in (500, Exception): - error_handler = value - else: - exception_handlers[key] = value - - middleware = ( - [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)] - + self.user_middleware - + [ - Middleware( - ExceptionMiddleware, - handlers=exception_handlers, - debug=debug, - ), - # Add FastAPI-specific AsyncExitStackMiddleware for closing files. - # Before this was also used for closing dependencies with yield but - # those now have their own AsyncExitStack, to properly support - # streaming responses while keeping compatibility with the previous - # versions (as of writing 0.117.1) that allowed doing - # except HTTPException inside a dependency with yield. - # This needs to happen after user middlewares because those create a - # new contextvars context copy by using a new AnyIO task group. - # This AsyncExitStack preserves the context for contextvars, not - # strictly necessary for closing files but it was one of the original - # intentions. - # If the AsyncExitStack lived outside of the custom middlewares and - # contextvars were set, for example in a dependency with 'yield' - # in that internal contextvars context, the values would not be - # available in the outer context of the AsyncExitStack. - # By placing the middleware and the AsyncExitStack here, inside all - # user middlewares, the same context is used. - # This is currently not needed, only for closing files, but used to be - # important when dependencies with yield were closed here. - Middleware(AsyncExitStackMiddleware), - ] - ) - - app = self.router - for cls, args, kwargs in reversed(middleware): - app = cls(app, *args, **kwargs) - return app - - def openapi(self) -> dict[str, Any]: - """ - Generate the OpenAPI schema of the application. This is called by FastAPI - internally. - - The first time it is called it stores the result in the attribute - `app.openapi_schema`, and next times it is called, it just returns that same - result. To avoid the cost of generating the schema every time. - - If you need to modify the generated OpenAPI schema, you could modify it. - - Read more in the - [FastAPI docs for OpenAPI](https://fastapi.tiangolo.com/how-to/extending-openapi/). - """ - routes_version = self.router._get_routes_version() - if not self.openapi_schema or self._openapi_routes_version != routes_version: - self.openapi_schema = get_openapi( - title=self.title, - version=self.version, - openapi_version=self.openapi_version, - summary=self.summary, - description=self.description, - terms_of_service=self.terms_of_service, - contact=self.contact, - license_info=self.license_info, - routes=self.routes, - webhooks=self.webhooks.routes, - tags=self.openapi_tags, - servers=self.servers, - separate_input_output_schemas=self.separate_input_output_schemas, - external_docs=self.openapi_external_docs, - ) - self._openapi_routes_version = routes_version - return self.openapi_schema - - def setup(self) -> None: - if self.openapi_url: - - async def openapi(req: Request) -> JSONResponse: - root_path = req.scope.get("root_path", "").rstrip("/") - schema = self.openapi() - if root_path and self.root_path_in_servers: - server_urls = {s.get("url") for s in schema.get("servers", [])} - if root_path not in server_urls: - schema = dict(schema) - schema["servers"] = [{"url": root_path}] + schema.get( - "servers", [] - ) - return JSONResponse(schema) - - self.add_route(self.openapi_url, openapi, include_in_schema=False) - if self.openapi_url and self.docs_url: - - async def swagger_ui_html(req: Request) -> HTMLResponse: - root_path = req.scope.get("root_path", "").rstrip("/") - openapi_url = root_path + self.openapi_url - oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url - if oauth2_redirect_url: - oauth2_redirect_url = root_path + oauth2_redirect_url - return get_swagger_ui_html( - openapi_url=openapi_url, - title=f"{self.title} - Swagger UI", - oauth2_redirect_url=oauth2_redirect_url, - init_oauth=self.swagger_ui_init_oauth, - swagger_ui_parameters=self.swagger_ui_parameters, - ) - - self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False) - - if self.swagger_ui_oauth2_redirect_url: - - async def swagger_ui_redirect(req: Request) -> HTMLResponse: - return get_swagger_ui_oauth2_redirect_html() - - self.add_route( - self.swagger_ui_oauth2_redirect_url, - swagger_ui_redirect, - include_in_schema=False, - ) - if self.openapi_url and self.redoc_url: - - async def redoc_html(req: Request) -> HTMLResponse: - root_path = req.scope.get("root_path", "").rstrip("/") - openapi_url = root_path + self.openapi_url - return get_redoc_html( - openapi_url=openapi_url, title=f"{self.title} - ReDoc" - ) - - self.add_route(self.redoc_url, redoc_html, include_in_schema=False) - - async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: - if self.root_path: - scope["root_path"] = self.root_path - await super().__call__(scope, receive, send) - - def add_api_route( - self, - path: str, - endpoint: Callable[..., Any], - *, - response_model: Any = Default(None), - status_code: int | None = None, - tags: list[str | Enum] | None = None, - dependencies: Sequence[Depends] | None = None, - summary: str | None = None, - description: str | None = None, - response_description: str = "Successful Response", - responses: dict[int | str, dict[str, Any]] | None = None, - deprecated: bool | None = None, - methods: list[str] | None = None, - operation_id: str | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - include_in_schema: bool = True, - response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), - name: str | None = None, - openapi_extra: dict[str, Any] | None = None, - generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( - generate_unique_id - ), - ) -> None: - self.router.add_api_route( - path, - endpoint=endpoint, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=methods, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def frontend( - self, - path: Annotated[ - str, - Doc( - """ - The URL path prefix where the frontend build should be served. - """ - ), - ], - *, - directory: Annotated[ - str | os.PathLike[str], - Doc( - """ - The directory containing the static frontend build output. - """ - ), - ], - fallback: Annotated[ - Literal["auto", "index.html", "404.html"] | None, - Doc( - """ - The fallback file behavior for missing frontend paths. - """ - ), - ] = "auto", - check_dir: Annotated[ - bool, - Doc( - """ - Check that the frontend directory exists when the app is created. - """ - ), - ] = True, - ) -> None: - """ - Serve a static frontend build as low-priority routes. - - Use this for frontend tools that build static files into a directory, - such as `dist`. **FastAPI** path operations are checked first, and - the frontend files are checked only if no normal route matched. - - A typical project could look like this: - - ```text - . - ├── pyproject.toml - ├── app - │ ├── __init__.py - │ └── main.py - └── dist - ├── index.html - └── assets - └── app.js - ``` - - Then in `app/main.py`: - - ```python - from fastapi import FastAPI - - app = FastAPI() - app.frontend("/", directory="dist") - ``` - """ - self.router.frontend( - path, - directory=directory, - fallback=fallback, - check_dir=check_dir, - ) - - def api_route( - self, - path: str, - *, - response_model: Any = Default(None), - status_code: int | None = None, - tags: list[str | Enum] | None = None, - dependencies: Sequence[Depends] | None = None, - summary: str | None = None, - description: str | None = None, - response_description: str = "Successful Response", - responses: dict[int | str, dict[str, Any]] | None = None, - deprecated: bool | None = None, - methods: list[str] | None = None, - operation_id: str | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - include_in_schema: bool = True, - response_class: type[Response] = Default(JSONResponse), - name: str | None = None, - openapi_extra: dict[str, Any] | None = None, - generate_unique_id_function: Callable[[routing.APIRoute], str] = Default( - generate_unique_id - ), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.router.add_api_route( - path, - func, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=methods, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - return func - - return decorator - - def add_api_websocket_route( - self, - path: str, - endpoint: Callable[..., Any], - name: str | None = None, - *, - dependencies: Sequence[Depends] | None = None, - ) -> None: - self.router.add_api_websocket_route( - path, - endpoint, - name=name, - dependencies=dependencies, - ) - - def websocket( - self, - path: Annotated[ - str, - Doc( - """ - WebSocket path. - """ - ), - ], - name: Annotated[ - str | None, - Doc( - """ - A name for the WebSocket. Only used internally. - """ - ), - ] = None, - *, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be used for this - WebSocket. - - Read more about it in the - [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). - """ - ), - ] = None, - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Decorate a WebSocket function. - - Read more about it in the - [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). - - **Example** - - ```python - from fastapi import FastAPI, WebSocket - - app = FastAPI() - - @app.websocket("/ws") - async def websocket_endpoint(websocket: WebSocket): - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text(f"Message text was: {data}") - ``` - """ - - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_api_websocket_route( - path, - func, - name=name, - dependencies=dependencies, - ) - return func - - return decorator - - def include_router( - self, - router: Annotated[routing.APIRouter, Doc("The `APIRouter` to include.")], - *, - prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to all the *path operations* in this - router. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to all the - *path operations* in this router. - - Read more about it in the - [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - - **Example** - - ```python - from fastapi import Depends, FastAPI - - from .dependencies import get_token_header - from .internal import admin - - app = FastAPI() - - app.include_router( - admin.router, - dependencies=[Depends(get_token_header)], - ) - ``` - """ - ), - ] = None, - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses to be shown in OpenAPI. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). - - And in the - [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark all the *path operations* in this router as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - **Example** - - ```python - from fastapi import FastAPI - - from .internal import old_api - - app = FastAPI() - - app.include_router( - old_api.router, - deprecated=True, - ) - ``` - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include (or not) all the *path operations* in this router in the - generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - **Example** - - ```python - from fastapi import FastAPI - - from .internal import old_api - - app = FastAPI() - - app.include_router( - old_api.router, - include_in_schema=False, - ) - ``` - """ - ), - ] = True, - default_response_class: Annotated[ - type[Response], - Doc( - """ - Default response class to be used for the *path operations* in this - router. - - Read more in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). - - **Example** - - ```python - from fastapi import FastAPI - from fastapi.responses import ORJSONResponse - - from .internal import old_api - - app = FastAPI() - - app.include_router( - old_api.router, - default_response_class=ORJSONResponse, - ) - ``` - """ - ), - ] = Default(JSONResponse), - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> None: - """ - Include an `APIRouter` in the same app. - - Read more about it in the - [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/). - - ## Example - - ```python - from fastapi import FastAPI - - from .users import users_router - - app = FastAPI() - - app.include_router(users_router) - ``` - """ - self.router.include_router( - router, - prefix=prefix, - tags=tags, - dependencies=dependencies, - responses=responses, - deprecated=deprecated, - include_in_schema=include_in_schema, - default_response_class=default_response_class, - callbacks=callbacks, - generate_unique_id_function=generate_unique_id_function, - ) - - def get( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP GET operation. - - ## Example - - ```python - from fastapi import FastAPI - - app = FastAPI() - - @app.get("/items/") - def read_items(): - return [{"name": "Empanada"}, {"name": "Arepa"}] - ``` - """ - return self.router.get( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def put( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP PUT operation. - - ## Example - - ```python - from fastapi import FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - - @app.put("/items/{item_id}") - def replace_item(item_id: str, item: Item): - return {"message": "Item replaced", "id": item_id} - ``` - """ - return self.router.put( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def post( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP POST operation. - - ## Example - - ```python - from fastapi import FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - - @app.post("/items/") - def create_item(item: Item): - return {"message": "Item created"} - ``` - """ - return self.router.post( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def delete( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP DELETE operation. - - ## Example - - ```python - from fastapi import FastAPI - - app = FastAPI() - - @app.delete("/items/{item_id}") - def delete_item(item_id: str): - return {"message": "Item deleted"} - ``` - """ - return self.router.delete( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def options( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP OPTIONS operation. - - ## Example - - ```python - from fastapi import FastAPI - - app = FastAPI() - - @app.options("/items/") - def get_item_options(): - return {"additions": ["Aji", "Guacamole"]} - ``` - """ - return self.router.options( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def head( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP HEAD operation. - - ## Example - - ```python - from fastapi import FastAPI, Response - - app = FastAPI() - - @app.head("/items/", status_code=204) - def get_items_headers(response: Response): - response.headers["X-Cat-Dog"] = "Alone in the world" - ``` - """ - return self.router.head( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def patch( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP PATCH operation. - - ## Example - - ```python - from fastapi import FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - - @app.patch("/items/") - def update_item(item: Item): - return {"message": "Item updated in place"} - ``` - """ - return self.router.patch( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def trace( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[routing.APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP TRACE operation. - - ## Example - - ```python - from fastapi import FastAPI - - app = FastAPI() - - @app.trace("/items/{item_id}") - def trace_item(item_id: str): - return None - ``` - """ - return self.router.trace( - path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def websocket_route( - self, path: str, name: str | None = None - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.router.add_websocket_route(path, func, name=name) - return func - - return decorator - - @deprecated( - """ - on_event is deprecated, use lifespan event handlers instead. - - Read more about it in the - [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/). - """ - ) - def on_event( - self, - event_type: Annotated[ - str, - Doc( - """ - The type of event. `startup` or `shutdown`. - """ - ), - ], - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add an event handler for the application. - - `on_event` is deprecated, use `lifespan` event handlers instead. - - Read more about it in the - [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated). - """ - return self.router.on_event(event_type) # ty: ignore[deprecated] - - def middleware( - self, - middleware_type: Annotated[ - str, - Doc( - """ - The type of middleware. Currently only supports `http`. - """ - ), - ], - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a middleware to the application. - - Read more about it in the - [FastAPI docs for Middleware](https://fastapi.tiangolo.com/tutorial/middleware/). - - ## Example - - ```python - import time - from typing import Awaitable, Callable - - from fastapi import FastAPI, Request, Response - - app = FastAPI() - - - @app.middleware("http") - async def add_process_time_header( - request: Request, call_next: Callable[[Request], Awaitable[Response]] - ) -> Response: - start_time = time.time() - response = await call_next(request) - process_time = time.time() - start_time - response.headers["X-Process-Time"] = str(process_time) - return response - ``` - """ - - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_middleware(BaseHTTPMiddleware, dispatch=func) - return func - - return decorator - - def exception_handler( - self, - exc_class_or_status_code: Annotated[ - int | type[Exception], - Doc( - """ - The Exception class this would handle, or a status code. - """ - ), - ], - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add an exception handler to the app. - - Read more about it in the - [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/). - - ## Example - - ```python - from fastapi import FastAPI, Request - from fastapi.responses import JSONResponse - - - class UnicornException(Exception): - def __init__(self, name: str): - self.name = name - - - app = FastAPI() - - - @app.exception_handler(UnicornException) - async def unicorn_exception_handler(request: Request, exc: UnicornException): - return JSONResponse( - status_code=418, - content={"message": f"Oops! {exc.name} did something. There goes a rainbow..."}, - ) - ``` - """ - - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_exception_handler(exc_class_or_status_code, func) - return func - - return decorator diff --git a/server/venv/Lib/site-packages/fastapi/background.py b/server/venv/Lib/site-packages/fastapi/background.py deleted file mode 100644 index 7677058..0000000 --- a/server/venv/Lib/site-packages/fastapi/background.py +++ /dev/null @@ -1,61 +0,0 @@ -from collections.abc import Callable -from typing import Annotated, Any - -from annotated_doc import Doc -from starlette.background import BackgroundTasks as StarletteBackgroundTasks -from typing_extensions import ParamSpec - -P = ParamSpec("P") - - -class BackgroundTasks(StarletteBackgroundTasks): - """ - A collection of background tasks that will be called after a response has been - sent to the client. - - Read more about it in the - [FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/). - - ## Example - - ```python - from fastapi import BackgroundTasks, FastAPI - - app = FastAPI() - - - def write_notification(email: str, message=""): - with open("log.txt", mode="w") as email_file: - content = f"notification for {email}: {message}" - email_file.write(content) - - - @app.post("/send-notification/{email}") - async def send_notification(email: str, background_tasks: BackgroundTasks): - background_tasks.add_task(write_notification, email, message="some notification") - return {"message": "Notification sent in the background"} - ``` - """ - - def add_task( - self, - func: Annotated[ - Callable[P, Any], - Doc( - """ - The function to call after the response is sent. - - It can be a regular `def` function or an `async def` function. - """ - ), - ], - *args: P.args, - **kwargs: P.kwargs, - ) -> None: - """ - Add a function to be called in the background after the response is sent. - - Read more about it in the - [FastAPI docs for Background Tasks](https://fastapi.tiangolo.com/tutorial/background-tasks/). - """ - return super().add_task(func, *args, **kwargs) diff --git a/server/venv/Lib/site-packages/fastapi/cli.py b/server/venv/Lib/site-packages/fastapi/cli.py deleted file mode 100644 index 8d3301e..0000000 --- a/server/venv/Lib/site-packages/fastapi/cli.py +++ /dev/null @@ -1,13 +0,0 @@ -try: - from fastapi_cli.cli import main as cli_main - -except ImportError: # pragma: no cover - cli_main = None # type: ignore - - -def main() -> None: - if not cli_main: # type: ignore[truthy-function] - message = 'To use the fastapi command, please install "fastapi[standard]":\n\n\tpip install "fastapi[standard]"\n' - print(message) - raise RuntimeError(message) # noqa: B904 - cli_main() diff --git a/server/venv/Lib/site-packages/fastapi/concurrency.py b/server/venv/Lib/site-packages/fastapi/concurrency.py deleted file mode 100644 index 76a5a2e..0000000 --- a/server/venv/Lib/site-packages/fastapi/concurrency.py +++ /dev/null @@ -1,41 +0,0 @@ -from collections.abc import AsyncGenerator -from contextlib import AbstractContextManager -from contextlib import asynccontextmanager as asynccontextmanager -from typing import TypeVar - -import anyio.to_thread -from anyio import CapacityLimiter -from starlette.concurrency import iterate_in_threadpool as iterate_in_threadpool # noqa -from starlette.concurrency import run_in_threadpool as run_in_threadpool # noqa -from starlette.concurrency import ( # noqa - run_until_first_complete as run_until_first_complete, -) - -_T = TypeVar("_T") - - -@asynccontextmanager -async def contextmanager_in_threadpool( - cm: AbstractContextManager[_T], -) -> AsyncGenerator[_T, None]: - # blocking __exit__ from running waiting on a free thread - # can create race conditions/deadlocks if the context manager itself - # has its own internal pool (e.g. a database connection pool) - # to avoid this we let __exit__ run without a capacity limit - # since we're creating a new limiter for each call, any non-zero limit - # works (1 is arbitrary) - exit_limiter = CapacityLimiter(1) - try: - yield await run_in_threadpool(cm.__enter__) - except Exception as e: - ok = bool( - await anyio.to_thread.run_sync( - cm.__exit__, type(e), e, e.__traceback__, limiter=exit_limiter - ) - ) - if not ok: - raise e - else: - await anyio.to_thread.run_sync( - cm.__exit__, None, None, None, limiter=exit_limiter - ) diff --git a/server/venv/Lib/site-packages/fastapi/datastructures.py b/server/venv/Lib/site-packages/fastapi/datastructures.py deleted file mode 100644 index 1da784c..0000000 --- a/server/venv/Lib/site-packages/fastapi/datastructures.py +++ /dev/null @@ -1,186 +0,0 @@ -from collections.abc import Callable, Mapping -from typing import ( - Annotated, - Any, - BinaryIO, - TypeVar, - cast, -) - -from annotated_doc import Doc -from pydantic import GetJsonSchemaHandler -from starlette.datastructures import URL as URL # noqa: F401 -from starlette.datastructures import Address as Address # noqa: F401 -from starlette.datastructures import FormData as FormData # noqa: F401 -from starlette.datastructures import Headers as Headers # noqa: F401 -from starlette.datastructures import QueryParams as QueryParams # noqa: F401 -from starlette.datastructures import State as State # noqa: F401 -from starlette.datastructures import UploadFile as StarletteUploadFile - - -class UploadFile(StarletteUploadFile): - """ - A file uploaded in a request. - - Define it as a *path operation function* (or dependency) parameter. - - If you are using a regular `def` function, you can use the `upload_file.file` - attribute to access the raw standard Python file (blocking, not async), useful and - needed for non-async code. - - Read more about it in the - [FastAPI docs for Request Files](https://fastapi.tiangolo.com/tutorial/request-files/). - - ## Example - - ```python - from typing import Annotated - - from fastapi import FastAPI, File, UploadFile - - app = FastAPI() - - - @app.post("/files/") - async def create_file(file: Annotated[bytes, File()]): - return {"file_size": len(file)} - - - @app.post("/uploadfile/") - async def create_upload_file(file: UploadFile): - return {"filename": file.filename} - ``` - """ - - file: Annotated[ - BinaryIO, - Doc("The standard Python file object (non-async)."), - ] - filename: Annotated[str | None, Doc("The original file name.")] - size: Annotated[int | None, Doc("The size of the file in bytes.")] - headers: Annotated[Headers, Doc("The headers of the request.")] - content_type: Annotated[ - str | None, Doc("The content type of the request, from the headers.") - ] - - async def write( - self, - data: Annotated[ - bytes, - Doc( - """ - The bytes to write to the file. - """ - ), - ], - ) -> None: - """ - Write some bytes to the file. - - You normally wouldn't use this from a file you read in a request. - - To be awaitable, compatible with async, this is run in threadpool. - """ - return await super().write(data) - - async def read( - self, - size: Annotated[ - int, - Doc( - """ - The number of bytes to read from the file. - """ - ), - ] = -1, - ) -> bytes: - """ - Read some bytes from the file. - - To be awaitable, compatible with async, this is run in threadpool. - """ - return await super().read(size) - - async def seek( - self, - offset: Annotated[ - int, - Doc( - """ - The position in bytes to seek to in the file. - """ - ), - ], - ) -> None: - """ - Move to a position in the file. - - Any next read or write will be done from that position. - - To be awaitable, compatible with async, this is run in threadpool. - """ - return await super().seek(offset) - - async def close(self) -> None: - """ - Close the file. - - To be awaitable, compatible with async, this is run in threadpool. - """ - return await super().close() - - @classmethod - def _validate(cls, __input_value: Any, _: Any) -> "UploadFile": - if not isinstance(__input_value, StarletteUploadFile): - raise ValueError(f"Expected UploadFile, received: {type(__input_value)}") - return cast(UploadFile, __input_value) - - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler - ) -> dict[str, Any]: - return {"type": "string", "contentMediaType": "application/octet-stream"} - - @classmethod - def __get_pydantic_core_schema__( - cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]] - ) -> Mapping[str, Any]: - from ._compat.v2 import with_info_plain_validator_function - - return with_info_plain_validator_function(cls._validate) - - -class DefaultPlaceholder: - """ - You shouldn't use this class directly. - - It's used internally to recognize when a default value has been overwritten, even - if the overridden default value was truthy. - """ - - def __init__(self, value: Any): - self.value = value - - def __bool__(self) -> bool: - return bool(self.value) - - def __eq__(self, o: object) -> bool: - return isinstance(o, DefaultPlaceholder) and o.value == self.value - - -DefaultType = TypeVar("DefaultType") - - -def Default(value: DefaultType) -> DefaultType: - """ - You shouldn't use this function directly. - - It's used internally to recognize when a default value has been overwritten, even - if the overridden default value was truthy. - """ - return DefaultPlaceholder(value) # type: ignore - - -# Sentinel for "parameter not provided" in Param/FieldInfo. -# Typed as None to satisfy ty -_Unset = Default(None) diff --git a/server/venv/Lib/site-packages/fastapi/dependencies/__init__.py b/server/venv/Lib/site-packages/fastapi/dependencies/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/fastapi/dependencies/models.py b/server/venv/Lib/site-packages/fastapi/dependencies/models.py deleted file mode 100644 index 25ffb0d..0000000 --- a/server/venv/Lib/site-packages/fastapi/dependencies/models.py +++ /dev/null @@ -1,193 +0,0 @@ -import inspect -import sys -from collections.abc import Callable -from dataclasses import dataclass, field -from functools import cached_property, partial -from typing import Any, Literal - -from fastapi._compat import ModelField -from fastapi.security.base import SecurityBase -from fastapi.types import DependencyCacheKey - -if sys.version_info >= (3, 13): # pragma: no cover - from inspect import iscoroutinefunction -else: # pragma: no cover - from asyncio import iscoroutinefunction - - -def _unwrapped_call(call: Callable[..., Any] | None) -> Any: - if call is None: - return call # pragma: no cover - unwrapped = inspect.unwrap(_impartial(call)) - return unwrapped - - -def _impartial(func: Callable[..., Any]) -> Callable[..., Any]: - while isinstance(func, partial): - func = func.func - return func - - -@dataclass -class Dependant: - path_params: list[ModelField] = field(default_factory=list) - query_params: list[ModelField] = field(default_factory=list) - header_params: list[ModelField] = field(default_factory=list) - cookie_params: list[ModelField] = field(default_factory=list) - body_params: list[ModelField] = field(default_factory=list) - dependencies: list["Dependant"] = field(default_factory=list) - name: str | None = None - call: Callable[..., Any] | None = None - request_param_name: str | None = None - websocket_param_name: str | None = None - http_connection_param_name: str | None = None - response_param_name: str | None = None - background_tasks_param_name: str | None = None - security_scopes_param_name: str | None = None - own_oauth_scopes: list[str] | None = None - parent_oauth_scopes: list[str] | None = None - use_cache: bool = True - path: str | None = None - scope: Literal["function", "request"] | None = None - - @cached_property - def oauth_scopes(self) -> list[str]: - scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else [] - # This doesn't use a set to preserve order, just in case - for scope in self.own_oauth_scopes or []: - if scope not in scopes: - scopes.append(scope) - return scopes - - @cached_property - def cache_key(self) -> DependencyCacheKey: - scopes_for_cache = ( - tuple(sorted(set(self.oauth_scopes or []))) if self._uses_scopes else () - ) - return ( - self.call, - scopes_for_cache, - self.computed_scope or "", - ) - - @cached_property - def _uses_scopes(self) -> bool: - if self.own_oauth_scopes: - return True - if self.security_scopes_param_name is not None: - return True - if self._is_security_scheme: - return True - for sub_dep in self.dependencies: - if sub_dep._uses_scopes: - return True - return False - - @cached_property - def _is_security_scheme(self) -> bool: - if self.call is None: - return False # pragma: no cover - unwrapped = _unwrapped_call(self.call) - return isinstance(unwrapped, SecurityBase) - - # Mainly to get the type of SecurityBase, but it's the same self.call - @cached_property - def _security_scheme(self) -> SecurityBase: - unwrapped = _unwrapped_call(self.call) - assert isinstance(unwrapped, SecurityBase) - return unwrapped - - @cached_property - def _security_dependencies(self) -> list["Dependant"]: - security_deps = [dep for dep in self.dependencies if dep._is_security_scheme] - return security_deps - - @cached_property - def is_gen_callable(self) -> bool: - if self.call is None: - return False # pragma: no cover - if inspect.isgeneratorfunction( - _impartial(self.call) - ) or inspect.isgeneratorfunction(_unwrapped_call(self.call)): - return True - if inspect.isclass(_unwrapped_call(self.call)): - return False - dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 - if dunder_call is None: - return False # pragma: no cover - if inspect.isgeneratorfunction( - _impartial(dunder_call) - ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)): - return True - dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004 - if dunder_unwrapped_call is None: - return False # pragma: no cover - if inspect.isgeneratorfunction( - _impartial(dunder_unwrapped_call) - ) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)): - return True - return False - - @cached_property - def is_async_gen_callable(self) -> bool: - if self.call is None: - return False # pragma: no cover - if inspect.isasyncgenfunction( - _impartial(self.call) - ) or inspect.isasyncgenfunction(_unwrapped_call(self.call)): - return True - if inspect.isclass(_unwrapped_call(self.call)): - return False - dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 - if dunder_call is None: - return False # pragma: no cover - if inspect.isasyncgenfunction( - _impartial(dunder_call) - ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)): - return True - dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004 - if dunder_unwrapped_call is None: - return False # pragma: no cover - if inspect.isasyncgenfunction( - _impartial(dunder_unwrapped_call) - ) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)): - return True - return False - - @cached_property - def is_coroutine_callable(self) -> bool: - if self.call is None: - return False # pragma: no cover - if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction( - _impartial(self.call) - ): - return True - if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction( - _unwrapped_call(self.call) - ): - return True - if inspect.isclass(_unwrapped_call(self.call)): - return False - dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004 - if dunder_call is None: - return False # pragma: no cover - if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction( - _unwrapped_call(dunder_call) - ): - return True - dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004 - if dunder_unwrapped_call is None: - return False # pragma: no cover - if iscoroutinefunction( - _impartial(dunder_unwrapped_call) - ) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)): - return True - return False - - @cached_property - def computed_scope(self) -> str | None: - if self.scope: - return self.scope - if self.is_gen_callable or self.is_async_gen_callable: - return "request" - return None diff --git a/server/venv/Lib/site-packages/fastapi/dependencies/utils.py b/server/venv/Lib/site-packages/fastapi/dependencies/utils.py deleted file mode 100644 index 40dffba..0000000 --- a/server/venv/Lib/site-packages/fastapi/dependencies/utils.py +++ /dev/null @@ -1,1061 +0,0 @@ -import dataclasses -import inspect -import sys -from collections.abc import ( - AsyncGenerator, - AsyncIterable, - AsyncIterator, - Callable, - Generator, - Iterable, - Iterator, - Mapping, - Sequence, -) -from contextlib import AsyncExitStack, contextmanager -from copy import copy, deepcopy -from dataclasses import dataclass -from typing import ( - Annotated, - Any, - ForwardRef, - Literal, - Union, - cast, - get_args, - get_origin, -) - -from fastapi import params -from fastapi._compat import ( - ModelField, - RequiredParam, - Undefined, - copy_field_info, - create_body_model, - evaluate_forwardref, - field_annotation_is_scalar, - field_annotation_is_scalar_sequence, - field_annotation_is_sequence, - get_cached_model_fields, - get_missing_field_error, - is_bytes_or_nonable_bytes_annotation, - is_bytes_sequence_annotation, - is_scalar_field, - is_uploadfile_or_nonable_uploadfile_annotation, - is_uploadfile_sequence_annotation, - lenient_issubclass, - sequence_types, - serialize_sequence_value, - value_is_sequence, -) -from fastapi.background import BackgroundTasks -from fastapi.concurrency import ( - asynccontextmanager, - contextmanager_in_threadpool, -) -from fastapi.dependencies.models import Dependant -from fastapi.exceptions import DependencyScopeError -from fastapi.logger import logger -from fastapi.security.oauth2 import SecurityScopes -from fastapi.types import DependencyCacheKey -from fastapi.utils import create_model_field, get_path_param_names -from pydantic import BaseModel, Json -from pydantic.fields import FieldInfo -from starlette.background import BackgroundTasks as StarletteBackgroundTasks -from starlette.concurrency import run_in_threadpool -from starlette.datastructures import ( - FormData, - Headers, - ImmutableMultiDict, - QueryParams, - UploadFile, -) -from starlette.requests import HTTPConnection, Request -from starlette.responses import Response -from starlette.websockets import WebSocket -from typing_inspection.typing_objects import is_typealiastype - -multipart_not_installed_error = ( - 'Form data requires "python-multipart" to be installed. \n' - 'You can install "python-multipart" with: \n\n' - "pip install python-multipart\n" -) -multipart_incorrect_install_error = ( - 'Form data requires "python-multipart" to be installed. ' - 'It seems you installed "multipart" instead. \n' - 'You can remove "multipart" with: \n\n' - "pip uninstall multipart\n\n" - 'And then install "python-multipart" with: \n\n' - "pip install python-multipart\n" -) - - -def ensure_multipart_is_installed() -> None: - try: - from python_multipart import __version__ - - # Import an attribute that can be mocked/deleted in testing - assert __version__ > "0.0.12" - except (ImportError, AssertionError): - try: - # __version__ is available in both multiparts, and can be mocked - from multipart import ( # type: ignore[no-redef,import-untyped] - __version__, - ) - - assert __version__ - try: - # parse_options_header is only available in the right multipart - from multipart.multipart import ( # type: ignore[import-untyped] - parse_options_header, - ) - - assert parse_options_header - except ImportError: - logger.error(multipart_incorrect_install_error) - raise RuntimeError(multipart_incorrect_install_error) from None - except ImportError: - logger.error(multipart_not_installed_error) - raise RuntimeError(multipart_not_installed_error) from None - - -def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant: - assert callable(depends.dependency), ( - "A parameter-less dependency must have a callable dependency" - ) - own_oauth_scopes: list[str] = [] - if isinstance(depends, params.Security) and depends.scopes: - own_oauth_scopes.extend(depends.scopes) - return get_dependant( - path=path, - call=depends.dependency, - scope=depends.scope, - own_oauth_scopes=own_oauth_scopes, - ) - - -def get_flat_dependant( - dependant: Dependant, - *, - skip_repeats: bool = False, - visited: list[DependencyCacheKey] | None = None, - parent_oauth_scopes: list[str] | None = None, -) -> Dependant: - if visited is None: - visited = [] - visited.append(dependant.cache_key) - use_parent_oauth_scopes = (parent_oauth_scopes or []) + ( - dependant.oauth_scopes or [] - ) - - flat_dependant = Dependant( - path_params=dependant.path_params.copy(), - query_params=dependant.query_params.copy(), - header_params=dependant.header_params.copy(), - cookie_params=dependant.cookie_params.copy(), - body_params=dependant.body_params.copy(), - name=dependant.name, - call=dependant.call, - request_param_name=dependant.request_param_name, - websocket_param_name=dependant.websocket_param_name, - http_connection_param_name=dependant.http_connection_param_name, - response_param_name=dependant.response_param_name, - background_tasks_param_name=dependant.background_tasks_param_name, - security_scopes_param_name=dependant.security_scopes_param_name, - own_oauth_scopes=dependant.own_oauth_scopes, - parent_oauth_scopes=use_parent_oauth_scopes, - use_cache=dependant.use_cache, - path=dependant.path, - scope=dependant.scope, - ) - for sub_dependant in dependant.dependencies: - if skip_repeats and sub_dependant.cache_key in visited: - continue - flat_sub = get_flat_dependant( - sub_dependant, - skip_repeats=skip_repeats, - visited=visited, - parent_oauth_scopes=flat_dependant.oauth_scopes, - ) - flat_dependant.dependencies.append(flat_sub) - flat_dependant.path_params.extend(flat_sub.path_params) - flat_dependant.query_params.extend(flat_sub.query_params) - flat_dependant.header_params.extend(flat_sub.header_params) - flat_dependant.cookie_params.extend(flat_sub.cookie_params) - flat_dependant.body_params.extend(flat_sub.body_params) - flat_dependant.dependencies.extend(flat_sub.dependencies) - - return flat_dependant - - -def _get_flat_fields_from_params(fields: list[ModelField]) -> list[ModelField]: - if not fields: - return fields - first_field = fields[0] - if len(fields) == 1 and lenient_issubclass( - first_field.field_info.annotation, BaseModel - ): - fields_to_extract = get_cached_model_fields(first_field.field_info.annotation) - return fields_to_extract - return fields - - -def get_flat_params(dependant: Dependant) -> list[ModelField]: - flat_dependant = get_flat_dependant(dependant, skip_repeats=True) - path_params = _get_flat_fields_from_params(flat_dependant.path_params) - query_params = _get_flat_fields_from_params(flat_dependant.query_params) - header_params = _get_flat_fields_from_params(flat_dependant.header_params) - cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) - return path_params + query_params + header_params + cookie_params - - -def _get_signature(call: Callable[..., Any]) -> inspect.Signature: - try: - signature = inspect.signature(call, eval_str=True) - except NameError: - # Handle type annotations with if TYPE_CHECKING, not used by FastAPI - # e.g. dependency return types - if sys.version_info >= (3, 14): - from annotationlib import Format - - signature = inspect.signature(call, annotation_format=Format.FORWARDREF) - else: - signature = inspect.signature(call) - return signature - - -def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: - signature = _get_signature(call) - unwrapped = inspect.unwrap(call) - globalns = getattr(unwrapped, "__globals__", {}) - typed_params = [ - inspect.Parameter( - name=param.name, - kind=param.kind, - default=param.default, - annotation=get_typed_annotation(param.annotation, globalns), - ) - for param in signature.parameters.values() - ] - typed_signature = inspect.Signature(typed_params) - return typed_signature - - -def get_typed_annotation(annotation: Any, globalns: dict[str, Any]) -> Any: - if isinstance(annotation, str): - annotation = ForwardRef(annotation) - annotation = evaluate_forwardref(annotation, globalns, globalns) - if annotation is type(None): - return None - return annotation - - -def get_typed_return_annotation(call: Callable[..., Any]) -> Any: - signature = _get_signature(call) - unwrapped = inspect.unwrap(call) - annotation = signature.return_annotation - - if annotation is inspect.Signature.empty: - return None - - globalns = getattr(unwrapped, "__globals__", {}) - return get_typed_annotation(annotation, globalns) - - -_STREAM_ORIGINS = { - AsyncIterable, - AsyncIterator, - AsyncGenerator, - Iterable, - Iterator, - Generator, -} - - -def get_stream_item_type(annotation: Any) -> Any | None: - origin = get_origin(annotation) - if origin is not None and origin in _STREAM_ORIGINS: - type_args = get_args(annotation) - if type_args: - return type_args[0] - return Any - return None - - -def get_dependant( - *, - path: str, - call: Callable[..., Any], - name: str | None = None, - own_oauth_scopes: list[str] | None = None, - parent_oauth_scopes: list[str] | None = None, - use_cache: bool = True, - scope: Literal["function", "request"] | None = None, -) -> Dependant: - dependant = Dependant( - call=call, - name=name, - path=path, - use_cache=use_cache, - scope=scope, - own_oauth_scopes=own_oauth_scopes, - parent_oauth_scopes=parent_oauth_scopes, - ) - current_scopes = (parent_oauth_scopes or []) + (own_oauth_scopes or []) - path_param_names = get_path_param_names(path) - endpoint_signature = get_typed_signature(call) - signature_params = endpoint_signature.parameters - for param_name, param in signature_params.items(): - is_path_param = param_name in path_param_names - param_details = analyze_param( - param_name=param_name, - annotation=param.annotation, - value=param.default, - is_path_param=is_path_param, - ) - if param_details.depends is not None: - assert param_details.depends.dependency - if ( - (dependant.is_gen_callable or dependant.is_async_gen_callable) - and dependant.computed_scope == "request" - and param_details.depends.scope == "function" - ): - assert dependant.call - call_name = getattr(dependant.call, "__name__", "") - raise DependencyScopeError( - f'The dependency "{call_name}" has a scope of ' - '"request", it cannot depend on dependencies with scope "function".' - ) - sub_own_oauth_scopes: list[str] = [] - if isinstance(param_details.depends, params.Security): - if param_details.depends.scopes: - sub_own_oauth_scopes = list(param_details.depends.scopes) - sub_dependant = get_dependant( - path=path, - call=param_details.depends.dependency, - name=param_name, - own_oauth_scopes=sub_own_oauth_scopes, - parent_oauth_scopes=current_scopes, - use_cache=param_details.depends.use_cache, - scope=param_details.depends.scope, - ) - dependant.dependencies.append(sub_dependant) - continue - if add_non_field_param_to_dependency( - param_name=param_name, - type_annotation=param_details.type_annotation, - dependant=dependant, - ): - assert param_details.field is None, ( - f"Cannot specify multiple FastAPI annotations for {param_name!r}" - ) - continue - assert param_details.field is not None - if isinstance(param_details.field.field_info, params.Body): - dependant.body_params.append(param_details.field) - else: - add_param_to_fields(field=param_details.field, dependant=dependant) - return dependant - - -def add_non_field_param_to_dependency( - *, param_name: str, type_annotation: Any, dependant: Dependant -) -> bool | None: - if lenient_issubclass(type_annotation, Request): - dependant.request_param_name = param_name - return True - elif lenient_issubclass(type_annotation, WebSocket): - dependant.websocket_param_name = param_name - return True - elif lenient_issubclass(type_annotation, HTTPConnection): - dependant.http_connection_param_name = param_name - return True - elif lenient_issubclass(type_annotation, Response): - dependant.response_param_name = param_name - return True - elif lenient_issubclass(type_annotation, StarletteBackgroundTasks): - dependant.background_tasks_param_name = param_name - return True - elif lenient_issubclass(type_annotation, SecurityScopes): - dependant.security_scopes_param_name = param_name - return True - return None - - -@dataclass -class ParamDetails: - type_annotation: Any - depends: params.Depends | None - field: ModelField | None - - -def analyze_param( - *, - param_name: str, - annotation: Any, - value: Any, - is_path_param: bool, -) -> ParamDetails: - field_info = None - depends = None - type_annotation: Any = Any - use_annotation: Any = Any - if is_typealiastype(annotation): - # unpack in case PEP 695 type syntax is used - annotation = annotation.__value__ - if annotation is not inspect.Signature.empty: - use_annotation = annotation - type_annotation = annotation - # Extract Annotated info - if get_origin(use_annotation) is Annotated: - annotated_args = get_args(annotation) - type_annotation = annotated_args[0] - fastapi_annotations = [ - arg - for arg in annotated_args[1:] - if isinstance(arg, (FieldInfo, params.Depends)) - ] - fastapi_specific_annotations = [ - arg - for arg in fastapi_annotations - if isinstance( - arg, - ( - params.Param, - params.Body, - params.Depends, - ), - ) - ] - if fastapi_specific_annotations: - fastapi_annotation: FieldInfo | params.Depends | None = ( - fastapi_specific_annotations[-1] - ) - else: - fastapi_annotation = None - # Set default for Annotated FieldInfo - if isinstance(fastapi_annotation, FieldInfo): - # Copy `field_info` because we mutate `field_info.default` below. - field_info = copy_field_info( - field_info=fastapi_annotation, - annotation=use_annotation, - ) - assert ( - field_info.default == Undefined or field_info.default == RequiredParam - ), ( - f"`{field_info.__class__.__name__}` default value cannot be set in" - f" `Annotated` for {param_name!r}. Set the default value with `=` instead." - ) - if value is not inspect.Signature.empty: - assert not is_path_param, "Path parameters cannot have default values" - field_info.default = value - else: - field_info.default = RequiredParam - # Get Annotated Depends - elif isinstance(fastapi_annotation, params.Depends): - depends = fastapi_annotation - # Get Depends from default value - if isinstance(value, params.Depends): - assert depends is None, ( - "Cannot specify `Depends` in `Annotated` and default value" - f" together for {param_name!r}" - ) - assert field_info is None, ( - "Cannot specify a FastAPI annotation in `Annotated` and `Depends` as a" - f" default value together for {param_name!r}" - ) - depends = value - # Get FieldInfo from default value - elif isinstance(value, FieldInfo): - assert field_info is None, ( - "Cannot specify FastAPI annotations in `Annotated` and default value" - f" together for {param_name!r}" - ) - field_info = value - if isinstance(field_info, FieldInfo): - field_info.annotation = type_annotation - - # Get Depends from type annotation - if depends is not None and depends.dependency is None: - # Copy `depends` before mutating it - depends = copy(depends) - depends = dataclasses.replace(depends, dependency=type_annotation) - - # Handle non-param type annotations like Request - # Only apply special handling when there's no explicit Depends - if there's a Depends, - # the dependency will be called and its return value used instead of the special injection - if depends is None and lenient_issubclass( - type_annotation, - ( - Request, - WebSocket, - HTTPConnection, - Response, - StarletteBackgroundTasks, - SecurityScopes, - ), - ): - assert field_info is None, ( - f"Cannot specify FastAPI annotation for type {type_annotation!r}" - ) - # Handle default assignations, neither field_info nor depends was not found in Annotated nor default value - elif field_info is None and depends is None: - default_value = value if value is not inspect.Signature.empty else RequiredParam - if is_path_param: - # We might check here that `default_value is RequiredParam`, but the fact is that the same - # parameter might sometimes be a path parameter and sometimes not. See - # `tests/test_infer_param_optionality.py` for an example. - field_info = params.Path(annotation=use_annotation) - elif is_uploadfile_or_nonable_uploadfile_annotation( - type_annotation - ) or is_uploadfile_sequence_annotation(type_annotation): - field_info = params.File(annotation=use_annotation, default=default_value) - elif not field_annotation_is_scalar(annotation=type_annotation): - field_info = params.Body(annotation=use_annotation, default=default_value) - else: - field_info = params.Query(annotation=use_annotation, default=default_value) - - field = None - # It's a field_info, not a dependency - if field_info is not None: - # Handle field_info.in_ - if is_path_param: - assert isinstance(field_info, params.Path), ( - f"Cannot use `{field_info.__class__.__name__}` for path param" - f" {param_name!r}" - ) - elif ( - isinstance(field_info, params.Param) - and getattr(field_info, "in_", None) is None - ): - field_info.in_ = params.ParamTypes.query - use_annotation_from_field_info = use_annotation - if isinstance(field_info, params.Form): - ensure_multipart_is_installed() - if not field_info.alias and getattr(field_info, "convert_underscores", None): - alias = param_name.replace("_", "-") - else: - alias = field_info.alias or param_name - field_info.alias = alias - field = create_model_field( - name=param_name, - type_=use_annotation_from_field_info, - default=field_info.default, - alias=alias, - field_info=field_info, - ) - if is_path_param: - assert is_scalar_field(field=field), ( - "Path params must be of one of the supported types" - ) - elif isinstance(field_info, params.Query): - assert ( - is_scalar_field(field) - or field_annotation_is_scalar_sequence(field.field_info.annotation) - or lenient_issubclass(field.field_info.annotation, BaseModel) - ), f"Query parameter {param_name!r} must be one of the supported types" - - return ParamDetails(type_annotation=type_annotation, depends=depends, field=field) - - -def add_param_to_fields(*, field: ModelField, dependant: Dependant) -> None: - field_info = field.field_info - field_info_in = getattr(field_info, "in_", None) - if field_info_in == params.ParamTypes.path: - dependant.path_params.append(field) - elif field_info_in == params.ParamTypes.query: - dependant.query_params.append(field) - elif field_info_in == params.ParamTypes.header: - dependant.header_params.append(field) - else: - assert field_info_in == params.ParamTypes.cookie, ( - f"non-body parameters must be in path, query, header or cookie: {field.name}" - ) - dependant.cookie_params.append(field) - - -async def _solve_generator( - *, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any] -) -> Any: - assert dependant.call - if dependant.is_async_gen_callable: - cm = asynccontextmanager(dependant.call)(**sub_values) - elif dependant.is_gen_callable: - cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values)) - return await stack.enter_async_context(cm) - - -@dataclass -class SolvedDependency: - values: dict[str, Any] - errors: list[Any] - background_tasks: StarletteBackgroundTasks | None - response: Response - dependency_cache: dict[DependencyCacheKey, Any] - - -async def solve_dependencies( - *, - request: Request | WebSocket, - dependant: Dependant, - body: dict[str, Any] | FormData | bytes | None = None, - background_tasks: StarletteBackgroundTasks | None = None, - response: Response | None = None, - dependency_overrides_provider: Any | None = None, - dependency_cache: dict[DependencyCacheKey, Any] | None = None, - # TODO: remove this parameter later, no longer used, not removing it yet as some - # people might be monkey patching this function (although that's not supported) - async_exit_stack: AsyncExitStack, - embed_body_fields: bool, -) -> SolvedDependency: - request_astack = request.scope.get("fastapi_inner_astack") - assert isinstance(request_astack, AsyncExitStack), ( - "fastapi_inner_astack not found in request scope" - ) - function_astack = request.scope.get("fastapi_function_astack") - assert isinstance(function_astack, AsyncExitStack), ( - "fastapi_function_astack not found in request scope" - ) - values: dict[str, Any] = {} - errors: list[Any] = [] - if response is None: - response = Response() - del response.headers["content-length"] - response.status_code = None # type: ignore - if dependency_cache is None: - dependency_cache = {} - for sub_dependant in dependant.dependencies: - sub_dependant.call = cast(Callable[..., Any], sub_dependant.call) - call = sub_dependant.call - use_sub_dependant = sub_dependant - if ( - dependency_overrides_provider - and dependency_overrides_provider.dependency_overrides - ): - original_call = sub_dependant.call - call = getattr( - dependency_overrides_provider, "dependency_overrides", {} - ).get(original_call, original_call) - use_path: str = sub_dependant.path # type: ignore - use_sub_dependant = get_dependant( - path=use_path, - call=call, - name=sub_dependant.name, - parent_oauth_scopes=sub_dependant.oauth_scopes, - scope=sub_dependant.scope, - ) - - solved_result = await solve_dependencies( - request=request, - dependant=use_sub_dependant, - body=body, - background_tasks=background_tasks, - response=response, - dependency_overrides_provider=dependency_overrides_provider, - dependency_cache=dependency_cache, - async_exit_stack=async_exit_stack, - embed_body_fields=embed_body_fields, - ) - background_tasks = solved_result.background_tasks - if solved_result.errors: - errors.extend(solved_result.errors) - continue - if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache: - solved = dependency_cache[sub_dependant.cache_key] - elif ( - use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable - ): - use_astack = request_astack - if sub_dependant.scope == "function": - use_astack = function_astack - solved = await _solve_generator( - dependant=use_sub_dependant, - stack=use_astack, - sub_values=solved_result.values, - ) - elif use_sub_dependant.is_coroutine_callable: - solved = await call(**solved_result.values) - else: - solved = await run_in_threadpool(call, **solved_result.values) - if sub_dependant.name is not None: - values[sub_dependant.name] = solved - if sub_dependant.cache_key not in dependency_cache: - dependency_cache[sub_dependant.cache_key] = solved - path_values, path_errors = request_params_to_args( - dependant.path_params, request.path_params - ) - query_values, query_errors = request_params_to_args( - dependant.query_params, request.query_params - ) - header_values, header_errors = request_params_to_args( - dependant.header_params, request.headers - ) - cookie_values, cookie_errors = request_params_to_args( - dependant.cookie_params, request.cookies - ) - values.update(path_values) - values.update(query_values) - values.update(header_values) - values.update(cookie_values) - errors += path_errors + query_errors + header_errors + cookie_errors - if dependant.body_params: - ( - body_values, - body_errors, - ) = await request_body_to_args( # body_params checked above - body_fields=dependant.body_params, - received_body=body, - embed_body_fields=embed_body_fields, - ) - values.update(body_values) - errors.extend(body_errors) - if dependant.http_connection_param_name: - values[dependant.http_connection_param_name] = request - if dependant.request_param_name and isinstance(request, Request): - values[dependant.request_param_name] = request - elif dependant.websocket_param_name and isinstance(request, WebSocket): - values[dependant.websocket_param_name] = request - if dependant.background_tasks_param_name: - if background_tasks is None: - background_tasks = BackgroundTasks() - values[dependant.background_tasks_param_name] = background_tasks - if dependant.response_param_name: - values[dependant.response_param_name] = response - if dependant.security_scopes_param_name: - values[dependant.security_scopes_param_name] = SecurityScopes( - scopes=dependant.oauth_scopes - ) - return SolvedDependency( - values=values, - errors=errors, - background_tasks=background_tasks, - response=response, - dependency_cache=dependency_cache, - ) - - -def _validate_value_with_model_field( - *, field: ModelField, value: Any, values: dict[str, Any], loc: tuple[str, ...] -) -> tuple[Any, list[Any]]: - if value is None: - if field.field_info.is_required(): - return None, [get_missing_field_error(loc=loc)] - else: - return deepcopy(field.default), [] - return field.validate(value, values, loc=loc) - - -def _is_json_field(field: ModelField) -> bool: - return any(type(item) is Json for item in field.field_info.metadata) - - -def _get_multidict_value( - field: ModelField, values: Mapping[str, Any], alias: str | None = None -) -> Any: - alias = alias or get_validation_alias(field) - if ( - (not _is_json_field(field)) - and field_annotation_is_sequence(field.field_info.annotation) - and isinstance(values, (ImmutableMultiDict, Headers)) - ): - value = values.getlist(alias) - else: - value = values.get(alias, None) - if ( - value is None - or ( - isinstance(field.field_info, params.Form) - and isinstance(value, str) # For type checks - and value == "" - ) - or ( - field_annotation_is_sequence(field.field_info.annotation) - and len(value) == 0 - ) - ): - if field.field_info.is_required(): - return - else: - return deepcopy(field.default) - return value - - -def request_params_to_args( - fields: Sequence[ModelField], - received_params: Mapping[str, Any] | QueryParams | Headers, -) -> tuple[dict[str, Any], list[Any]]: - values: dict[str, Any] = {} - errors: list[dict[str, Any]] = [] - - if not fields: - return values, errors - - first_field = fields[0] - fields_to_extract = fields - single_not_embedded_field = False - default_convert_underscores = True - if len(fields) == 1 and lenient_issubclass( - first_field.field_info.annotation, BaseModel - ): - fields_to_extract = get_cached_model_fields(first_field.field_info.annotation) - single_not_embedded_field = True - # If headers are in a Pydantic model, the way to disable convert_underscores - # would be with Header(convert_underscores=False) at the Pydantic model level - default_convert_underscores = getattr( - first_field.field_info, "convert_underscores", True - ) - - params_to_process: dict[str, Any] = {} - - processed_keys = set() - - for field in fields_to_extract: - alias = None - if isinstance(received_params, Headers): - # Handle fields extracted from a Pydantic Model for a header, each field - # doesn't have a FieldInfo of type Header with the default convert_underscores=True - convert_underscores = getattr( - field.field_info, "convert_underscores", default_convert_underscores - ) - if convert_underscores: - alias = get_validation_alias(field) - if alias == field.name: - alias = alias.replace("_", "-") - value = _get_multidict_value(field, received_params, alias=alias) - if value is not None: - params_to_process[get_validation_alias(field)] = value - processed_keys.add(alias or get_validation_alias(field)) - # For headers with convert_underscores=True, mark both the converted - # header name and the original field alias as processed to avoid - # accepting the original alias as an extra header. - processed_keys.add(get_validation_alias(field)) - - for key in received_params.keys(): - if key not in processed_keys: - if isinstance(received_params, (ImmutableMultiDict, Headers)): - value = received_params.getlist(key) - if isinstance(value, list) and (len(value) == 1): - params_to_process[key] = value[0] - else: - params_to_process[key] = value - else: - params_to_process[key] = received_params.get(key) - - if single_not_embedded_field: - field_info = first_field.field_info - assert isinstance(field_info, params.Param), ( - "Params must be subclasses of Param" - ) - loc: tuple[str, ...] = (field_info.in_.value,) - v_, errors_ = _validate_value_with_model_field( - field=first_field, value=params_to_process, values=values, loc=loc - ) - return {first_field.name: v_}, errors_ - - for field in fields: - value = _get_multidict_value(field, received_params) - field_info = field.field_info - assert isinstance(field_info, params.Param), ( - "Params must be subclasses of Param" - ) - loc = (field_info.in_.value, get_validation_alias(field)) - v_, errors_ = _validate_value_with_model_field( - field=field, value=value, values=values, loc=loc - ) - if errors_: - errors.extend(errors_) - else: - values[field.name] = v_ - return values, errors - - -def is_union_of_base_models(field_type: Any) -> bool: - """Check if field type is a Union where all members are BaseModel subclasses.""" - from fastapi.types import UnionType - - origin = get_origin(field_type) - - # Check if it's a Union type (covers both typing.Union and types.UnionType in Python 3.10+) - if origin is not Union and origin is not UnionType: - return False - - union_args = get_args(field_type) - - for arg in union_args: - if not lenient_issubclass(arg, BaseModel): - return False - - return True - - -def _should_embed_body_fields(fields: list[ModelField]) -> bool: - if not fields: - return False - # More than one dependency could have the same field, it would show up as multiple - # fields but it's the same one, so count them by name - body_param_names_set = {field.name for field in fields} - # A top level field has to be a single field, not multiple - if len(body_param_names_set) > 1: - return True - first_field = fields[0] - # If it explicitly specifies it is embedded, it has to be embedded - if getattr(first_field.field_info, "embed", None): - return True - # If it's a Form (or File) field, it has to be a BaseModel (or a union of BaseModels) to be top level - # otherwise it has to be embedded, so that the key value pair can be extracted - if ( - isinstance(first_field.field_info, params.Form) - and not lenient_issubclass(first_field.field_info.annotation, BaseModel) - and not is_union_of_base_models(first_field.field_info.annotation) - ): - return True - return False - - -async def _extract_form_body( - body_fields: list[ModelField], - received_body: FormData, -) -> dict[str, Any]: - values = {} - - for field in body_fields: - value = _get_multidict_value(field, received_body) - field_info = field.field_info - if ( - isinstance(field_info, params.File) - and is_bytes_or_nonable_bytes_annotation(field.field_info.annotation) - and isinstance(value, UploadFile) - ): - value = await value.read() - elif ( - is_bytes_sequence_annotation(field.field_info.annotation) - and isinstance(field_info, params.File) - and value_is_sequence(value) - ): - # For types - assert isinstance(value, sequence_types) - results: list[bytes | str] = [] - for sub_value in value: - results.append(await sub_value.read()) - value = serialize_sequence_value(field=field, value=results) - if value is not None: - values[get_validation_alias(field)] = value - field_aliases = {get_validation_alias(field) for field in body_fields} - for key in received_body.keys(): - if key not in field_aliases: - param_values = received_body.getlist(key) - if len(param_values) == 1: - values[key] = param_values[0] - else: - values[key] = param_values - return values - - -async def request_body_to_args( - body_fields: list[ModelField], - received_body: dict[str, Any] | FormData | bytes | None, - embed_body_fields: bool, -) -> tuple[dict[str, Any], list[dict[str, Any]]]: - values: dict[str, Any] = {} - errors: list[dict[str, Any]] = [] - assert body_fields, "request_body_to_args() should be called with fields" - single_not_embedded_field = len(body_fields) == 1 and not embed_body_fields - first_field = body_fields[0] - body_to_process = received_body - - fields_to_extract: list[ModelField] = body_fields - - if ( - single_not_embedded_field - and lenient_issubclass(first_field.field_info.annotation, BaseModel) - and isinstance(received_body, FormData) - ): - fields_to_extract = get_cached_model_fields(first_field.field_info.annotation) - - if isinstance(received_body, FormData): - body_to_process = await _extract_form_body(fields_to_extract, received_body) - - if single_not_embedded_field: - loc: tuple[str, ...] = ("body",) - v_, errors_ = _validate_value_with_model_field( - field=first_field, value=body_to_process, values=values, loc=loc - ) - return {first_field.name: v_}, errors_ - for field in body_fields: - loc = ("body", get_validation_alias(field)) - value: Any | None = None - if body_to_process is not None and not isinstance(body_to_process, bytes): - try: - value = body_to_process.get(get_validation_alias(field)) - # If the received body is a list, not a dict - except AttributeError: - errors.append(get_missing_field_error(loc)) - continue - v_, errors_ = _validate_value_with_model_field( - field=field, value=value, values=values, loc=loc - ) - if errors_: - errors.extend(errors_) - else: - values[field.name] = v_ - return values, errors - - -def get_body_field( - *, flat_dependant: Dependant, name: str, embed_body_fields: bool -) -> ModelField | None: - """ - Get a ModelField representing the request body for a path operation, combining - all body parameters into a single field if necessary. - - Used to check if it's form data (with `isinstance(body_field, params.Form)`) - or JSON and to generate the JSON Schema for a request body. - - This is **not** used to validate/parse the request body, that's done with each - individual body parameter. - """ - if not flat_dependant.body_params: - return None - first_param = flat_dependant.body_params[0] - if not embed_body_fields: - return first_param - model_name = "Body_" + name - BodyModel = create_body_model( - fields=flat_dependant.body_params, model_name=model_name - ) - required = any( - True for f in flat_dependant.body_params if f.field_info.is_required() - ) - BodyFieldInfo_kwargs: dict[str, Any] = { - "annotation": BodyModel, - "alias": "body", - } - if not required: - BodyFieldInfo_kwargs["default"] = None - if any(isinstance(f.field_info, params.File) for f in flat_dependant.body_params): - BodyFieldInfo: type[params.Body] = params.File - elif any(isinstance(f.field_info, params.Form) for f in flat_dependant.body_params): - BodyFieldInfo = params.Form - else: - BodyFieldInfo = params.Body - - body_param_media_types = [ - f.field_info.media_type - for f in flat_dependant.body_params - if isinstance(f.field_info, params.Body) - ] - if len(set(body_param_media_types)) == 1: - BodyFieldInfo_kwargs["media_type"] = body_param_media_types[0] - final_field = create_model_field( - name="body", - type_=BodyModel, - alias="body", - field_info=BodyFieldInfo(**BodyFieldInfo_kwargs), - ) - return final_field - - -def get_validation_alias(field: ModelField) -> str: - va = getattr(field, "validation_alias", None) - return va or field.alias diff --git a/server/venv/Lib/site-packages/fastapi/encoders.py b/server/venv/Lib/site-packages/fastapi/encoders.py deleted file mode 100644 index c9f882d..0000000 --- a/server/venv/Lib/site-packages/fastapi/encoders.py +++ /dev/null @@ -1,364 +0,0 @@ -import dataclasses -import datetime -from collections import defaultdict, deque -from collections.abc import Callable -from decimal import Decimal -from enum import Enum -from ipaddress import ( - IPv4Address, - IPv4Interface, - IPv4Network, - IPv6Address, - IPv6Interface, - IPv6Network, -) -from pathlib import Path, PurePath -from re import Pattern -from types import GeneratorType -from typing import Annotated, Any -from uuid import UUID - -from annotated_doc import Doc -from fastapi.exceptions import PydanticV1NotSupportedError -from fastapi.types import IncEx -from pydantic import BaseModel -from pydantic.networks import AnyUrl, NameEmail -from pydantic.types import SecretBytes, SecretStr -from pydantic_core import PydanticUndefinedType - -from ._compat import ( - Url, - is_pydantic_v1_model_instance, -) - -try: - # pydantic.color.Color is deprecated since v2.0b3, but supporting for bwd-compat - from pydantic.color import Color # ty: ignore[deprecated] -except ImportError: # pragma: no cover - - class Color: # type: ignore[no-redef] - pass - - -try: - # Supporting the new Color format for newer versions of Pydantic - from pydantic_extra_types.color import Color as PyExtraColor -except ImportError: # pragma: no cover - - class PyExtraColor: # type: ignore[no-redef] - pass - - -# Taken from Pydantic v1 as is -def isoformat(o: datetime.date | datetime.time) -> str: - return o.isoformat() - - -# Adapted from Pydantic v1 -# TODO: pv2 should this return strings instead? -def decimal_encoder(dec_value: Decimal) -> int | float: - """ - Encodes a Decimal as int if there's no exponent, otherwise float - - This is useful when we use ConstrainedDecimal to represent Numeric(x,0) - where an integer (but not int typed) is used. Encoding this as a float - results in failed round-tripping between encode and parse. - Our Id type is a prime example of this. - - >>> decimal_encoder(Decimal("1.0")) - 1.0 - - >>> decimal_encoder(Decimal("1")) - 1 - - >>> decimal_encoder(Decimal("NaN")) - nan - """ - exponent = dec_value.as_tuple().exponent - if isinstance(exponent, int) and exponent >= 0: - return int(dec_value) - else: - return float(dec_value) - - -ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = { - bytes: lambda o: o.decode(), - Color: str, - PyExtraColor: str, - datetime.date: isoformat, - datetime.datetime: isoformat, - datetime.time: isoformat, - datetime.timedelta: lambda td: td.total_seconds(), - Decimal: decimal_encoder, - Enum: lambda o: o.value, - frozenset: list, - deque: list, - GeneratorType: list, - IPv4Address: str, - IPv4Interface: str, - IPv4Network: str, - IPv6Address: str, - IPv6Interface: str, - IPv6Network: str, - NameEmail: str, - Path: str, - Pattern: lambda o: o.pattern, - SecretBytes: str, - SecretStr: str, - set: list, - UUID: str, - Url: str, - AnyUrl: str, -} - - -def generate_encoders_by_class_tuples( - type_encoder_map: dict[Any, Callable[[Any], Any]], -) -> dict[Callable[[Any], Any], tuple[Any, ...]]: - encoders_by_class_tuples: dict[Callable[[Any], Any], tuple[Any, ...]] = defaultdict( - tuple - ) - for type_, encoder in type_encoder_map.items(): - encoders_by_class_tuples[encoder] += (type_,) - return encoders_by_class_tuples - - -encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE) - - -def jsonable_encoder( - obj: Annotated[ - Any, - Doc( - """ - The input object to convert to JSON. - """ - ), - ], - include: Annotated[ - IncEx | None, - Doc( - """ - Pydantic's `include` parameter, passed to Pydantic models to set the - fields to include. - """ - ), - ] = None, - exclude: Annotated[ - IncEx | None, - Doc( - """ - Pydantic's `exclude` parameter, passed to Pydantic models to set the - fields to exclude. - """ - ), - ] = None, - by_alias: Annotated[ - bool, - Doc( - """ - Pydantic's `by_alias` parameter, passed to Pydantic models to define if - the output should use the alias names (when provided) or the Python - attribute names. In an API, if you set an alias, it's probably because you - want to use it in the result, so you probably want to leave this set to - `True`. - """ - ), - ] = True, - exclude_unset: Annotated[ - bool, - Doc( - """ - Pydantic's `exclude_unset` parameter, passed to Pydantic models to define - if it should exclude from the output the fields that were not explicitly - set (and that only had their default values). - """ - ), - ] = False, - exclude_defaults: Annotated[ - bool, - Doc( - """ - Pydantic's `exclude_defaults` parameter, passed to Pydantic models to define - if it should exclude from the output the fields that had the same default - value, even when they were explicitly set. - """ - ), - ] = False, - exclude_none: Annotated[ - bool, - Doc( - """ - Pydantic's `exclude_none` parameter, passed to Pydantic models to define - if it should exclude from the output any fields that have a `None` value. - """ - ), - ] = False, - custom_encoder: Annotated[ - dict[Any, Callable[[Any], Any]] | None, - Doc( - """ - Pydantic's `custom_encoder` parameter, passed to Pydantic models to define - a custom encoder. - """ - ), - ] = None, - sqlalchemy_safe: Annotated[ - bool, - Doc( - """ - Exclude from the output any fields that start with the name `_sa`. - - This is mainly a hack for compatibility with SQLAlchemy objects, they - store internal SQLAlchemy-specific state in attributes named with `_sa`, - and those objects can't (and shouldn't be) serialized to JSON. - """ - ), - ] = True, -) -> Any: - """ - Convert any object to something that can be encoded in JSON. - - This is used internally by FastAPI to make sure anything you return can be - encoded as JSON before it is sent to the client. - - You can also use it yourself, for example to convert objects before saving them - in a database that supports only JSON. - - Read more about it in the - [FastAPI docs for JSON Compatible Encoder](https://fastapi.tiangolo.com/tutorial/encoder/). - """ - custom_encoder = custom_encoder or {} - if custom_encoder: - if type(obj) in custom_encoder: - return custom_encoder[type(obj)](obj) - else: - for encoder_type, encoder_instance in custom_encoder.items(): - if isinstance(obj, encoder_type): - return encoder_instance(obj) - if include is not None and not isinstance(include, (set, dict)): - include = set(include) # type: ignore[assignment] # ty: ignore[invalid-assignment] - if exclude is not None and not isinstance(exclude, (set, dict)): - exclude = set(exclude) # type: ignore[assignment] # ty: ignore[invalid-assignment] - if isinstance(obj, BaseModel): - obj_dict = obj.model_dump( - mode="json", - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_none=exclude_none, - exclude_defaults=exclude_defaults, - ) - return jsonable_encoder( - obj_dict, - exclude_none=exclude_none, - exclude_defaults=exclude_defaults, - sqlalchemy_safe=sqlalchemy_safe, - ) - if dataclasses.is_dataclass(obj): - assert not isinstance(obj, type) - obj_dict = dataclasses.asdict(obj) - return jsonable_encoder( - obj_dict, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - custom_encoder=custom_encoder, - sqlalchemy_safe=sqlalchemy_safe, - ) - if isinstance(obj, Enum): - return obj.value - if isinstance(obj, PurePath): - return str(obj) - if isinstance(obj, (str, int, float, type(None))): - return obj - if isinstance(obj, PydanticUndefinedType): - return None - if isinstance(obj, dict): - encoded_dict = {} - allowed_keys = set(obj.keys()) - if include is not None: - allowed_keys &= set(include) - if exclude is not None: - allowed_keys -= set(exclude) - for key, value in obj.items(): - if ( - ( - not sqlalchemy_safe - or (not isinstance(key, str)) - or (not key.startswith("_sa")) - ) - and (value is not None or not exclude_none) - and key in allowed_keys - ): - encoded_key = jsonable_encoder( - key, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_none=exclude_none, - custom_encoder=custom_encoder, - sqlalchemy_safe=sqlalchemy_safe, - ) - encoded_value = jsonable_encoder( - value, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_none=exclude_none, - custom_encoder=custom_encoder, - sqlalchemy_safe=sqlalchemy_safe, - ) - encoded_dict[encoded_key] = encoded_value - return encoded_dict - if isinstance(obj, (list, set, frozenset, GeneratorType, tuple, deque)): - encoded_list = [] - for item in obj: - encoded_list.append( - jsonable_encoder( - item, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - custom_encoder=custom_encoder, - sqlalchemy_safe=sqlalchemy_safe, - ) - ) - return encoded_list - - if type(obj) in ENCODERS_BY_TYPE: - return ENCODERS_BY_TYPE[type(obj)](obj) - for encoder, classes_tuple in encoders_by_class_tuples.items(): - if isinstance(obj, classes_tuple): - return encoder(obj) - if is_pydantic_v1_model_instance(obj): - raise PydanticV1NotSupportedError( - "pydantic.v1 models are no longer supported by FastAPI." - f" Please update the model {obj!r}." - ) - try: - data = dict(obj) - except Exception as e: - errors: list[Exception] = [] - errors.append(e) - try: - data = vars(obj) - except Exception as e: - errors.append(e) - raise ValueError(errors) from e - return jsonable_encoder( - data, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - custom_encoder=custom_encoder, - sqlalchemy_safe=sqlalchemy_safe, - ) diff --git a/server/venv/Lib/site-packages/fastapi/exception_handlers.py b/server/venv/Lib/site-packages/fastapi/exception_handlers.py deleted file mode 100644 index 475dd7b..0000000 --- a/server/venv/Lib/site-packages/fastapi/exception_handlers.py +++ /dev/null @@ -1,34 +0,0 @@ -from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import RequestValidationError, WebSocketRequestValidationError -from fastapi.utils import is_body_allowed_for_status_code -from fastapi.websockets import WebSocket -from starlette.exceptions import HTTPException -from starlette.requests import Request -from starlette.responses import JSONResponse, Response -from starlette.status import WS_1008_POLICY_VIOLATION - - -async def http_exception_handler(request: Request, exc: HTTPException) -> Response: - headers = getattr(exc, "headers", None) - if not is_body_allowed_for_status_code(exc.status_code): - return Response(status_code=exc.status_code, headers=headers) - return JSONResponse( - {"detail": exc.detail}, status_code=exc.status_code, headers=headers - ) - - -async def request_validation_exception_handler( - request: Request, exc: RequestValidationError -) -> JSONResponse: - return JSONResponse( - status_code=422, - content={"detail": jsonable_encoder(exc.errors())}, - ) - - -async def websocket_request_validation_exception_handler( - websocket: WebSocket, exc: WebSocketRequestValidationError -) -> None: - await websocket.close( - code=WS_1008_POLICY_VIOLATION, reason=jsonable_encoder(exc.errors()) - ) diff --git a/server/venv/Lib/site-packages/fastapi/exceptions.py b/server/venv/Lib/site-packages/fastapi/exceptions.py deleted file mode 100644 index d7065c5..0000000 --- a/server/venv/Lib/site-packages/fastapi/exceptions.py +++ /dev/null @@ -1,256 +0,0 @@ -from collections.abc import Mapping, Sequence -from typing import Annotated, Any, TypedDict - -from annotated_doc import Doc -from pydantic import BaseModel, create_model -from starlette.exceptions import HTTPException as StarletteHTTPException -from starlette.exceptions import WebSocketException as StarletteWebSocketException - - -class EndpointContext(TypedDict, total=False): - function: str - path: str - file: str - line: int - - -class HTTPException(StarletteHTTPException): - """ - An HTTP exception you can raise in your own code to show errors to the client. - - This is for client errors, invalid authentication, invalid data, etc. Not for server - errors in your code. - - Read more about it in the - [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/). - - ## Example - - ```python - from fastapi import FastAPI, HTTPException - - app = FastAPI() - - items = {"foo": "The Foo Wrestlers"} - - - @app.get("/items/{item_id}") - async def read_item(item_id: str): - if item_id not in items: - raise HTTPException(status_code=404, detail="Item not found") - return {"item": items[item_id]} - ``` - """ - - def __init__( - self, - status_code: Annotated[ - int, - Doc( - """ - HTTP status code to send to the client. - - Read more about it in the - [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/#use-httpexception) - """ - ), - ], - detail: Annotated[ - Any, - Doc( - """ - Any data to be sent to the client in the `detail` key of the JSON - response. - - Read more about it in the - [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/#use-httpexception) - """ - ), - ] = None, - headers: Annotated[ - Mapping[str, str] | None, - Doc( - """ - Any headers to send to the client in the response. - - Read more about it in the - [FastAPI docs for Handling Errors](https://fastapi.tiangolo.com/tutorial/handling-errors/#add-custom-headers) - - """ - ), - ] = None, - ) -> None: - super().__init__(status_code=status_code, detail=detail, headers=headers) - - -class WebSocketException(StarletteWebSocketException): - """ - A WebSocket exception you can raise in your own code to show errors to the client. - - This is for client errors, invalid authentication, invalid data, etc. Not for server - errors in your code. - - Read more about it in the - [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). - - ## Example - - ```python - from typing import Annotated - - from fastapi import ( - Cookie, - FastAPI, - WebSocket, - WebSocketException, - status, - ) - - app = FastAPI() - - @app.websocket("/items/{item_id}/ws") - async def websocket_endpoint( - *, - websocket: WebSocket, - session: Annotated[str | None, Cookie()] = None, - item_id: str, - ): - if session is None: - raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION) - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text(f"Session cookie is: {session}") - await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}") - ``` - """ - - def __init__( - self, - code: Annotated[ - int, - Doc( - """ - A closing code from the - [valid codes defined in the specification](https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1). - """ - ), - ], - reason: Annotated[ - str | None, - Doc( - """ - The reason to close the WebSocket connection. - - It is UTF-8-encoded data. The interpretation of the reason is up to the - application, it is not specified by the WebSocket specification. - - It could contain text that could be human-readable or interpretable - by the client code, etc. - """ - ), - ] = None, - ) -> None: - super().__init__(code=code, reason=reason) - - -RequestErrorModel: type[BaseModel] = create_model("Request") -WebSocketErrorModel: type[BaseModel] = create_model("WebSocket") - - -class FastAPIError(RuntimeError): - """ - A generic, FastAPI-specific error. - """ - - -class DependencyScopeError(FastAPIError): - """ - A dependency declared that it depends on another dependency with an invalid - (narrower) scope. - """ - - -class ValidationException(Exception): - def __init__( - self, - errors: Sequence[Any], - *, - endpoint_ctx: EndpointContext | None = None, - ) -> None: - self._errors = errors - self.endpoint_ctx = endpoint_ctx - - ctx = endpoint_ctx or {} - self.endpoint_function = ctx.get("function") - self.endpoint_path = ctx.get("path") - self.endpoint_file = ctx.get("file") - self.endpoint_line = ctx.get("line") - - def errors(self) -> Sequence[Any]: - return self._errors - - def _format_endpoint_context(self) -> str: - if not (self.endpoint_file and self.endpoint_line and self.endpoint_function): - if self.endpoint_path: - return f"\n Endpoint: {self.endpoint_path}" - return "" - - context = f'\n File "{self.endpoint_file}", line {self.endpoint_line}, in {self.endpoint_function}' - if self.endpoint_path: - context += f"\n {self.endpoint_path}" - return context - - def __str__(self) -> str: - message = f"{len(self._errors)} validation error{'s' if len(self._errors) != 1 else ''}:\n" - for err in self._errors: - message += f" {err}\n" - message += self._format_endpoint_context() - return message.rstrip() - - -class RequestValidationError(ValidationException): - def __init__( - self, - errors: Sequence[Any], - *, - body: Any = None, - endpoint_ctx: EndpointContext | None = None, - ) -> None: - super().__init__(errors, endpoint_ctx=endpoint_ctx) - self.body = body - - -class WebSocketRequestValidationError(ValidationException): - def __init__( - self, - errors: Sequence[Any], - *, - endpoint_ctx: EndpointContext | None = None, - ) -> None: - super().__init__(errors, endpoint_ctx=endpoint_ctx) - - -class ResponseValidationError(ValidationException): - def __init__( - self, - errors: Sequence[Any], - *, - body: Any = None, - endpoint_ctx: EndpointContext | None = None, - ) -> None: - super().__init__(errors, endpoint_ctx=endpoint_ctx) - self.body = body - - -class PydanticV1NotSupportedError(FastAPIError): - """ - A pydantic.v1 model is used, which is no longer supported. - """ - - -class FastAPIDeprecationWarning(UserWarning): - """ - A custom deprecation warning as DeprecationWarning is ignored - Ref: https://sethmlarson.dev/deprecations-via-warnings-dont-work-for-python-libraries - """ diff --git a/server/venv/Lib/site-packages/fastapi/logger.py b/server/venv/Lib/site-packages/fastapi/logger.py deleted file mode 100644 index 5b2c4ad..0000000 --- a/server/venv/Lib/site-packages/fastapi/logger.py +++ /dev/null @@ -1,3 +0,0 @@ -import logging - -logger = logging.getLogger("fastapi") diff --git a/server/venv/Lib/site-packages/fastapi/middleware/__init__.py b/server/venv/Lib/site-packages/fastapi/middleware/__init__.py deleted file mode 100644 index 620296d..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from starlette.middleware import Middleware as Middleware diff --git a/server/venv/Lib/site-packages/fastapi/middleware/asyncexitstack.py b/server/venv/Lib/site-packages/fastapi/middleware/asyncexitstack.py deleted file mode 100644 index 4ce3f5a..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/asyncexitstack.py +++ /dev/null @@ -1,18 +0,0 @@ -from contextlib import AsyncExitStack - -from starlette.types import ASGIApp, Receive, Scope, Send - - -# Used mainly to close files after the request is done, dependencies are closed -# in their own AsyncExitStack -class AsyncExitStackMiddleware: - def __init__( - self, app: ASGIApp, context_name: str = "fastapi_middleware_astack" - ) -> None: - self.app = app - self.context_name = context_name - - async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: - async with AsyncExitStack() as stack: - scope[self.context_name] = stack - await self.app(scope, receive, send) diff --git a/server/venv/Lib/site-packages/fastapi/middleware/cors.py b/server/venv/Lib/site-packages/fastapi/middleware/cors.py deleted file mode 100644 index 8dfaad0..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/cors.py +++ /dev/null @@ -1 +0,0 @@ -from starlette.middleware.cors import CORSMiddleware as CORSMiddleware # noqa diff --git a/server/venv/Lib/site-packages/fastapi/middleware/gzip.py b/server/venv/Lib/site-packages/fastapi/middleware/gzip.py deleted file mode 100644 index bbeb2cc..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/gzip.py +++ /dev/null @@ -1 +0,0 @@ -from starlette.middleware.gzip import GZipMiddleware as GZipMiddleware # noqa diff --git a/server/venv/Lib/site-packages/fastapi/middleware/httpsredirect.py b/server/venv/Lib/site-packages/fastapi/middleware/httpsredirect.py deleted file mode 100644 index b7a3d8e..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/httpsredirect.py +++ /dev/null @@ -1,3 +0,0 @@ -from starlette.middleware.httpsredirect import ( # noqa - HTTPSRedirectMiddleware as HTTPSRedirectMiddleware, -) diff --git a/server/venv/Lib/site-packages/fastapi/middleware/trustedhost.py b/server/venv/Lib/site-packages/fastapi/middleware/trustedhost.py deleted file mode 100644 index 08d7e03..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/trustedhost.py +++ /dev/null @@ -1,3 +0,0 @@ -from starlette.middleware.trustedhost import ( # noqa - TrustedHostMiddleware as TrustedHostMiddleware, -) diff --git a/server/venv/Lib/site-packages/fastapi/middleware/wsgi.py b/server/venv/Lib/site-packages/fastapi/middleware/wsgi.py deleted file mode 100644 index 69e4dca..0000000 --- a/server/venv/Lib/site-packages/fastapi/middleware/wsgi.py +++ /dev/null @@ -1,3 +0,0 @@ -from starlette.middleware.wsgi import ( - WSGIMiddleware as WSGIMiddleware, -) # pragma: no cover # noqa diff --git a/server/venv/Lib/site-packages/fastapi/openapi/__init__.py b/server/venv/Lib/site-packages/fastapi/openapi/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/fastapi/openapi/constants.py b/server/venv/Lib/site-packages/fastapi/openapi/constants.py deleted file mode 100644 index d724ee3..0000000 --- a/server/venv/Lib/site-packages/fastapi/openapi/constants.py +++ /dev/null @@ -1,3 +0,0 @@ -METHODS_WITH_BODY = {"GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"} -REF_PREFIX = "#/components/schemas/" -REF_TEMPLATE = "#/components/schemas/{model}" diff --git a/server/venv/Lib/site-packages/fastapi/openapi/docs.py b/server/venv/Lib/site-packages/fastapi/openapi/docs.py deleted file mode 100644 index 0d9242f..0000000 --- a/server/venv/Lib/site-packages/fastapi/openapi/docs.py +++ /dev/null @@ -1,389 +0,0 @@ -import json -from typing import Annotated, Any - -from annotated_doc import Doc -from fastapi.encoders import jsonable_encoder -from starlette.responses import HTMLResponse - - -def _html_safe_json(value: Any) -> str: - """Serialize a value to JSON with HTML special characters escaped. - - This prevents injection when the JSON is embedded inside a - - - - - """ - return HTMLResponse(html) - - -def get_redoc_html( - *, - openapi_url: Annotated[ - str, - Doc( - """ - The OpenAPI URL that ReDoc should load and use. - - This is normally done automatically by FastAPI using the default URL - `/openapi.json`. - - Read more about it in the - [FastAPI docs for Conditional OpenAPI](https://fastapi.tiangolo.com/how-to/conditional-openapi/#conditional-openapi-from-settings-and-env-vars) - """ - ), - ], - title: Annotated[ - str, - Doc( - """ - The HTML `` content, normally shown in the browser tab. - - Read more about it in the - [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/) - """ - ), - ], - redoc_js_url: Annotated[ - str, - Doc( - """ - The URL to use to load the ReDoc JavaScript. - - It is normally set to a CDN URL. - - Read more about it in the - [FastAPI docs for Custom Docs UI Static Assets](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/) - """ - ), - ] = "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js", - redoc_favicon_url: Annotated[ - str, - Doc( - """ - The URL of the favicon to use. It is normally shown in the browser tab. - """ - ), - ] = "https://fastapi.tiangolo.com/img/favicon.png", - with_google_fonts: Annotated[ - bool, - Doc( - """ - Load and use Google Fonts. - """ - ), - ] = True, -) -> HTMLResponse: - """ - Generate and return the HTML response that loads ReDoc for the alternative - API docs (normally served at `/redoc`). - - You would only call this function yourself if you needed to override some parts, - for example the URLs to use to load ReDoc's JavaScript and CSS. - - Read more about it in the - [FastAPI docs for Custom Docs UI Static Assets (Self-Hosting)](https://fastapi.tiangolo.com/how-to/custom-docs-ui-assets/). - """ - html = f""" - <!DOCTYPE html> - <html> - <head> - <title>{title} - - - - """ - if with_google_fonts: - html += """ - - """ - html += f""" - - - - - - - - - - - """ - return HTMLResponse(html) - - -def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse: - """ - Generate the HTML response with the OAuth2 redirection for Swagger UI. - - You normally don't need to use or change this. - """ - # copied from https://github.com/swagger-api/swagger-ui/blob/v4.14.0/dist/oauth2-redirect.html - html = """ - - - - Swagger UI: OAuth2 Redirect - - - - - - """ - return HTMLResponse(content=html) diff --git a/server/venv/Lib/site-packages/fastapi/openapi/models.py b/server/venv/Lib/site-packages/fastapi/openapi/models.py deleted file mode 100644 index ca26bf9..0000000 --- a/server/venv/Lib/site-packages/fastapi/openapi/models.py +++ /dev/null @@ -1,435 +0,0 @@ -from collections.abc import Callable, Iterable, Mapping -from enum import Enum -from typing import Annotated, Any, Literal, Optional, Union - -from fastapi._compat import with_info_plain_validator_function -from fastapi.logger import logger -from pydantic import ( - AnyUrl, - BaseModel, - Field, - GetJsonSchemaHandler, -) -from typing_extensions import TypedDict -from typing_extensions import deprecated as typing_deprecated - -try: - import email_validator - - assert email_validator # make autoflake ignore the unused import - from pydantic import EmailStr -except ImportError: # pragma: no cover - - class EmailStr(str): # type: ignore[no-redef] - @classmethod - def __get_validators__(cls) -> Iterable[Callable[..., Any]]: - yield cls.validate - - @classmethod - def validate(cls, v: Any) -> str: - logger.warning( - "email-validator not installed, email fields will be treated as str.\n" - "To install, run: pip install email-validator" - ) - return str(v) - - @classmethod - def _validate(cls, __input_value: Any, _: Any) -> str: - logger.warning( - "email-validator not installed, email fields will be treated as str.\n" - "To install, run: pip install email-validator" - ) - return str(__input_value) - - @classmethod - def __get_pydantic_json_schema__( - cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler - ) -> dict[str, Any]: - return {"type": "string", "format": "email"} - - @classmethod - def __get_pydantic_core_schema__( - cls, source: type[Any], handler: Callable[[Any], Mapping[str, Any]] - ) -> Mapping[str, Any]: - return with_info_plain_validator_function(cls._validate) - - -class BaseModelWithConfig(BaseModel): - model_config = {"extra": "allow"} - - -class Contact(BaseModelWithConfig): - name: str | None = None - url: AnyUrl | None = None - email: EmailStr | None = None - - -class License(BaseModelWithConfig): - name: str - identifier: str | None = None - url: AnyUrl | None = None - - -class Info(BaseModelWithConfig): - title: str - summary: str | None = None - description: str | None = None - termsOfService: str | None = None - contact: Contact | None = None - license: License | None = None - version: str - - -class ServerVariable(BaseModelWithConfig): - enum: Annotated[list[str] | None, Field(min_length=1)] = None - default: str - description: str | None = None - - -class Server(BaseModelWithConfig): - url: AnyUrl | str - description: str | None = None - variables: dict[str, ServerVariable] | None = None - - -class Reference(BaseModel): - ref: str = Field(alias="$ref") - - -class Discriminator(BaseModel): - propertyName: str - mapping: dict[str, str] | None = None - - -class XML(BaseModelWithConfig): - name: str | None = None - namespace: str | None = None - prefix: str | None = None - attribute: bool | None = None - wrapped: bool | None = None - - -class ExternalDocumentation(BaseModelWithConfig): - description: str | None = None - url: AnyUrl - - -# Ref JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation#name-type -SchemaType = Literal[ - "array", "boolean", "integer", "null", "number", "object", "string" -] - - -class Schema(BaseModelWithConfig): - # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-json-schema-core-vocabu - # Core Vocabulary - schema_: str | None = Field(default=None, alias="$schema") - vocabulary: str | None = Field(default=None, alias="$vocabulary") - id: str | None = Field(default=None, alias="$id") - anchor: str | None = Field(default=None, alias="$anchor") - dynamicAnchor: str | None = Field(default=None, alias="$dynamicAnchor") - ref: str | None = Field(default=None, alias="$ref") - dynamicRef: str | None = Field(default=None, alias="$dynamicRef") - defs: dict[str, "SchemaOrBool"] | None = Field(default=None, alias="$defs") - comment: str | None = Field(default=None, alias="$comment") - # Ref: JSON Schema 2020-12: https://json-schema.org/draft/2020-12/json-schema-core.html#name-a-vocabulary-for-applying-s - # A Vocabulary for Applying Subschemas - allOf: list["SchemaOrBool"] | None = None - anyOf: list["SchemaOrBool"] | None = None - oneOf: list["SchemaOrBool"] | None = None - not_: Optional["SchemaOrBool"] = Field(default=None, alias="not") - if_: Optional["SchemaOrBool"] = Field(default=None, alias="if") - then: Optional["SchemaOrBool"] = None - else_: Optional["SchemaOrBool"] = Field(default=None, alias="else") - dependentSchemas: dict[str, "SchemaOrBool"] | None = None - prefixItems: list["SchemaOrBool"] | None = None - items: Optional["SchemaOrBool"] = None - contains: Optional["SchemaOrBool"] = None - properties: dict[str, "SchemaOrBool"] | None = None - patternProperties: dict[str, "SchemaOrBool"] | None = None - additionalProperties: Optional["SchemaOrBool"] = None - propertyNames: Optional["SchemaOrBool"] = None - unevaluatedItems: Optional["SchemaOrBool"] = None - unevaluatedProperties: Optional["SchemaOrBool"] = None - # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-structural - # A Vocabulary for Structural Validation - type: SchemaType | list[SchemaType] | None = None - enum: list[Any] | None = None - const: Any | None = None - multipleOf: float | None = Field(default=None, gt=0) - maximum: float | None = None - exclusiveMaximum: float | None = None - minimum: float | None = None - exclusiveMinimum: float | None = None - maxLength: int | None = Field(default=None, ge=0) - minLength: int | None = Field(default=None, ge=0) - pattern: str | None = None - maxItems: int | None = Field(default=None, ge=0) - minItems: int | None = Field(default=None, ge=0) - uniqueItems: bool | None = None - maxContains: int | None = Field(default=None, ge=0) - minContains: int | None = Field(default=None, ge=0) - maxProperties: int | None = Field(default=None, ge=0) - minProperties: int | None = Field(default=None, ge=0) - required: list[str] | None = None - dependentRequired: dict[str, set[str]] | None = None - # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-vocabularies-for-semantic-c - # Vocabularies for Semantic Content With "format" - format: str | None = None - # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-the-conten - # A Vocabulary for the Contents of String-Encoded Data - contentEncoding: str | None = None - contentMediaType: str | None = None - contentSchema: Optional["SchemaOrBool"] = None - # Ref: JSON Schema Validation 2020-12: https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta - # A Vocabulary for Basic Meta-Data Annotations - title: str | None = None - description: str | None = None - default: Any | None = None - deprecated: bool | None = None - readOnly: bool | None = None - writeOnly: bool | None = None - examples: list[Any] | None = None - # Ref: OpenAPI 3.1.0: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#schema-object - # Schema Object - discriminator: Discriminator | None = None - xml: XML | None = None - externalDocs: ExternalDocumentation | None = None - example: Annotated[ - Any | None, - typing_deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = None - - -# Ref: https://json-schema.org/draft/2020-12/json-schema-core.html#name-json-schema-documents -# A JSON Schema MUST be an object or a boolean. -SchemaOrBool = Schema | bool - - -class Example(TypedDict, total=False): - summary: str | None - description: str | None - value: Any | None - externalValue: AnyUrl | None - - __pydantic_config__ = {"extra": "allow"} # type: ignore[misc] # ty: ignore[invalid-typed-dict-statement] - - -class ParameterInType(Enum): - query = "query" - header = "header" - path = "path" - cookie = "cookie" - - -class Encoding(BaseModelWithConfig): - contentType: str | None = None - headers: dict[str, Union["Header", Reference]] | None = None - style: str | None = None - explode: bool | None = None - allowReserved: bool | None = None - - -class MediaType(BaseModelWithConfig): - schema_: Schema | Reference | None = Field(default=None, alias="schema") - example: Any | None = None - examples: dict[str, Example | Reference] | None = None - encoding: dict[str, Encoding] | None = None - - -class ParameterBase(BaseModelWithConfig): - description: str | None = None - required: bool | None = None - deprecated: bool | None = None - # Serialization rules for simple scenarios - style: str | None = None - explode: bool | None = None - allowReserved: bool | None = None - schema_: Schema | Reference | None = Field(default=None, alias="schema") - example: Any | None = None - examples: dict[str, Example | Reference] | None = None - # Serialization rules for more complex scenarios - content: dict[str, MediaType] | None = None - - -class Parameter(ParameterBase): - name: str - in_: ParameterInType = Field(alias="in") - - -class Header(ParameterBase): - pass - - -class RequestBody(BaseModelWithConfig): - description: str | None = None - content: dict[str, MediaType] - required: bool | None = None - - -class Link(BaseModelWithConfig): - operationRef: str | None = None - operationId: str | None = None - parameters: dict[str, Any | str] | None = None - requestBody: Any | str | None = None - description: str | None = None - server: Server | None = None - - -class Response(BaseModelWithConfig): - description: str - headers: dict[str, Header | Reference] | None = None - content: dict[str, MediaType] | None = None - links: dict[str, Link | Reference] | None = None - - -class Operation(BaseModelWithConfig): - tags: list[str] | None = None - summary: str | None = None - description: str | None = None - externalDocs: ExternalDocumentation | None = None - operationId: str | None = None - parameters: list[Parameter | Reference] | None = None - requestBody: RequestBody | Reference | None = None - # Using Any for Specification Extensions - responses: dict[str, Response | Any] | None = None - callbacks: dict[str, dict[str, "PathItem"] | Reference] | None = None - deprecated: bool | None = None - security: list[dict[str, list[str]]] | None = None - servers: list[Server] | None = None - - -class PathItem(BaseModelWithConfig): - ref: str | None = Field(default=None, alias="$ref") - summary: str | None = None - description: str | None = None - get: Operation | None = None - put: Operation | None = None - post: Operation | None = None - delete: Operation | None = None - options: Operation | None = None - head: Operation | None = None - patch: Operation | None = None - trace: Operation | None = None - servers: list[Server] | None = None - parameters: list[Parameter | Reference] | None = None - - -class SecuritySchemeType(Enum): - apiKey = "apiKey" - http = "http" - oauth2 = "oauth2" - openIdConnect = "openIdConnect" - - -class SecurityBase(BaseModelWithConfig): - type_: SecuritySchemeType = Field(alias="type") - description: str | None = None - - -class APIKeyIn(Enum): - query = "query" - header = "header" - cookie = "cookie" - - -class APIKey(SecurityBase): - type_: SecuritySchemeType = Field(default=SecuritySchemeType.apiKey, alias="type") - in_: APIKeyIn = Field(alias="in") - name: str - - -class HTTPBase(SecurityBase): - type_: SecuritySchemeType = Field(default=SecuritySchemeType.http, alias="type") - scheme: str - - -class HTTPBearer(HTTPBase): - scheme: Literal["bearer"] = "bearer" - bearerFormat: str | None = None - - -class OAuthFlow(BaseModelWithConfig): - refreshUrl: str | None = None - scopes: dict[str, str] = {} - - -class OAuthFlowImplicit(OAuthFlow): - authorizationUrl: str - - -class OAuthFlowPassword(OAuthFlow): - tokenUrl: str - - -class OAuthFlowClientCredentials(OAuthFlow): - tokenUrl: str - - -class OAuthFlowAuthorizationCode(OAuthFlow): - authorizationUrl: str - tokenUrl: str - - -class OAuthFlows(BaseModelWithConfig): - implicit: OAuthFlowImplicit | None = None - password: OAuthFlowPassword | None = None - clientCredentials: OAuthFlowClientCredentials | None = None - authorizationCode: OAuthFlowAuthorizationCode | None = None - - -class OAuth2(SecurityBase): - type_: SecuritySchemeType = Field(default=SecuritySchemeType.oauth2, alias="type") - flows: OAuthFlows - - -class OpenIdConnect(SecurityBase): - type_: SecuritySchemeType = Field( - default=SecuritySchemeType.openIdConnect, alias="type" - ) - openIdConnectUrl: str - - -SecurityScheme = APIKey | HTTPBase | OAuth2 | OpenIdConnect | HTTPBearer - - -class Components(BaseModelWithConfig): - schemas: dict[str, Schema | Reference] | None = None - responses: dict[str, Response | Reference] | None = None - parameters: dict[str, Parameter | Reference] | None = None - examples: dict[str, Example | Reference] | None = None - requestBodies: dict[str, RequestBody | Reference] | None = None - headers: dict[str, Header | Reference] | None = None - securitySchemes: dict[str, SecurityScheme | Reference] | None = None - links: dict[str, Link | Reference] | None = None - # Using Any for Specification Extensions - callbacks: dict[str, dict[str, PathItem] | Reference | Any] | None = None - pathItems: dict[str, PathItem | Reference] | None = None - - -class Tag(BaseModelWithConfig): - name: str - description: str | None = None - externalDocs: ExternalDocumentation | None = None - - -class OpenAPI(BaseModelWithConfig): - openapi: str - info: Info - jsonSchemaDialect: str | None = None - servers: list[Server] | None = None - # Using Any for Specification Extensions - paths: dict[str, PathItem | Any] | None = None - webhooks: dict[str, PathItem | Reference] | None = None - components: Components | None = None - security: list[dict[str, list[str]]] | None = None - tags: list[Tag] | None = None - externalDocs: ExternalDocumentation | None = None - - -Schema.model_rebuild() -Operation.model_rebuild() -Encoding.model_rebuild() diff --git a/server/venv/Lib/site-packages/fastapi/openapi/utils.py b/server/venv/Lib/site-packages/fastapi/openapi/utils.py deleted file mode 100644 index 2e0aca1..0000000 --- a/server/venv/Lib/site-packages/fastapi/openapi/utils.py +++ /dev/null @@ -1,617 +0,0 @@ -import copy -import http.client -import inspect -import warnings -from collections.abc import Sequence -from typing import Any, Literal, cast - -from fastapi import routing -from fastapi._compat import ( - ModelField, - get_definitions, - get_flat_models_from_fields, - get_model_name_map, - get_schema_from_model_field, - lenient_issubclass, -) -from fastapi.datastructures import DefaultPlaceholder, _Unset -from fastapi.dependencies.models import Dependant -from fastapi.dependencies.utils import ( - _get_flat_fields_from_params, - get_flat_dependant, - get_flat_params, - get_validation_alias, -) -from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import FastAPIDeprecationWarning -from fastapi.openapi.constants import METHODS_WITH_BODY, REF_PREFIX -from fastapi.openapi.models import OpenAPI -from fastapi.params import Body, ParamTypes -from fastapi.responses import Response -from fastapi.sse import _SSE_EVENT_SCHEMA -from fastapi.types import ModelNameMap -from fastapi.utils import ( - deep_dict_update, - generate_operation_id_for_path, - is_body_allowed_for_status_code, -) -from pydantic import BaseModel -from starlette.responses import JSONResponse -from starlette.routing import BaseRoute - -validation_error_definition = { - "title": "ValidationError", - "type": "object", - "properties": { - "loc": { - "title": "Location", - "type": "array", - "items": {"anyOf": [{"type": "string"}, {"type": "integer"}]}, - }, - "msg": {"title": "Message", "type": "string"}, - "type": {"title": "Error Type", "type": "string"}, - "input": {"title": "Input"}, - "ctx": {"title": "Context", "type": "object"}, - }, - "required": ["loc", "msg", "type"], -} - -validation_error_response_definition = { - "title": "HTTPValidationError", - "type": "object", - "properties": { - "detail": { - "title": "Detail", - "type": "array", - "items": {"$ref": REF_PREFIX + "ValidationError"}, - } - }, -} - -status_code_ranges: dict[str, str] = { - "1XX": "Information", - "2XX": "Success", - "3XX": "Redirection", - "4XX": "Client Error", - "5XX": "Server Error", - "DEFAULT": "Default Response", -} - - -def get_openapi_security_definitions( - flat_dependant: Dependant, -) -> tuple[dict[str, Any], list[dict[str, Any]]]: - security_definitions = {} - # Use a dict to merge scopes for same security scheme - operation_security_dict: dict[str, list[str]] = {} - for security_dependency in flat_dependant._security_dependencies: - security_definition = jsonable_encoder( - security_dependency._security_scheme.model, - by_alias=True, - exclude_none=True, - ) - security_name = security_dependency._security_scheme.scheme_name - security_definitions[security_name] = security_definition - # Merge scopes for the same security scheme - if security_name not in operation_security_dict: - operation_security_dict[security_name] = [] - for scope in security_dependency.oauth_scopes or []: - if scope not in operation_security_dict[security_name]: - operation_security_dict[security_name].append(scope) - operation_security = [ - {name: scopes} for name, scopes in operation_security_dict.items() - ] - return security_definitions, operation_security - - -def _get_openapi_operation_parameters( - *, - dependant: Dependant, - model_name_map: ModelNameMap, - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] - ], - separate_input_output_schemas: bool = True, -) -> list[dict[str, Any]]: - parameters = [] - flat_dependant = get_flat_dependant(dependant, skip_repeats=True) - path_params = _get_flat_fields_from_params(flat_dependant.path_params) - query_params = _get_flat_fields_from_params(flat_dependant.query_params) - header_params = _get_flat_fields_from_params(flat_dependant.header_params) - cookie_params = _get_flat_fields_from_params(flat_dependant.cookie_params) - parameter_groups = [ - (ParamTypes.path, path_params), - (ParamTypes.query, query_params), - (ParamTypes.header, header_params), - (ParamTypes.cookie, cookie_params), - ] - default_convert_underscores = True - if len(flat_dependant.header_params) == 1: - first_field = flat_dependant.header_params[0] - if lenient_issubclass(first_field.field_info.annotation, BaseModel): - default_convert_underscores = getattr( - first_field.field_info, "convert_underscores", True - ) - for param_type, param_group in parameter_groups: - for param in param_group: - field_info = param.field_info - # field_info = cast(Param, field_info) - if not getattr(field_info, "include_in_schema", True): - continue - param_schema = get_schema_from_model_field( - field=param, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - name = get_validation_alias(param) - convert_underscores = getattr( - param.field_info, - "convert_underscores", - default_convert_underscores, - ) - if ( - param_type == ParamTypes.header - and name == param.name - and convert_underscores - ): - name = param.name.replace("_", "-") - - parameter = { - "name": name, - "in": param_type.value, - "required": param.field_info.is_required(), - "schema": param_schema, - } - if field_info.description: - parameter["description"] = field_info.description - openapi_examples = getattr(field_info, "openapi_examples", None) - example = getattr(field_info, "example", None) - if openapi_examples: - parameter["examples"] = jsonable_encoder(openapi_examples) - elif example is not _Unset: - parameter["example"] = jsonable_encoder(example) - if getattr(field_info, "deprecated", None): - parameter["deprecated"] = True - parameters.append(parameter) - return parameters - - -def get_openapi_operation_request_body( - *, - body_field: ModelField | None, - model_name_map: ModelNameMap, - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] - ], - separate_input_output_schemas: bool = True, -) -> dict[str, Any] | None: - if not body_field: - return None - assert isinstance(body_field, ModelField) - body_schema = get_schema_from_model_field( - field=body_field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - field_info = cast(Body, body_field.field_info) - request_media_type = field_info.media_type - required = body_field.field_info.is_required() - request_body_oai: dict[str, Any] = {} - if required: - request_body_oai["required"] = required - request_media_content: dict[str, Any] = {"schema": body_schema} - if field_info.openapi_examples: - request_media_content["examples"] = jsonable_encoder( - field_info.openapi_examples - ) - elif field_info.example is not _Unset: - request_media_content["example"] = jsonable_encoder(field_info.example) - request_body_oai["content"] = {request_media_type: request_media_content} - return request_body_oai - - -def generate_operation_id( - *, route: routing._APIRouteLike, method: str -) -> str: # pragma: nocover - warnings.warn( - message="fastapi.openapi.utils.generate_operation_id() was deprecated, " - "it is not used internally, and will be removed soon", - category=FastAPIDeprecationWarning, - stacklevel=2, - ) - if route.operation_id: - return route.operation_id - path: str = route.path_format - return generate_operation_id_for_path(name=route.name, path=path, method=method) - - -def generate_operation_summary(*, route: routing._APIRouteLike, method: str) -> str: - if route.summary: - return route.summary - return route.name.replace("_", " ").title() - - -def get_openapi_operation_metadata( - *, route: routing._APIRouteLike, method: str, operation_ids: set[str] -) -> dict[str, Any]: - operation: dict[str, Any] = {} - if route.tags: - operation["tags"] = route.tags - operation["summary"] = generate_operation_summary(route=route, method=method) - if route.description: - operation["description"] = route.description - operation_id = route.operation_id or route.unique_id - if operation_id in operation_ids: - endpoint_name = getattr(route.endpoint, "__name__", "") - message = f"Duplicate Operation ID {operation_id} for function {endpoint_name}" - file_name = getattr(route.endpoint, "__globals__", {}).get("__file__") - if file_name: - message += f" at {file_name}" - warnings.warn(message, stacklevel=1) - operation_ids.add(operation_id) - operation["operationId"] = operation_id - if route.deprecated: - operation["deprecated"] = route.deprecated - return operation - - -def get_openapi_path( - *, - route: routing._APIRouteLike, - operation_ids: set[str], - model_name_map: ModelNameMap, - field_mapping: dict[ - tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any] - ], - separate_input_output_schemas: bool = True, -) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: - path = {} - security_schemes: dict[str, Any] = {} - definitions: dict[str, Any] = {} - assert route.methods is not None, "Methods must be a list" - if isinstance(route.response_class, DefaultPlaceholder): - current_response_class: type[Response] = route.response_class.value - else: - current_response_class = route.response_class - assert current_response_class, "A response class is needed to generate OpenAPI" - route_response_media_type: str | None = current_response_class.media_type - if route.include_in_schema: - for method in route.methods: - operation = get_openapi_operation_metadata( - route=route, method=method, operation_ids=operation_ids - ) - parameters: list[dict[str, Any]] = [] - flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True) - security_definitions, operation_security = get_openapi_security_definitions( - flat_dependant=flat_dependant - ) - if operation_security: - operation.setdefault("security", []).extend(operation_security) - if security_definitions: - security_schemes.update(security_definitions) - operation_parameters = _get_openapi_operation_parameters( - dependant=route.dependant, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - parameters.extend(operation_parameters) - if parameters: - all_parameters = { - (param["in"], param["name"]): param for param in parameters - } - required_parameters = { - (param["in"], param["name"]): param - for param in parameters - if param.get("required") - } - # Make sure required definitions of the same parameter take precedence - # over non-required definitions - all_parameters.update(required_parameters) - operation["parameters"] = list(all_parameters.values()) - if method in METHODS_WITH_BODY: - request_body_oai = get_openapi_operation_request_body( - body_field=route.body_field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - if request_body_oai: - operation["requestBody"] = request_body_oai - if route.callbacks: - callbacks = {} - for callback in route.callbacks: - if isinstance(callback, routing.APIRoute): - ( - cb_path, - cb_security_schemes, - cb_definitions, - ) = get_openapi_path( - route=cast(routing._APIRouteLike, callback), - operation_ids=operation_ids, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - callbacks[callback.name] = {callback.path: cb_path} - operation["callbacks"] = callbacks - if route.status_code is not None: - status_code = str(route.status_code) - else: - # It would probably make more sense for all response classes to have an - # explicit default status_code, and to extract it from them, instead of - # doing this inspection tricks, that would probably be in the future - # TODO: probably make status_code a default class attribute for all - # responses in Starlette - response_signature = inspect.signature(current_response_class.__init__) - status_code_param = response_signature.parameters.get("status_code") - if status_code_param is not None: - if isinstance(status_code_param.default, int): - status_code = str(status_code_param.default) - operation.setdefault("responses", {}).setdefault(status_code, {})[ - "description" - ] = route.response_description - if is_body_allowed_for_status_code(route.status_code): - # Check for JSONL streaming (generator endpoints) - if route.is_json_stream: - jsonl_content: dict[str, Any] = {} - if route.stream_item_field: - item_schema = get_schema_from_model_field( - field=route.stream_item_field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - jsonl_content["itemSchema"] = item_schema - else: - jsonl_content["itemSchema"] = {} - operation.setdefault("responses", {}).setdefault( - status_code, {} - ).setdefault("content", {})["application/jsonl"] = jsonl_content - elif route.is_sse_stream: - sse_content: dict[str, Any] = {} - item_schema = copy.deepcopy(_SSE_EVENT_SCHEMA) - if route.stream_item_field: - content_schema = get_schema_from_model_field( - field=route.stream_item_field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - item_schema["required"] = ["data"] - item_schema["properties"]["data"] = { - "type": "string", - "contentMediaType": "application/json", - "contentSchema": content_schema, - } - sse_content["itemSchema"] = item_schema - operation.setdefault("responses", {}).setdefault( - status_code, {} - ).setdefault("content", {})["text/event-stream"] = sse_content - elif route_response_media_type: - response_schema = {"type": "string"} - if lenient_issubclass(current_response_class, JSONResponse): - if route.response_field: - response_schema = get_schema_from_model_field( - field=route.response_field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - else: - response_schema = {} - operation.setdefault("responses", {}).setdefault( - status_code, {} - ).setdefault("content", {}).setdefault( - route_response_media_type, {} - )["schema"] = response_schema - if route.responses: - operation_responses = operation.setdefault("responses", {}) - for ( - additional_status_code, - additional_response, - ) in route.responses.items(): - process_response = copy.deepcopy(additional_response) - process_response.pop("model", None) - status_code_key = str(additional_status_code).upper() - if status_code_key == "DEFAULT": - status_code_key = "default" - openapi_response = operation_responses.setdefault( - status_code_key, {} - ) - assert isinstance(process_response, dict), ( - "An additional response must be a dict" - ) - field = route.response_fields.get(additional_status_code) - additional_field_schema: dict[str, Any] | None = None - if field: - additional_field_schema = get_schema_from_model_field( - field=field, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - media_type = route_response_media_type or "application/json" - additional_schema = ( - process_response.setdefault("content", {}) - .setdefault(media_type, {}) - .setdefault("schema", {}) - ) - deep_dict_update(additional_schema, additional_field_schema) - status_text: str | None = status_code_ranges.get( - str(additional_status_code).upper() - ) or http.client.responses.get(int(additional_status_code)) - description = ( - process_response.get("description") - or openapi_response.get("description") - or status_text - or "Additional Response" - ) - deep_dict_update(openapi_response, process_response) - openapi_response["description"] = description - http422 = "422" - all_route_params = get_flat_params(route.dependant) - if (all_route_params or route.body_field) and not any( - status in operation["responses"] - for status in [http422, "4XX", "default"] - ): - operation["responses"][http422] = { - "description": "Validation Error", - "content": { - "application/json": { - "schema": {"$ref": REF_PREFIX + "HTTPValidationError"} - } - }, - } - if "ValidationError" not in definitions: - definitions.update( - { - "ValidationError": validation_error_definition, - "HTTPValidationError": validation_error_response_definition, - } - ) - if route.openapi_extra: - deep_dict_update(operation, route.openapi_extra) - path[method.lower()] = operation - return path, security_schemes, definitions - - -def _get_api_route_for_openapi( - route_context: routing.RouteContext, -) -> routing._APIRouteLike | None: - if isinstance(route_context.original_route, routing.APIRoute): - return cast(routing._APIRouteLike, route_context) - return None - - -def get_fields_from_routes( - routes: Sequence[BaseRoute | routing.RouteContext], -) -> list[ModelField]: - body_fields_from_routes: list[ModelField] = [] - responses_from_routes: list[ModelField] = [] - request_fields_from_routes: list[ModelField] = [] - callback_flat_models: list[ModelField] = [] - for route_context in routing.iter_route_contexts(routes): - api_route = _get_api_route_for_openapi(route_context) - if api_route is None: - continue - if api_route.include_in_schema: - if api_route.body_field: - assert isinstance(api_route.body_field, ModelField), ( - "A request body must be a Pydantic Field" - ) - body_fields_from_routes.append(api_route.body_field) - if api_route.response_field: - responses_from_routes.append(api_route.response_field) - if api_route.response_fields: - responses_from_routes.extend(api_route.response_fields.values()) - if api_route.stream_item_field: - responses_from_routes.append(api_route.stream_item_field) - if api_route.callbacks: - callback_flat_models.extend(get_fields_from_routes(api_route.callbacks)) - params = get_flat_params(api_route.dependant) - request_fields_from_routes.extend(params) - - flat_models = callback_flat_models + list( - body_fields_from_routes + responses_from_routes + request_fields_from_routes - ) - return flat_models - - -def get_openapi( - *, - title: str, - version: str, - openapi_version: str = "3.1.0", - summary: str | None = None, - description: str | None = None, - routes: Sequence[BaseRoute | routing.RouteContext], - webhooks: Sequence[BaseRoute | routing.RouteContext] | None = None, - tags: list[dict[str, Any]] | None = None, - servers: list[dict[str, str | Any]] | None = None, - terms_of_service: str | None = None, - contact: dict[str, str | Any] | None = None, - license_info: dict[str, str | Any] | None = None, - separate_input_output_schemas: bool = True, - external_docs: dict[str, Any] | None = None, -) -> dict[str, Any]: - info: dict[str, Any] = {"title": title, "version": version} - if summary: - info["summary"] = summary - if description: - info["description"] = description - if terms_of_service: - info["termsOfService"] = terms_of_service - if contact: - info["contact"] = contact - if license_info: - info["license"] = license_info - output: dict[str, Any] = {"openapi": openapi_version, "info": info} - if servers: - output["servers"] = servers - components: dict[str, dict[str, Any]] = {} - paths: dict[str, dict[str, Any]] = {} - webhook_paths: dict[str, dict[str, Any]] = {} - operation_ids: set[str] = set() - all_fields = get_fields_from_routes(list(routes) + list(webhooks or [])) - flat_models = get_flat_models_from_fields(all_fields, known_models=set()) - model_name_map = get_model_name_map(flat_models) - field_mapping, definitions = get_definitions( - fields=all_fields, - model_name_map=model_name_map, - separate_input_output_schemas=separate_input_output_schemas, - ) - for route_context in routing.iter_route_contexts(routes): - api_route = _get_api_route_for_openapi(route_context) - if api_route is not None: - result = get_openapi_path( - route=api_route, - operation_ids=operation_ids, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - if result: - path, security_schemes, path_definitions = result - if path: - paths.setdefault(api_route.path_format, {}).update(path) - if security_schemes: - components.setdefault("securitySchemes", {}).update( - security_schemes - ) - if path_definitions: - definitions.update(path_definitions) - for webhook_context in routing.iter_route_contexts(webhooks or []): - api_webhook = _get_api_route_for_openapi(webhook_context) - if api_webhook is not None: - result = get_openapi_path( - route=api_webhook, - operation_ids=operation_ids, - model_name_map=model_name_map, - field_mapping=field_mapping, - separate_input_output_schemas=separate_input_output_schemas, - ) - if result: - path, security_schemes, path_definitions = result - if path: - webhook_paths.setdefault(api_webhook.path_format, {}).update(path) - if security_schemes: - components.setdefault("securitySchemes", {}).update( - security_schemes - ) - if path_definitions: - definitions.update(path_definitions) - if definitions: - components["schemas"] = {k: definitions[k] for k in sorted(definitions)} - if components: - output["components"] = components - output["paths"] = paths - if webhook_paths: - output["webhooks"] = webhook_paths - if tags: - output["tags"] = tags - if external_docs: - output["externalDocs"] = external_docs - return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True) # type: ignore[no-any-return] diff --git a/server/venv/Lib/site-packages/fastapi/param_functions.py b/server/venv/Lib/site-packages/fastapi/param_functions.py deleted file mode 100644 index 1856178..0000000 --- a/server/venv/Lib/site-packages/fastapi/param_functions.py +++ /dev/null @@ -1,2460 +0,0 @@ -from collections.abc import Callable, Sequence -from typing import Annotated, Any, Literal - -from annotated_doc import Doc -from fastapi import params -from fastapi._compat import Undefined -from fastapi.datastructures import _Unset -from fastapi.openapi.models import Example -from pydantic import AliasChoices, AliasPath -from typing_extensions import deprecated - - -def Path( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = ..., - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - - Read more about it in the - [FastAPI docs for Path Parameters and Numeric Validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#declare-metadata) - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - """ - Declare a path parameter for a *path operation*. - - Read more about it in the - [FastAPI docs for Path Parameters and Numeric Validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/). - - ```python - from typing import Annotated - - from fastapi import FastAPI, Path - - app = FastAPI() - - - @app.get("/items/{item_id}") - async def read_items( - item_id: Annotated[int, Path(title="The ID of the item to get")], - ): - return {"item_id": item_id} - ``` - """ - return params.Path( - default=default, - default_factory=default_factory, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def Query( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#alternative-old-query-as-the-default-value) - """ - ), - ] = Undefined, - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#alias-parameters) - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#declare-more-metadata) - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#declare-more-metadata) - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - - Read more about it in the - [FastAPI docs about Path parameters numeric validations](https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/#number-validations-greater-than-and-less-than-or-equal) - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/) - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/) - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#add-regular-expressions - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#deprecating-parameters) - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs about Query parameters](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - return params.Query( - default=default, - default_factory=default_factory, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def Header( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - """ - ), - ] = Undefined, - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - convert_underscores: Annotated[ - bool, - Doc( - """ - Automatically convert underscores to hyphens in the parameter field name. - - Read more about it in the - [FastAPI docs for Header Parameters](https://fastapi.tiangolo.com/tutorial/header-params/#automatic-conversion) - """ - ), - ] = True, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - return params.Header( - default=default, - default_factory=default_factory, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - convert_underscores=convert_underscores, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def Cookie( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - """ - ), - ] = Undefined, - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - return params.Cookie( - default=default, - default_factory=default_factory, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def Body( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - """ - ), - ] = Undefined, - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - embed: Annotated[ - bool | None, - Doc( - """ - When `embed` is `True`, the parameter will be expected in a JSON body as a - key instead of being the JSON body itself. - - This happens automatically when more than one `Body` parameter is declared. - - Read more about it in the - [FastAPI docs for Body - Multiple Parameters](https://fastapi.tiangolo.com/tutorial/body-multiple-params/#embed-a-single-body-parameter). - """ - ), - ] = None, - media_type: Annotated[ - str, - Doc( - """ - The media type of this parameter field. Changing it would affect the - generated OpenAPI, but currently it doesn't affect the parsing of the data. - """ - ), - ] = "application/json", - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - return params.Body( - default=default, - default_factory=default_factory, - embed=embed, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def Form( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - """ - ), - ] = Undefined, - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - media_type: Annotated[ - str, - Doc( - """ - The media type of this parameter field. Changing it would affect the - generated OpenAPI, but currently it doesn't affect the parsing of the data. - """ - ), - ] = "application/x-www-form-urlencoded", - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - return params.Form( - default=default, - default_factory=default_factory, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def File( # noqa: N802 - default: Annotated[ - Any, - Doc( - """ - Default value if the parameter field is not set. - """ - ), - ] = Undefined, - *, - default_factory: Annotated[ - Callable[[], Any] | None, - Doc( - """ - A callable to generate the default value. - - This doesn't affect `Path` parameters as the value is always required. - The parameter is available only for compatibility. - """ - ), - ] = _Unset, - media_type: Annotated[ - str, - Doc( - """ - The media type of this parameter field. Changing it would affect the - generated OpenAPI, but currently it doesn't affect the parsing of the data. - """ - ), - ] = "multipart/form-data", - alias: Annotated[ - str | None, - Doc( - """ - An alternative name for the parameter field. - - This will be used to extract the data and for the generated OpenAPI. - It is particularly useful when you can't use the name you want because it - is a Python reserved keyword or similar. - """ - ), - ] = None, - alias_priority: Annotated[ - int | None, - Doc( - """ - Priority of the alias. This affects whether an alias generator is used. - """ - ), - ] = _Unset, - validation_alias: Annotated[ - str | AliasPath | AliasChoices | None, - Doc( - """ - 'Whitelist' validation step. The parameter field will be the single one - allowed by the alias or set of aliases defined. - """ - ), - ] = None, - serialization_alias: Annotated[ - str | None, - Doc( - """ - 'Blacklist' validation step. The vanilla parameter field will be the - single one of the alias' or set of aliases' fields and all the other - fields will be ignored at serialization time. - """ - ), - ] = None, - title: Annotated[ - str | None, - Doc( - """ - Human-readable title. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Human-readable description. - """ - ), - ] = None, - gt: Annotated[ - float | None, - Doc( - """ - Greater than. If set, value must be greater than this. Only applicable to - numbers. - """ - ), - ] = None, - ge: Annotated[ - float | None, - Doc( - """ - Greater than or equal. If set, value must be greater than or equal to - this. Only applicable to numbers. - """ - ), - ] = None, - lt: Annotated[ - float | None, - Doc( - """ - Less than. If set, value must be less than this. Only applicable to numbers. - """ - ), - ] = None, - le: Annotated[ - float | None, - Doc( - """ - Less than or equal. If set, value must be less than or equal to this. - Only applicable to numbers. - """ - ), - ] = None, - min_length: Annotated[ - int | None, - Doc( - """ - Minimum length for strings. - """ - ), - ] = None, - max_length: Annotated[ - int | None, - Doc( - """ - Maximum length for strings. - """ - ), - ] = None, - pattern: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - ] = None, - regex: Annotated[ - str | None, - Doc( - """ - RegEx pattern for strings. - """ - ), - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: Annotated[ - str | None, - Doc( - """ - Parameter field name for discriminating the type in a tagged union. - """ - ), - ] = None, - strict: Annotated[ - bool | None, - Doc( - """ - If `True`, strict validation is applied to the field. - """ - ), - ] = _Unset, - multiple_of: Annotated[ - float | None, - Doc( - """ - Value must be a multiple of this. Only applicable to numbers. - """ - ), - ] = _Unset, - allow_inf_nan: Annotated[ - bool | None, - Doc( - """ - Allow `inf`, `-inf`, `nan`. Only applicable to numbers. - """ - ), - ] = _Unset, - max_digits: Annotated[ - int | None, - Doc( - """ - Maximum number of digits allowed for decimal values. - """ - ), - ] = _Unset, - decimal_places: Annotated[ - int | None, - Doc( - """ - Maximum number of decimal places allowed for decimal values. - """ - ), - ] = _Unset, - examples: Annotated[ - list[Any] | None, - Doc( - """ - Example values for this field. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/) - """ - ), - ] = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: Annotated[ - dict[str, Example] | None, - Doc( - """ - OpenAPI-specific examples. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Swagger UI (that provides the `/docs` interface) has better support for the - OpenAPI-specific examples than the JSON Schema `examples`, that's the main - use case for this. - - Read more about it in the - [FastAPI docs for Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/#using-the-openapi_examples-parameter). - """ - ), - ] = None, - deprecated: Annotated[ - deprecated | str | bool | None, - Doc( - """ - Mark this parameter field as deprecated. - - It will affect the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) this parameter field in the generated OpenAPI. - You probably don't need it, but it's available. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - json_schema_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Any additional JSON schema data. - """ - ), - ] = None, - **extra: Annotated[ - Any, - Doc( - """ - Include extra fields used by the JSON Schema. - """ - ), - deprecated( - """ - The `extra` kwargs is deprecated. Use `json_schema_extra` instead. - """ - ), - ], -) -> Any: - return params.File( - default=default, - default_factory=default_factory, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - example=example, - examples=examples, - openapi_examples=openapi_examples, - deprecated=deprecated, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -def Depends( # noqa: N802 - dependency: Annotated[ - Callable[..., Any] | None, - Doc( - """ - A "dependable" callable (like a function). - - Don't call it directly, FastAPI will call it for you, just pass the object - directly. - - Read more about it in the - [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/) - """ - ), - ] = None, - *, - use_cache: Annotated[ - bool, - Doc( - """ - By default, after a dependency is called the first time in a request, if - the dependency is declared again for the rest of the request (for example - if the dependency is needed by several dependencies), the value will be - re-used for the rest of the request. - - Set `use_cache` to `False` to disable this behavior and ensure the - dependency is called again (if declared more than once) in the same request. - - Read more about it in the - [FastAPI docs about sub-dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/sub-dependencies/#using-the-same-dependency-multiple-times) - """ - ), - ] = True, - scope: Annotated[ - Literal["function", "request"] | None, - Doc( - """ - Mainly for dependencies with `yield`, define when the dependency function - should start (the code before `yield`) and when it should end (the code - after `yield`). - - * `"function"`: start the dependency before the *path operation function* - that handles the request, end the dependency after the *path operation - function* ends, but **before** the response is sent back to the client. - So, the dependency function will be executed **around** the *path operation - **function***. - * `"request"`: start the dependency before the *path operation function* - that handles the request (similar to when using `"function"`), but end - **after** the response is sent back to the client. So, the dependency - function will be executed **around** the **request** and response cycle. - - Read more about it in the - [FastAPI docs for FastAPI Dependencies with yield](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#early-exit-and-scope) - """ - ), - ] = None, -) -> Any: - """ - Declare a FastAPI dependency. - - It takes a single "dependable" callable (like a function). - - Don't call it directly, FastAPI will call it for you. - - Read more about it in the - [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/). - - **Example** - - ```python - from typing import Annotated - - from fastapi import Depends, FastAPI - - app = FastAPI() - - - async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100): - return {"q": q, "skip": skip, "limit": limit} - - - @app.get("/items/") - async def read_items(commons: Annotated[dict, Depends(common_parameters)]): - return commons - ``` - """ - return params.Depends(dependency=dependency, use_cache=use_cache, scope=scope) - - -def Security( # noqa: N802 - dependency: Annotated[ - Callable[..., Any] | None, - Doc( - """ - A "dependable" callable (like a function). - - Don't call it directly, FastAPI will call it for you, just pass the object - directly. - - Read more about it in the - [FastAPI docs for Dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/) - """ - ), - ] = None, - *, - scopes: Annotated[ - Sequence[str] | None, - Doc( - """ - OAuth2 scopes required for the *path operation* that uses this Security - dependency. - - The term "scope" comes from the OAuth2 specification, it seems to be - intentionally vague and interpretable. It normally refers to permissions, - in cases to roles. - - These scopes are integrated with OpenAPI (and the API docs at `/docs`). - So they are visible in the OpenAPI specification. - - Read more about it in the - [FastAPI docs about OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/) - """ - ), - ] = None, - use_cache: Annotated[ - bool, - Doc( - """ - By default, after a dependency is called the first time in a request, if - the dependency is declared again for the rest of the request (for example - if the dependency is needed by several dependencies), the value will be - re-used for the rest of the request. - - Set `use_cache` to `False` to disable this behavior and ensure the - dependency is called again (if declared more than once) in the same request. - - Read more about it in the - [FastAPI docs about sub-dependencies](https://fastapi.tiangolo.com/tutorial/dependencies/sub-dependencies/#using-the-same-dependency-multiple-times) - """ - ), - ] = True, -) -> Any: - """ - Declare a FastAPI Security dependency. - - The only difference with a regular dependency is that it can declare OAuth2 - scopes that will be integrated with OpenAPI and the automatic UI docs (by default - at `/docs`). - - It takes a single "dependable" callable (like a function). - - Don't call it directly, FastAPI will call it for you. - - Read more about it in the - [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/) and - in the - [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/). - - **Example** - - ```python - from typing import Annotated - - from fastapi import Security, FastAPI - - from .db import User - from .security import get_current_active_user - - app = FastAPI() - - @app.get("/users/me/items/") - async def read_own_items( - current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])] - ): - return [{"item_id": "Foo", "owner": current_user.username}] - ``` - """ - return params.Security(dependency=dependency, scopes=scopes, use_cache=use_cache) diff --git a/server/venv/Lib/site-packages/fastapi/params.py b/server/venv/Lib/site-packages/fastapi/params.py deleted file mode 100644 index d3f2ae1..0000000 --- a/server/venv/Lib/site-packages/fastapi/params.py +++ /dev/null @@ -1,754 +0,0 @@ -import warnings -from collections.abc import Callable, Sequence -from dataclasses import dataclass -from enum import Enum -from typing import Annotated, Any, Literal - -from fastapi.exceptions import FastAPIDeprecationWarning -from fastapi.openapi.models import Example -from pydantic import AliasChoices, AliasPath -from pydantic.fields import FieldInfo -from typing_extensions import deprecated - -from ._compat import ( - Undefined, -) -from .datastructures import _Unset - - -class ParamTypes(Enum): - query = "query" - header = "header" - path = "path" - cookie = "cookie" - - -class Param(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-class] - in_: ParamTypes - - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - if example is not _Unset: - warnings.warn( - "`example` has been deprecated, please use `examples` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - self.example = example - self.include_in_schema = include_in_schema - self.openapi_examples = openapi_examples - kwargs = dict( - default=default, - default_factory=default_factory, - alias=alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - discriminator=discriminator, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - **extra, - ) - if examples is not None: - kwargs["examples"] = examples - if regex is not None: - warnings.warn( - "`regex` has been deprecated, please use `pattern` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - current_json_schema_extra = json_schema_extra or extra - kwargs["deprecated"] = deprecated - - if serialization_alias in (_Unset, None) and isinstance(alias, str): - serialization_alias = alias - if validation_alias in (_Unset, None): - validation_alias = alias - kwargs.update( - { - "annotation": annotation, - "alias_priority": alias_priority, - "validation_alias": validation_alias, - "serialization_alias": serialization_alias, - "strict": strict, - "json_schema_extra": current_json_schema_extra, - } - ) - kwargs["pattern"] = pattern or regex - - use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} - - super().__init__(**use_kwargs) # ty: ignore[invalid-argument-type] - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.default})" - - -class Path(Param): # type: ignore[misc] - in_ = ParamTypes.path - - def __init__( - self, - default: Any = ..., - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - assert default is ..., "Path parameters cannot have a default value" - self.in_ = self.in_ - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Query(Param): # type: ignore[misc] - in_ = ParamTypes.query - - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Header(Param): # type: ignore[misc] - in_ = ParamTypes.header - - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - convert_underscores: bool = True, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - self.convert_underscores = convert_underscores - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Cookie(Param): # type: ignore[misc] - in_ = ParamTypes.cookie - - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class Body(FieldInfo): # type: ignore[misc] # ty: ignore[subclass-of-final-class] - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - embed: bool | None = None, - media_type: str = "application/json", - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - self.embed = embed - self.media_type = media_type - if example is not _Unset: - warnings.warn( - "`example` has been deprecated, please use `examples` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - self.example = example - self.include_in_schema = include_in_schema - self.openapi_examples = openapi_examples - kwargs = dict( - default=default, - default_factory=default_factory, - alias=alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - discriminator=discriminator, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - **extra, - ) - if examples is not None: - kwargs["examples"] = examples - if regex is not None: - warnings.warn( - "`regex` has been deprecated, please use `pattern` instead", - category=FastAPIDeprecationWarning, - stacklevel=4, - ) - current_json_schema_extra = json_schema_extra or extra - kwargs["deprecated"] = deprecated - if serialization_alias in (_Unset, None) and isinstance(alias, str): - serialization_alias = alias - if validation_alias in (_Unset, None): - validation_alias = alias - kwargs.update( - { - "annotation": annotation, - "alias_priority": alias_priority, - "validation_alias": validation_alias, - "serialization_alias": serialization_alias, - "strict": strict, - "json_schema_extra": current_json_schema_extra, - } - ) - kwargs["pattern"] = pattern or regex - - use_kwargs = {k: v for k, v in kwargs.items() if v is not _Unset} - - super().__init__(**use_kwargs) # ty: ignore[invalid-argument-type] - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.default})" - - -class Form(Body): # type: ignore[misc] - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - media_type: str = "application/x-www-form-urlencoded", - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -class File(Form): # type: ignore[misc] - def __init__( - self, - default: Any = Undefined, - *, - default_factory: Callable[[], Any] | None = _Unset, - annotation: Any | None = None, - media_type: str = "multipart/form-data", - alias: str | None = None, - alias_priority: int | None = _Unset, - validation_alias: str | AliasPath | AliasChoices | None = None, - serialization_alias: str | None = None, - title: str | None = None, - description: str | None = None, - gt: float | None = None, - ge: float | None = None, - lt: float | None = None, - le: float | None = None, - min_length: int | None = None, - max_length: int | None = None, - pattern: str | None = None, - regex: Annotated[ - str | None, - deprecated( - "Deprecated in FastAPI 0.100.0 and Pydantic v2, use `pattern` instead." - ), - ] = None, - discriminator: str | None = None, - strict: bool | None = _Unset, - multiple_of: float | None = _Unset, - allow_inf_nan: bool | None = _Unset, - max_digits: int | None = _Unset, - decimal_places: int | None = _Unset, - examples: list[Any] | None = None, - example: Annotated[ - Any | None, - deprecated( - "Deprecated in OpenAPI 3.1.0 that now uses JSON Schema 2020-12, " - "although still supported. Use examples instead." - ), - ] = _Unset, - openapi_examples: dict[str, Example] | None = None, - deprecated: deprecated | str | bool | None = None, - include_in_schema: bool = True, - json_schema_extra: dict[str, Any] | None = None, - **extra: Any, - ): - super().__init__( - default=default, - default_factory=default_factory, - annotation=annotation, - media_type=media_type, - alias=alias, - alias_priority=alias_priority, - validation_alias=validation_alias, - serialization_alias=serialization_alias, - title=title, - description=description, - gt=gt, - ge=ge, - lt=lt, - le=le, - min_length=min_length, - max_length=max_length, - pattern=pattern, - regex=regex, - discriminator=discriminator, - strict=strict, - multiple_of=multiple_of, - allow_inf_nan=allow_inf_nan, - max_digits=max_digits, - decimal_places=decimal_places, - deprecated=deprecated, - example=example, - examples=examples, - openapi_examples=openapi_examples, - include_in_schema=include_in_schema, - json_schema_extra=json_schema_extra, - **extra, - ) - - -@dataclass(frozen=True) -class Depends: - dependency: Callable[..., Any] | None = None - use_cache: bool = True - scope: Literal["function", "request"] | None = None - - -@dataclass(frozen=True) -class Security(Depends): - scopes: Sequence[str] | None = None diff --git a/server/venv/Lib/site-packages/fastapi/py.typed b/server/venv/Lib/site-packages/fastapi/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/fastapi/requests.py b/server/venv/Lib/site-packages/fastapi/requests.py deleted file mode 100644 index d16552c..0000000 --- a/server/venv/Lib/site-packages/fastapi/requests.py +++ /dev/null @@ -1,2 +0,0 @@ -from starlette.requests import HTTPConnection as HTTPConnection # noqa: F401 -from starlette.requests import Request as Request # noqa: F401 diff --git a/server/venv/Lib/site-packages/fastapi/responses.py b/server/venv/Lib/site-packages/fastapi/responses.py deleted file mode 100644 index 29df4b7..0000000 --- a/server/venv/Lib/site-packages/fastapi/responses.py +++ /dev/null @@ -1,98 +0,0 @@ -import importlib -from typing import Any, Protocol, cast - -from fastapi.exceptions import FastAPIDeprecationWarning -from fastapi.sse import EventSourceResponse as EventSourceResponse # noqa -from starlette.responses import FileResponse as FileResponse # noqa -from starlette.responses import HTMLResponse as HTMLResponse # noqa -from starlette.responses import JSONResponse as JSONResponse # noqa -from starlette.responses import PlainTextResponse as PlainTextResponse # noqa -from starlette.responses import RedirectResponse as RedirectResponse # noqa -from starlette.responses import Response as Response # noqa -from starlette.responses import StreamingResponse as StreamingResponse # noqa -from typing_extensions import deprecated - - -class _UjsonModule(Protocol): - def dumps(self, __obj: Any, *, ensure_ascii: bool = ...) -> str: ... - - -class _OrjsonModule(Protocol): - OPT_NON_STR_KEYS: int - OPT_SERIALIZE_NUMPY: int - - def dumps(self, __obj: Any, *, option: int = ...) -> bytes: ... - - -try: - ujson = cast(_UjsonModule, importlib.import_module("ujson")) -except ModuleNotFoundError: # pragma: nocover - ujson = None # type: ignore[assignment] - - -try: - orjson = cast(_OrjsonModule, importlib.import_module("orjson")) -except ModuleNotFoundError: # pragma: nocover - orjson = None # type: ignore[assignment] - - -@deprecated( - "UJSONResponse is deprecated, FastAPI now serializes data directly to JSON " - "bytes via Pydantic when a return type or response model is set, which is " - "faster and doesn't need a custom response class. Read more in the FastAPI " - "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model " - "and https://fastapi.tiangolo.com/tutorial/response-model/", - category=FastAPIDeprecationWarning, - stacklevel=2, -) -class UJSONResponse(JSONResponse): - """JSON response using the ujson library to serialize data to JSON. - - **Deprecated**: `UJSONResponse` is deprecated. FastAPI now serializes data - directly to JSON bytes via Pydantic when a return type or response model is - set, which is faster and doesn't need a custom response class. - - Read more in the - [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model) - and the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - - **Note**: `ujson` is not included with FastAPI and must be installed - separately, e.g. `pip install ujson`. - """ - - def render(self, content: Any) -> bytes: - assert ujson is not None, "ujson must be installed to use UJSONResponse" - return ujson.dumps(content, ensure_ascii=False).encode("utf-8") - - -@deprecated( - "ORJSONResponse is deprecated, FastAPI now serializes data directly to JSON " - "bytes via Pydantic when a return type or response model is set, which is " - "faster and doesn't need a custom response class. Read more in the FastAPI " - "docs: https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model " - "and https://fastapi.tiangolo.com/tutorial/response-model/", - category=FastAPIDeprecationWarning, - stacklevel=2, -) -class ORJSONResponse(JSONResponse): - """JSON response using the orjson library to serialize data to JSON. - - **Deprecated**: `ORJSONResponse` is deprecated. FastAPI now serializes data - directly to JSON bytes via Pydantic when a return type or response model is - set, which is faster and doesn't need a custom response class. - - Read more in the - [FastAPI docs for Custom Response](https://fastapi.tiangolo.com/advanced/custom-response/#orjson-or-response-model) - and the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - - **Note**: `orjson` is not included with FastAPI and must be installed - separately, e.g. `pip install orjson`. - """ - - def render(self, content: Any) -> bytes: - assert orjson is not None, "orjson must be installed to use ORJSONResponse" - return orjson.dumps( - content, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY - ) diff --git a/server/venv/Lib/site-packages/fastapi/routing.py b/server/venv/Lib/site-packages/fastapi/routing.py deleted file mode 100644 index 3a2d754..0000000 --- a/server/venv/Lib/site-packages/fastapi/routing.py +++ /dev/null @@ -1,6231 +0,0 @@ -import contextlib -import copy -import email.message -import errno -import functools -import inspect -import json -import os -import stat -import types -from collections.abc import ( - AsyncIterator, - Awaitable, - Callable, - Collection, - Coroutine, - Generator, - Iterator, - Mapping, - Sequence, -) -from contextlib import ( - AbstractAsyncContextManager, - AbstractContextManager, - AsyncExitStack, - asynccontextmanager, -) -from contextvars import ContextVar -from dataclasses import dataclass, field -from enum import Enum, IntEnum -from typing import ( - Annotated, - Any, - Literal, - Protocol, - TypeVar, - cast, -) - -import anyio -from annotated_doc import Doc -from anyio.abc import ObjectReceiveStream -from fastapi import params -from fastapi._compat import ( - ModelField, - Undefined, - lenient_issubclass, -) -from fastapi.datastructures import Default, DefaultPlaceholder -from fastapi.dependencies.models import Dependant -from fastapi.dependencies.utils import ( - _should_embed_body_fields, - get_body_field, - get_dependant, - get_flat_dependant, - get_parameterless_sub_dependant, - get_stream_item_type, - get_typed_return_annotation, - solve_dependencies, -) -from fastapi.encoders import jsonable_encoder -from fastapi.exceptions import ( - EndpointContext, - FastAPIError, - RequestValidationError, - ResponseValidationError, - WebSocketRequestValidationError, -) -from fastapi.sse import ( - _PING_INTERVAL, - KEEPALIVE_COMMENT, - EventSourceResponse, - ServerSentEvent, - format_sse_event, -) -from fastapi.types import DecoratedCallable, IncEx -from fastapi.utils import ( - create_model_field, - generate_unique_id, - get_value_or_default, - is_body_allowed_for_status_code, -) -from starlette import routing -from starlette._exception_handler import wrap_app_handling_exceptions -from starlette._utils import get_route_path, is_async_callable -from starlette.concurrency import iterate_in_threadpool, run_in_threadpool -from starlette.datastructures import URL, FormData, URLPath -from starlette.exceptions import HTTPException -from starlette.requests import Request -from starlette.responses import ( - JSONResponse, - PlainTextResponse, - RedirectResponse, - Response, - StreamingResponse, -) -from starlette.routing import ( - BaseRoute, - Match, - NoMatchFound, - compile_path, - get_name, -) -from starlette.routing import Mount as Mount # noqa -from starlette.staticfiles import StaticFiles -from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send -from starlette.websockets import WebSocket -from typing_extensions import deprecated - - -# Copy of starlette.routing.request_response modified to include the -# dependencies' AsyncExitStack -def request_response( - func: Callable[[Request], Awaitable[Response] | Response], -) -> ASGIApp: - """ - Takes a function or coroutine `func(request) -> response`, - and returns an ASGI application. - """ - f: Callable[[Request], Awaitable[Response]] = ( - func # type: ignore[assignment] - if is_async_callable(func) - else functools.partial(run_in_threadpool, func) # type: ignore[call-arg] - ) # ty: ignore[invalid-assignment] - - async def app(scope: Scope, receive: Receive, send: Send) -> None: - request = Request(scope, receive, send) - - async def app(scope: Scope, receive: Receive, send: Send) -> None: - # Starts customization - response_awaited = False - async with AsyncExitStack() as request_stack: - scope["fastapi_inner_astack"] = request_stack - async with AsyncExitStack() as function_stack: - scope["fastapi_function_astack"] = function_stack - response = await f(request) - await response(scope, receive, send) - # Continues customization - response_awaited = True - if not response_awaited: - raise FastAPIError( - "Response not awaited. There's a high chance that the " - "application code is raising an exception and a dependency with yield " - "has a block with a bare except, or a block with except Exception, " - "and is not raising the exception again. Read more about it in the " - "docs: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#dependencies-with-yield-and-except" - ) - - # Same as in Starlette - await wrap_app_handling_exceptions(app, request)(scope, receive, send) - - return app - - -# Copy of starlette.routing.websocket_session modified to include the -# dependencies' AsyncExitStack -def websocket_session( - func: Callable[[WebSocket], Awaitable[None]], -) -> ASGIApp: - """ - Takes a coroutine `func(session)`, and returns an ASGI application. - """ - # assert asyncio.iscoroutinefunction(func), "WebSocket endpoints must be async" - - async def app(scope: Scope, receive: Receive, send: Send) -> None: - session = WebSocket(scope, receive=receive, send=send) - - async def app(scope: Scope, receive: Receive, send: Send) -> None: - async with AsyncExitStack() as request_stack: - scope["fastapi_inner_astack"] = request_stack - async with AsyncExitStack() as function_stack: - scope["fastapi_function_astack"] = function_stack - await func(session) - - # Same as in Starlette - await wrap_app_handling_exceptions(app, session)(scope, receive, send) - - return app - - -_T = TypeVar("_T") - - -# Vendored from starlette.routing to avoid importing private symbols -class _AsyncLiftContextManager(AbstractAsyncContextManager[_T]): - """ - Wraps a synchronous context manager to make it async. - - This is vendored from Starlette to avoid importing private symbols. - """ - - def __init__(self, cm: AbstractContextManager[_T]) -> None: - self._cm = cm - - async def __aenter__(self) -> _T: - return self._cm.__enter__() - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_value: BaseException | None, - traceback: types.TracebackType | None, - ) -> bool | None: - return self._cm.__exit__(exc_type, exc_value, traceback) - - -# Vendored from starlette.routing to avoid importing private symbols -def _wrap_gen_lifespan_context( - lifespan_context: Callable[[Any], Generator[Any, Any, Any]], -) -> Callable[[Any], AbstractAsyncContextManager[Any]]: - """ - Wrap a generator-based lifespan context into an async context manager. - - This is vendored from Starlette to avoid importing private symbols. - """ - cmgr = contextlib.contextmanager(lifespan_context) - - @functools.wraps(cmgr) - def wrapper(app: Any) -> _AsyncLiftContextManager[Any]: - return _AsyncLiftContextManager(cmgr(app)) - - return wrapper - - -def _merge_lifespan_context( - original_context: Lifespan[Any], nested_context: Lifespan[Any] -) -> Lifespan[Any]: - @asynccontextmanager - async def merged_lifespan( - app: AppType, - ) -> AsyncIterator[Mapping[str, Any] | None]: - async with original_context(app) as maybe_original_state: - async with nested_context(app) as maybe_nested_state: - if maybe_nested_state is None and maybe_original_state is None: - yield None # old ASGI compatibility - else: - yield {**(maybe_nested_state or {}), **(maybe_original_state or {})} - - return merged_lifespan # type: ignore[return-value] # ty: ignore[invalid-return-type] - - -class _DefaultLifespan: - """ - Default lifespan context manager that runs on_startup and on_shutdown handlers. - - This is a copy of the Starlette _DefaultLifespan class that was removed - in Starlette. FastAPI keeps it to maintain backward compatibility with - on_startup and on_shutdown event handlers. - - Ref: https://github.com/Kludex/starlette/pull/3117 - """ - - def __init__(self, router: "APIRouter") -> None: - self._router = router - - async def __aenter__(self) -> None: - await self._router._startup() - - async def __aexit__(self, *exc_info: object) -> None: - await self._router._shutdown() - - def __call__(self: _T, app: object) -> _T: - return self - - -# Cache for endpoint context to avoid re-extracting on every request -_endpoint_context_cache: dict[int, EndpointContext] = {} - - -def _extract_endpoint_context(func: Any) -> EndpointContext: - """Extract endpoint context with caching to avoid repeated file I/O.""" - func_id = id(func) - - if func_id in _endpoint_context_cache: - return _endpoint_context_cache[func_id] - - try: - ctx: EndpointContext = {} - - if (source_file := inspect.getsourcefile(func)) is not None: - ctx["file"] = source_file - if (line_number := inspect.getsourcelines(func)[1]) is not None: - ctx["line"] = line_number - if (func_name := getattr(func, "__name__", None)) is not None: - ctx["function"] = func_name - except Exception: - ctx = EndpointContext() - - _endpoint_context_cache[func_id] = ctx - return ctx - - -async def serialize_response( - *, - field: ModelField | None = None, - response_content: Any, - include: IncEx | None = None, - exclude: IncEx | None = None, - by_alias: bool = True, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - is_coroutine: bool = True, - endpoint_ctx: EndpointContext | None = None, - dump_json: bool = False, -) -> Any: - if field: - if is_coroutine: - value, errors = field.validate(response_content, {}, loc=("response",)) - else: - value, errors = await run_in_threadpool( - field.validate, response_content, {}, loc=("response",) - ) - if errors: - ctx = endpoint_ctx or EndpointContext() - raise ResponseValidationError( - errors=errors, - body=response_content, - endpoint_ctx=ctx, - ) - serializer = field.serialize_json if dump_json else field.serialize - return serializer( - value, - include=include, - exclude=exclude, - by_alias=by_alias, - exclude_unset=exclude_unset, - exclude_defaults=exclude_defaults, - exclude_none=exclude_none, - ) - - else: - return jsonable_encoder(response_content) - - -async def run_endpoint_function( - *, dependant: Dependant, values: dict[str, Any], is_coroutine: bool -) -> Any: - # Only called by get_request_handler. Has been split into its own function to - # facilitate profiling endpoints, since inner functions are harder to profile. - assert dependant.call is not None, "dependant.call must be a function" - - if is_coroutine: - return await dependant.call(**values) - else: - return await run_in_threadpool(dependant.call, **values) - - -def _build_response_args( - *, status_code: int | None, solved_result: Any -) -> dict[str, Any]: - response_args: dict[str, Any] = { - "background": solved_result.background_tasks, - } - # If status_code was set, use it, otherwise use the default from the - # response class, in the case of redirect it's 307 - current_status_code = ( - status_code if status_code else solved_result.response.status_code - ) - if current_status_code is not None: - response_args["status_code"] = current_status_code - if solved_result.response.status_code: - response_args["status_code"] = solved_result.response.status_code - return response_args - - -def get_request_handler( - dependant: Dependant, - body_field: ModelField | None = None, - status_code: int | None = None, - response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), - response_field: ModelField | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - dependency_overrides_provider: Any | None = None, - embed_body_fields: bool = False, - strict_content_type: bool | DefaultPlaceholder = Default(True), - stream_item_field: ModelField | None = None, - is_json_stream: bool = False, -) -> Callable[[Request], Coroutine[Any, Any, Response]]: - assert dependant.call is not None, "dependant.call must be a function" - is_coroutine = dependant.is_coroutine_callable - is_body_form = body_field and isinstance(body_field.field_info, params.Form) - if isinstance(response_class, DefaultPlaceholder): - actual_response_class: type[Response] = response_class.value - else: - actual_response_class = response_class - is_sse_stream = lenient_issubclass(actual_response_class, EventSourceResponse) - if isinstance(strict_content_type, DefaultPlaceholder): - actual_strict_content_type: bool = strict_content_type.value - else: - actual_strict_content_type = strict_content_type - - async def app(request: Request) -> Response: - response: Response | None = None - file_stack = request.scope.get("fastapi_middleware_astack") - assert isinstance(file_stack, AsyncExitStack), ( - "fastapi_middleware_astack not found in request scope" - ) - - # Extract endpoint context for error messages - endpoint_ctx = ( - _extract_endpoint_context(dependant.call) - if dependant.call - else EndpointContext() - ) - - if dependant.path: - # For mounted sub-apps, include the mount path prefix - mount_path = request.scope.get("root_path", "").rstrip("/") - endpoint_ctx["path"] = f"{request.method} {mount_path}{dependant.path}" - - # Read body and auto-close files - try: - body: Any = None - if body_field: - if is_body_form: - body = await request.form() - file_stack.push_async_callback(body.close) - else: - body_bytes = await request.body() - if body_bytes: - json_body: Any = Undefined - content_type_value = request.headers.get("content-type") - if not content_type_value: - if not actual_strict_content_type: - json_body = await request.json() - else: - message = email.message.Message() - message["content-type"] = content_type_value - if message.get_content_maintype() == "application": - subtype = message.get_content_subtype() - if subtype == "json" or subtype.endswith("+json"): - json_body = await request.json() - if json_body != Undefined: - body = json_body - else: - body = body_bytes - except json.JSONDecodeError as e: - validation_error = RequestValidationError( - [ - { - "type": "json_invalid", - "loc": ("body", e.pos), - "msg": "JSON decode error", - "input": {}, - "ctx": {"error": e.msg}, - } - ], - body=e.doc, - endpoint_ctx=endpoint_ctx, - ) - raise validation_error from e - except HTTPException: - # If a middleware raises an HTTPException, it should be raised again - raise - except Exception as e: - http_error = HTTPException( - status_code=400, detail="There was an error parsing the body" - ) - raise http_error from e - - # Solve dependencies and run path operation function, auto-closing dependencies - errors: list[Any] = [] - async_exit_stack = request.scope.get("fastapi_inner_astack") - assert isinstance(async_exit_stack, AsyncExitStack), ( - "fastapi_inner_astack not found in request scope" - ) - solved_result = await solve_dependencies( - request=request, - dependant=dependant, - body=cast(dict[str, Any] | FormData | bytes | None, body), - dependency_overrides_provider=dependency_overrides_provider, - async_exit_stack=async_exit_stack, - embed_body_fields=embed_body_fields, - ) - errors = solved_result.errors - assert dependant.call # For types - if not errors: - # Shared serializer for stream items (JSONL and SSE). - # Validates against stream_item_field when set, then - # serializes to JSON bytes. - def _serialize_data(data: Any) -> bytes: - if stream_item_field: - value, errors_ = stream_item_field.validate( - data, {}, loc=("response",) - ) - if errors_: - ctx = endpoint_ctx or EndpointContext() - raise ResponseValidationError( - errors=errors_, - body=data, - endpoint_ctx=ctx, - ) - return stream_item_field.serialize_json( - value, - include=response_model_include, - exclude=response_model_exclude, - by_alias=response_model_by_alias, - exclude_unset=response_model_exclude_unset, - exclude_defaults=response_model_exclude_defaults, - exclude_none=response_model_exclude_none, - ) - else: - data = jsonable_encoder(data) - return json.dumps(data).encode("utf-8") - - if is_sse_stream: - # Generator endpoint: stream as Server-Sent Events - gen = dependant.call(**solved_result.values) - - def _serialize_sse_item(item: Any) -> bytes: - if isinstance(item, ServerSentEvent): - # User controls the event structure. - # Serialize the data payload if present. - # For ServerSentEvent items we skip stream_item_field - # validation (the user may mix types intentionally). - if item.raw_data is not None: - data_str: str | None = item.raw_data - elif item.data is not None: - if hasattr(item.data, "model_dump_json"): - data_str = item.data.model_dump_json() - else: - data_str = json.dumps(jsonable_encoder(item.data)) - else: - data_str = None - return format_sse_event( - data_str=data_str, - event=item.event, - id=item.id, - retry=item.retry, - comment=item.comment, - ) - else: - # Plain object: validate + serialize via - # stream_item_field (if set) and wrap in data field - return format_sse_event( - data_str=_serialize_data(item).decode("utf-8") - ) - - if dependant.is_async_gen_callable: - sse_aiter: AsyncIterator[Any] = gen.__aiter__() - else: - sse_aiter = iterate_in_threadpool(gen) - - @asynccontextmanager - async def _sse_producer_cm() -> AsyncIterator[ - ObjectReceiveStream[bytes] - ]: - # Use a memory stream to decouple generator iteration - # from the keepalive timer. A producer task pulls items - # from the generator independently, so - # `anyio.fail_after` never wraps the generator's - # `__anext__` directly - avoiding CancelledError that - # would finalize the generator and also working for sync - # generators running in a thread pool. - # - # This context manager is entered on the request-scoped - # AsyncExitStack so its __aexit__ (which cancels the - # task group) is called by the exit stack after the - # streaming response completes — not by async generator - # finalization via GeneratorExit. - # Ref: https://peps.python.org/pep-0789/ - send_stream, receive_stream = anyio.create_memory_object_stream[ - bytes - ](max_buffer_size=1) - - async def _producer() -> None: - async with send_stream: - async for raw_item in sse_aiter: - await send_stream.send(_serialize_sse_item(raw_item)) - - send_keepalive, receive_keepalive = ( - anyio.create_memory_object_stream[bytes](max_buffer_size=1) - ) - - async def _keepalive_inserter() -> None: - """Read from the producer and forward to the output, - inserting keepalive comments on timeout.""" - async with send_keepalive, receive_stream: - try: - while True: - try: - with anyio.fail_after(_PING_INTERVAL): - data = await receive_stream.receive() - await send_keepalive.send(data) - except TimeoutError: - await send_keepalive.send(KEEPALIVE_COMMENT) - except anyio.EndOfStream: - pass - - async with anyio.create_task_group() as tg: - tg.start_soon(_producer) - tg.start_soon(_keepalive_inserter) - yield receive_keepalive - tg.cancel_scope.cancel() - - # Enter the SSE context manager on the request-scoped - # exit stack. The stack outlives the streaming response, - # so __aexit__ runs via proper structured teardown, not - # via GeneratorExit thrown into an async generator. - sse_receive_stream = await async_exit_stack.enter_async_context( - _sse_producer_cm() - ) - # Ensure the receive stream is closed when the exit stack - # unwinds, preventing ResourceWarning from __del__. - async_exit_stack.push_async_callback(sse_receive_stream.aclose) - - async def _sse_with_checkpoints( - stream: ObjectReceiveStream[bytes], - ) -> AsyncIterator[bytes]: - async for data in stream: - yield data - # Guarantee a checkpoint so cancellation can be - # delivered even when the producer is faster than - # the consumer and receive() never suspends. - await anyio.sleep(0) - - sse_stream_content: AsyncIterator[bytes] | Iterator[bytes] = ( - _sse_with_checkpoints(sse_receive_stream) - ) - - response = StreamingResponse( - sse_stream_content, - media_type="text/event-stream", - background=solved_result.background_tasks, - ) - response.headers["Cache-Control"] = "no-cache" - # For Nginx proxies to not buffer server sent events - response.headers["X-Accel-Buffering"] = "no" - response.headers.raw.extend(solved_result.response.headers.raw) - elif is_json_stream: - # Generator endpoint: stream as JSONL - gen = dependant.call(**solved_result.values) - - def _serialize_item(item: Any) -> bytes: - return _serialize_data(item) + b"\n" - - if dependant.is_async_gen_callable: - - async def _async_stream_jsonl() -> AsyncIterator[bytes]: - async for item in gen: - yield _serialize_item(item) - # To allow for cancellation to trigger - # Ref: https://github.com/fastapi/fastapi/issues/14680 - await anyio.sleep(0) - - jsonl_stream_content: AsyncIterator[bytes] | Iterator[bytes] = ( - _async_stream_jsonl() - ) - else: - - def _sync_stream_jsonl() -> Iterator[bytes]: - for item in gen: # ty: ignore[not-iterable] - yield _serialize_item(item) - - jsonl_stream_content = _sync_stream_jsonl() - - response = StreamingResponse( - jsonl_stream_content, - media_type="application/jsonl", - background=solved_result.background_tasks, - ) - response.headers.raw.extend(solved_result.response.headers.raw) - elif dependant.is_async_gen_callable or dependant.is_gen_callable: - # Raw streaming with explicit response_class (e.g. StreamingResponse) - gen = dependant.call(**solved_result.values) - if dependant.is_async_gen_callable: - - async def _async_stream_raw( - async_gen: AsyncIterator[Any], - ) -> AsyncIterator[Any]: - async for chunk in async_gen: - yield chunk - # To allow for cancellation to trigger - # Ref: https://github.com/fastapi/fastapi/issues/14680 - await anyio.sleep(0) - - gen = _async_stream_raw(gen) - response_args = _build_response_args( - status_code=status_code, solved_result=solved_result - ) - response = actual_response_class(content=gen, **response_args) - response.headers.raw.extend(solved_result.response.headers.raw) - else: - raw_response = await run_endpoint_function( - dependant=dependant, - values=solved_result.values, - is_coroutine=is_coroutine, - ) - if isinstance(raw_response, Response): - if raw_response.background is None: - raw_response.background = solved_result.background_tasks - response = raw_response - else: - response_args = _build_response_args( - status_code=status_code, solved_result=solved_result - ) - # Use the fast path (dump_json) when no custom response - # class was set and a response field with a TypeAdapter - # exists. Serializes directly to JSON bytes via Pydantic's - # Rust core, skipping the intermediate Python dict + - # json.dumps() step. - use_dump_json = response_field is not None and isinstance( - response_class, DefaultPlaceholder - ) - content = await serialize_response( - field=response_field, - response_content=raw_response, - include=response_model_include, - exclude=response_model_exclude, - by_alias=response_model_by_alias, - exclude_unset=response_model_exclude_unset, - exclude_defaults=response_model_exclude_defaults, - exclude_none=response_model_exclude_none, - is_coroutine=is_coroutine, - endpoint_ctx=endpoint_ctx, - dump_json=use_dump_json, - ) - if use_dump_json: - response = Response( - content=content, - media_type="application/json", - **response_args, - ) - else: - response = actual_response_class(content, **response_args) - if not is_body_allowed_for_status_code(response.status_code): - response.body = b"" - response.headers.raw.extend(solved_result.response.headers.raw) - if errors: - validation_error = RequestValidationError( - errors, body=body, endpoint_ctx=endpoint_ctx - ) - raise validation_error - - # Return response - assert response - return response - - return app - - -def get_websocket_app( - dependant: Dependant, - dependency_overrides_provider: Any | None = None, - embed_body_fields: bool = False, -) -> Callable[[WebSocket], Coroutine[Any, Any, Any]]: - async def app(websocket: WebSocket) -> None: - endpoint_ctx = ( - _extract_endpoint_context(dependant.call) - if dependant.call - else EndpointContext() - ) - if dependant.path: - # For mounted sub-apps, include the mount path prefix - mount_path = websocket.scope.get("root_path", "").rstrip("/") - endpoint_ctx["path"] = f"WS {mount_path}{dependant.path}" - async_exit_stack = websocket.scope.get("fastapi_inner_astack") - assert isinstance(async_exit_stack, AsyncExitStack), ( - "fastapi_inner_astack not found in request scope" - ) - solved_result = await solve_dependencies( - request=websocket, - dependant=dependant, - dependency_overrides_provider=dependency_overrides_provider, - async_exit_stack=async_exit_stack, - embed_body_fields=embed_body_fields, - ) - if solved_result.errors: - raise WebSocketRequestValidationError( - solved_result.errors, - endpoint_ctx=endpoint_ctx, - ) - assert dependant.call is not None, "dependant.call must be a function" - await dependant.call(**solved_result.values) - - return app - - -class APIWebSocketRoute(routing.WebSocketRoute): - def __init__( - self, - path: str, - endpoint: Callable[..., Any], - *, - name: str | None = None, - dependencies: Sequence[params.Depends] | None = None, - dependency_overrides_provider: Any | None = None, - ) -> None: - self.path = path - self.endpoint = endpoint - self.name = get_name(endpoint) if name is None else name - self.dependencies = list(dependencies or []) - self.path_regex, self.path_format, self.param_convertors = compile_path(path) - self.dependant = get_dependant( - path=self.path_format, call=self.endpoint, scope="function" - ) - for depends in self.dependencies[::-1]: - self.dependant.dependencies.insert( - 0, - get_parameterless_sub_dependant(depends=depends, path=self.path_format), - ) - self._flat_dependant = get_flat_dependant(self.dependant) - self._embed_body_fields = _should_embed_body_fields( - self._flat_dependant.body_params - ) - self.app = websocket_session( - get_websocket_app( - dependant=self.dependant, - dependency_overrides_provider=dependency_overrides_provider, - embed_body_fields=self._embed_body_fields, - ) - ) - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - match, child_scope = super().matches(scope) - if match != Match.NONE: - child_scope["route"] = self - return match, child_scope - - -_FASTAPI_SCOPE_KEY = "fastapi" -_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY = "effective_route_context" -_FASTAPI_FRONTEND_PATH_KEY = "frontend_path" -_FASTAPI_INCLUDED_ROUTER_KEY = "included_router" -_effective_route_context_var: ContextVar[Any | None] = ContextVar( - "fastapi_effective_route_context", default=None -) -_SCOPE_MISSING = object() - - -class _RouteWithPath(Protocol): - path: str - - -def _get_fastapi_scope(scope: Scope) -> dict[str, Any]: - fastapi_scope = scope.setdefault(_FASTAPI_SCOPE_KEY, {}) - assert isinstance(fastapi_scope, dict) - return fastapi_scope - - -def _update_scope(scope: Scope, child_scope: Scope) -> None: - fastapi_child_scope = child_scope.get(_FASTAPI_SCOPE_KEY) - for key, value in child_scope.items(): - if key != _FASTAPI_SCOPE_KEY: - scope[key] = value - if isinstance(fastapi_child_scope, dict): - _get_fastapi_scope(scope).update(fastapi_child_scope) - - -def _get_scope_effective_route_context(scope: Scope) -> Any | None: - return scope.get(_FASTAPI_SCOPE_KEY, {}).get(_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY) - - -def _get_scope_included_router(scope: Scope) -> Any | None: - return scope.get(_FASTAPI_SCOPE_KEY, {}).get(_FASTAPI_INCLUDED_ROUTER_KEY) - - -def _restore_fastapi_scope_key(scope: Scope, key: str, previous: Any) -> None: - fastapi_scope = scope.get(_FASTAPI_SCOPE_KEY) - if not isinstance(fastapi_scope, dict): - return - if previous is _SCOPE_MISSING: - fastapi_scope.pop(key, None) - else: - fastapi_scope[key] = previous - - -class _APIRouteLike(Protocol): - path: str - endpoint: Callable[..., Any] - stream_item_type: Any | None - response_model: Any - summary: str | None - response_description: str - deprecated: bool | None - operation_id: str | None - response_model_include: IncEx | None - response_model_exclude: IncEx | None - response_model_by_alias: bool - response_model_exclude_unset: bool - response_model_exclude_defaults: bool - response_model_exclude_none: bool - include_in_schema: bool - response_class: type[Response] | DefaultPlaceholder - dependency_overrides_provider: Any | None - callbacks: list[BaseRoute] | None - openapi_extra: dict[str, Any] | None - generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder - strict_content_type: bool | DefaultPlaceholder - tags: list[str | Enum] - responses: dict[int | str, dict[str, Any]] - name: str - path_regex: Any - path_format: str - param_convertors: dict[str, Any] - methods: set[str] - unique_id: str - status_code: int | None - response_field: ModelField | None - stream_item_field: ModelField | None - dependencies: list[params.Depends] - description: str - response_fields: dict[int | str, ModelField] - dependant: Dependant - _flat_dependant: Dependant - _embed_body_fields: bool - body_field: ModelField | None - is_sse_stream: bool - is_json_stream: bool - - -def _populate_api_route_state( - route: _APIRouteLike, - path: str, - endpoint: Callable[..., Any], - *, - response_model: Any = Default(None), - status_code: int | None = None, - tags: list[str | Enum] | None = None, - dependencies: Sequence[params.Depends] | None = None, - summary: str | None = None, - description: str | None = None, - response_description: str = "Successful Response", - responses: dict[int | str, dict[str, Any]] | None = None, - deprecated: bool | None = None, - name: str | None = None, - methods: set[str] | list[str] | None = None, - operation_id: str | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - include_in_schema: bool = True, - response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), - dependency_overrides_provider: Any | None = None, - callbacks: list[BaseRoute] | None = None, - openapi_extra: dict[str, Any] | None = None, - generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = Default( - generate_unique_id - ), - strict_content_type: bool | DefaultPlaceholder = Default(True), -) -> None: - route.path = path - route.endpoint = endpoint - route.stream_item_type = None - if isinstance(response_model, DefaultPlaceholder): - return_annotation = get_typed_return_annotation(endpoint) - if lenient_issubclass(return_annotation, Response): - response_model = None - else: - stream_item = get_stream_item_type(return_annotation) - if stream_item is not None: - # Extract item type for JSONL or SSE streaming when - # response_class is DefaultPlaceholder (JSONL) or - # EventSourceResponse (SSE). - # ServerSentEvent is excluded: it's a transport - # wrapper, not a data model, so it shouldn't feed - # into validation or OpenAPI schema generation. - if ( - isinstance(response_class, DefaultPlaceholder) - or lenient_issubclass(response_class, EventSourceResponse) - ) and not lenient_issubclass(stream_item, ServerSentEvent): - route.stream_item_type = stream_item - response_model = None - else: - response_model = return_annotation - route.response_model = response_model - route.summary = summary - route.response_description = response_description - route.deprecated = deprecated - route.operation_id = operation_id - route.response_model_include = response_model_include - route.response_model_exclude = response_model_exclude - route.response_model_by_alias = response_model_by_alias - route.response_model_exclude_unset = response_model_exclude_unset - route.response_model_exclude_defaults = response_model_exclude_defaults - route.response_model_exclude_none = response_model_exclude_none - route.include_in_schema = include_in_schema - route.response_class = response_class - route.dependency_overrides_provider = dependency_overrides_provider - route.callbacks = callbacks - route.openapi_extra = openapi_extra - route.generate_unique_id_function = generate_unique_id_function - route.strict_content_type = strict_content_type - route.tags = tags or [] - route.responses = responses or {} - route.name = get_name(endpoint) if name is None else name - route.path_regex, route.path_format, route.param_convertors = compile_path(path) - if methods is None: - methods = ["GET"] - route.methods = {method.upper() for method in methods} - if isinstance(generate_unique_id_function, DefaultPlaceholder): - current_generate_unique_id: Callable[[Any], str] = ( - generate_unique_id_function.value - ) - else: - current_generate_unique_id = generate_unique_id_function - route.unique_id = route.operation_id or current_generate_unique_id(route) - # normalize enums e.g. http.HTTPStatus - if isinstance(status_code, IntEnum): - status_code = int(status_code) - route.status_code = status_code - if route.response_model: - assert is_body_allowed_for_status_code(status_code), ( - f"Status code {status_code} must not have a response body" - ) - response_name = "Response_" + route.unique_id - route.response_field = create_model_field( - name=response_name, - type_=route.response_model, - mode="serialization", - ) - else: - route.response_field = None - if route.stream_item_type: - stream_item_name = "StreamItem_" + route.unique_id - route.stream_item_field = create_model_field( - name=stream_item_name, - type_=route.stream_item_type, - mode="serialization", - ) - else: - route.stream_item_field = None - route.dependencies = list(dependencies or []) - route.description = description or inspect.cleandoc(route.endpoint.__doc__ or "") - # if a "form feed" character (page break) is found in the description text, - # truncate description text to the content preceding the first "form feed" - route.description = route.description.split("\f")[0].strip() - response_fields = {} - for additional_status_code, response in route.responses.items(): - assert isinstance(response, dict), "An additional response must be a dict" - model = response.get("model") - if model: - assert is_body_allowed_for_status_code(additional_status_code), ( - f"Status code {additional_status_code} must not have a response body" - ) - response_name = f"Response_{additional_status_code}_{route.unique_id}" - response_field = create_model_field( - name=response_name, type_=model, mode="serialization" - ) - response_fields[additional_status_code] = response_field - if response_fields: - route.response_fields = response_fields - else: - route.response_fields = {} - - assert callable(endpoint), "An endpoint must be a callable" - route.dependant = get_dependant( - path=route.path_format, call=route.endpoint, scope="function" - ) - for depends in route.dependencies[::-1]: - route.dependant.dependencies.insert( - 0, - get_parameterless_sub_dependant(depends=depends, path=route.path_format), - ) - route._flat_dependant = get_flat_dependant(route.dependant) - route._embed_body_fields = _should_embed_body_fields( - route._flat_dependant.body_params - ) - route.body_field = get_body_field( - flat_dependant=route._flat_dependant, - name=route.unique_id, - embed_body_fields=route._embed_body_fields, - ) - # Detect generator endpoints that should stream as JSONL or SSE - is_generator = ( - route.dependant.is_async_gen_callable or route.dependant.is_gen_callable - ) - route.is_sse_stream = is_generator and lenient_issubclass( - response_class, EventSourceResponse - ) - route.is_json_stream = is_generator and isinstance( - response_class, DefaultPlaceholder - ) - - -class APIRoute(routing.Route): - stream_item_type: Any | None - response_model: Any - summary: str | None - response_description: str - deprecated: bool | None - operation_id: str | None - response_model_include: IncEx | None - response_model_exclude: IncEx | None - response_model_by_alias: bool - response_model_exclude_unset: bool - response_model_exclude_defaults: bool - response_model_exclude_none: bool - include_in_schema: bool - response_class: type[Response] | DefaultPlaceholder - dependency_overrides_provider: Any | None - callbacks: list[BaseRoute] | None - openapi_extra: dict[str, Any] | None - generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder - strict_content_type: bool | DefaultPlaceholder - tags: list[str | Enum] - responses: dict[int | str, dict[str, Any]] - unique_id: str - status_code: int | None - response_field: ModelField | None - stream_item_field: ModelField | None - dependencies: list[params.Depends] - description: str - response_fields: dict[int | str, ModelField] - dependant: Dependant - _flat_dependant: Dependant - _embed_body_fields: bool - body_field: ModelField | None - is_sse_stream: bool - is_json_stream: bool - - def __init__( - self, - path: str, - endpoint: Callable[..., Any], - *, - response_model: Any = Default(None), - status_code: int | None = None, - tags: list[str | Enum] | None = None, - dependencies: Sequence[params.Depends] | None = None, - summary: str | None = None, - description: str | None = None, - response_description: str = "Successful Response", - responses: dict[int | str, dict[str, Any]] | None = None, - deprecated: bool | None = None, - name: str | None = None, - methods: set[str] | list[str] | None = None, - operation_id: str | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - include_in_schema: bool = True, - response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), - dependency_overrides_provider: Any | None = None, - callbacks: list[BaseRoute] | None = None, - openapi_extra: dict[str, Any] | None = None, - generate_unique_id_function: Callable[["APIRoute"], str] - | DefaultPlaceholder = Default(generate_unique_id), - strict_content_type: bool | DefaultPlaceholder = Default(True), - ) -> None: - _populate_api_route_state( - cast(_APIRouteLike, self), - path, - endpoint, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - name=name, - methods=methods, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - dependency_overrides_provider=dependency_overrides_provider, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - strict_content_type=strict_content_type, - ) - self.app = request_response(self.get_route_handler()) - - def get_route_handler(self) -> Callable[[Request], Coroutine[Any, Any, Response]]: - route = cast(_APIRouteLike, self) - # TODO: Replace or deprecate this no-scope hook so included-route - # effective context can be passed explicitly instead of via ContextVar. - effective_context = _effective_route_context_var.get() - if effective_context is not None and effective_context.original_route is self: - route = cast(_APIRouteLike, effective_context) - return get_request_handler( - dependant=route.dependant, - body_field=route.body_field, - status_code=route.status_code, - response_class=route.response_class, - response_field=route.response_field, - response_model_include=route.response_model_include, - response_model_exclude=route.response_model_exclude, - response_model_by_alias=route.response_model_by_alias, - response_model_exclude_unset=route.response_model_exclude_unset, - response_model_exclude_defaults=route.response_model_exclude_defaults, - response_model_exclude_none=route.response_model_exclude_none, - dependency_overrides_provider=route.dependency_overrides_provider, - embed_body_fields=route._embed_body_fields, - strict_content_type=route.strict_content_type, - stream_item_field=route.stream_item_field, - is_json_stream=route.is_json_stream, - ) - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - effective_context = _get_scope_effective_route_context(scope) - if effective_context is not None and effective_context.original_route is self: - match, child_scope = effective_context.matches(scope) - else: - match, child_scope = super().matches(scope) - if match != Match.NONE: - child_scope["route"] = self - return match, child_scope - - async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: - effective_context = _get_scope_effective_route_context(scope) - if effective_context is not None and effective_context.original_route is self: - methods = effective_context.methods - if methods and scope["method"] not in methods: - headers = {"Allow": ", ".join(methods)} - if "app" in scope: - raise HTTPException(status_code=405, headers=headers) - response = PlainTextResponse( - "Method Not Allowed", status_code=405, headers=headers - ) - await response(scope, receive, send) - return - token = _effective_route_context_var.set(effective_context) - try: - app = request_response(self.get_route_handler()) - finally: - _effective_route_context_var.reset(token) - await app(scope, receive, send) - return - await super().handle(scope, receive, send) - - -@dataclass -class _RouterIncludeContext: - included_router: "APIRouter" - prefix: str = "" - tags: list[str | Enum] = field(default_factory=list) - dependencies: list[params.Depends] = field(default_factory=list) - default_response_class: type[Response] | DefaultPlaceholder = field( - default_factory=lambda: Default(JSONResponse) - ) - responses: dict[int | str, dict[str, Any]] = field(default_factory=dict) - callbacks: list[BaseRoute] = field(default_factory=list) - deprecated: bool | None = None - include_in_schema: bool = True - generate_unique_id_function: Callable[[APIRoute], str] | DefaultPlaceholder = field( - default_factory=lambda: Default(generate_unique_id) - ) - strict_content_type: bool | DefaultPlaceholder = field( - default_factory=lambda: Default(True) - ) - dependency_overrides_provider: Any | None = None - - @classmethod - def for_include( - cls, - *, - parent_router: "APIRouter", - included_router: "APIRouter", - prefix: str = "", - tags: list[str | Enum] | None = None, - dependencies: Sequence[params.Depends] | None = None, - default_response_class: type[Response] | DefaultPlaceholder = Default( - JSONResponse - ), - responses: dict[int | str, dict[str, Any]] | None = None, - callbacks: list[BaseRoute] | None = None, - deprecated: bool | None = None, - include_in_schema: bool = True, - generate_unique_id_function: Callable[[APIRoute], str] - | DefaultPlaceholder = Default(generate_unique_id), - ) -> "_RouterIncludeContext": - return cls( - included_router=included_router, - prefix=parent_router.prefix + prefix, - tags=[*parent_router.tags, *(tags or [])], - dependencies=[*parent_router.dependencies, *(dependencies or [])], - default_response_class=get_value_or_default( - default_response_class, parent_router.default_response_class - ), - responses={**parent_router.responses, **(responses or {})}, - callbacks=[*parent_router.callbacks, *(callbacks or [])], - deprecated=deprecated or parent_router.deprecated, - include_in_schema=parent_router.include_in_schema and include_in_schema, - generate_unique_id_function=get_value_or_default( - generate_unique_id_function, parent_router.generate_unique_id_function - ), - strict_content_type=parent_router.strict_content_type, - dependency_overrides_provider=parent_router.dependency_overrides_provider, - ) - - def combine( - self, child_context: "_RouterIncludeContext" - ) -> "_RouterIncludeContext": - return _RouterIncludeContext( - included_router=child_context.included_router, - prefix=self.prefix + child_context.prefix, - tags=[*self.tags, *child_context.tags], - dependencies=[*self.dependencies, *child_context.dependencies], - default_response_class=get_value_or_default( - child_context.default_response_class, self.default_response_class - ), - responses={**self.responses, **child_context.responses}, - callbacks=[*self.callbacks, *child_context.callbacks], - deprecated=self.deprecated or child_context.deprecated, - include_in_schema=self.include_in_schema - and child_context.include_in_schema, - generate_unique_id_function=get_value_or_default( - child_context.generate_unique_id_function, - self.generate_unique_id_function, - ), - strict_content_type=get_value_or_default( - child_context.strict_content_type, self.strict_content_type - ), - dependency_overrides_provider=self.dependency_overrides_provider, - ) - - def path_for(self, route: _RouteWithPath) -> str: - return self.prefix + route.path - - -@dataclass -class _EffectiveRouteContext: - original_route: BaseRoute - starlette_route: BaseRoute | None = None - path: str = "" - endpoint: Callable[..., Any] | None = None - stream_item_type: Any | None = None - response_model: Any = None - summary: str | None = None - response_description: str = "Successful Response" - deprecated: bool | None = None - operation_id: str | None = None - response_model_include: IncEx | None = None - response_model_exclude: IncEx | None = None - response_model_by_alias: bool = True - response_model_exclude_unset: bool = False - response_model_exclude_defaults: bool = False - response_model_exclude_none: bool = False - include_in_schema: bool = True - response_class: type[Response] | DefaultPlaceholder = field( - default_factory=lambda: Default(JSONResponse) - ) - dependency_overrides_provider: Any | None = None - callbacks: list[BaseRoute] | None = None - openapi_extra: dict[str, Any] | None = None - generate_unique_id_function: Callable[[Any], str] | DefaultPlaceholder = field( - default_factory=lambda: Default(generate_unique_id) - ) - strict_content_type: bool | DefaultPlaceholder = field( - default_factory=lambda: Default(True) - ) - tags: list[str | Enum] = field(default_factory=list) - responses: dict[int | str, dict[str, Any]] = field(default_factory=dict) - name: str = "" - path_regex: Any = None - path_format: str = "" - param_convertors: dict[str, Any] = field(default_factory=dict) - methods: set[str] = field(default_factory=set) - unique_id: str = "" - status_code: int | None = None - response_field: ModelField | None = None - stream_item_field: ModelField | None = None - dependencies: list[params.Depends] = field(default_factory=list) - description: str = "" - response_fields: dict[int | str, ModelField] = field(default_factory=dict) - dependant: Dependant | None = None - _flat_dependant: Dependant | None = None - _embed_body_fields: bool = False - body_field: ModelField | None = None - is_sse_stream: bool = False - is_json_stream: bool = False - - @classmethod - def from_api_route( - cls, - *, - original_route: APIRoute, - include_context: _RouterIncludeContext, - ) -> "_EffectiveRouteContext": - route = cast(_APIRouteLike, original_route) - context = cls(original_route=original_route) - _populate_api_route_state( - cast(_APIRouteLike, context), - include_context.path_for(original_route), - route.endpoint, - response_model=route.response_model, - status_code=route.status_code, - tags=[*include_context.tags, *route.tags], - dependencies=[*include_context.dependencies, *route.dependencies], - summary=route.summary, - description=route.description, - response_description=route.response_description, - responses={**include_context.responses, **route.responses}, - deprecated=route.deprecated or include_context.deprecated, - methods=route.methods, - operation_id=route.operation_id, - response_model_include=route.response_model_include, - response_model_exclude=route.response_model_exclude, - response_model_by_alias=route.response_model_by_alias, - response_model_exclude_unset=route.response_model_exclude_unset, - response_model_exclude_defaults=route.response_model_exclude_defaults, - response_model_exclude_none=route.response_model_exclude_none, - include_in_schema=route.include_in_schema - and include_context.include_in_schema, - response_class=get_value_or_default( - route.response_class, - include_context.included_router.default_response_class, - include_context.default_response_class, - ), - name=route.name, - dependency_overrides_provider=include_context.dependency_overrides_provider, - callbacks=[*include_context.callbacks, *(route.callbacks or [])], - openapi_extra=route.openapi_extra, - generate_unique_id_function=get_value_or_default( - route.generate_unique_id_function, - include_context.included_router.generate_unique_id_function, - include_context.generate_unique_id_function, - ), - strict_content_type=get_value_or_default( - route.strict_content_type, - include_context.included_router.strict_content_type, - include_context.strict_content_type, - ), - ) - return context - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - if not isinstance(self.original_route, APIRoute): - assert self.starlette_route is not None - return self.starlette_route.matches(scope) - if scope["type"] != "http": - return Match.NONE, {} - route_path = get_route_path(scope) - match = self.path_regex.match(route_path) - if not match: - return Match.NONE, {} - matched_params = match.groupdict() - for key, value in matched_params.items(): - matched_params[key] = self.param_convertors[key].convert(value) - path_params = dict(scope.get("path_params", {})) - path_params.update(matched_params) - child_scope = {"endpoint": self.endpoint, "path_params": path_params} - methods = self.methods - if methods and scope["method"] not in methods: - return Match.PARTIAL, child_scope - return Match.FULL, child_scope - - def url_path_for(self, name: str, /, **path_params: Any) -> Any: - if not isinstance(self.original_route, APIRoute): - assert self.starlette_route is not None - return self.starlette_route.url_path_for(name, **path_params) - seen_params = set(path_params.keys()) - param_convertors = self.param_convertors - expected_params = set(param_convertors.keys()) - if name != self.name or seen_params != expected_params: - raise routing.NoMatchFound(name, path_params) - path, remaining_params = routing.replace_params( - self.path_format, param_convertors, path_params - ) - assert not remaining_params - return URLPath(path=path, protocol="http") - - -@dataclass(frozen=True) -class RouteContext: - route: BaseRoute - _route_context: _EffectiveRouteContext | None = field(default=None, repr=False) - - @property - def original_route(self) -> BaseRoute: - if self._route_context is not None: - return self._route_context.original_route - return self.route - - @property - def _effective_route(self) -> BaseRoute | _EffectiveRouteContext: - if self._route_context is not None: - return self._route_context - return self.route - - @property - def path(self) -> str | None: - return getattr(self._effective_route, "path", None) - - @property - def path_format(self) -> str | None: - return getattr(self._effective_route, "path_format", None) - - @property - def name(self) -> str | None: - return getattr(self._effective_route, "name", None) - - @property - def methods(self) -> set[str] | None: - return getattr(self._effective_route, "methods", None) - - @property - def endpoint(self) -> Callable[..., Any] | None: - return getattr(self._effective_route, "endpoint", None) - - def __getattr__(self, name: str) -> Any: - return getattr(self._effective_route, name) - - -@dataclass -class _IncludedRouter(BaseRoute): - original_router: "APIRouter" - include_context: _RouterIncludeContext - _effective_candidates: list["_EffectiveRouteContext | _IncludedRouter"] = field( - default_factory=list - ) - _effective_candidates_version: int | None = None - _effective_low_priority_routes: list["_EffectiveRouteContext"] = field( - default_factory=list - ) - _effective_low_priority_routes_version: int | None = None - - def effective_candidates(self) -> list["_EffectiveRouteContext | _IncludedRouter"]: - routes_version = self.original_router._get_routes_version() - if routes_version == self._effective_candidates_version: - return self._effective_candidates - self._effective_candidates = [] - candidates = self.original_router.routes - for route in candidates: - if isinstance(route, _IncludedRouter): - child_context = self.include_context.combine(route.include_context) - child_branch = _IncludedRouter( - original_router=route.original_router, - include_context=child_context, - ) - self._effective_candidates.append(child_branch) - continue - route_context = self._build_effective_context(route) - if route_context is not None: - self._effective_candidates.append(route_context) - self._effective_candidates_version = routes_version - return self._effective_candidates - - def effective_low_priority_routes(self) -> list["_EffectiveRouteContext"]: - routes_version = self.original_router._get_routes_version() - if routes_version == self._effective_low_priority_routes_version: - return self._effective_low_priority_routes - self._effective_low_priority_routes = [] - for route in self.original_router._low_priority_routes: - route_context = self._build_effective_context(route) - if route_context is not None: - self._effective_low_priority_routes.append(route_context) - for route in self.original_router.routes: - if isinstance(route, _IncludedRouter): - child_context = self.include_context.combine(route.include_context) - child_branch = _IncludedRouter( - original_router=route.original_router, - include_context=child_context, - ) - self._effective_low_priority_routes.extend( - child_branch.effective_low_priority_routes() - ) - self._effective_low_priority_routes_version = routes_version - return self._effective_low_priority_routes - - def _build_effective_context( - self, route: BaseRoute - ) -> _EffectiveRouteContext | None: - if isinstance(route, APIRoute): - return _EffectiveRouteContext.from_api_route( - original_route=route, - include_context=self.include_context, - ) - if isinstance(route, _FrontendRouteGroup): - return _EffectiveRouteContext( - original_route=route, - starlette_route=route.with_prefix(self.include_context.prefix), - ) - if isinstance(route, routing.Route): - starlette_route: BaseRoute = routing.Route( - self.include_context.path_for(route), - endpoint=route.endpoint, - methods=list(route.methods or []), - name=route.name, - include_in_schema=route.include_in_schema, - ) - return _EffectiveRouteContext( - original_route=route, - starlette_route=starlette_route, - ) - if isinstance(route, APIWebSocketRoute): - starlette_route = APIWebSocketRoute( - self.include_context.path_for(route), - endpoint=route.endpoint, - name=route.name, - dependencies=[*self.include_context.dependencies, *route.dependencies], - dependency_overrides_provider=( - self.include_context.dependency_overrides_provider - ), - ) - return _EffectiveRouteContext( - original_route=route, - starlette_route=starlette_route, - ) - if isinstance(route, routing.WebSocketRoute): - starlette_route = routing.WebSocketRoute( - self.include_context.path_for(route), route.endpoint, name=route.name - ) - return _EffectiveRouteContext( - original_route=route, - starlette_route=starlette_route, - ) - if isinstance(route, routing.Mount): - starlette_route = copy.copy(route) - starlette_route.path = self.include_context.path_for(route).rstrip("/") - ( - starlette_route.path_regex, - starlette_route.path_format, - starlette_route.param_convertors, - ) = compile_path(starlette_route.path + "/{path:path}") - return _EffectiveRouteContext( - original_route=route, - starlette_route=starlette_route, - ) - if isinstance(route, routing.Host): - if self.include_context.prefix: - prefixed_app: ASGIApp = routing.Router( - routes=[routing.Mount(self.include_context.prefix, app=route.app)] - ) - else: - prefixed_app = route.app - starlette_route = routing.Host( - route.host, app=prefixed_app, name=route.name - ) - return _EffectiveRouteContext( - original_route=route, - starlette_route=starlette_route, - ) - return None - - def _match( - self, scope: Scope - ) -> tuple[Match, Scope, BaseRoute | None, _EffectiveRouteContext | None]: - partial: tuple[Scope, BaseRoute, _EffectiveRouteContext | None] | None = None - for candidate in self.effective_candidates(): - if isinstance(candidate, _IncludedRouter): - match, child_scope = candidate.matches(scope) - route: BaseRoute = candidate - route_context = None - elif isinstance(candidate.original_route, APIRoute): - route_context = candidate - fastapi_scope = _get_fastapi_scope(scope) - previous_context = fastapi_scope.get( - _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY, _SCOPE_MISSING - ) - fastapi_scope[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = route_context - try: - match, child_scope = candidate.original_route.matches(scope) - finally: - _restore_fastapi_scope_key( - scope, _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY, previous_context - ) - route = candidate.original_route - else: - route_context = candidate - match, child_scope = candidate.matches(scope) - route = candidate.starlette_route or candidate.original_route - if match == Match.FULL: - return match, child_scope, route, route_context - if match == Match.PARTIAL and partial is None: - partial = (child_scope, route, route_context) - if partial is not None: - child_scope, route, route_context = partial - return Match.PARTIAL, child_scope, route, route_context - return Match.NONE, {}, None, None - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - fastapi_scope = _get_fastapi_scope(scope) - previous_router = fastapi_scope.get( - _FASTAPI_INCLUDED_ROUTER_KEY, _SCOPE_MISSING - ) - fastapi_scope[_FASTAPI_INCLUDED_ROUTER_KEY] = self - try: - match, _ = self.original_router.matches(scope) - return match, {} - finally: - _restore_fastapi_scope_key( - scope, _FASTAPI_INCLUDED_ROUTER_KEY, previous_router - ) - - async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: - _get_fastapi_scope(scope)[_FASTAPI_INCLUDED_ROUTER_KEY] = self - await self.original_router.handle(scope, receive, send) - - async def _handle_selected( - self, scope: Scope, receive: Receive, send: Send - ) -> None: - match, child_scope, route, effective_context = self._match(scope) - if match == Match.NONE or route is None: - await self.original_router.default(scope, receive, send) - return - scope.update(child_scope) - if isinstance(route, _IncludedRouter): - await route.handle(scope, receive, send) - return - if effective_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( - effective_context - ) - original_route = effective_context.original_route - if isinstance(original_route, APIRoute): - scope["route"] = original_route - await original_route.handle(scope, receive, send) - return - await route.handle(scope, receive, send) - - def effective_route_contexts(self) -> Iterator[_EffectiveRouteContext]: - for candidate in self.effective_candidates(): - if isinstance(candidate, _IncludedRouter): - yield from candidate.effective_route_contexts() - else: - yield candidate - - def url_path_for(self, name: str, /, **path_params: Any) -> Any: - for route_context in self.effective_route_contexts(): - try: - return route_context.url_path_for(name, **path_params) - except routing.NoMatchFound: - pass - raise routing.NoMatchFound(name, path_params) - - -def _iter_included_route_candidates(routes: Sequence[BaseRoute]) -> Iterator[BaseRoute]: - for route, route_context in _iter_routes_with_context(routes): - if route_context is not None and route_context.starlette_route is not None: - yield route_context.starlette_route - else: - yield route - - -def iter_route_contexts( - routes: Sequence[BaseRoute | RouteContext], -) -> Iterator[RouteContext]: - for route in routes: - if isinstance(route, RouteContext): - yield route - continue - for original_route, route_context in _iter_routes_with_context([route]): - if route_context is None: - yield RouteContext(original_route) - else: - yield RouteContext(original_route, route_context) - - -def _iter_routes_with_context( - routes: Sequence[BaseRoute], -) -> Iterator[tuple[BaseRoute, _EffectiveRouteContext | None]]: - for route in routes: - if isinstance(route, _IncludedRouter): - for route_context in route.effective_route_contexts(): - yield route_context.original_route, route_context - else: - yield route, None - - -def _normalize_frontend_path(path: str) -> str: - if not path: - raise AssertionError("A frontend path cannot be empty") - if not path.startswith("/"): - raise AssertionError("A frontend path must start with '/'") - if path != "/": - path = path.rstrip("/") - return path - - -def _join_frontend_paths(prefix: str, path: str) -> str: - if not prefix: - return path - if path == "/": - return prefix - return prefix + path - - -def _frontend_path_specificity(path: str) -> int: - if path == "/": - return 0 - return len(path) - - -def _get_resolved_absolute_path(path: str | os.PathLike[str]) -> str: - return os.path.realpath(os.fspath(path)) - - -class _FrontendStaticFiles(StaticFiles): - def __init__( - self, - *, - directory: str | os.PathLike[str], - fallback: Literal["auto", "index.html", "404.html"] | None, - check_dir: bool = True, - ) -> None: - self.fallback = fallback - if check_dir and not os.path.isdir(directory): - raise RuntimeError( - f"Frontend directory '{directory}' does not exist. " - f"Resolved absolute path: '{_get_resolved_absolute_path(directory)}'" - ) - super().__init__( - directory=directory, - html=True, - check_dir=check_dir, - follow_symlink=False, - ) - if check_dir and fallback in {"index.html", "404.html"}: - self._check_fallback_file(fallback) - - def _check_fallback_file(self, fallback: str) -> None: - _, stat_result = self.lookup_path(fallback) - if stat_result is None or not stat.S_ISREG(stat_result.st_mode): - raise RuntimeError( - f"Frontend fallback file '{fallback}' does not exist in " - f"directory '{self.directory}'. Resolved absolute directory: " - f"'{self._get_resolved_directory()}'" - ) - - def _get_resolved_directory(self) -> str: - assert self.directory is not None - return _get_resolved_absolute_path(self.directory) - - def get_path(self, scope: Scope) -> str: - path = _get_fastapi_scope(scope).get(_FASTAPI_FRONTEND_PATH_KEY, "") - assert isinstance(path, str) - return os.path.normpath(os.path.join(*path.split("/"))) - - async def get_response(self, path: str, scope: Scope) -> Response: - if scope["method"] not in ("GET", "HEAD"): - raise HTTPException(status_code=405) - - try: - full_path, stat_result = await run_in_threadpool(self.lookup_path, path) - except PermissionError: - raise HTTPException(status_code=401) from None - except OSError as exc: - if exc.errno == errno.ENAMETOOLONG: - raise HTTPException(status_code=404) from None - raise exc - except ValueError: - raise HTTPException(status_code=404) from None - - if stat_result and stat.S_ISREG(stat_result.st_mode): - return self.file_response(full_path, stat_result, scope) - - if stat_result and stat.S_ISDIR(stat_result.st_mode): - index_path = os.path.join(path, "index.html") - full_path, stat_result = await run_in_threadpool( - self.lookup_path, index_path - ) - if stat_result is not None and stat.S_ISREG(stat_result.st_mode): - if not scope["path"].endswith("/"): - url = URL(scope=scope) - url = url.replace(path=url.path + "/") - return RedirectResponse(url=url) - return self.file_response(full_path, stat_result, scope) - - if self.fallback == "404.html" or ( - self.fallback == "auto" and self._fallback_file_exists("404.html") - ): - return await self._fallback_response("404.html", scope, status_code=404) - - if ( - self.fallback == "index.html" - or (self.fallback == "auto" and self._fallback_file_exists("index.html")) - ) and _is_frontend_navigation_request(scope): - return await self._fallback_response("index.html", scope, status_code=200) - - raise HTTPException(status_code=404) - - def _fallback_file_exists(self, fallback: str) -> bool: - _, stat_result = self.lookup_path(fallback) - return stat_result is not None and stat.S_ISREG(stat_result.st_mode) - - async def _fallback_response( - self, fallback: str, scope: Scope, *, status_code: int - ) -> Response: - full_path, stat_result = await run_in_threadpool(self.lookup_path, fallback) - if stat_result is None or not stat.S_ISREG(stat_result.st_mode): - raise RuntimeError( - f"Frontend fallback file '{fallback}' does not exist in " - f"directory '{self.directory}'. Resolved absolute directory: " - f"'{self._get_resolved_directory()}'" - ) - return self.file_response( - full_path, stat_result, scope, status_code=status_code - ) - - -def _iter_accept_media_types(accept: str) -> Iterator[tuple[str, float]]: - for raw_value in accept.split(","): - message = email.message.Message() - message["content-type"] = raw_value.strip() - q = message.get_param("q") - quality = 1.0 - if isinstance(q, str): - try: - quality = float(q) - except ValueError: - pass - yield ( - f"{message.get_content_maintype()}/{message.get_content_subtype()}", - quality, - ) - - -def _is_frontend_navigation_request(scope: Scope) -> bool: - route_path = get_route_path(scope) - final_segment = route_path.rsplit("/", 1)[-1] - if os.path.splitext(final_segment)[1]: - return False - request = Request(scope) - wildcard_accepted = False - html_rejected = False - for media_type, quality in _iter_accept_media_types( - request.headers.get("accept", "") - ): - if media_type in {"text/html", "application/xhtml+xml"}: - if quality == 0: - html_rejected = True - else: - return True - elif media_type == "*/*" and quality != 0: - wildcard_accepted = True - return wildcard_accepted and not html_rejected - - -class _FrontendRoute(BaseRoute): - def __init__( - self, - path: str, - *, - directory: str | os.PathLike[str], - fallback: Literal["auto", "index.html", "404.html"] | None = "auto", - check_dir: bool = True, - ) -> None: - if fallback not in {"auto", "index.html", "404.html", None}: - raise AssertionError( - "fallback must be 'auto', 'index.html', '404.html', or None" - ) - self.path = _normalize_frontend_path(path) - self.methods = {"GET", "HEAD"} - self.app = _FrontendStaticFiles( - directory=directory, fallback=fallback, check_dir=check_dir - ) - - def with_path(self, path: str) -> "_FrontendRoute": - route = copy.copy(self) - route.path = _normalize_frontend_path(path) - return route - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - if scope["type"] != "http": - return Match.NONE, {} - frontend_path = self._get_frontend_path(get_route_path(scope)) - if frontend_path is None: - return Match.NONE, {} - child_scope = {_FASTAPI_SCOPE_KEY: {_FASTAPI_FRONTEND_PATH_KEY: frontend_path}} - if scope["method"] not in self.methods: - return Match.PARTIAL, child_scope - return Match.FULL, child_scope - - def _get_frontend_path(self, route_path: str) -> str | None: - if self.path == "/": - return route_path.lstrip("/") - if route_path == self.path: - return "" - prefix = self.path + "/" - if route_path.startswith(prefix): - return route_path[len(prefix) :] - return None - - async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: - await self.app(scope, receive, send) - - def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: - raise NoMatchFound(name, path_params) - - -class _FrontendRouteGroup(BaseRoute): - def __init__(self) -> None: - self.routes: list[_FrontendRoute] = [] - - def add_frontend_route( - self, - path: str, - *, - directory: str | os.PathLike[str], - fallback: Literal["auto", "index.html", "404.html"] | None = "auto", - check_dir: bool = True, - ) -> None: - self.routes.append( - _FrontendRoute( - path, - directory=directory, - fallback=fallback, - check_dir=check_dir, - ) - ) - - def with_prefix(self, prefix: str) -> "_FrontendRouteGroup": - route_group = copy.copy(self) - route_group.routes = [ - route.with_path(_join_frontend_paths(prefix, route.path)) - for route in self.routes - ] - return route_group - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - match, child_scope, _ = self._match(scope) - return match, child_scope - - def _match(self, scope: Scope) -> tuple[Match, Scope, _FrontendRoute | None]: - full: tuple[Scope, _FrontendRoute] | None = None - partial: tuple[Scope, _FrontendRoute] | None = None - for route in self.routes: - match, child_scope = route.matches(scope) - if match == Match.FULL: - if full is None or _frontend_path_specificity( - route.path - ) > _frontend_path_specificity(full[1].path): - full = (child_scope, route) - elif match == Match.PARTIAL: - if partial is None or _frontend_path_specificity( - route.path - ) > _frontend_path_specificity(partial[1].path): - partial = (child_scope, route) - if full is not None: - child_scope, route = full - return Match.FULL, child_scope, route - if partial is not None: - child_scope, route = partial - return Match.PARTIAL, child_scope, route - return Match.NONE, {}, None - - async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: - match, child_scope, route = self._match(scope) - if match == Match.NONE or route is None: - raise HTTPException(status_code=404) - _update_scope(scope, child_scope) - await route.handle(scope, receive, send) - - def url_path_for(self, name: str, /, **path_params: Any) -> URLPath: - raise NoMatchFound(name, path_params) - - -class APIRouter(routing.Router): - """ - `APIRouter` class, used to group *path operations*, for example to structure - an app in multiple files. It would then be included in the `FastAPI` app, or - in another `APIRouter` (ultimately included in the app). - - Read more about it in the - [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/). - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - - app = FastAPI() - router = APIRouter() - - - @router.get("/users/", tags=["users"]) - async def read_users(): - return [{"username": "Rick"}, {"username": "Morty"}] - - - app.include_router(router) - ``` - """ - - def __init__( - self, - *, - prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to all the *path operations* in this - router. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to all the - *path operations* in this router. - - Read more about it in the - [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - """ - ), - ] = None, - default_response_class: Annotated[ - type[Response], - Doc( - """ - The default response class to be used. - - Read more in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). - """ - ), - ] = Default(JSONResponse), - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses to be shown in OpenAPI. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). - - And in the - [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - OpenAPI callbacks that should apply to all *path operations* in this - router. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - routes: Annotated[ - list[BaseRoute] | None, - Doc( - """ - **Note**: you probably shouldn't use this parameter, it is inherited - from Starlette and supported for compatibility. - - --- - - A list of routes to serve incoming HTTP and WebSocket requests. - """ - ), - deprecated( - """ - You normally wouldn't use this parameter with FastAPI, it is inherited - from Starlette and supported for compatibility. - - In FastAPI, you normally would use the *path operation methods*, - like `router.get()`, `router.post()`, etc. - """ - ), - ] = None, - redirect_slashes: Annotated[ - bool, - Doc( - """ - Whether to detect and redirect slashes in URLs when the client doesn't - use the same format. - """ - ), - ] = True, - default: Annotated[ - ASGIApp | None, - Doc( - """ - Default function handler for this router. Used to handle - 404 Not Found errors. - """ - ), - ] = None, - dependency_overrides_provider: Annotated[ - Any | None, - Doc( - """ - Only used internally by FastAPI to handle dependency overrides. - - You shouldn't need to use it. It normally points to the `FastAPI` app - object. - """ - ), - ] = None, - route_class: Annotated[ - type[APIRoute], - Doc( - """ - Custom route (*path operation*) class to be used by this router. - - Read more about it in the - [FastAPI docs for Custom Request and APIRoute class](https://fastapi.tiangolo.com/how-to/custom-request-and-route/#custom-apiroute-class-in-a-router). - """ - ), - ] = APIRoute, - on_startup: Annotated[ - Sequence[Callable[[], Any]] | None, - Doc( - """ - A list of startup event handler functions. - - You should instead use the `lifespan` handlers. - - Read more in the [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). - """ - ), - ] = None, - on_shutdown: Annotated[ - Sequence[Callable[[], Any]] | None, - Doc( - """ - A list of shutdown event handler functions. - - You should instead use the `lifespan` handlers. - - Read more in the - [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). - """ - ), - ] = None, - # the generic to Lifespan[AppType] is the type of the top level application - # which the router cannot know statically, so we use typing.Any - lifespan: Annotated[ - Lifespan[Any] | None, - Doc( - """ - A `Lifespan` context manager handler. This replaces `startup` and - `shutdown` functions with a single context manager. - - Read more in the - [FastAPI docs for `lifespan`](https://fastapi.tiangolo.com/advanced/events/). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark all *path operations* in this router as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - To include (or not) all the *path operations* in this router in the - generated OpenAPI. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - strict_content_type: Annotated[ - bool, - Doc( - """ - Enable strict checking for request Content-Type headers. - - When `True` (the default), requests with a body that do not include - a `Content-Type` header will **not** be parsed as JSON. - - This prevents potential cross-site request forgery (CSRF) attacks - that exploit the browser's ability to send requests without a - Content-Type header, bypassing CORS preflight checks. In particular - applicable for apps that need to be run locally (in localhost). - - When `False`, requests without a `Content-Type` header will have - their body parsed as JSON, which maintains compatibility with - certain clients that don't send `Content-Type` headers. - - Read more about it in the - [FastAPI docs for Strict Content-Type](https://fastapi.tiangolo.com/advanced/strict-content-type/). - """ - ), - ] = Default(True), - ) -> None: - # Determine the lifespan context to use - if lifespan is None: - # Use the default lifespan that runs on_startup/on_shutdown handlers - lifespan_context: Lifespan[Any] = _DefaultLifespan(self) - elif inspect.isasyncgenfunction(lifespan): - lifespan_context = asynccontextmanager(lifespan) - elif inspect.isgeneratorfunction(lifespan): - lifespan_context = _wrap_gen_lifespan_context(lifespan) - else: - lifespan_context = lifespan - self.lifespan_context = lifespan_context - - super().__init__( - routes=routes, - redirect_slashes=redirect_slashes, - default=default, - lifespan=lifespan_context, - ) - if prefix: - assert prefix.startswith("/"), "A path prefix must start with '/'" - assert not prefix.endswith("/"), ( - "A path prefix must not end with '/', as the routes will start with '/'" - ) - - # Handle on_startup/on_shutdown locally since Starlette removed support - # Ref: https://github.com/Kludex/starlette/pull/3117 - # TODO: deprecate this once the lifespan (or alternative) interface is improved - self.on_startup: list[Callable[[], Any]] = ( - [] if on_startup is None else list(on_startup) - ) - self.on_shutdown: list[Callable[[], Any]] = ( - [] if on_shutdown is None else list(on_shutdown) - ) - - self.prefix = prefix - self.tags: list[str | Enum] = tags or [] - self.dependencies = list(dependencies or []) - self.deprecated = deprecated - self.include_in_schema = include_in_schema - self.responses = responses or {} - self.callbacks = callbacks or [] - self.dependency_overrides_provider = dependency_overrides_provider - self.route_class = route_class - self.default_response_class = default_response_class - self.generate_unique_id_function = generate_unique_id_function - self.strict_content_type = strict_content_type - self._routes_version = 0 - self._low_priority_routes: list[BaseRoute] = [] - self._frontend_routes: _FrontendRouteGroup | None = None - - def _mark_routes_changed(self) -> None: - self._routes_version += 1 - - def _get_routes_version(self, seen: set[int] | None = None) -> int: - if seen is None: - seen = set() - router_id = id(self) - if router_id in seen: - return self._routes_version - seen.add(router_id) - version = self._routes_version - for route in self.routes: - if isinstance(route, _IncludedRouter): - version += route.original_router._get_routes_version(seen) - return version - - def _contains_router( - self, router: "APIRouter", seen: set[int] | None = None - ) -> bool: - if seen is None: - seen = set() - router_id = id(self) - if router_id in seen: - return False - seen.add(router_id) - for route in self.routes: - if not isinstance(route, _IncludedRouter): - continue - if route.original_router is router: - return True - if route.original_router._contains_router(router, seen): - return True - return False - - def add_route( - self, - path: str, - endpoint: Callable[[Request], Awaitable[Response] | Response], - methods: Collection[str] | None = None, - name: str | None = None, - include_in_schema: bool = True, - ) -> None: - super().add_route( - path, - endpoint, - methods=methods, - name=name, - include_in_schema=include_in_schema, - ) - self._mark_routes_changed() - - def add_websocket_route( - self, - path: str, - endpoint: Callable[[WebSocket], Awaitable[None]], - name: str | None = None, - ) -> None: - super().add_websocket_route(path, endpoint, name=name) - self._mark_routes_changed() - - def frontend( - self, - path: Annotated[ - str, - Doc( - """ - The URL path prefix where the frontend build should be served. - """ - ), - ], - *, - directory: Annotated[ - str | os.PathLike[str], - Doc( - """ - The directory containing the static frontend build output. - """ - ), - ], - fallback: Annotated[ - Literal["auto", "index.html", "404.html"] | None, - Doc( - """ - The fallback file behavior for missing frontend paths. - """ - ), - ] = "auto", - check_dir: Annotated[ - bool, - Doc( - """ - Check that the frontend directory exists when the app is created. - """ - ), - ] = True, - ) -> None: - """ - Serve a static frontend build as low-priority routes. - - Use this for frontend tools that build static files into a directory, - such as `dist`. **FastAPI** path operations are checked first, and - the frontend files are checked only if no normal route matched. - - A typical project could look like this: - - ```text - . - ├── pyproject.toml - ├── app - │ ├── __init__.py - │ └── main.py - └── dist - ├── index.html - └── assets - └── app.js - ``` - - Then in `app/main.py`: - - ```python - from fastapi import APIRouter, FastAPI - - app = FastAPI() - router = APIRouter() - router.frontend("/", directory="dist") - app.include_router(router) - ``` - """ - normalized_path = _normalize_frontend_path(path) - if self._frontend_routes is None: - self._frontend_routes = _FrontendRouteGroup() - self._low_priority_routes.append(self._frontend_routes) - self._frontend_routes.add_frontend_route( - _join_frontend_paths(self.prefix, normalized_path), - directory=directory, - fallback=fallback, - check_dir=check_dir, - ) - self._mark_routes_changed() - - async def app(self, scope: Scope, receive: Receive, send: Send) -> None: - assert scope["type"] in ("http", "websocket", "lifespan") - - if "router" not in scope: - scope["router"] = self - - if scope["type"] == "lifespan": - await self.lifespan(scope, receive, send) - return - - partial: tuple[BaseRoute, Scope] | None = None - for route in self.routes: - match, child_scope = route.matches(scope) - if match == Match.FULL: - scope.update(child_scope) - await route.handle(scope, receive, send) - return - if match == Match.PARTIAL and partial is None: - partial = (route, child_scope) - - if partial is not None: - route, child_scope = partial - scope.update(child_scope) - await route.handle(scope, receive, send) - return - - route_path = get_route_path(scope) - if scope["type"] == "http" and self.redirect_slashes and route_path != "/": - redirect_scope = dict(scope) - if route_path.endswith("/"): - redirect_scope["path"] = redirect_scope["path"].rstrip("/") - else: - redirect_scope["path"] = redirect_scope["path"] + "/" - - for route in self.routes: - match, _ = route.matches(redirect_scope) - if match != Match.NONE: - redirect_url = URL(scope=redirect_scope) - response = RedirectResponse(url=str(redirect_url)) - await response(scope, receive, send) - return - - ( - low_priority_match, - low_priority_scope, - low_priority_route, - low_priority_context, - ) = self._match_low_priority(scope) - if low_priority_match != Match.NONE and low_priority_route is not None: - _update_scope(scope, low_priority_scope) - if low_priority_context is not None: - _get_fastapi_scope(scope)[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = ( - low_priority_context - ) - original_route = low_priority_context.original_route - if isinstance(original_route, APIRoute): - scope["route"] = original_route - await original_route.handle(scope, receive, send) - return - await low_priority_route.handle(scope, receive, send) - return - - await self.default(scope, receive, send) - - async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: - included_router = _get_scope_included_router(scope) - if ( - isinstance(included_router, _IncludedRouter) - and included_router.original_router is self - ): - await included_router._handle_selected(scope, receive, send) - return - await self.app(scope, receive, send) - - def matches(self, scope: Scope) -> tuple[Match, Scope]: - included_router = _get_scope_included_router(scope) - if ( - isinstance(included_router, _IncludedRouter) - and included_router.original_router is self - ): - match, child_scope, _, _ = included_router._match(scope) - return match, child_scope - return Match.NONE, {} - - def _iter_low_priority_routes( - self, - ) -> Iterator[BaseRoute | _EffectiveRouteContext]: - yield from self._low_priority_routes - for route in self.routes: - if isinstance(route, _IncludedRouter): - yield from route.effective_low_priority_routes() - - def _match_low_priority( - self, scope: Scope - ) -> tuple[Match, Scope, BaseRoute | None, _EffectiveRouteContext | None]: - full: tuple[Scope, BaseRoute, _EffectiveRouteContext | None] | None = None - partial: tuple[Scope, BaseRoute, _EffectiveRouteContext | None] | None = None - for candidate in self._iter_low_priority_routes(): - route: BaseRoute - if isinstance(candidate, _EffectiveRouteContext): - route_context: _EffectiveRouteContext | None = candidate - original_route = candidate.original_route - if isinstance(original_route, APIRoute): - fastapi_scope = _get_fastapi_scope(scope) - previous_context = fastapi_scope.get( - _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY, _SCOPE_MISSING - ) - fastapi_scope[_FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY] = route_context - try: - match, child_scope = original_route.matches(scope) - finally: - _restore_fastapi_scope_key( - scope, - _FASTAPI_EFFECTIVE_ROUTE_CONTEXT_KEY, - previous_context, - ) - route = original_route - else: - match, child_scope = candidate.matches(scope) - route = candidate.starlette_route or original_route - else: - route_context = None - match, child_scope = candidate.matches(scope) - route = candidate - if match == Match.FULL: - if full is None: - full = (child_scope, route, route_context) - elif match == Match.PARTIAL: - if partial is None: - partial = (child_scope, route, route_context) - if full is not None: - child_scope, route, route_context = full - return Match.FULL, child_scope, route, route_context - if partial is not None: - child_scope, route, route_context = partial - return Match.PARTIAL, child_scope, route, route_context - return Match.NONE, {}, None, None - - def route( - self, - path: str, - methods: Collection[str] | None = None, - name: str | None = None, - include_in_schema: bool = True, - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_route( - path, - func, - methods=methods, - name=name, - include_in_schema=include_in_schema, - ) - return func - - return decorator - - def add_api_route( - self, - path: str, - endpoint: Callable[..., Any], - *, - response_model: Any = Default(None), - status_code: int | None = None, - tags: list[str | Enum] | None = None, - dependencies: Sequence[params.Depends] | None = None, - summary: str | None = None, - description: str | None = None, - response_description: str = "Successful Response", - responses: dict[int | str, dict[str, Any]] | None = None, - deprecated: bool | None = None, - methods: set[str] | list[str] | None = None, - operation_id: str | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - include_in_schema: bool = True, - response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse), - name: str | None = None, - route_class_override: type[APIRoute] | None = None, - callbacks: list[BaseRoute] | None = None, - openapi_extra: dict[str, Any] | None = None, - generate_unique_id_function: Callable[[APIRoute], str] - | DefaultPlaceholder = Default(generate_unique_id), - strict_content_type: bool | DefaultPlaceholder = Default(True), - ) -> None: - route_class = route_class_override or self.route_class - responses = responses or {} - combined_responses = {**self.responses, **responses} - current_response_class = get_value_or_default( - response_class, self.default_response_class - ) - current_tags = self.tags.copy() - if tags: - current_tags.extend(tags) - current_dependencies = self.dependencies.copy() - if dependencies: - current_dependencies.extend(dependencies) - current_callbacks = self.callbacks.copy() - if callbacks: - current_callbacks.extend(callbacks) - current_generate_unique_id = get_value_or_default( - generate_unique_id_function, self.generate_unique_id_function - ) - route = route_class( - self.prefix + path, - endpoint=endpoint, - response_model=response_model, - status_code=status_code, - tags=current_tags, - dependencies=current_dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=combined_responses, - deprecated=deprecated or self.deprecated, - methods=methods, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema and self.include_in_schema, - response_class=current_response_class, - name=name, - dependency_overrides_provider=self.dependency_overrides_provider, - callbacks=current_callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=current_generate_unique_id, - strict_content_type=get_value_or_default( - strict_content_type, self.strict_content_type - ), - ) - self.routes.append(route) - self._mark_routes_changed() - - def api_route( - self, - path: str, - *, - response_model: Any = Default(None), - status_code: int | None = None, - tags: list[str | Enum] | None = None, - dependencies: Sequence[params.Depends] | None = None, - summary: str | None = None, - description: str | None = None, - response_description: str = "Successful Response", - responses: dict[int | str, dict[str, Any]] | None = None, - deprecated: bool | None = None, - methods: list[str] | None = None, - operation_id: str | None = None, - response_model_include: IncEx | None = None, - response_model_exclude: IncEx | None = None, - response_model_by_alias: bool = True, - response_model_exclude_unset: bool = False, - response_model_exclude_defaults: bool = False, - response_model_exclude_none: bool = False, - include_in_schema: bool = True, - response_class: type[Response] = Default(JSONResponse), - name: str | None = None, - callbacks: list[BaseRoute] | None = None, - openapi_extra: dict[str, Any] | None = None, - generate_unique_id_function: Callable[[APIRoute], str] = Default( - generate_unique_id - ), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_api_route( - path, - func, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=methods, - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - return func - - return decorator - - def add_api_websocket_route( - self, - path: str, - endpoint: Callable[..., Any], - name: str | None = None, - *, - dependencies: Sequence[params.Depends] | None = None, - ) -> None: - current_dependencies = self.dependencies.copy() - if dependencies: - current_dependencies.extend(dependencies) - - route = APIWebSocketRoute( - self.prefix + path, - endpoint=endpoint, - name=name, - dependencies=current_dependencies, - dependency_overrides_provider=self.dependency_overrides_provider, - ) - self.routes.append(route) - self._mark_routes_changed() - - def websocket( - self, - path: Annotated[ - str, - Doc( - """ - WebSocket path. - """ - ), - ], - name: Annotated[ - str | None, - Doc( - """ - A name for the WebSocket. Only used internally. - """ - ), - ] = None, - *, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be used for this - WebSocket. - - Read more about it in the - [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). - """ - ), - ] = None, - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Decorate a WebSocket function. - - Read more about it in the - [FastAPI docs for WebSockets](https://fastapi.tiangolo.com/advanced/websockets/). - - **Example** - - ## Example - - ```python - from fastapi import APIRouter, FastAPI, WebSocket - - app = FastAPI() - router = APIRouter() - - @router.websocket("/ws") - async def websocket_endpoint(websocket: WebSocket): - await websocket.accept() - while True: - data = await websocket.receive_text() - await websocket.send_text(f"Message text was: {data}") - - app.include_router(router) - ``` - """ - - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_api_websocket_route( - path, func, name=name, dependencies=dependencies - ) - return func - - return decorator - - def websocket_route( - self, path: str, name: str | None = None - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_websocket_route(path, func, name=name) - return func - - return decorator - - def include_router( - self, - router: Annotated["APIRouter", Doc("The `APIRouter` to include.")], - *, - prefix: Annotated[str, Doc("An optional path prefix for the router.")] = "", - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to all the *path operations* in this - router. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to all the - *path operations* in this router. - - Read more about it in the - [FastAPI docs for Bigger Applications - Multiple Files](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - """ - ), - ] = None, - default_response_class: Annotated[ - type[Response], - Doc( - """ - The default response class to be used. - - Read more in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#default-response-class). - """ - ), - ] = Default(JSONResponse), - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses to be shown in OpenAPI. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Additional Responses in OpenAPI](https://fastapi.tiangolo.com/advanced/additional-responses/). - - And in the - [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-custom-prefix-tags-responses-and-dependencies). - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - OpenAPI callbacks that should apply to all *path operations* in this - router. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark all *path operations* in this router as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include (or not) all the *path operations* in this router in the - generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = True, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> None: - """ - Include another `APIRouter` in the same current `APIRouter`. - - Read more about it in the - [FastAPI docs for Bigger Applications](https://fastapi.tiangolo.com/tutorial/bigger-applications/). - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - - app = FastAPI() - internal_router = APIRouter() - users_router = APIRouter() - - @users_router.get("/users/") - def read_users(): - return [{"name": "Rick"}, {"name": "Morty"}] - - internal_router.include_router(users_router) - app.include_router(internal_router) - ``` - """ - assert self is not router, ( - "Cannot include the same APIRouter instance into itself. " - "Did you mean to include a different router?" - ) - assert not router._contains_router(self), ( - "Cannot include an APIRouter instance that already includes this router. " - "Did you mean to include a different router?" - ) - if prefix: - assert prefix.startswith("/"), "A path prefix must start with '/'" - assert not prefix.endswith("/"), ( - "A path prefix must not end with '/', as the routes will start with '/'" - ) - else: - for route, route_context in _iter_routes_with_context(router.routes): - if route_context is None: - path = getattr(route, "path", None) - name = getattr(route, "name", "unknown") - elif route_context.starlette_route is not None: - path = getattr(route_context.starlette_route, "path", None) - name = getattr(route_context.starlette_route, "name", "unknown") - else: - path = route_context.path - name = route_context.name - if path is not None and not path: - raise FastAPIError( - f"Prefix and path cannot be both empty (path operation: {name})" - ) - include_context = _RouterIncludeContext.for_include( - parent_router=self, - included_router=router, - prefix=prefix, - tags=tags, - dependencies=dependencies, - default_response_class=default_response_class, - responses=responses, - callbacks=callbacks, - deprecated=deprecated, - include_in_schema=include_in_schema, - generate_unique_id_function=generate_unique_id_function, - ) - self.routes.append( - _IncludedRouter(original_router=router, include_context=include_context) - ) - self._mark_routes_changed() - for handler in router.on_startup: - self.add_event_handler("startup", handler) - for handler in router.on_shutdown: - self.add_event_handler("shutdown", handler) - self.lifespan_context = _merge_lifespan_context( - self.lifespan_context, - router.lifespan_context, - ) - - def get( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP GET operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - - app = FastAPI() - router = APIRouter() - - @router.get("/items/") - def read_items(): - return [{"name": "Empanada"}, {"name": "Arepa"}] - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["GET"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def put( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP PUT operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - router = APIRouter() - - @router.put("/items/{item_id}") - def replace_item(item_id: str, item: Item): - return {"message": "Item replaced", "id": item_id} - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["PUT"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def post( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP POST operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - router = APIRouter() - - @router.post("/items/") - def create_item(item: Item): - return {"message": "Item created"} - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["POST"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def delete( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP DELETE operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - - app = FastAPI() - router = APIRouter() - - @router.delete("/items/{item_id}") - def delete_item(item_id: str): - return {"message": "Item deleted"} - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["DELETE"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def options( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP OPTIONS operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - - app = FastAPI() - router = APIRouter() - - @router.options("/items/") - def get_item_options(): - return {"additions": ["Aji", "Guacamole"]} - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["OPTIONS"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def head( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP HEAD operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - router = APIRouter() - - @router.head("/items/", status_code=204) - def get_items_headers(response: Response): - response.headers["X-Cat-Dog"] = "Alone in the world" - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["HEAD"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def patch( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP PATCH operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - router = APIRouter() - - @router.patch("/items/") - def update_item(item: Item): - return {"message": "Item updated in place"} - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["PATCH"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - def trace( - self, - path: Annotated[ - str, - Doc( - """ - The URL path to be used for this *path operation*. - - For example, in `http://example.com/items`, the path is `/items`. - """ - ), - ], - *, - response_model: Annotated[ - Any, - Doc( - """ - The type to use for the response. - - It could be any valid Pydantic *field* type. So, it doesn't have to - be a Pydantic model, it could be other things, like a `list`, `dict`, - etc. - - It will be used for: - - * Documentation: the generated OpenAPI (and the UI at `/docs`) will - show it as the response (JSON Schema). - * Serialization: you could return an arbitrary object and the - `response_model` would be used to serialize that object into the - corresponding JSON. - * Filtering: the JSON sent to the client will only contain the data - (fields) defined in the `response_model`. If you returned an object - that contains an attribute `password` but the `response_model` does - not include that field, the JSON sent to the client would not have - that `password`. - * Validation: whatever you return will be serialized with the - `response_model`, converting any data as necessary to generate the - corresponding JSON. But if the data in the object returned is not - valid, that would mean a violation of the contract with the client, - so it's an error from the API developer. So, FastAPI will raise an - error and return a 500 error code (Internal Server Error). - - Read more about it in the - [FastAPI docs for Response Model](https://fastapi.tiangolo.com/tutorial/response-model/). - """ - ), - ] = Default(None), - status_code: Annotated[ - int | None, - Doc( - """ - The default status code to be used for the response. - - You could override the status code by returning a response directly. - - Read more about it in the - [FastAPI docs for Response Status Code](https://fastapi.tiangolo.com/tutorial/response-status-code/). - """ - ), - ] = None, - tags: Annotated[ - list[str | Enum] | None, - Doc( - """ - A list of tags to be applied to the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/#tags). - """ - ), - ] = None, - dependencies: Annotated[ - Sequence[params.Depends] | None, - Doc( - """ - A list of dependencies (using `Depends()`) to be applied to the - *path operation*. - - Read more about it in the - [FastAPI docs for Dependencies in path operation decorators](https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-in-path-operation-decorators/). - """ - ), - ] = None, - summary: Annotated[ - str | None, - Doc( - """ - A summary for the *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - A description for the *path operation*. - - If not provided, it will be extracted automatically from the docstring - of the *path operation function*. - - It can contain Markdown. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Path Operation Configuration](https://fastapi.tiangolo.com/tutorial/path-operation-configuration/). - """ - ), - ] = None, - response_description: Annotated[ - str, - Doc( - """ - The description for the default response. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = "Successful Response", - responses: Annotated[ - dict[int | str, dict[str, Any]] | None, - Doc( - """ - Additional responses that could be returned by this *path operation*. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - deprecated: Annotated[ - bool | None, - Doc( - """ - Mark this *path operation* as deprecated. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - operation_id: Annotated[ - str | None, - Doc( - """ - Custom operation ID to be used by this *path operation*. - - By default, it is generated automatically. - - If you provide a custom operation ID, you need to make sure it is - unique for the whole API. - - You can customize the - operation ID generation with the parameter - `generate_unique_id_function` in the `FastAPI` class. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = None, - response_model_include: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to include only certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_exclude: Annotated[ - IncEx | None, - Doc( - """ - Configuration passed to Pydantic to exclude certain fields in the - response data. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = None, - response_model_by_alias: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response model - should be serialized by alias when an alias is used. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_include-and-response_model_exclude). - """ - ), - ] = True, - response_model_exclude_unset: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that were not set and - have their default values. This is different from - `response_model_exclude_defaults` in that if the fields are set, - they will be included in the response, even if the value is the same - as the default. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_defaults: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data - should have all the fields, including the ones that have the same value - as the default. This is different from `response_model_exclude_unset` - in that if the fields are set but contain the same default values, - they will be excluded from the response. - - When `True`, default values are omitted from the response. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#use-the-response_model_exclude_unset-parameter). - """ - ), - ] = False, - response_model_exclude_none: Annotated[ - bool, - Doc( - """ - Configuration passed to Pydantic to define if the response data should - exclude fields set to `None`. - - This is much simpler (less smart) than `response_model_exclude_unset` - and `response_model_exclude_defaults`. You probably want to use one of - those two instead of this one, as those allow returning `None` values - when it makes sense. - - Read more about it in the - [FastAPI docs for Response Model - Return Type](https://fastapi.tiangolo.com/tutorial/response-model/#response_model_exclude_none). - """ - ), - ] = False, - include_in_schema: Annotated[ - bool, - Doc( - """ - Include this *path operation* in the generated OpenAPI schema. - - This affects the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for Query Parameters and String Validations](https://fastapi.tiangolo.com/tutorial/query-params-str-validations/#exclude-parameters-from-openapi). - """ - ), - ] = True, - response_class: Annotated[ - type[Response], - Doc( - """ - Response class to be used for this *path operation*. - - This will not be used if you return a response directly. - - Read more about it in the - [FastAPI docs for Custom Response - HTML, Stream, File, others](https://fastapi.tiangolo.com/advanced/custom-response/#redirectresponse). - """ - ), - ] = Default(JSONResponse), - name: Annotated[ - str | None, - Doc( - """ - Name for this *path operation*. Only used internally. - """ - ), - ] = None, - callbacks: Annotated[ - list[BaseRoute] | None, - Doc( - """ - List of *path operations* that will be used as OpenAPI callbacks. - - This is only for OpenAPI documentation, the callbacks won't be used - directly. - - It will be added to the generated OpenAPI (e.g. visible at `/docs`). - - Read more about it in the - [FastAPI docs for OpenAPI Callbacks](https://fastapi.tiangolo.com/advanced/openapi-callbacks/). - """ - ), - ] = None, - openapi_extra: Annotated[ - dict[str, Any] | None, - Doc( - """ - Extra metadata to be included in the OpenAPI schema for this *path - operation*. - - Read more about it in the - [FastAPI docs for Path Operation Advanced Configuration](https://fastapi.tiangolo.com/advanced/path-operation-advanced-configuration/#custom-openapi-path-operation-schema). - """ - ), - ] = None, - generate_unique_id_function: Annotated[ - Callable[[APIRoute], str], - Doc( - """ - Customize the function used to generate unique IDs for the *path - operations* shown in the generated OpenAPI. - - This is particularly useful when automatically generating clients or - SDKs for your API. - - Read more about it in the - [FastAPI docs about how to Generate Clients](https://fastapi.tiangolo.com/advanced/generate-clients/#custom-generate-unique-id-function). - """ - ), - ] = Default(generate_unique_id), - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add a *path operation* using an HTTP TRACE operation. - - ## Example - - ```python - from fastapi import APIRouter, FastAPI - from pydantic import BaseModel - - class Item(BaseModel): - name: str - description: str | None = None - - app = FastAPI() - router = APIRouter() - - @router.trace("/items/{item_id}") - def trace_item(item_id: str): - return None - - app.include_router(router) - ``` - """ - return self.api_route( - path=path, - response_model=response_model, - status_code=status_code, - tags=tags, - dependencies=dependencies, - summary=summary, - description=description, - response_description=response_description, - responses=responses, - deprecated=deprecated, - methods=["TRACE"], - operation_id=operation_id, - response_model_include=response_model_include, - response_model_exclude=response_model_exclude, - response_model_by_alias=response_model_by_alias, - response_model_exclude_unset=response_model_exclude_unset, - response_model_exclude_defaults=response_model_exclude_defaults, - response_model_exclude_none=response_model_exclude_none, - include_in_schema=include_in_schema, - response_class=response_class, - name=name, - callbacks=callbacks, - openapi_extra=openapi_extra, - generate_unique_id_function=generate_unique_id_function, - ) - - # TODO: remove this once the lifespan (or alternative) interface is improved - async def _startup(self) -> None: - """ - Run any `.on_startup` event handlers. - - This method is kept for backward compatibility after Starlette removed - support for on_startup/on_shutdown handlers. - - Ref: https://github.com/Kludex/starlette/pull/3117 - """ - for handler in self.on_startup: - if is_async_callable(handler): - await handler() - else: - handler() - - # TODO: remove this once the lifespan (or alternative) interface is improved - async def _shutdown(self) -> None: - """ - Run any `.on_shutdown` event handlers. - - This method is kept for backward compatibility after Starlette removed - support for on_startup/on_shutdown handlers. - - Ref: https://github.com/Kludex/starlette/pull/3117 - """ - for handler in self.on_shutdown: - if is_async_callable(handler): - await handler() - else: - handler() - - # TODO: remove this once the lifespan (or alternative) interface is improved - def add_event_handler( - self, - event_type: str, - func: Callable[[], Any], - ) -> None: - """ - Add an event handler function for startup or shutdown. - - This method is kept for backward compatibility after Starlette removed - support for on_startup/on_shutdown handlers. - - Ref: https://github.com/Kludex/starlette/pull/3117 - """ - assert event_type in ("startup", "shutdown") - if event_type == "startup": - self.on_startup.append(func) - else: - self.on_shutdown.append(func) - - @deprecated( - """ - on_event is deprecated, use lifespan event handlers instead. - - Read more about it in the - [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/). - """ - ) - def on_event( - self, - event_type: Annotated[ - str, - Doc( - """ - The type of event. `startup` or `shutdown`. - """ - ), - ], - ) -> Callable[[DecoratedCallable], DecoratedCallable]: - """ - Add an event handler for the router. - - `on_event` is deprecated, use `lifespan` event handlers instead. - - Read more about it in the - [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated). - """ - - def decorator(func: DecoratedCallable) -> DecoratedCallable: - self.add_event_handler(event_type, func) - return func - - return decorator diff --git a/server/venv/Lib/site-packages/fastapi/security/__init__.py b/server/venv/Lib/site-packages/fastapi/security/__init__.py deleted file mode 100644 index 3aa6bf2..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -from .api_key import APIKeyCookie as APIKeyCookie -from .api_key import APIKeyHeader as APIKeyHeader -from .api_key import APIKeyQuery as APIKeyQuery -from .http import HTTPAuthorizationCredentials as HTTPAuthorizationCredentials -from .http import HTTPBasic as HTTPBasic -from .http import HTTPBasicCredentials as HTTPBasicCredentials -from .http import HTTPBearer as HTTPBearer -from .http import HTTPDigest as HTTPDigest -from .oauth2 import OAuth2 as OAuth2 -from .oauth2 import OAuth2AuthorizationCodeBearer as OAuth2AuthorizationCodeBearer -from .oauth2 import OAuth2PasswordBearer as OAuth2PasswordBearer -from .oauth2 import OAuth2PasswordRequestForm as OAuth2PasswordRequestForm -from .oauth2 import OAuth2PasswordRequestFormStrict as OAuth2PasswordRequestFormStrict -from .oauth2 import SecurityScopes as SecurityScopes -from .open_id_connect_url import OpenIdConnect as OpenIdConnect diff --git a/server/venv/Lib/site-packages/fastapi/security/api_key.py b/server/venv/Lib/site-packages/fastapi/security/api_key.py deleted file mode 100644 index 83a4585..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/api_key.py +++ /dev/null @@ -1,320 +0,0 @@ -from typing import Annotated - -from annotated_doc import Doc -from fastapi.openapi.models import APIKey, APIKeyIn -from fastapi.security.base import SecurityBase -from starlette.exceptions import HTTPException -from starlette.requests import Request -from starlette.status import HTTP_401_UNAUTHORIZED - - -class APIKeyBase(SecurityBase): - model: APIKey - - def __init__( - self, - location: APIKeyIn, - name: str, - description: str | None, - scheme_name: str | None, - auto_error: bool, - ): - self.auto_error = auto_error - - self.model: APIKey = APIKey( - **{"in": location}, # ty: ignore[invalid-argument-type] - name=name, - description=description, - ) - self.scheme_name = scheme_name or self.__class__.__name__ - - def make_not_authenticated_error(self) -> HTTPException: - """ - The WWW-Authenticate header is not standardized for API Key authentication but - the HTTP specification requires that an error of 401 "Unauthorized" must - include a WWW-Authenticate header. - - Ref: https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized - - For this, this method sends a custom challenge `APIKey`. - """ - return HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers={"WWW-Authenticate": "APIKey"}, - ) - - def check_api_key(self, api_key: str | None) -> str | None: - if not api_key: - if self.auto_error: - raise self.make_not_authenticated_error() - return None - return api_key - - -class APIKeyQuery(APIKeyBase): - """ - API key authentication using a query parameter. - - This defines the name of the query parameter that should be provided in the request - with the API key and integrates that into the OpenAPI documentation. It extracts - the key value sent in the query parameter automatically and provides it as the - dependency result. But it doesn't define how to send that API key to the client. - - ## Usage - - Create an instance object and use that object as the dependency in `Depends()`. - - The dependency result will be a string containing the key value. - - ## Example - - ```python - from fastapi import Depends, FastAPI - from fastapi.security import APIKeyQuery - - app = FastAPI() - - query_scheme = APIKeyQuery(name="api_key") - - - @app.get("/items/") - async def read_items(api_key: str = Depends(query_scheme)): - return {"api_key": api_key} - ``` - """ - - def __init__( - self, - *, - name: Annotated[ - str, - Doc("Query parameter name."), - ], - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if the query parameter is not provided, `APIKeyQuery` will - automatically cancel the request and send the client an error. - - If `auto_error` is set to `False`, when the query parameter is not - available, instead of erroring out, the dependency result will be - `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, in a query - parameter or in an HTTP Bearer token). - """ - ), - ] = True, - ): - super().__init__( - location=APIKeyIn.query, - name=name, - scheme_name=scheme_name, - description=description, - auto_error=auto_error, - ) - - async def __call__(self, request: Request) -> str | None: - api_key = request.query_params.get(self.model.name) - return self.check_api_key(api_key) - - -class APIKeyHeader(APIKeyBase): - """ - API key authentication using a header. - - This defines the name of the header that should be provided in the request with - the API key and integrates that into the OpenAPI documentation. It extracts - the key value sent in the header automatically and provides it as the dependency - result. But it doesn't define how to send that key to the client. - - ## Usage - - Create an instance object and use that object as the dependency in `Depends()`. - - The dependency result will be a string containing the key value. - - ## Example - - ```python - from fastapi import Depends, FastAPI - from fastapi.security import APIKeyHeader - - app = FastAPI() - - header_scheme = APIKeyHeader(name="x-key") - - - @app.get("/items/") - async def read_items(key: str = Depends(header_scheme)): - return {"key": key} - ``` - """ - - def __init__( - self, - *, - name: Annotated[str, Doc("Header name.")], - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if the header is not provided, `APIKeyHeader` will - automatically cancel the request and send the client an error. - - If `auto_error` is set to `False`, when the header is not available, - instead of erroring out, the dependency result will be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, in a header or - in an HTTP Bearer token). - """ - ), - ] = True, - ): - super().__init__( - location=APIKeyIn.header, - name=name, - scheme_name=scheme_name, - description=description, - auto_error=auto_error, - ) - - async def __call__(self, request: Request) -> str | None: - api_key = request.headers.get(self.model.name) - return self.check_api_key(api_key) - - -class APIKeyCookie(APIKeyBase): - """ - API key authentication using a cookie. - - This defines the name of the cookie that should be provided in the request with - the API key and integrates that into the OpenAPI documentation. It extracts - the key value sent in the cookie automatically and provides it as the dependency - result. But it doesn't define how to set that cookie. - - ## Usage - - Create an instance object and use that object as the dependency in `Depends()`. - - The dependency result will be a string containing the key value. - - ## Example - - ```python - from fastapi import Depends, FastAPI - from fastapi.security import APIKeyCookie - - app = FastAPI() - - cookie_scheme = APIKeyCookie(name="session") - - - @app.get("/items/") - async def read_items(session: str = Depends(cookie_scheme)): - return {"session": session} - ``` - """ - - def __init__( - self, - *, - name: Annotated[str, Doc("Cookie name.")], - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if the cookie is not provided, `APIKeyCookie` will - automatically cancel the request and send the client an error. - - If `auto_error` is set to `False`, when the cookie is not available, - instead of erroring out, the dependency result will be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, in a cookie or - in an HTTP Bearer token). - """ - ), - ] = True, - ): - super().__init__( - location=APIKeyIn.cookie, - name=name, - scheme_name=scheme_name, - description=description, - auto_error=auto_error, - ) - - async def __call__(self, request: Request) -> str | None: - api_key = request.cookies.get(self.model.name) - return self.check_api_key(api_key) diff --git a/server/venv/Lib/site-packages/fastapi/security/base.py b/server/venv/Lib/site-packages/fastapi/security/base.py deleted file mode 100644 index c43555d..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/base.py +++ /dev/null @@ -1,6 +0,0 @@ -from fastapi.openapi.models import SecurityBase as SecurityBaseModel - - -class SecurityBase: - model: SecurityBaseModel - scheme_name: str diff --git a/server/venv/Lib/site-packages/fastapi/security/http.py b/server/venv/Lib/site-packages/fastapi/security/http.py deleted file mode 100644 index a32948e..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/http.py +++ /dev/null @@ -1,417 +0,0 @@ -import binascii -from base64 import b64decode -from typing import Annotated - -from annotated_doc import Doc -from fastapi.exceptions import HTTPException -from fastapi.openapi.models import HTTPBase as HTTPBaseModel -from fastapi.openapi.models import HTTPBearer as HTTPBearerModel -from fastapi.security.base import SecurityBase -from fastapi.security.utils import get_authorization_scheme_param -from pydantic import BaseModel -from starlette.requests import Request -from starlette.status import HTTP_401_UNAUTHORIZED - - -class HTTPBasicCredentials(BaseModel): - """ - The HTTP Basic credentials given as the result of using `HTTPBasic` in a - dependency. - - Read more about it in the - [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/). - """ - - username: Annotated[str, Doc("The HTTP Basic username.")] - password: Annotated[str, Doc("The HTTP Basic password.")] - - -class HTTPAuthorizationCredentials(BaseModel): - """ - The HTTP authorization credentials in the result of using `HTTPBearer` or - `HTTPDigest` in a dependency. - - The HTTP authorization header value is split by the first space. - - The first part is the `scheme`, the second part is the `credentials`. - - For example, in an HTTP Bearer token scheme, the client will send a header - like: - - ``` - Authorization: Bearer deadbeef12346 - ``` - - In this case: - - * `scheme` will have the value `"Bearer"` - * `credentials` will have the value `"deadbeef12346"` - """ - - scheme: Annotated[ - str, - Doc( - """ - The HTTP authorization scheme extracted from the header value. - """ - ), - ] - credentials: Annotated[ - str, - Doc( - """ - The HTTP authorization credentials extracted from the header value. - """ - ), - ] - - -class HTTPBase(SecurityBase): - model: HTTPBaseModel - - def __init__( - self, - *, - scheme: str, - scheme_name: str | None = None, - description: str | None = None, - auto_error: bool = True, - ): - self.model = HTTPBaseModel(scheme=scheme, description=description) - self.scheme_name = scheme_name or self.__class__.__name__ - self.auto_error = auto_error - - def make_authenticate_headers(self) -> dict[str, str]: - return {"WWW-Authenticate": f"{self.model.scheme.title()}"} - - def make_not_authenticated_error(self) -> HTTPException: - return HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers=self.make_authenticate_headers(), - ) - - async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: - authorization = request.headers.get("Authorization") - scheme, credentials = get_authorization_scheme_param(authorization) - if not (authorization and scheme and credentials): - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) - - -class HTTPBasic(HTTPBase): - """ - HTTP Basic authentication. - - Ref: https://datatracker.ietf.org/doc/html/rfc7617 - - ## Usage - - Create an instance object and use that object as the dependency in `Depends()`. - - The dependency result will be an `HTTPBasicCredentials` object containing the - `username` and the `password`. - - Read more about it in the - [FastAPI docs for HTTP Basic Auth](https://fastapi.tiangolo.com/advanced/security/http-basic-auth/). - - ## Example - - ```python - from typing import Annotated - - from fastapi import Depends, FastAPI - from fastapi.security import HTTPBasic, HTTPBasicCredentials - - app = FastAPI() - - security = HTTPBasic() - - - @app.get("/users/me") - def read_current_user(credentials: Annotated[HTTPBasicCredentials, Depends(security)]): - return {"username": credentials.username, "password": credentials.password} - ``` - """ - - def __init__( - self, - *, - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - realm: Annotated[ - str | None, - Doc( - """ - HTTP Basic authentication realm. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if the HTTP Basic authentication is not provided (a - header), `HTTPBasic` will automatically cancel the request and send the - client an error. - - If `auto_error` is set to `False`, when the HTTP Basic authentication - is not available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, in HTTP Basic - authentication or in an HTTP Bearer token). - """ - ), - ] = True, - ): - self.model = HTTPBaseModel(scheme="basic", description=description) - self.scheme_name = scheme_name or self.__class__.__name__ - self.realm = realm - self.auto_error = auto_error - - def make_authenticate_headers(self) -> dict[str, str]: - if self.realm: - return {"WWW-Authenticate": f'Basic realm="{self.realm}"'} - return {"WWW-Authenticate": "Basic"} - - async def __call__( # type: ignore - self, request: Request - ) -> HTTPBasicCredentials | None: - authorization = request.headers.get("Authorization") - scheme, param = get_authorization_scheme_param(authorization) - if not authorization or scheme.lower() != "basic": - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - try: - data = b64decode(param).decode("ascii") - except (ValueError, UnicodeDecodeError, binascii.Error) as e: - raise self.make_not_authenticated_error() from e - username, separator, password = data.partition(":") - if not separator: - raise self.make_not_authenticated_error() - return HTTPBasicCredentials(username=username, password=password) - - -class HTTPBearer(HTTPBase): - """ - HTTP Bearer token authentication. - - ## Usage - - Create an instance object and use that object as the dependency in `Depends()`. - - The dependency result will be an `HTTPAuthorizationCredentials` object containing - the `scheme` and the `credentials`. - - ## Example - - ```python - from typing import Annotated - - from fastapi import Depends, FastAPI - from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer - - app = FastAPI() - - security = HTTPBearer() - - - @app.get("/users/me") - def read_current_user( - credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)] - ): - return {"scheme": credentials.scheme, "credentials": credentials.credentials} - ``` - """ - - def __init__( - self, - *, - bearerFormat: Annotated[str | None, Doc("Bearer token format.")] = None, - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if the HTTP Bearer token is not provided (in an - `Authorization` header), `HTTPBearer` will automatically cancel the - request and send the client an error. - - If `auto_error` is set to `False`, when the HTTP Bearer token - is not available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, in an HTTP - Bearer token or in a cookie). - """ - ), - ] = True, - ): - self.model = HTTPBearerModel(bearerFormat=bearerFormat, description=description) - self.scheme_name = scheme_name or self.__class__.__name__ - self.auto_error = auto_error - - async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: - authorization = request.headers.get("Authorization") - scheme, credentials = get_authorization_scheme_param(authorization) - if not (authorization and scheme and credentials): - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - if scheme.lower() != "bearer": - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) - - -class HTTPDigest(HTTPBase): - """ - HTTP Digest authentication. - - **Warning**: this is only a stub to connect the components with OpenAPI in FastAPI, - but it doesn't implement the full Digest scheme, you would need to subclass it - and implement it in your code. - - Ref: https://datatracker.ietf.org/doc/html/rfc7616 - - ## Usage - - Create an instance object and use that object as the dependency in `Depends()`. - - The dependency result will be an `HTTPAuthorizationCredentials` object containing - the `scheme` and the `credentials`. - - ## Example - - ```python - from typing import Annotated - - from fastapi import Depends, FastAPI - from fastapi.security import HTTPAuthorizationCredentials, HTTPDigest - - app = FastAPI() - - security = HTTPDigest() - - - @app.get("/users/me") - def read_current_user( - credentials: Annotated[HTTPAuthorizationCredentials, Depends(security)] - ): - return {"scheme": credentials.scheme, "credentials": credentials.credentials} - ``` - """ - - def __init__( - self, - *, - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if the HTTP Digest is not provided, `HTTPDigest` will - automatically cancel the request and send the client an error. - - If `auto_error` is set to `False`, when the HTTP Digest is not - available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, in HTTP - Digest or in a cookie). - """ - ), - ] = True, - ): - self.model = HTTPBaseModel(scheme="digest", description=description) - self.scheme_name = scheme_name or self.__class__.__name__ - self.auto_error = auto_error - - async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None: - authorization = request.headers.get("Authorization") - scheme, credentials = get_authorization_scheme_param(authorization) - if not (authorization and scheme and credentials): - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - if scheme.lower() != "digest": - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials) diff --git a/server/venv/Lib/site-packages/fastapi/security/oauth2.py b/server/venv/Lib/site-packages/fastapi/security/oauth2.py deleted file mode 100644 index 3fd9e41..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/oauth2.py +++ /dev/null @@ -1,693 +0,0 @@ -from typing import Annotated, Any, cast - -from annotated_doc import Doc -from fastapi.exceptions import HTTPException -from fastapi.openapi.models import OAuth2 as OAuth2Model -from fastapi.openapi.models import OAuthFlows as OAuthFlowsModel -from fastapi.param_functions import Form -from fastapi.security.base import SecurityBase -from fastapi.security.utils import get_authorization_scheme_param -from starlette.requests import Request -from starlette.status import HTTP_401_UNAUTHORIZED - - -class OAuth2PasswordRequestForm: - """ - This is a dependency class to collect the `username` and `password` as form data - for an OAuth2 password flow. - - The OAuth2 specification dictates that for a password flow the data should be - collected using form data (instead of JSON) and that it should have the specific - fields `username` and `password`. - - All the initialization parameters are extracted from the request. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - - ## Example - - ```python - from typing import Annotated - - from fastapi import Depends, FastAPI - from fastapi.security import OAuth2PasswordRequestForm - - app = FastAPI() - - - @app.post("/login") - def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]): - data = {} - data["scopes"] = [] - for scope in form_data.scopes: - data["scopes"].append(scope) - if form_data.client_id: - data["client_id"] = form_data.client_id - if form_data.client_secret: - data["client_secret"] = form_data.client_secret - return data - ``` - - Note that for OAuth2 the scope `items:read` is a single scope in an opaque string. - You could have custom internal logic to separate it by colon characters (`:`) or - similar, and get the two parts `items` and `read`. Many applications do that to - group and organize permissions, you could do it as well in your application, just - know that it is application specific, it's not part of the specification. - """ - - def __init__( - self, - *, - grant_type: Annotated[ - str | None, - Form(pattern="^password$"), - Doc( - """ - The OAuth2 spec says it is required and MUST be the fixed string - "password". Nevertheless, this dependency class is permissive and - allows not passing it. If you want to enforce it, use instead the - `OAuth2PasswordRequestFormStrict` dependency. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ] = None, - username: Annotated[ - str, - Form(), - Doc( - """ - `username` string. The OAuth2 spec requires the exact field name - `username`. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ], - password: Annotated[ - str, - Form(json_schema_extra={"format": "password"}), - Doc( - """ - `password` string. The OAuth2 spec requires the exact field name - `password`. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ], - scope: Annotated[ - str, - Form(), - Doc( - """ - A single string with actually several scopes separated by spaces. Each - scope is also a string. - - For example, a single string with: - - ```python - "items:read items:write users:read profile openid" - ```` - - would represent the scopes: - - * `items:read` - * `items:write` - * `users:read` - * `profile` - * `openid` - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ] = "", - client_id: Annotated[ - str | None, - Form(), - Doc( - """ - If there's a `client_id`, it can be sent as part of the form fields. - But the OAuth2 specification recommends sending the `client_id` and - `client_secret` (if any) using HTTP Basic auth. - """ - ), - ] = None, - client_secret: Annotated[ - str | None, - Form(json_schema_extra={"format": "password"}), - Doc( - """ - If there's a `client_secret` (and a `client_id`), they can be sent - as part of the form fields. But the OAuth2 specification recommends - sending the `client_id` and `client_secret` (if any) using HTTP Basic - auth. - """ - ), - ] = None, - ): - self.grant_type = grant_type - self.username = username - self.password = password - self.scopes = scope.split() - self.client_id = client_id - self.client_secret = client_secret - - -class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm): - """ - This is a dependency class to collect the `username` and `password` as form data - for an OAuth2 password flow. - - The OAuth2 specification dictates that for a password flow the data should be - collected using form data (instead of JSON) and that it should have the specific - fields `username` and `password`. - - All the initialization parameters are extracted from the request. - - The only difference between `OAuth2PasswordRequestFormStrict` and - `OAuth2PasswordRequestForm` is that `OAuth2PasswordRequestFormStrict` requires the - client to send the form field `grant_type` with the value `"password"`, which - is required in the OAuth2 specification (it seems that for no particular reason), - while for `OAuth2PasswordRequestForm` `grant_type` is optional. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - - ## Example - - ```python - from typing import Annotated - - from fastapi import Depends, FastAPI - from fastapi.security import OAuth2PasswordRequestForm - - app = FastAPI() - - - @app.post("/login") - def login(form_data: Annotated[OAuth2PasswordRequestFormStrict, Depends()]): - data = {} - data["scopes"] = [] - for scope in form_data.scopes: - data["scopes"].append(scope) - if form_data.client_id: - data["client_id"] = form_data.client_id - if form_data.client_secret: - data["client_secret"] = form_data.client_secret - return data - ``` - - Note that for OAuth2 the scope `items:read` is a single scope in an opaque string. - You could have custom internal logic to separate it by colon characters (`:`) or - similar, and get the two parts `items` and `read`. Many applications do that to - group and organize permissions, you could do it as well in your application, just - know that it is application specific, it's not part of the specification. - - - grant_type: the OAuth2 spec says it is required and MUST be the fixed string "password". - This dependency is strict about it. If you want to be permissive, use instead the - OAuth2PasswordRequestForm dependency class. - username: username string. The OAuth2 spec requires the exact field name "username". - password: password string. The OAuth2 spec requires the exact field name "password". - scope: Optional string. Several scopes (each one a string) separated by spaces. E.g. - "items:read items:write users:read profile openid" - client_id: optional string. OAuth2 recommends sending the client_id and client_secret (if any) - using HTTP Basic auth, as: client_id:client_secret - client_secret: optional string. OAuth2 recommends sending the client_id and client_secret (if any) - using HTTP Basic auth, as: client_id:client_secret - """ - - def __init__( - self, - grant_type: Annotated[ - str, - Form(pattern="^password$"), - Doc( - """ - The OAuth2 spec says it is required and MUST be the fixed string - "password". This dependency is strict about it. If you want to be - permissive, use instead the `OAuth2PasswordRequestForm` dependency - class. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ], - username: Annotated[ - str, - Form(), - Doc( - """ - `username` string. The OAuth2 spec requires the exact field name - `username`. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ], - password: Annotated[ - str, - Form(), - Doc( - """ - `password` string. The OAuth2 spec requires the exact field name - `password`. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ], - scope: Annotated[ - str, - Form(), - Doc( - """ - A single string with actually several scopes separated by spaces. Each - scope is also a string. - - For example, a single string with: - - ```python - "items:read items:write users:read profile openid" - ```` - - would represent the scopes: - - * `items:read` - * `items:write` - * `users:read` - * `profile` - * `openid` - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ] = "", - client_id: Annotated[ - str | None, - Form(), - Doc( - """ - If there's a `client_id`, it can be sent as part of the form fields. - But the OAuth2 specification recommends sending the `client_id` and - `client_secret` (if any) using HTTP Basic auth. - """ - ), - ] = None, - client_secret: Annotated[ - str | None, - Form(), - Doc( - """ - If there's a `client_secret` (and a `client_id`), they can be sent - as part of the form fields. But the OAuth2 specification recommends - sending the `client_id` and `client_secret` (if any) using HTTP Basic - auth. - """ - ), - ] = None, - ): - super().__init__( - grant_type=grant_type, - username=username, - password=password, - scope=scope, - client_id=client_id, - client_secret=client_secret, - ) - - -class OAuth2(SecurityBase): - """ - This is the base class for OAuth2 authentication, an instance of it would be used - as a dependency. All other OAuth2 classes inherit from it and customize it for - each OAuth2 flow. - - You normally would not create a new class inheriting from it but use one of the - existing subclasses, and maybe compose them if you want to support multiple flows. - - Read more about it in the - [FastAPI docs for Security](https://fastapi.tiangolo.com/tutorial/security/). - """ - - def __init__( - self, - *, - flows: Annotated[ - OAuthFlowsModel | dict[str, dict[str, Any]], - Doc( - """ - The dictionary of OAuth2 flows. - """ - ), - ] = OAuthFlowsModel(), - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if no HTTP Authorization header is provided, required for - OAuth2 authentication, it will automatically cancel the request and - send the client an error. - - If `auto_error` is set to `False`, when the HTTP Authorization header - is not available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, with OAuth2 - or in a cookie). - """ - ), - ] = True, - ): - self.model = OAuth2Model( - flows=cast(OAuthFlowsModel, flows), description=description - ) - self.scheme_name = scheme_name or self.__class__.__name__ - self.auto_error = auto_error - - def make_not_authenticated_error(self) -> HTTPException: - """ - The OAuth 2 specification doesn't define the challenge that should be used, - because a `Bearer` token is not really the only option to authenticate. - - But declaring any other authentication challenge would be application-specific - as it's not defined in the specification. - - For practical reasons, this method uses the `Bearer` challenge by default, as - it's probably the most common one. - - If you are implementing an OAuth2 authentication scheme other than the provided - ones in FastAPI (based on bearer tokens), you might want to override this. - - Ref: https://datatracker.ietf.org/doc/html/rfc6749 - """ - return HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers={"WWW-Authenticate": "Bearer"}, - ) - - async def __call__(self, request: Request) -> str | None: - authorization = request.headers.get("Authorization") - if not authorization: - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - return authorization - - -class OAuth2PasswordBearer(OAuth2): - """ - OAuth2 flow for authentication using a bearer token obtained with a password. - An instance of it would be used as a dependency. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - - def __init__( - self, - tokenUrl: Annotated[ - str, - Doc( - """ - The URL to obtain the OAuth2 token. This would be the *path operation* - that has `OAuth2PasswordRequestForm` as a dependency. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ], - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - scopes: Annotated[ - dict[str, str] | None, - Doc( - """ - The OAuth2 scopes that would be required by the *path operations* that - use this dependency. - - Read more about it in the - [FastAPI docs for Simple OAuth2 with Password and Bearer](https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if no HTTP Authorization header is provided, required for - OAuth2 authentication, it will automatically cancel the request and - send the client an error. - - If `auto_error` is set to `False`, when the HTTP Authorization header - is not available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, with OAuth2 - or in a cookie). - """ - ), - ] = True, - refreshUrl: Annotated[ - str | None, - Doc( - """ - The URL to refresh the token and obtain a new one. - """ - ), - ] = None, - ): - if not scopes: - scopes = {} - flows = OAuthFlowsModel( - password=cast( - Any, - { - "tokenUrl": tokenUrl, - "refreshUrl": refreshUrl, - "scopes": scopes, - }, - ) - ) - super().__init__( - flows=flows, - scheme_name=scheme_name, - description=description, - auto_error=auto_error, - ) - - async def __call__(self, request: Request) -> str | None: - authorization = request.headers.get("Authorization") - scheme, param = get_authorization_scheme_param(authorization) - if not authorization or scheme.lower() != "bearer": - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - return param - - -class OAuth2AuthorizationCodeBearer(OAuth2): - """ - OAuth2 flow for authentication using a bearer token obtained with an OAuth2 code - flow. An instance of it would be used as a dependency. - """ - - def __init__( - self, - authorizationUrl: str, - tokenUrl: Annotated[ - str, - Doc( - """ - The URL to obtain the OAuth2 token. - """ - ), - ], - refreshUrl: Annotated[ - str | None, - Doc( - """ - The URL to refresh the token and obtain a new one. - """ - ), - ] = None, - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - scopes: Annotated[ - dict[str, str] | None, - Doc( - """ - The OAuth2 scopes that would be required by the *path operations* that - use this dependency. - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if no HTTP Authorization header is provided, required for - OAuth2 authentication, it will automatically cancel the request and - send the client an error. - - If `auto_error` is set to `False`, when the HTTP Authorization header - is not available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, with OAuth2 - or in a cookie). - """ - ), - ] = True, - ): - if not scopes: - scopes = {} - flows = OAuthFlowsModel( - authorizationCode=cast( - Any, - { - "authorizationUrl": authorizationUrl, - "tokenUrl": tokenUrl, - "refreshUrl": refreshUrl, - "scopes": scopes, - }, - ) - ) - super().__init__( - flows=flows, - scheme_name=scheme_name, - description=description, - auto_error=auto_error, - ) - - async def __call__(self, request: Request) -> str | None: - authorization = request.headers.get("Authorization") - scheme, param = get_authorization_scheme_param(authorization) - if not authorization or scheme.lower() != "bearer": - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None # pragma: nocover - return param - - -class SecurityScopes: - """ - This is a special class that you can define in a parameter in a dependency to - obtain the OAuth2 scopes required by all the dependencies in the same chain. - - This way, multiple dependencies can have different scopes, even when used in the - same *path operation*. And with this, you can access all the scopes required in - all those dependencies in a single place. - - Read more about it in the - [FastAPI docs for OAuth2 scopes](https://fastapi.tiangolo.com/advanced/security/oauth2-scopes/). - """ - - def __init__( - self, - scopes: Annotated[ - list[str] | None, - Doc( - """ - This will be filled by FastAPI. - """ - ), - ] = None, - ): - self.scopes: Annotated[ - list[str], - Doc( - """ - The list of all the scopes required by dependencies. - """ - ), - ] = scopes or [] - self.scope_str: Annotated[ - str, - Doc( - """ - All the scopes required by all the dependencies in a single string - separated by spaces, as defined in the OAuth2 specification. - """ - ), - ] = " ".join(self.scopes) diff --git a/server/venv/Lib/site-packages/fastapi/security/open_id_connect_url.py b/server/venv/Lib/site-packages/fastapi/security/open_id_connect_url.py deleted file mode 100644 index 125a819..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/open_id_connect_url.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import Annotated - -from annotated_doc import Doc -from fastapi.openapi.models import OpenIdConnect as OpenIdConnectModel -from fastapi.security.base import SecurityBase -from starlette.exceptions import HTTPException -from starlette.requests import Request -from starlette.status import HTTP_401_UNAUTHORIZED - - -class OpenIdConnect(SecurityBase): - """ - OpenID Connect authentication class. An instance of it would be used as a - dependency. - - **Warning**: this is only a stub to connect the components with OpenAPI in FastAPI, - but it doesn't implement the full OpenIdConnect scheme, for example, it doesn't use - the OpenIDConnect URL. You would need to subclass it and implement it in your - code. - """ - - def __init__( - self, - *, - openIdConnectUrl: Annotated[ - str, - Doc( - """ - The OpenID Connect URL. - """ - ), - ], - scheme_name: Annotated[ - str | None, - Doc( - """ - Security scheme name. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - description: Annotated[ - str | None, - Doc( - """ - Security scheme description. - - It will be included in the generated OpenAPI (e.g. visible at `/docs`). - """ - ), - ] = None, - auto_error: Annotated[ - bool, - Doc( - """ - By default, if no HTTP Authorization header is provided, required for - OpenID Connect authentication, it will automatically cancel the request - and send the client an error. - - If `auto_error` is set to `False`, when the HTTP Authorization header - is not available, instead of erroring out, the dependency result will - be `None`. - - This is useful when you want to have optional authentication. - - It is also useful when you want to have authentication that can be - provided in one of multiple optional ways (for example, with OpenID - Connect or in a cookie). - """ - ), - ] = True, - ): - self.model = OpenIdConnectModel( - openIdConnectUrl=openIdConnectUrl, description=description - ) - self.scheme_name = scheme_name or self.__class__.__name__ - self.auto_error = auto_error - - def make_not_authenticated_error(self) -> HTTPException: - return HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Not authenticated", - headers={"WWW-Authenticate": "Bearer"}, - ) - - async def __call__(self, request: Request) -> str | None: - authorization = request.headers.get("Authorization") - if not authorization: - if self.auto_error: - raise self.make_not_authenticated_error() - else: - return None - return authorization diff --git a/server/venv/Lib/site-packages/fastapi/security/utils.py b/server/venv/Lib/site-packages/fastapi/security/utils.py deleted file mode 100644 index 8ee66fd..0000000 --- a/server/venv/Lib/site-packages/fastapi/security/utils.py +++ /dev/null @@ -1,7 +0,0 @@ -def get_authorization_scheme_param( - authorization_header_value: str | None, -) -> tuple[str, str]: - if not authorization_header_value: - return "", "" - scheme, _, param = authorization_header_value.partition(" ") - return scheme, param.strip() diff --git a/server/venv/Lib/site-packages/fastapi/sse.py b/server/venv/Lib/site-packages/fastapi/sse.py deleted file mode 100644 index 1e2bd86..0000000 --- a/server/venv/Lib/site-packages/fastapi/sse.py +++ /dev/null @@ -1,235 +0,0 @@ -from typing import Annotated, Any - -from annotated_doc import Doc -from pydantic import AfterValidator, BaseModel, Field, model_validator -from starlette.responses import StreamingResponse - -# Canonical SSE event schema matching the OpenAPI 3.2 spec -# (Section 4.14.4 "Special Considerations for Server-Sent Events") -_SSE_EVENT_SCHEMA: dict[str, Any] = { - "type": "object", - "properties": { - "data": {"type": "string"}, - "event": {"type": "string"}, - "id": {"type": "string"}, - "retry": {"type": "integer", "minimum": 0}, - }, -} - - -class EventSourceResponse(StreamingResponse): - """Streaming response with `text/event-stream` media type. - - Use as `response_class=EventSourceResponse` on a *path operation* that uses `yield` - to enable Server Sent Events (SSE) responses. - - Works with **any HTTP method** (`GET`, `POST`, etc.), which makes it compatible - with protocols like MCP that stream SSE over `POST`. - - The actual encoding logic lives in the FastAPI routing layer. This class - serves mainly as a marker and sets the correct `Content-Type`. - """ - - media_type = "text/event-stream" - - -def _check_single_line(v: str | None, field_name: str) -> str | None: - if v is not None and ("\r" in v or "\n" in v): - raise ValueError(f"SSE '{field_name}' must be a single line") - return v - - -def _check_event_single_line(v: str | None) -> str | None: - return _check_single_line(v, "event") - - -def _check_id_valid(v: str | None) -> str | None: - if v is not None and "\0" in v: - raise ValueError("SSE 'id' must not contain null characters") - return _check_single_line(v, "id") - - -class ServerSentEvent(BaseModel): - """Represents a single Server-Sent Event. - - When `yield`ed from a *path operation function* that uses - `response_class=EventSourceResponse`, each `ServerSentEvent` is encoded - into the [SSE wire format](https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream) - (`text/event-stream`). - - If you yield a plain object (dict, Pydantic model, etc.) instead, it is - automatically JSON-encoded and sent as the `data:` field. - - All `data` values **including plain strings** are JSON-serialized. - - For example, `data="hello"` produces `data: "hello"` on the wire (with - quotes). - """ - - data: Annotated[ - Any, - Doc( - """ - The event payload. - - Can be any JSON-serializable value: a Pydantic model, dict, list, - string, number, etc. It is **always** serialized to JSON: strings - are quoted (`"hello"` becomes `data: "hello"` on the wire). - - Mutually exclusive with `raw_data`. - """ - ), - ] = None - raw_data: Annotated[ - str | None, - Doc( - """ - Raw string to send as the `data:` field **without** JSON encoding. - - Use this when you need to send pre-formatted text, HTML fragments, - CSV lines, or any non-JSON payload. The string is placed directly - into the `data:` field as-is. - - Mutually exclusive with `data`. - """ - ), - ] = None - event: Annotated[ - str | None, - AfterValidator(_check_event_single_line), - Doc( - """ - Optional event type name. - - Maps to `addEventListener(event, ...)` on the browser. When omitted, - the browser dispatches on the generic `message` event. Must be a - single line. - """ - ), - ] = None - id: Annotated[ - str | None, - AfterValidator(_check_id_valid), - Doc( - """ - Optional event ID. - - The browser sends this value back as the `Last-Event-ID` header on - automatic reconnection. **Must be a single line** and must not contain - null (`\\0`) characters. - """ - ), - ] = None - retry: Annotated[ - int | None, - Field(ge=0), - Doc( - """ - Optional reconnection time in **milliseconds**. - - Tells the browser how long to wait before reconnecting after the - connection is lost. Must be a non-negative integer. - """ - ), - ] = None - comment: Annotated[ - str | None, - Doc( - """ - Optional comment line(s). - - Comment lines start with `:` in the SSE wire format and are ignored by - `EventSource` clients. Useful for keep-alive pings to prevent - proxy/load-balancer timeouts. - """ - ), - ] = None - - @model_validator(mode="after") - def _check_data_exclusive(self) -> "ServerSentEvent": - if self.data is not None and self.raw_data is not None: - raise ValueError( - "Cannot set both 'data' and 'raw_data' on the same " - "ServerSentEvent. Use 'data' for JSON-serialized payloads " - "or 'raw_data' for pre-formatted strings." - ) - return self - - -def format_sse_event( - *, - data_str: Annotated[ - str | None, - Doc( - """ - Pre-serialized data string to use as the `data:` field. - """ - ), - ] = None, - event: Annotated[ - str | None, - Doc( - """ - Optional event type name (`event:` field). - """ - ), - ] = None, - id: Annotated[ - str | None, - Doc( - """ - Optional event ID (`id:` field). - """ - ), - ] = None, - retry: Annotated[ - int | None, - Doc( - """ - Optional reconnection time in milliseconds (`retry:` field). - """ - ), - ] = None, - comment: Annotated[ - str | None, - Doc( - """ - Optional comment line(s) (`:` prefix). - """ - ), - ] = None, -) -> bytes: - """Build SSE wire-format bytes from **pre-serialized** data. - - The result always ends with `\n\n` (the event terminator). - """ - lines: list[str] = [] - - if comment is not None: - for line in comment.splitlines(): - lines.append(f": {line}") - - if event is not None: - lines.append(f"event: {event}") - - if data_str is not None: - for line in data_str.splitlines(): - lines.append(f"data: {line}") - - if id is not None: - lines.append(f"id: {id}") - - if retry is not None: - lines.append(f"retry: {retry}") - - lines.append("") - lines.append("") - return "\n".join(lines).encode("utf-8") - - -# Keep-alive comment, per the SSE spec recommendation -KEEPALIVE_COMMENT = b": ping\n\n" - -# Seconds between keep-alive pings when a generator is idle. -# Private but importable so tests can monkeypatch it. -_PING_INTERVAL: float = 15.0 diff --git a/server/venv/Lib/site-packages/fastapi/staticfiles.py b/server/venv/Lib/site-packages/fastapi/staticfiles.py deleted file mode 100644 index 299015d..0000000 --- a/server/venv/Lib/site-packages/fastapi/staticfiles.py +++ /dev/null @@ -1 +0,0 @@ -from starlette.staticfiles import StaticFiles as StaticFiles # noqa diff --git a/server/venv/Lib/site-packages/fastapi/templating.py b/server/venv/Lib/site-packages/fastapi/templating.py deleted file mode 100644 index 0cb8684..0000000 --- a/server/venv/Lib/site-packages/fastapi/templating.py +++ /dev/null @@ -1 +0,0 @@ -from starlette.templating import Jinja2Templates as Jinja2Templates # noqa diff --git a/server/venv/Lib/site-packages/fastapi/testclient.py b/server/venv/Lib/site-packages/fastapi/testclient.py deleted file mode 100644 index 4012406..0000000 --- a/server/venv/Lib/site-packages/fastapi/testclient.py +++ /dev/null @@ -1 +0,0 @@ -from starlette.testclient import TestClient as TestClient # noqa diff --git a/server/venv/Lib/site-packages/fastapi/types.py b/server/venv/Lib/site-packages/fastapi/types.py deleted file mode 100644 index 1fb86e1..0000000 --- a/server/venv/Lib/site-packages/fastapi/types.py +++ /dev/null @@ -1,12 +0,0 @@ -import types -from collections.abc import Callable -from enum import Enum -from typing import Any, TypeVar, Union - -from pydantic import BaseModel -from pydantic.main import IncEx as IncEx - -DecoratedCallable = TypeVar("DecoratedCallable", bound=Callable[..., Any]) -UnionType = getattr(types, "UnionType", Union) -ModelNameMap = dict[type[BaseModel] | type[Enum], str] -DependencyCacheKey = tuple[Callable[..., Any] | None, tuple[str, ...], str] diff --git a/server/venv/Lib/site-packages/fastapi/utils.py b/server/venv/Lib/site-packages/fastapi/utils.py deleted file mode 100644 index 12eaa2b..0000000 --- a/server/venv/Lib/site-packages/fastapi/utils.py +++ /dev/null @@ -1,136 +0,0 @@ -import re -import warnings -from typing import ( - TYPE_CHECKING, - Any, - Literal, -) - -import fastapi -from fastapi._compat import ( - ModelField, - PydanticSchemaGenerationError, - Undefined, - annotation_is_pydantic_v1, -) -from fastapi.datastructures import DefaultPlaceholder, DefaultType -from fastapi.exceptions import FastAPIDeprecationWarning, PydanticV1NotSupportedError -from pydantic.fields import FieldInfo - -from ._compat import v2 - -if TYPE_CHECKING: # pragma: nocover - from .routing import APIRoute - - -def is_body_allowed_for_status_code(status_code: int | str | None) -> bool: - if status_code is None: - return True - # Ref: https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#patterned-fields-1 - if status_code in { - "default", - "1XX", - "2XX", - "3XX", - "4XX", - "5XX", - }: - return True - current_status_code = int(status_code) - return not (current_status_code < 200 or current_status_code in {204, 205, 304}) - - -def get_path_param_names(path: str) -> set[str]: - return set(re.findall("{(.*?)}", path)) - - -_invalid_args_message = ( - "Invalid args for response field! Hint: " - "check that {type_} is a valid Pydantic field type. " - "If you are using a return type annotation that is not a valid Pydantic " - "field (e.g. Union[Response, dict, None]) you can disable generating the " - "response model from the type annotation with the path operation decorator " - "parameter response_model=None. Read more: " - "https://fastapi.tiangolo.com/tutorial/response-model/" -) - - -def create_model_field( - name: str, - type_: Any, - default: Any | None = Undefined, - field_info: FieldInfo | None = None, - alias: str | None = None, - mode: Literal["validation", "serialization"] = "validation", -) -> ModelField: - if annotation_is_pydantic_v1(type_): - raise PydanticV1NotSupportedError( - "pydantic.v1 models are no longer supported by FastAPI." - f" Please update the response model {type_!r}." - ) - field_info = field_info or FieldInfo(annotation=type_, default=default, alias=alias) - try: - return v2.ModelField(mode=mode, name=name, field_info=field_info) - except PydanticSchemaGenerationError: - raise fastapi.exceptions.FastAPIError( - _invalid_args_message.format(type_=type_) - ) from None - - -def generate_operation_id_for_path( - *, name: str, path: str, method: str -) -> str: # pragma: nocover - warnings.warn( - message="fastapi.utils.generate_operation_id_for_path() was deprecated, " - "it is not used internally, and will be removed soon", - category=FastAPIDeprecationWarning, - stacklevel=2, - ) - operation_id = f"{name}{path}" - operation_id = re.sub(r"\W", "_", operation_id) - operation_id = f"{operation_id}_{method.lower()}" - return operation_id - - -def generate_unique_id(route: "APIRoute") -> str: - operation_id = f"{route.name}{route.path_format}" - operation_id = re.sub(r"\W", "_", operation_id) - assert route.methods - operation_id = f"{operation_id}_{list(route.methods)[0].lower()}" - return operation_id - - -def deep_dict_update(main_dict: dict[Any, Any], update_dict: dict[Any, Any]) -> None: - for key, value in update_dict.items(): - if ( - key in main_dict - and isinstance(main_dict[key], dict) - and isinstance(value, dict) - ): - deep_dict_update(main_dict[key], value) - elif ( - key in main_dict - and isinstance(main_dict[key], list) - and isinstance(update_dict[key], list) - ): - main_dict[key] = main_dict[key] + update_dict[key] - else: - main_dict[key] = value - - -def get_value_or_default( - first_item: DefaultPlaceholder | DefaultType, - *extra_items: DefaultPlaceholder | DefaultType, -) -> DefaultPlaceholder | DefaultType: - """ - Pass items or `DefaultPlaceholder`s by descending priority. - - The first one to _not_ be a `DefaultPlaceholder` will be returned. - - Otherwise, the first item (a `DefaultPlaceholder`) will be returned. - """ - items = (first_item,) + extra_items - for item in items: - if not isinstance(item, DefaultPlaceholder): - return item - return first_item diff --git a/server/venv/Lib/site-packages/fastapi/websockets.py b/server/venv/Lib/site-packages/fastapi/websockets.py deleted file mode 100644 index 55a4ac4..0000000 --- a/server/venv/Lib/site-packages/fastapi/websockets.py +++ /dev/null @@ -1,3 +0,0 @@ -from starlette.websockets import WebSocket as WebSocket # noqa -from starlette.websockets import WebSocketDisconnect as WebSocketDisconnect # noqa -from starlette.websockets import WebSocketState as WebSocketState # noqa diff --git a/server/venv/Lib/site-packages/gssapi64.dll b/server/venv/Lib/site-packages/gssapi64.dll deleted file mode 100644 index 408ffb2..0000000 Binary files a/server/venv/Lib/site-packages/gssapi64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER b/server/venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA b/server/venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA deleted file mode 100644 index 8a2f639..0000000 --- a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/METADATA +++ /dev/null @@ -1,202 +0,0 @@ -Metadata-Version: 2.4 -Name: h11 -Version: 0.16.0 -Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 -Home-page: https://github.com/python-hyper/h11 -Author: Nathaniel J. Smith -Author-email: njs@pobox.com -License: MIT -Classifier: Development Status :: 3 - Alpha -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: System :: Networking -Requires-Python: >=3.8 -License-File: LICENSE.txt -Dynamic: author -Dynamic: author-email -Dynamic: classifier -Dynamic: description -Dynamic: home-page -Dynamic: license -Dynamic: license-file -Dynamic: requires-python -Dynamic: summary - -h11 -=== - -.. image:: https://travis-ci.org/python-hyper/h11.svg?branch=master - :target: https://travis-ci.org/python-hyper/h11 - :alt: Automated test status - -.. image:: https://codecov.io/gh/python-hyper/h11/branch/master/graph/badge.svg - :target: https://codecov.io/gh/python-hyper/h11 - :alt: Test coverage - -.. image:: https://readthedocs.org/projects/h11/badge/?version=latest - :target: http://h11.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status - -This is a little HTTP/1.1 library written from scratch in Python, -heavily inspired by `hyper-h2 `_. - -It's a "bring-your-own-I/O" library; h11 contains no IO code -whatsoever. This means you can hook h11 up to your favorite network -API, and that could be anything you want: synchronous, threaded, -asynchronous, or your own implementation of `RFC 6214 -`_ -- h11 won't judge you. -(Compare this to the current state of the art, where every time a `new -network API `_ comes along then someone -gets to start over reimplementing the entire HTTP protocol from -scratch.) Cory Benfield made an `excellent blog post describing the -benefits of this approach -`_, or if you like video -then here's his `PyCon 2016 talk on the same theme -`_. - -This also means that h11 is not immediately useful out of the box: -it's a toolkit for building programs that speak HTTP, not something -that could directly replace ``requests`` or ``twisted.web`` or -whatever. But h11 makes it much easier to implement something like -``requests`` or ``twisted.web``. - -At a high level, working with h11 goes like this: - -1) First, create an ``h11.Connection`` object to track the state of a - single HTTP/1.1 connection. - -2) When you read data off the network, pass it to - ``conn.receive_data(...)``; you'll get back a list of objects - representing high-level HTTP "events". - -3) When you want to send a high-level HTTP event, create the - corresponding "event" object and pass it to ``conn.send(...)``; - this will give you back some bytes that you can then push out - through the network. - -For example, a client might instantiate and then send a -``h11.Request`` object, then zero or more ``h11.Data`` objects for the -request body (e.g., if this is a POST), and then a -``h11.EndOfMessage`` to indicate the end of the message. Then the -server would then send back a ``h11.Response``, some ``h11.Data``, and -its own ``h11.EndOfMessage``. If either side violates the protocol, -you'll get a ``h11.ProtocolError`` exception. - -h11 is suitable for implementing both servers and clients, and has a -pleasantly symmetric API: the events you send as a client are exactly -the ones that you receive as a server and vice-versa. - -`Here's an example of a tiny HTTP client -`_ - -It also has `a fine manual `_. - -FAQ ---- - -*Whyyyyy?* - -I wanted to play with HTTP in `Curio -`__ and `Trio -`__, which at the time didn't have any -HTTP libraries. So I thought, no big deal, Python has, like, a dozen -different implementations of HTTP, surely I can find one that's -reusable. I didn't find one, but I did find Cory's call-to-arms -blog-post. So I figured, well, fine, if I have to implement HTTP from -scratch, at least I can make sure no-one *else* has to ever again. - -*Should I use it?* - -Maybe. You should be aware that it's a very young project. But, it's -feature complete and has an exhaustive test-suite and complete docs, -so the next step is for people to try using it and see how it goes -:-). If you do then please let us know -- if nothing else we'll want -to talk to you before making any incompatible changes! - -*What are the features/limitations?* - -Roughly speaking, it's trying to be a robust, complete, and non-hacky -implementation of the first "chapter" of the HTTP/1.1 spec: `RFC 7230: -HTTP/1.1 Message Syntax and Routing -`_. That is, it mostly focuses on -implementing HTTP at the level of taking bytes on and off the wire, -and the headers related to that, and tries to be anal about spec -conformance. It doesn't know about higher-level concerns like URL -routing, conditional GETs, cross-origin cookie policies, or content -negotiation. But it does know how to take care of framing, -cross-version differences in keep-alive handling, and the "obsolete -line folding" rule, so you can focus your energies on the hard / -interesting parts for your application, and it tries to support the -full specification in the sense that any useful HTTP/1.1 conformant -application should be able to use h11. - -It's pure Python, and has no dependencies outside of the standard -library. - -It has a test suite with 100.0% coverage for both statements and -branches. - -Currently it supports Python 3 (testing on 3.8-3.12) and PyPy 3. -The last Python 2-compatible version was h11 0.11.x. -(Originally it had a Cython wrapper for `http-parser -`_ and a beautiful nested state -machine implemented with ``yield from`` to postprocess the output. But -I had to take these out -- the new *parser* needs fewer lines-of-code -than the old *parser wrapper*, is written in pure Python, uses no -exotic language syntax, and has more features. It's sad, really; that -old state machine was really slick. I just need a few sentences here -to mourn that.) - -I don't know how fast it is. I haven't benchmarked or profiled it yet, -so it's probably got a few pointless hot spots, and I've been trying -to err on the side of simplicity and robustness instead of -micro-optimization. But at the architectural level I tried hard to -avoid fundamentally bad decisions, e.g., I believe that all the -parsing algorithms remain linear-time even in the face of pathological -input like slowloris, and there are no byte-by-byte loops. (I also -believe that it maintains bounded memory usage in the face of -arbitrary/pathological input.) - -The whole library is ~800 lines-of-code. You can read and understand -the whole thing in less than an hour. Most of the energy invested in -this so far has been spent on trying to keep things simple by -minimizing special-cases and ad hoc state manipulation; even though it -is now quite small and simple, I'm still annoyed that I haven't -figured out how to make it even smaller and simpler. (Unfortunately, -HTTP does not lend itself to simplicity.) - -The API is ~feature complete and I don't expect the general outlines -to change much, but you can't judge an API's ergonomics until you -actually document and use it, so I'd expect some changes in the -details. - -*How do I try it?* - -.. code-block:: sh - - $ pip install h11 - $ git clone git@github.com:python-hyper/h11 - $ cd h11/examples - $ python basic-client.py - -and go from there. - -*License?* - -MIT - -*Code of conduct?* - -Contributors are requested to follow our `code of conduct -`_ in -all project spaces. diff --git a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD b/server/venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD deleted file mode 100644 index d64dc2d..0000000 --- a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/RECORD +++ /dev/null @@ -1,29 +0,0 @@ -h11-0.16.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -h11-0.16.0.dist-info/METADATA,sha256=KPMmCYrAn8unm48YD5YIfIQf4kViFct7hyqcfVzRnWQ,8348 -h11-0.16.0.dist-info/RECORD,, -h11-0.16.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91 -h11-0.16.0.dist-info/licenses/LICENSE.txt,sha256=N9tbuFkm2yikJ6JYZ_ELEjIAOuob5pzLhRE4rbjm82E,1124 -h11-0.16.0.dist-info/top_level.txt,sha256=F7dC4jl3zeh8TGHEPaWJrMbeuoWbS379Gwdi-Yvdcis,4 -h11/__init__.py,sha256=iO1KzkSO42yZ6ffg-VMgbx_ZVTWGUY00nRYEWn-s3kY,1507 -h11/__pycache__/__init__.cpython-313.pyc,, -h11/__pycache__/_abnf.cpython-313.pyc,, -h11/__pycache__/_connection.cpython-313.pyc,, -h11/__pycache__/_events.cpython-313.pyc,, -h11/__pycache__/_headers.cpython-313.pyc,, -h11/__pycache__/_readers.cpython-313.pyc,, -h11/__pycache__/_receivebuffer.cpython-313.pyc,, -h11/__pycache__/_state.cpython-313.pyc,, -h11/__pycache__/_util.cpython-313.pyc,, -h11/__pycache__/_version.cpython-313.pyc,, -h11/__pycache__/_writers.cpython-313.pyc,, -h11/_abnf.py,sha256=ybixr0xsupnkA6GFAyMubuXF6Tc1lb_hF890NgCsfNc,4815 -h11/_connection.py,sha256=k9YRVf6koZqbttBW36xSWaJpWdZwa-xQVU9AHEo9DuI,26863 -h11/_events.py,sha256=I97aXoal1Wu7dkL548BANBUCkOIbe-x5CioYA9IBY14,11792 -h11/_headers.py,sha256=P7D-lBNxHwdLZPLimmYwrPG-9ZkjElvvJZJdZAgSP-4,10412 -h11/_readers.py,sha256=a4RypORUCC3d0q_kxPuBIM7jTD8iLt5X91TH0FsduN4,8590 -h11/_receivebuffer.py,sha256=xrspsdsNgWFxRfQcTXxR8RrdjRXXTK0Io5cQYWpJ1Ws,5252 -h11/_state.py,sha256=_5LG_BGR8FCcFQeBPH-TMHgm_-B-EUcWCnQof_9XjFE,13231 -h11/_util.py,sha256=LWkkjXyJaFlAy6Lt39w73UStklFT5ovcvo0TkY7RYuk,4888 -h11/_version.py,sha256=GVSsbPSPDcOuF6ptfIiXnVJoaEm3ygXbMnqlr_Giahw,686 -h11/_writers.py,sha256=oFKm6PtjeHfbj4RLX7VB7KDc1gIY53gXG3_HR9ltmTA,5081 -h11/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7 diff --git a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL b/server/venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL deleted file mode 100644 index 1eb3c49..0000000 --- a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: setuptools (78.1.0) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt b/server/venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt deleted file mode 100644 index 8f080ea..0000000 --- a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/licenses/LICENSE.txt +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Nathaniel J. Smith and other contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt b/server/venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt deleted file mode 100644 index 0d24def..0000000 --- a/server/venv/Lib/site-packages/h11-0.16.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -h11 diff --git a/server/venv/Lib/site-packages/h11/__init__.py b/server/venv/Lib/site-packages/h11/__init__.py deleted file mode 100644 index 989e92c..0000000 --- a/server/venv/Lib/site-packages/h11/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -# A highish-level implementation of the HTTP/1.1 wire protocol (RFC 7230), -# containing no networking code at all, loosely modelled on hyper-h2's generic -# implementation of HTTP/2 (and in particular the h2.connection.H2Connection -# class). There's still a bunch of subtle details you need to get right if you -# want to make this actually useful, because it doesn't implement all the -# semantics to check that what you're asking to write to the wire is sensible, -# but at least it gets you out of dealing with the wire itself. - -from h11._connection import Connection, NEED_DATA, PAUSED -from h11._events import ( - ConnectionClosed, - Data, - EndOfMessage, - Event, - InformationalResponse, - Request, - Response, -) -from h11._state import ( - CLIENT, - CLOSED, - DONE, - ERROR, - IDLE, - MIGHT_SWITCH_PROTOCOL, - MUST_CLOSE, - SEND_BODY, - SEND_RESPONSE, - SERVER, - SWITCHED_PROTOCOL, -) -from h11._util import LocalProtocolError, ProtocolError, RemoteProtocolError -from h11._version import __version__ - -PRODUCT_ID = "python-h11/" + __version__ - - -__all__ = ( - "Connection", - "NEED_DATA", - "PAUSED", - "ConnectionClosed", - "Data", - "EndOfMessage", - "Event", - "InformationalResponse", - "Request", - "Response", - "CLIENT", - "CLOSED", - "DONE", - "ERROR", - "IDLE", - "MUST_CLOSE", - "SEND_BODY", - "SEND_RESPONSE", - "SERVER", - "SWITCHED_PROTOCOL", - "ProtocolError", - "LocalProtocolError", - "RemoteProtocolError", -) diff --git a/server/venv/Lib/site-packages/h11/_abnf.py b/server/venv/Lib/site-packages/h11/_abnf.py deleted file mode 100644 index 933587f..0000000 --- a/server/venv/Lib/site-packages/h11/_abnf.py +++ /dev/null @@ -1,132 +0,0 @@ -# We use native strings for all the re patterns, to take advantage of string -# formatting, and then convert to bytestrings when compiling the final re -# objects. - -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#whitespace -# OWS = *( SP / HTAB ) -# ; optional whitespace -OWS = r"[ \t]*" - -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#rule.token.separators -# token = 1*tchar -# -# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" -# / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" -# / DIGIT / ALPHA -# ; any VCHAR, except delimiters -token = r"[-!#$%&'*+.^_`|~0-9a-zA-Z]+" - -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#header.fields -# field-name = token -field_name = token - -# The standard says: -# -# field-value = *( field-content / obs-fold ) -# field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] -# field-vchar = VCHAR / obs-text -# obs-fold = CRLF 1*( SP / HTAB ) -# ; obsolete line folding -# ; see Section 3.2.4 -# -# https://tools.ietf.org/html/rfc5234#appendix-B.1 -# -# VCHAR = %x21-7E -# ; visible (printing) characters -# -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#rule.quoted-string -# obs-text = %x80-FF -# -# However, the standard definition of field-content is WRONG! It disallows -# fields containing a single visible character surrounded by whitespace, -# e.g. "foo a bar". -# -# See: https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 -# -# So our definition of field_content attempts to fix it up... -# -# Also, we allow lots of control characters, because apparently people assume -# that they're legal in practice (e.g., google analytics makes cookies with -# \x01 in them!): -# https://github.com/python-hyper/h11/issues/57 -# We still don't allow NUL or whitespace, because those are often treated as -# meta-characters and letting them through can lead to nasty issues like SSRF. -vchar = r"[\x21-\x7e]" -vchar_or_obs_text = r"[^\x00\s]" -field_vchar = vchar_or_obs_text -field_content = r"{field_vchar}+(?:[ \t]+{field_vchar}+)*".format(**globals()) - -# We handle obs-fold at a different level, and our fixed-up field_content -# already grows to swallow the whole value, so ? instead of * -field_value = r"({field_content})?".format(**globals()) - -# header-field = field-name ":" OWS field-value OWS -header_field = ( - r"(?P{field_name})" - r":" - r"{OWS}" - r"(?P{field_value})" - r"{OWS}".format(**globals()) -) - -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#request.line -# -# request-line = method SP request-target SP HTTP-version CRLF -# method = token -# HTTP-version = HTTP-name "/" DIGIT "." DIGIT -# HTTP-name = %x48.54.54.50 ; "HTTP", case-sensitive -# -# request-target is complicated (see RFC 7230 sec 5.3) -- could be path, full -# URL, host+port (for connect), or even "*", but in any case we are guaranteed -# that it contists of the visible printing characters. -method = token -request_target = r"{vchar}+".format(**globals()) -http_version = r"HTTP/(?P[0-9]\.[0-9])" -request_line = ( - r"(?P{method})" - r" " - r"(?P{request_target})" - r" " - r"{http_version}".format(**globals()) -) - -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#status.line -# -# status-line = HTTP-version SP status-code SP reason-phrase CRLF -# status-code = 3DIGIT -# reason-phrase = *( HTAB / SP / VCHAR / obs-text ) -status_code = r"[0-9]{3}" -reason_phrase = r"([ \t]|{vchar_or_obs_text})*".format(**globals()) -status_line = ( - r"{http_version}" - r" " - r"(?P{status_code})" - # However, there are apparently a few too many servers out there that just - # leave out the reason phrase: - # https://github.com/scrapy/scrapy/issues/345#issuecomment-281756036 - # https://github.com/seanmonstar/httparse/issues/29 - # so make it optional. ?: is a non-capturing group. - r"(?: (?P{reason_phrase}))?".format(**globals()) -) - -HEXDIG = r"[0-9A-Fa-f]" -# Actually -# -# chunk-size = 1*HEXDIG -# -# but we impose an upper-limit to avoid ridiculosity. len(str(2**64)) == 20 -chunk_size = r"({HEXDIG}){{1,20}}".format(**globals()) -# Actually -# -# chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) -# -# but we aren't parsing the things so we don't really care. -chunk_ext = r";.*" -chunk_header = ( - r"(?P{chunk_size})" - r"(?P{chunk_ext})?" - r"{OWS}\r\n".format( - **globals() - ) # Even though the specification does not allow for extra whitespaces, - # we are lenient with trailing whitespaces because some servers on the wild use it. -) diff --git a/server/venv/Lib/site-packages/h11/_connection.py b/server/venv/Lib/site-packages/h11/_connection.py deleted file mode 100644 index e37d82a..0000000 --- a/server/venv/Lib/site-packages/h11/_connection.py +++ /dev/null @@ -1,659 +0,0 @@ -# This contains the main Connection class. Everything in h11 revolves around -# this. -from typing import ( - Any, - Callable, - cast, - Dict, - List, - Optional, - overload, - Tuple, - Type, - Union, -) - -from ._events import ( - ConnectionClosed, - Data, - EndOfMessage, - Event, - InformationalResponse, - Request, - Response, -) -from ._headers import get_comma_header, has_expect_100_continue, set_comma_header -from ._readers import READERS, ReadersType -from ._receivebuffer import ReceiveBuffer -from ._state import ( - _SWITCH_CONNECT, - _SWITCH_UPGRADE, - CLIENT, - ConnectionState, - DONE, - ERROR, - MIGHT_SWITCH_PROTOCOL, - SEND_BODY, - SERVER, - SWITCHED_PROTOCOL, -) -from ._util import ( # Import the internal things we need - LocalProtocolError, - RemoteProtocolError, - Sentinel, -) -from ._writers import WRITERS, WritersType - -# Everything in __all__ gets re-exported as part of the h11 public API. -__all__ = ["Connection", "NEED_DATA", "PAUSED"] - - -class NEED_DATA(Sentinel, metaclass=Sentinel): - pass - - -class PAUSED(Sentinel, metaclass=Sentinel): - pass - - -# If we ever have this much buffered without it making a complete parseable -# event, we error out. The only time we really buffer is when reading the -# request/response line + headers together, so this is effectively the limit on -# the size of that. -# -# Some precedents for defaults: -# - node.js: 80 * 1024 -# - tomcat: 8 * 1024 -# - IIS: 16 * 1024 -# - Apache: <8 KiB per line> -DEFAULT_MAX_INCOMPLETE_EVENT_SIZE = 16 * 1024 - - -# RFC 7230's rules for connection lifecycles: -# - If either side says they want to close the connection, then the connection -# must close. -# - HTTP/1.1 defaults to keep-alive unless someone says Connection: close -# - HTTP/1.0 defaults to close unless both sides say Connection: keep-alive -# (and even this is a mess -- e.g. if you're implementing a proxy then -# sending Connection: keep-alive is forbidden). -# -# We simplify life by simply not supporting keep-alive with HTTP/1.0 peers. So -# our rule is: -# - If someone says Connection: close, we will close -# - If someone uses HTTP/1.0, we will close. -def _keep_alive(event: Union[Request, Response]) -> bool: - connection = get_comma_header(event.headers, b"connection") - if b"close" in connection: - return False - if getattr(event, "http_version", b"1.1") < b"1.1": - return False - return True - - -def _body_framing( - request_method: bytes, event: Union[Request, Response] -) -> Tuple[str, Union[Tuple[()], Tuple[int]]]: - # Called when we enter SEND_BODY to figure out framing information for - # this body. - # - # These are the only two events that can trigger a SEND_BODY state: - assert type(event) in (Request, Response) - # Returns one of: - # - # ("content-length", count) - # ("chunked", ()) - # ("http/1.0", ()) - # - # which are (lookup key, *args) for constructing body reader/writer - # objects. - # - # Reference: https://tools.ietf.org/html/rfc7230#section-3.3.3 - # - # Step 1: some responses always have an empty body, regardless of what the - # headers say. - if type(event) is Response: - if ( - event.status_code in (204, 304) - or request_method == b"HEAD" - or (request_method == b"CONNECT" and 200 <= event.status_code < 300) - ): - return ("content-length", (0,)) - # Section 3.3.3 also lists another case -- responses with status_code - # < 200. For us these are InformationalResponses, not Responses, so - # they can't get into this function in the first place. - assert event.status_code >= 200 - - # Step 2: check for Transfer-Encoding (T-E beats C-L): - transfer_encodings = get_comma_header(event.headers, b"transfer-encoding") - if transfer_encodings: - assert transfer_encodings == [b"chunked"] - return ("chunked", ()) - - # Step 3: check for Content-Length - content_lengths = get_comma_header(event.headers, b"content-length") - if content_lengths: - return ("content-length", (int(content_lengths[0]),)) - - # Step 4: no applicable headers; fallback/default depends on type - if type(event) is Request: - return ("content-length", (0,)) - else: - return ("http/1.0", ()) - - -################################################################ -# -# The main Connection class -# -################################################################ - - -class Connection: - """An object encapsulating the state of an HTTP connection. - - Args: - our_role: If you're implementing a client, pass :data:`h11.CLIENT`. If - you're implementing a server, pass :data:`h11.SERVER`. - - max_incomplete_event_size (int): - The maximum number of bytes we're willing to buffer of an - incomplete event. In practice this mostly sets a limit on the - maximum size of the request/response line + headers. If this is - exceeded, then :meth:`next_event` will raise - :exc:`RemoteProtocolError`. - - """ - - def __init__( - self, - our_role: Type[Sentinel], - max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, - ) -> None: - self._max_incomplete_event_size = max_incomplete_event_size - # State and role tracking - if our_role not in (CLIENT, SERVER): - raise ValueError(f"expected CLIENT or SERVER, not {our_role!r}") - self.our_role = our_role - self.their_role: Type[Sentinel] - if our_role is CLIENT: - self.their_role = SERVER - else: - self.their_role = CLIENT - self._cstate = ConnectionState() - - # Callables for converting data->events or vice-versa given the - # current state - self._writer = self._get_io_object(self.our_role, None, WRITERS) - self._reader = self._get_io_object(self.their_role, None, READERS) - - # Holds any unprocessed received data - self._receive_buffer = ReceiveBuffer() - # If this is true, then it indicates that the incoming connection was - # closed *after* the end of whatever's in self._receive_buffer: - self._receive_buffer_closed = False - - # Extra bits of state that don't fit into the state machine. - # - # These two are only used to interpret framing headers for figuring - # out how to read/write response bodies. their_http_version is also - # made available as a convenient public API. - self.their_http_version: Optional[bytes] = None - self._request_method: Optional[bytes] = None - # This is pure flow-control and doesn't at all affect the set of legal - # transitions, so no need to bother ConnectionState with it: - self.client_is_waiting_for_100_continue = False - - @property - def states(self) -> Dict[Type[Sentinel], Type[Sentinel]]: - """A dictionary like:: - - {CLIENT: , SERVER: } - - See :ref:`state-machine` for details. - - """ - return dict(self._cstate.states) - - @property - def our_state(self) -> Type[Sentinel]: - """The current state of whichever role we are playing. See - :ref:`state-machine` for details. - """ - return self._cstate.states[self.our_role] - - @property - def their_state(self) -> Type[Sentinel]: - """The current state of whichever role we are NOT playing. See - :ref:`state-machine` for details. - """ - return self._cstate.states[self.their_role] - - @property - def they_are_waiting_for_100_continue(self) -> bool: - return self.their_role is CLIENT and self.client_is_waiting_for_100_continue - - def start_next_cycle(self) -> None: - """Attempt to reset our connection state for a new request/response - cycle. - - If both client and server are in :data:`DONE` state, then resets them - both to :data:`IDLE` state in preparation for a new request/response - cycle on this same connection. Otherwise, raises a - :exc:`LocalProtocolError`. - - See :ref:`keepalive-and-pipelining`. - - """ - old_states = dict(self._cstate.states) - self._cstate.start_next_cycle() - self._request_method = None - # self.their_http_version gets left alone, since it presumably lasts - # beyond a single request/response cycle - assert not self.client_is_waiting_for_100_continue - self._respond_to_state_changes(old_states) - - def _process_error(self, role: Type[Sentinel]) -> None: - old_states = dict(self._cstate.states) - self._cstate.process_error(role) - self._respond_to_state_changes(old_states) - - def _server_switch_event(self, event: Event) -> Optional[Type[Sentinel]]: - if type(event) is InformationalResponse and event.status_code == 101: - return _SWITCH_UPGRADE - if type(event) is Response: - if ( - _SWITCH_CONNECT in self._cstate.pending_switch_proposals - and 200 <= event.status_code < 300 - ): - return _SWITCH_CONNECT - return None - - # All events go through here - def _process_event(self, role: Type[Sentinel], event: Event) -> None: - # First, pass the event through the state machine to make sure it - # succeeds. - old_states = dict(self._cstate.states) - if role is CLIENT and type(event) is Request: - if event.method == b"CONNECT": - self._cstate.process_client_switch_proposal(_SWITCH_CONNECT) - if get_comma_header(event.headers, b"upgrade"): - self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE) - server_switch_event = None - if role is SERVER: - server_switch_event = self._server_switch_event(event) - self._cstate.process_event(role, type(event), server_switch_event) - - # Then perform the updates triggered by it. - - if type(event) is Request: - self._request_method = event.method - - if role is self.their_role and type(event) in ( - Request, - Response, - InformationalResponse, - ): - event = cast(Union[Request, Response, InformationalResponse], event) - self.their_http_version = event.http_version - - # Keep alive handling - # - # RFC 7230 doesn't really say what one should do if Connection: close - # shows up on a 1xx InformationalResponse. I think the idea is that - # this is not supposed to happen. In any case, if it does happen, we - # ignore it. - if type(event) in (Request, Response) and not _keep_alive( - cast(Union[Request, Response], event) - ): - self._cstate.process_keep_alive_disabled() - - # 100-continue - if type(event) is Request and has_expect_100_continue(event): - self.client_is_waiting_for_100_continue = True - if type(event) in (InformationalResponse, Response): - self.client_is_waiting_for_100_continue = False - if role is CLIENT and type(event) in (Data, EndOfMessage): - self.client_is_waiting_for_100_continue = False - - self._respond_to_state_changes(old_states, event) - - def _get_io_object( - self, - role: Type[Sentinel], - event: Optional[Event], - io_dict: Union[ReadersType, WritersType], - ) -> Optional[Callable[..., Any]]: - # event may be None; it's only used when entering SEND_BODY - state = self._cstate.states[role] - if state is SEND_BODY: - # Special case: the io_dict has a dict of reader/writer factories - # that depend on the request/response framing. - framing_type, args = _body_framing( - cast(bytes, self._request_method), cast(Union[Request, Response], event) - ) - return io_dict[SEND_BODY][framing_type](*args) # type: ignore[index] - else: - # General case: the io_dict just has the appropriate reader/writer - # for this state - return io_dict.get((role, state)) # type: ignore[return-value] - - # This must be called after any action that might have caused - # self._cstate.states to change. - def _respond_to_state_changes( - self, - old_states: Dict[Type[Sentinel], Type[Sentinel]], - event: Optional[Event] = None, - ) -> None: - # Update reader/writer - if self.our_state != old_states[self.our_role]: - self._writer = self._get_io_object(self.our_role, event, WRITERS) - if self.their_state != old_states[self.their_role]: - self._reader = self._get_io_object(self.their_role, event, READERS) - - @property - def trailing_data(self) -> Tuple[bytes, bool]: - """Data that has been received, but not yet processed, represented as - a tuple with two elements, where the first is a byte-string containing - the unprocessed data itself, and the second is a bool that is True if - the receive connection was closed. - - See :ref:`switching-protocols` for discussion of why you'd want this. - """ - return (bytes(self._receive_buffer), self._receive_buffer_closed) - - def receive_data(self, data: bytes) -> None: - """Add data to our internal receive buffer. - - This does not actually do any processing on the data, just stores - it. To trigger processing, you have to call :meth:`next_event`. - - Args: - data (:term:`bytes-like object`): - The new data that was just received. - - Special case: If *data* is an empty byte-string like ``b""``, - then this indicates that the remote side has closed the - connection (end of file). Normally this is convenient, because - standard Python APIs like :meth:`file.read` or - :meth:`socket.recv` use ``b""`` to indicate end-of-file, while - other failures to read are indicated using other mechanisms - like raising :exc:`TimeoutError`. When using such an API you - can just blindly pass through whatever you get from ``read`` - to :meth:`receive_data`, and everything will work. - - But, if you have an API where reading an empty string is a - valid non-EOF condition, then you need to be aware of this and - make sure to check for such strings and avoid passing them to - :meth:`receive_data`. - - Returns: - Nothing, but after calling this you should call :meth:`next_event` - to parse the newly received data. - - Raises: - RuntimeError: - Raised if you pass an empty *data*, indicating EOF, and then - pass a non-empty *data*, indicating more data that somehow - arrived after the EOF. - - (Calling ``receive_data(b"")`` multiple times is fine, - and equivalent to calling it once.) - - """ - if data: - if self._receive_buffer_closed: - raise RuntimeError("received close, then received more data?") - self._receive_buffer += data - else: - self._receive_buffer_closed = True - - def _extract_next_receive_event( - self, - ) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: - state = self.their_state - # We don't pause immediately when they enter DONE, because even in - # DONE state we can still process a ConnectionClosed() event. But - # if we have data in our buffer, then we definitely aren't getting - # a ConnectionClosed() immediately and we need to pause. - if state is DONE and self._receive_buffer: - return PAUSED - if state is MIGHT_SWITCH_PROTOCOL or state is SWITCHED_PROTOCOL: - return PAUSED - assert self._reader is not None - event = self._reader(self._receive_buffer) - if event is None: - if not self._receive_buffer and self._receive_buffer_closed: - # In some unusual cases (basically just HTTP/1.0 bodies), EOF - # triggers an actual protocol event; in that case, we want to - # return that event, and then the state will change and we'll - # get called again to generate the actual ConnectionClosed(). - if hasattr(self._reader, "read_eof"): - event = self._reader.read_eof() - else: - event = ConnectionClosed() - if event is None: - event = NEED_DATA - return event # type: ignore[no-any-return] - - def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: - """Parse the next event out of our receive buffer, update our internal - state, and return it. - - This is a mutating operation -- think of it like calling :func:`next` - on an iterator. - - Returns: - : One of three things: - - 1) An event object -- see :ref:`events`. - - 2) The special constant :data:`NEED_DATA`, which indicates that - you need to read more data from your socket and pass it to - :meth:`receive_data` before this method will be able to return - any more events. - - 3) The special constant :data:`PAUSED`, which indicates that we - are not in a state where we can process incoming data (usually - because the peer has finished their part of the current - request/response cycle, and you have not yet called - :meth:`start_next_cycle`). See :ref:`flow-control` for details. - - Raises: - RemoteProtocolError: - The peer has misbehaved. You should close the connection - (possibly after sending some kind of 4xx response). - - Once this method returns :class:`ConnectionClosed` once, then all - subsequent calls will also return :class:`ConnectionClosed`. - - If this method raises any exception besides :exc:`RemoteProtocolError` - then that's a bug -- if it happens please file a bug report! - - If this method raises any exception then it also sets - :attr:`Connection.their_state` to :data:`ERROR` -- see - :ref:`error-handling` for discussion. - - """ - - if self.their_state is ERROR: - raise RemoteProtocolError("Can't receive data when peer state is ERROR") - try: - event = self._extract_next_receive_event() - if event not in [NEED_DATA, PAUSED]: - self._process_event(self.their_role, cast(Event, event)) - if event is NEED_DATA: - if len(self._receive_buffer) > self._max_incomplete_event_size: - # 431 is "Request header fields too large" which is pretty - # much the only situation where we can get here - raise RemoteProtocolError( - "Receive buffer too long", error_status_hint=431 - ) - if self._receive_buffer_closed: - # We're still trying to complete some event, but that's - # never going to happen because no more data is coming - raise RemoteProtocolError("peer unexpectedly closed connection") - return event - except BaseException as exc: - self._process_error(self.their_role) - if isinstance(exc, LocalProtocolError): - exc._reraise_as_remote_protocol_error() - else: - raise - - @overload - def send(self, event: ConnectionClosed) -> None: - ... - - @overload - def send( - self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage] - ) -> bytes: - ... - - @overload - def send(self, event: Event) -> Optional[bytes]: - ... - - def send(self, event: Event) -> Optional[bytes]: - """Convert a high-level event into bytes that can be sent to the peer, - while updating our internal state machine. - - Args: - event: The :ref:`event ` to send. - - Returns: - If ``type(event) is ConnectionClosed``, then returns - ``None``. Otherwise, returns a :term:`bytes-like object`. - - Raises: - LocalProtocolError: - Sending this event at this time would violate our - understanding of the HTTP/1.1 protocol. - - If this method raises any exception then it also sets - :attr:`Connection.our_state` to :data:`ERROR` -- see - :ref:`error-handling` for discussion. - - """ - data_list = self.send_with_data_passthrough(event) - if data_list is None: - return None - else: - return b"".join(data_list) - - def send_with_data_passthrough(self, event: Event) -> Optional[List[bytes]]: - """Identical to :meth:`send`, except that in situations where - :meth:`send` returns a single :term:`bytes-like object`, this instead - returns a list of them -- and when sending a :class:`Data` event, this - list is guaranteed to contain the exact object you passed in as - :attr:`Data.data`. See :ref:`sendfile` for discussion. - - """ - if self.our_state is ERROR: - raise LocalProtocolError("Can't send data when our state is ERROR") - try: - if type(event) is Response: - event = self._clean_up_response_headers_for_sending(event) - # We want to call _process_event before calling the writer, - # because if someone tries to do something invalid then this will - # give a sensible error message, while our writers all just assume - # they will only receive valid events. But, _process_event might - # change self._writer. So we have to do a little dance: - writer = self._writer - self._process_event(self.our_role, event) - if type(event) is ConnectionClosed: - return None - else: - # In any situation where writer is None, process_event should - # have raised ProtocolError - assert writer is not None - data_list: List[bytes] = [] - writer(event, data_list.append) - return data_list - except: - self._process_error(self.our_role) - raise - - def send_failed(self) -> None: - """Notify the state machine that we failed to send the data it gave - us. - - This causes :attr:`Connection.our_state` to immediately become - :data:`ERROR` -- see :ref:`error-handling` for discussion. - - """ - self._process_error(self.our_role) - - # When sending a Response, we take responsibility for a few things: - # - # - Sometimes you MUST set Connection: close. We take care of those - # times. (You can also set it yourself if you want, and if you do then - # we'll respect that and close the connection at the right time. But you - # don't have to worry about that unless you want to.) - # - # - The user has to set Content-Length if they want it. Otherwise, for - # responses that have bodies (e.g. not HEAD), then we will automatically - # select the right mechanism for streaming a body of unknown length, - # which depends on depending on the peer's HTTP version. - # - # This function's *only* responsibility is making sure headers are set up - # right -- everything downstream just looks at the headers. There are no - # side channels. - def _clean_up_response_headers_for_sending(self, response: Response) -> Response: - assert type(response) is Response - - headers = response.headers - need_close = False - - # HEAD requests need some special handling: they always act like they - # have Content-Length: 0, and that's how _body_framing treats - # them. But their headers are supposed to match what we would send if - # the request was a GET. (Technically there is one deviation allowed: - # we're allowed to leave out the framing headers -- see - # https://tools.ietf.org/html/rfc7231#section-4.3.2 . But it's just as - # easy to get them right.) - method_for_choosing_headers = cast(bytes, self._request_method) - if method_for_choosing_headers == b"HEAD": - method_for_choosing_headers = b"GET" - framing_type, _ = _body_framing(method_for_choosing_headers, response) - if framing_type in ("chunked", "http/1.0"): - # This response has a body of unknown length. - # If our peer is HTTP/1.1, we use Transfer-Encoding: chunked - # If our peer is HTTP/1.0, we use no framing headers, and close the - # connection afterwards. - # - # Make sure to clear Content-Length (in principle user could have - # set both and then we ignored Content-Length b/c - # Transfer-Encoding overwrote it -- this would be naughty of them, - # but the HTTP spec says that if our peer does this then we have - # to fix it instead of erroring out, so we'll accord the user the - # same respect). - headers = set_comma_header(headers, b"content-length", []) - if self.their_http_version is None or self.their_http_version < b"1.1": - # Either we never got a valid request and are sending back an - # error (their_http_version is None), so we assume the worst; - # or else we did get a valid HTTP/1.0 request, so we know that - # they don't understand chunked encoding. - headers = set_comma_header(headers, b"transfer-encoding", []) - # This is actually redundant ATM, since currently we - # unconditionally disable keep-alive when talking to HTTP/1.0 - # peers. But let's be defensive just in case we add - # Connection: keep-alive support later: - if self._request_method != b"HEAD": - need_close = True - else: - headers = set_comma_header(headers, b"transfer-encoding", [b"chunked"]) - - if not self._cstate.keep_alive or need_close: - # Make sure Connection: close is set - connection = set(get_comma_header(headers, b"connection")) - connection.discard(b"keep-alive") - connection.add(b"close") - headers = set_comma_header(headers, b"connection", sorted(connection)) - - return Response( - headers=headers, - status_code=response.status_code, - http_version=response.http_version, - reason=response.reason, - ) diff --git a/server/venv/Lib/site-packages/h11/_events.py b/server/venv/Lib/site-packages/h11/_events.py deleted file mode 100644 index ca1c3ad..0000000 --- a/server/venv/Lib/site-packages/h11/_events.py +++ /dev/null @@ -1,369 +0,0 @@ -# High level events that make up HTTP/1.1 conversations. Loosely inspired by -# the corresponding events in hyper-h2: -# -# http://python-hyper.org/h2/en/stable/api.html#events -# -# Don't subclass these. Stuff will break. - -import re -from abc import ABC -from dataclasses import dataclass -from typing import List, Tuple, Union - -from ._abnf import method, request_target -from ._headers import Headers, normalize_and_validate -from ._util import bytesify, LocalProtocolError, validate - -# Everything in __all__ gets re-exported as part of the h11 public API. -__all__ = [ - "Event", - "Request", - "InformationalResponse", - "Response", - "Data", - "EndOfMessage", - "ConnectionClosed", -] - -method_re = re.compile(method.encode("ascii")) -request_target_re = re.compile(request_target.encode("ascii")) - - -class Event(ABC): - """ - Base class for h11 events. - """ - - __slots__ = () - - -@dataclass(init=False, frozen=True) -class Request(Event): - """The beginning of an HTTP request. - - Fields: - - .. attribute:: method - - An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte - string. :term:`Bytes-like objects ` and native - strings containing only ascii characters will be automatically - converted to byte strings. - - .. attribute:: target - - The target of an HTTP request, e.g. ``b"/index.html"``, or one of the - more exotic formats described in `RFC 7320, section 5.3 - `_. Always a byte - string. :term:`Bytes-like objects ` and native - strings containing only ascii characters will be automatically - converted to byte strings. - - .. attribute:: headers - - Request headers, represented as a list of (name, value) pairs. See - :ref:`the header normalization rules ` for details. - - .. attribute:: http_version - - The HTTP protocol version, represented as a byte string like - ``b"1.1"``. See :ref:`the HTTP version normalization rules - ` for details. - - """ - - __slots__ = ("method", "headers", "target", "http_version") - - method: bytes - headers: Headers - target: bytes - http_version: bytes - - def __init__( - self, - *, - method: Union[bytes, str], - headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], - target: Union[bytes, str], - http_version: Union[bytes, str] = b"1.1", - _parsed: bool = False, - ) -> None: - super().__init__() - if isinstance(headers, Headers): - object.__setattr__(self, "headers", headers) - else: - object.__setattr__( - self, "headers", normalize_and_validate(headers, _parsed=_parsed) - ) - if not _parsed: - object.__setattr__(self, "method", bytesify(method)) - object.__setattr__(self, "target", bytesify(target)) - object.__setattr__(self, "http_version", bytesify(http_version)) - else: - object.__setattr__(self, "method", method) - object.__setattr__(self, "target", target) - object.__setattr__(self, "http_version", http_version) - - # "A server MUST respond with a 400 (Bad Request) status code to any - # HTTP/1.1 request message that lacks a Host header field and to any - # request message that contains more than one Host header field or a - # Host header field with an invalid field-value." - # -- https://tools.ietf.org/html/rfc7230#section-5.4 - host_count = 0 - for name, value in self.headers: - if name == b"host": - host_count += 1 - if self.http_version == b"1.1" and host_count == 0: - raise LocalProtocolError("Missing mandatory Host: header") - if host_count > 1: - raise LocalProtocolError("Found multiple Host: headers") - - validate(method_re, self.method, "Illegal method characters") - validate(request_target_re, self.target, "Illegal target characters") - - # This is an unhashable type. - __hash__ = None # type: ignore - - -@dataclass(init=False, frozen=True) -class _ResponseBase(Event): - __slots__ = ("headers", "http_version", "reason", "status_code") - - headers: Headers - http_version: bytes - reason: bytes - status_code: int - - def __init__( - self, - *, - headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], - status_code: int, - http_version: Union[bytes, str] = b"1.1", - reason: Union[bytes, str] = b"", - _parsed: bool = False, - ) -> None: - super().__init__() - if isinstance(headers, Headers): - object.__setattr__(self, "headers", headers) - else: - object.__setattr__( - self, "headers", normalize_and_validate(headers, _parsed=_parsed) - ) - if not _parsed: - object.__setattr__(self, "reason", bytesify(reason)) - object.__setattr__(self, "http_version", bytesify(http_version)) - if not isinstance(status_code, int): - raise LocalProtocolError("status code must be integer") - # Because IntEnum objects are instances of int, but aren't - # duck-compatible (sigh), see gh-72. - object.__setattr__(self, "status_code", int(status_code)) - else: - object.__setattr__(self, "reason", reason) - object.__setattr__(self, "http_version", http_version) - object.__setattr__(self, "status_code", status_code) - - self.__post_init__() - - def __post_init__(self) -> None: - pass - - # This is an unhashable type. - __hash__ = None # type: ignore - - -@dataclass(init=False, frozen=True) -class InformationalResponse(_ResponseBase): - """An HTTP informational response. - - Fields: - - .. attribute:: status_code - - The status code of this response, as an integer. For an - :class:`InformationalResponse`, this is always in the range [100, - 200). - - .. attribute:: headers - - Request headers, represented as a list of (name, value) pairs. See - :ref:`the header normalization rules ` for - details. - - .. attribute:: http_version - - The HTTP protocol version, represented as a byte string like - ``b"1.1"``. See :ref:`the HTTP version normalization rules - ` for details. - - .. attribute:: reason - - The reason phrase of this response, as a byte string. For example: - ``b"OK"``, or ``b"Not Found"``. - - """ - - def __post_init__(self) -> None: - if not (100 <= self.status_code < 200): - raise LocalProtocolError( - "InformationalResponse status_code should be in range " - "[100, 200), not {}".format(self.status_code) - ) - - # This is an unhashable type. - __hash__ = None # type: ignore - - -@dataclass(init=False, frozen=True) -class Response(_ResponseBase): - """The beginning of an HTTP response. - - Fields: - - .. attribute:: status_code - - The status code of this response, as an integer. For an - :class:`Response`, this is always in the range [200, - 1000). - - .. attribute:: headers - - Request headers, represented as a list of (name, value) pairs. See - :ref:`the header normalization rules ` for details. - - .. attribute:: http_version - - The HTTP protocol version, represented as a byte string like - ``b"1.1"``. See :ref:`the HTTP version normalization rules - ` for details. - - .. attribute:: reason - - The reason phrase of this response, as a byte string. For example: - ``b"OK"``, or ``b"Not Found"``. - - """ - - def __post_init__(self) -> None: - if not (200 <= self.status_code < 1000): - raise LocalProtocolError( - "Response status_code should be in range [200, 1000), not {}".format( - self.status_code - ) - ) - - # This is an unhashable type. - __hash__ = None # type: ignore - - -@dataclass(init=False, frozen=True) -class Data(Event): - """Part of an HTTP message body. - - Fields: - - .. attribute:: data - - A :term:`bytes-like object` containing part of a message body. Or, if - using the ``combine=False`` argument to :meth:`Connection.send`, then - any object that your socket writing code knows what to do with, and for - which calling :func:`len` returns the number of bytes that will be - written -- see :ref:`sendfile` for details. - - .. attribute:: chunk_start - - A marker that indicates whether this data object is from the start of a - chunked transfer encoding chunk. This field is ignored when when a Data - event is provided to :meth:`Connection.send`: it is only valid on - events emitted from :meth:`Connection.next_event`. You probably - shouldn't use this attribute at all; see - :ref:`chunk-delimiters-are-bad` for details. - - .. attribute:: chunk_end - - A marker that indicates whether this data object is the last for a - given chunked transfer encoding chunk. This field is ignored when when - a Data event is provided to :meth:`Connection.send`: it is only valid - on events emitted from :meth:`Connection.next_event`. You probably - shouldn't use this attribute at all; see - :ref:`chunk-delimiters-are-bad` for details. - - """ - - __slots__ = ("data", "chunk_start", "chunk_end") - - data: bytes - chunk_start: bool - chunk_end: bool - - def __init__( - self, data: bytes, chunk_start: bool = False, chunk_end: bool = False - ) -> None: - object.__setattr__(self, "data", data) - object.__setattr__(self, "chunk_start", chunk_start) - object.__setattr__(self, "chunk_end", chunk_end) - - # This is an unhashable type. - __hash__ = None # type: ignore - - -# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that -# are forbidden to be sent in a trailer, since processing them as if they were -# present in the header section might bypass external security filters." -# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part -# Unfortunately, the list of forbidden fields is long and vague :-/ -@dataclass(init=False, frozen=True) -class EndOfMessage(Event): - """The end of an HTTP message. - - Fields: - - .. attribute:: headers - - Default value: ``[]`` - - Any trailing headers attached to this message, represented as a list of - (name, value) pairs. See :ref:`the header normalization rules - ` for details. - - Must be empty unless ``Transfer-Encoding: chunked`` is in use. - - """ - - __slots__ = ("headers",) - - headers: Headers - - def __init__( - self, - *, - headers: Union[ - Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None - ] = None, - _parsed: bool = False, - ) -> None: - super().__init__() - if headers is None: - headers = Headers([]) - elif not isinstance(headers, Headers): - headers = normalize_and_validate(headers, _parsed=_parsed) - - object.__setattr__(self, "headers", headers) - - # This is an unhashable type. - __hash__ = None # type: ignore - - -@dataclass(frozen=True) -class ConnectionClosed(Event): - """This event indicates that the sender has closed their outgoing - connection. - - Note that this does not necessarily mean that they can't *receive* further - data, because TCP connections are composed to two one-way channels which - can be closed independently. See :ref:`closing` for details. - - No fields. - """ - - pass diff --git a/server/venv/Lib/site-packages/h11/_headers.py b/server/venv/Lib/site-packages/h11/_headers.py deleted file mode 100644 index 31da3e2..0000000 --- a/server/venv/Lib/site-packages/h11/_headers.py +++ /dev/null @@ -1,282 +0,0 @@ -import re -from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union - -from ._abnf import field_name, field_value -from ._util import bytesify, LocalProtocolError, validate - -if TYPE_CHECKING: - from ._events import Request - -try: - from typing import Literal -except ImportError: - from typing_extensions import Literal # type: ignore - -CONTENT_LENGTH_MAX_DIGITS = 20 # allow up to 1 billion TB - 1 - - -# Facts -# ----- -# -# Headers are: -# keys: case-insensitive ascii -# values: mixture of ascii and raw bytes -# -# "Historically, HTTP has allowed field content with text in the ISO-8859-1 -# charset [ISO-8859-1], supporting other charsets only through use of -# [RFC2047] encoding. In practice, most HTTP header field values use only a -# subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD -# limit their field values to US-ASCII octets. A recipient SHOULD treat other -# octets in field content (obs-text) as opaque data." -# And it deprecates all non-ascii values -# -# Leading/trailing whitespace in header names is forbidden -# -# Values get leading/trailing whitespace stripped -# -# Content-Disposition actually needs to contain unicode semantically; to -# accomplish this it has a terrifically weird way of encoding the filename -# itself as ascii (and even this still has lots of cross-browser -# incompatibilities) -# -# Order is important: -# "a proxy MUST NOT change the order of these field values when forwarding a -# message" -# (and there are several headers where the order indicates a preference) -# -# Multiple occurences of the same header: -# "A sender MUST NOT generate multiple header fields with the same field name -# in a message unless either the entire field value for that header field is -# defined as a comma-separated list [or the header is Set-Cookie which gets a -# special exception]" - RFC 7230. (cookies are in RFC 6265) -# -# So every header aside from Set-Cookie can be merged by b", ".join if it -# occurs repeatedly. But, of course, they can't necessarily be split by -# .split(b","), because quoting. -# -# Given all this mess (case insensitive, duplicates allowed, order is -# important, ...), there doesn't appear to be any standard way to handle -# headers in Python -- they're almost like dicts, but... actually just -# aren't. For now we punt and just use a super simple representation: headers -# are a list of pairs -# -# [(name1, value1), (name2, value2), ...] -# -# where all entries are bytestrings, names are lowercase and have no -# leading/trailing whitespace, and values are bytestrings with no -# leading/trailing whitespace. Searching and updating are done via naive O(n) -# methods. -# -# Maybe a dict-of-lists would be better? - -_content_length_re = re.compile(rb"[0-9]+") -_field_name_re = re.compile(field_name.encode("ascii")) -_field_value_re = re.compile(field_value.encode("ascii")) - - -class Headers(Sequence[Tuple[bytes, bytes]]): - """ - A list-like interface that allows iterating over headers as byte-pairs - of (lowercased-name, value). - - Internally we actually store the representation as three-tuples, - including both the raw original casing, in order to preserve casing - over-the-wire, and the lowercased name, for case-insensitive comparisions. - - r = Request( - method="GET", - target="/", - headers=[("Host", "example.org"), ("Connection", "keep-alive")], - http_version="1.1", - ) - assert r.headers == [ - (b"host", b"example.org"), - (b"connection", b"keep-alive") - ] - assert r.headers.raw_items() == [ - (b"Host", b"example.org"), - (b"Connection", b"keep-alive") - ] - """ - - __slots__ = "_full_items" - - def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None: - self._full_items = full_items - - def __bool__(self) -> bool: - return bool(self._full_items) - - def __eq__(self, other: object) -> bool: - return list(self) == list(other) # type: ignore - - def __len__(self) -> int: - return len(self._full_items) - - def __repr__(self) -> str: - return "" % repr(list(self)) - - def __getitem__(self, idx: int) -> Tuple[bytes, bytes]: # type: ignore[override] - _, name, value = self._full_items[idx] - return (name, value) - - def raw_items(self) -> List[Tuple[bytes, bytes]]: - return [(raw_name, value) for raw_name, _, value in self._full_items] - - -HeaderTypes = Union[ - List[Tuple[bytes, bytes]], - List[Tuple[bytes, str]], - List[Tuple[str, bytes]], - List[Tuple[str, str]], -] - - -@overload -def normalize_and_validate(headers: Headers, _parsed: Literal[True]) -> Headers: - ... - - -@overload -def normalize_and_validate(headers: HeaderTypes, _parsed: Literal[False]) -> Headers: - ... - - -@overload -def normalize_and_validate( - headers: Union[Headers, HeaderTypes], _parsed: bool = False -) -> Headers: - ... - - -def normalize_and_validate( - headers: Union[Headers, HeaderTypes], _parsed: bool = False -) -> Headers: - new_headers = [] - seen_content_length = None - saw_transfer_encoding = False - for name, value in headers: - # For headers coming out of the parser, we can safely skip some steps, - # because it always returns bytes and has already run these regexes - # over the data: - if not _parsed: - name = bytesify(name) - value = bytesify(value) - validate(_field_name_re, name, "Illegal header name {!r}", name) - validate(_field_value_re, value, "Illegal header value {!r}", value) - assert isinstance(name, bytes) - assert isinstance(value, bytes) - - raw_name = name - name = name.lower() - if name == b"content-length": - lengths = {length.strip() for length in value.split(b",")} - if len(lengths) != 1: - raise LocalProtocolError("conflicting Content-Length headers") - value = lengths.pop() - validate(_content_length_re, value, "bad Content-Length") - if len(value) > CONTENT_LENGTH_MAX_DIGITS: - raise LocalProtocolError("bad Content-Length") - if seen_content_length is None: - seen_content_length = value - new_headers.append((raw_name, name, value)) - elif seen_content_length != value: - raise LocalProtocolError("conflicting Content-Length headers") - elif name == b"transfer-encoding": - # "A server that receives a request message with a transfer coding - # it does not understand SHOULD respond with 501 (Not - # Implemented)." - # https://tools.ietf.org/html/rfc7230#section-3.3.1 - if saw_transfer_encoding: - raise LocalProtocolError( - "multiple Transfer-Encoding headers", error_status_hint=501 - ) - # "All transfer-coding names are case-insensitive" - # -- https://tools.ietf.org/html/rfc7230#section-4 - value = value.lower() - if value != b"chunked": - raise LocalProtocolError( - "Only Transfer-Encoding: chunked is supported", - error_status_hint=501, - ) - saw_transfer_encoding = True - new_headers.append((raw_name, name, value)) - else: - new_headers.append((raw_name, name, value)) - return Headers(new_headers) - - -def get_comma_header(headers: Headers, name: bytes) -> List[bytes]: - # Should only be used for headers whose value is a list of - # comma-separated, case-insensitive values. - # - # The header name `name` is expected to be lower-case bytes. - # - # Connection: meets these criteria (including cast insensitivity). - # - # Content-Length: technically is just a single value (1*DIGIT), but the - # standard makes reference to implementations that do multiple values, and - # using this doesn't hurt. Ditto, case insensitivity doesn't things either - # way. - # - # Transfer-Encoding: is more complex (allows for quoted strings), so - # splitting on , is actually wrong. For example, this is legal: - # - # Transfer-Encoding: foo; options="1,2", chunked - # - # and should be parsed as - # - # foo; options="1,2" - # chunked - # - # but this naive function will parse it as - # - # foo; options="1 - # 2" - # chunked - # - # However, this is okay because the only thing we are going to do with - # any Transfer-Encoding is reject ones that aren't just "chunked", so - # both of these will be treated the same anyway. - # - # Expect: the only legal value is the literal string - # "100-continue". Splitting on commas is harmless. Case insensitive. - # - out: List[bytes] = [] - for _, found_name, found_raw_value in headers._full_items: - if found_name == name: - found_raw_value = found_raw_value.lower() - for found_split_value in found_raw_value.split(b","): - found_split_value = found_split_value.strip() - if found_split_value: - out.append(found_split_value) - return out - - -def set_comma_header(headers: Headers, name: bytes, new_values: List[bytes]) -> Headers: - # The header name `name` is expected to be lower-case bytes. - # - # Note that when we store the header we use title casing for the header - # names, in order to match the conventional HTTP header style. - # - # Simply calling `.title()` is a blunt approach, but it's correct - # here given the cases where we're using `set_comma_header`... - # - # Connection, Content-Length, Transfer-Encoding. - new_headers: List[Tuple[bytes, bytes]] = [] - for found_raw_name, found_name, found_raw_value in headers._full_items: - if found_name != name: - new_headers.append((found_raw_name, found_raw_value)) - for new_value in new_values: - new_headers.append((name.title(), new_value)) - return normalize_and_validate(new_headers) - - -def has_expect_100_continue(request: "Request") -> bool: - # https://tools.ietf.org/html/rfc7231#section-5.1.1 - # "A server that receives a 100-continue expectation in an HTTP/1.0 request - # MUST ignore that expectation." - if request.http_version < b"1.1": - return False - expect = get_comma_header(request.headers, b"expect") - return b"100-continue" in expect diff --git a/server/venv/Lib/site-packages/h11/_readers.py b/server/venv/Lib/site-packages/h11/_readers.py deleted file mode 100644 index 576804c..0000000 --- a/server/venv/Lib/site-packages/h11/_readers.py +++ /dev/null @@ -1,250 +0,0 @@ -# Code to read HTTP data -# -# Strategy: each reader is a callable which takes a ReceiveBuffer object, and -# either: -# 1) consumes some of it and returns an Event -# 2) raises a LocalProtocolError (for consistency -- e.g. we call validate() -# and it might raise a LocalProtocolError, so simpler just to always use -# this) -# 3) returns None, meaning "I need more data" -# -# If they have a .read_eof attribute, then this will be called if an EOF is -# received -- but this is optional. Either way, the actual ConnectionClosed -# event will be generated afterwards. -# -# READERS is a dict describing how to pick a reader. It maps states to either: -# - a reader -# - or, for body readers, a dict of per-framing reader factories - -import re -from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union - -from ._abnf import chunk_header, header_field, request_line, status_line -from ._events import Data, EndOfMessage, InformationalResponse, Request, Response -from ._receivebuffer import ReceiveBuffer -from ._state import ( - CLIENT, - CLOSED, - DONE, - IDLE, - MUST_CLOSE, - SEND_BODY, - SEND_RESPONSE, - SERVER, -) -from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate - -__all__ = ["READERS"] - -header_field_re = re.compile(header_field.encode("ascii")) -obs_fold_re = re.compile(rb"[ \t]+") - - -def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]: - it = iter(lines) - last: Optional[bytes] = None - for line in it: - match = obs_fold_re.match(line) - if match: - if last is None: - raise LocalProtocolError("continuation line at start of headers") - if not isinstance(last, bytearray): - # Cast to a mutable type, avoiding copy on append to ensure O(n) time - last = bytearray(last) - last += b" " - last += line[match.end() :] - else: - if last is not None: - yield last - last = line - if last is not None: - yield last - - -def _decode_header_lines( - lines: Iterable[bytes], -) -> Iterable[Tuple[bytes, bytes]]: - for line in _obsolete_line_fold(lines): - matches = validate(header_field_re, line, "illegal header line: {!r}", line) - yield (matches["field_name"], matches["field_value"]) - - -request_line_re = re.compile(request_line.encode("ascii")) - - -def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]: - lines = buf.maybe_extract_lines() - if lines is None: - if buf.is_next_line_obviously_invalid_request_line(): - raise LocalProtocolError("illegal request line") - return None - if not lines: - raise LocalProtocolError("no request line received") - matches = validate( - request_line_re, lines[0], "illegal request line: {!r}", lines[0] - ) - return Request( - headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches - ) - - -status_line_re = re.compile(status_line.encode("ascii")) - - -def maybe_read_from_SEND_RESPONSE_server( - buf: ReceiveBuffer, -) -> Union[InformationalResponse, Response, None]: - lines = buf.maybe_extract_lines() - if lines is None: - if buf.is_next_line_obviously_invalid_request_line(): - raise LocalProtocolError("illegal request line") - return None - if not lines: - raise LocalProtocolError("no response line received") - matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0]) - http_version = ( - b"1.1" if matches["http_version"] is None else matches["http_version"] - ) - reason = b"" if matches["reason"] is None else matches["reason"] - status_code = int(matches["status_code"]) - class_: Union[Type[InformationalResponse], Type[Response]] = ( - InformationalResponse if status_code < 200 else Response - ) - return class_( - headers=list(_decode_header_lines(lines[1:])), - _parsed=True, - status_code=status_code, - reason=reason, - http_version=http_version, - ) - - -class ContentLengthReader: - def __init__(self, length: int) -> None: - self._length = length - self._remaining = length - - def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: - if self._remaining == 0: - return EndOfMessage() - data = buf.maybe_extract_at_most(self._remaining) - if data is None: - return None - self._remaining -= len(data) - return Data(data=data) - - def read_eof(self) -> NoReturn: - raise RemoteProtocolError( - "peer closed connection without sending complete message body " - "(received {} bytes, expected {})".format( - self._length - self._remaining, self._length - ) - ) - - -chunk_header_re = re.compile(chunk_header.encode("ascii")) - - -class ChunkedReader: - def __init__(self) -> None: - self._bytes_in_chunk = 0 - # After reading a chunk, we have to throw away the trailing \r\n. - # This tracks the bytes that we need to match and throw away. - self._bytes_to_discard = b"" - self._reading_trailer = False - - def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: - if self._reading_trailer: - lines = buf.maybe_extract_lines() - if lines is None: - return None - return EndOfMessage(headers=list(_decode_header_lines(lines))) - if self._bytes_to_discard: - data = buf.maybe_extract_at_most(len(self._bytes_to_discard)) - if data is None: - return None - if data != self._bytes_to_discard[: len(data)]: - raise LocalProtocolError( - f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})" - ) - self._bytes_to_discard = self._bytes_to_discard[len(data) :] - if self._bytes_to_discard: - return None - # else, fall through and read some more - assert self._bytes_to_discard == b"" - if self._bytes_in_chunk == 0: - # We need to refill our chunk count - chunk_header = buf.maybe_extract_next_line() - if chunk_header is None: - return None - matches = validate( - chunk_header_re, - chunk_header, - "illegal chunk header: {!r}", - chunk_header, - ) - # XX FIXME: we discard chunk extensions. Does anyone care? - self._bytes_in_chunk = int(matches["chunk_size"], base=16) - if self._bytes_in_chunk == 0: - self._reading_trailer = True - return self(buf) - chunk_start = True - else: - chunk_start = False - assert self._bytes_in_chunk > 0 - data = buf.maybe_extract_at_most(self._bytes_in_chunk) - if data is None: - return None - self._bytes_in_chunk -= len(data) - if self._bytes_in_chunk == 0: - self._bytes_to_discard = b"\r\n" - chunk_end = True - else: - chunk_end = False - return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end) - - def read_eof(self) -> NoReturn: - raise RemoteProtocolError( - "peer closed connection without sending complete message body " - "(incomplete chunked read)" - ) - - -class Http10Reader: - def __call__(self, buf: ReceiveBuffer) -> Optional[Data]: - data = buf.maybe_extract_at_most(999999999) - if data is None: - return None - return Data(data=data) - - def read_eof(self) -> EndOfMessage: - return EndOfMessage() - - -def expect_nothing(buf: ReceiveBuffer) -> None: - if buf: - raise LocalProtocolError("Got data when expecting EOF") - return None - - -ReadersType = Dict[ - Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]], - Union[Callable[..., Any], Dict[str, Callable[..., Any]]], -] - -READERS: ReadersType = { - (CLIENT, IDLE): maybe_read_from_IDLE_client, - (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server, - (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server, - (CLIENT, DONE): expect_nothing, - (CLIENT, MUST_CLOSE): expect_nothing, - (CLIENT, CLOSED): expect_nothing, - (SERVER, DONE): expect_nothing, - (SERVER, MUST_CLOSE): expect_nothing, - (SERVER, CLOSED): expect_nothing, - SEND_BODY: { - "chunked": ChunkedReader, - "content-length": ContentLengthReader, - "http/1.0": Http10Reader, - }, -} diff --git a/server/venv/Lib/site-packages/h11/_receivebuffer.py b/server/venv/Lib/site-packages/h11/_receivebuffer.py deleted file mode 100644 index e5c4e08..0000000 --- a/server/venv/Lib/site-packages/h11/_receivebuffer.py +++ /dev/null @@ -1,153 +0,0 @@ -import re -import sys -from typing import List, Optional, Union - -__all__ = ["ReceiveBuffer"] - - -# Operations we want to support: -# - find next \r\n or \r\n\r\n (\n or \n\n are also acceptable), -# or wait until there is one -# - read at-most-N bytes -# Goals: -# - on average, do this fast -# - worst case, do this in O(n) where n is the number of bytes processed -# Plan: -# - store bytearray, offset, how far we've searched for a separator token -# - use the how-far-we've-searched data to avoid rescanning -# - while doing a stream of uninterrupted processing, advance offset instead -# of constantly copying -# WARNING: -# - I haven't benchmarked or profiled any of this yet. -# -# Note that starting in Python 3.4, deleting the initial n bytes from a -# bytearray is amortized O(n), thanks to some excellent work by Antoine -# Martin: -# -# https://bugs.python.org/issue19087 -# -# This means that if we only supported 3.4+, we could get rid of the code here -# involving self._start and self.compress, because it's doing exactly the same -# thing that bytearray now does internally. -# -# BUT unfortunately, we still support 2.7, and reading short segments out of a -# long buffer MUST be O(bytes read) to avoid DoS issues, so we can't actually -# delete this code. Yet: -# -# https://pythonclock.org/ -# -# (Two things to double-check first though: make sure PyPy also has the -# optimization, and benchmark to make sure it's a win, since we do have a -# slightly clever thing where we delay calling compress() until we've -# processed a whole event, which could in theory be slightly more efficient -# than the internal bytearray support.) -blank_line_regex = re.compile(b"\n\r?\n", re.MULTILINE) - - -class ReceiveBuffer: - def __init__(self) -> None: - self._data = bytearray() - self._next_line_search = 0 - self._multiple_lines_search = 0 - - def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer": - self._data += byteslike - return self - - def __bool__(self) -> bool: - return bool(len(self)) - - def __len__(self) -> int: - return len(self._data) - - # for @property unprocessed_data - def __bytes__(self) -> bytes: - return bytes(self._data) - - def _extract(self, count: int) -> bytearray: - # extracting an initial slice of the data buffer and return it - out = self._data[:count] - del self._data[:count] - - self._next_line_search = 0 - self._multiple_lines_search = 0 - - return out - - def maybe_extract_at_most(self, count: int) -> Optional[bytearray]: - """ - Extract a fixed number of bytes from the buffer. - """ - out = self._data[:count] - if not out: - return None - - return self._extract(count) - - def maybe_extract_next_line(self) -> Optional[bytearray]: - """ - Extract the first line, if it is completed in the buffer. - """ - # Only search in buffer space that we've not already looked at. - search_start_index = max(0, self._next_line_search - 1) - partial_idx = self._data.find(b"\r\n", search_start_index) - - if partial_idx == -1: - self._next_line_search = len(self._data) - return None - - # + 2 is to compensate len(b"\r\n") - idx = partial_idx + 2 - - return self._extract(idx) - - def maybe_extract_lines(self) -> Optional[List[bytearray]]: - """ - Extract everything up to the first blank line, and return a list of lines. - """ - # Handle the case where we have an immediate empty line. - if self._data[:1] == b"\n": - self._extract(1) - return [] - - if self._data[:2] == b"\r\n": - self._extract(2) - return [] - - # Only search in buffer space that we've not already looked at. - match = blank_line_regex.search(self._data, self._multiple_lines_search) - if match is None: - self._multiple_lines_search = max(0, len(self._data) - 2) - return None - - # Truncate the buffer and return it. - idx = match.span(0)[-1] - out = self._extract(idx) - lines = out.split(b"\n") - - for line in lines: - if line.endswith(b"\r"): - del line[-1] - - assert lines[-2] == lines[-1] == b"" - - del lines[-2:] - - return lines - - # In theory we should wait until `\r\n` before starting to validate - # incoming data. However it's interesting to detect (very) invalid data - # early given they might not even contain `\r\n` at all (hence only - # timeout will get rid of them). - # This is not a 100% effective detection but more of a cheap sanity check - # allowing for early abort in some useful cases. - # This is especially interesting when peer is messing up with HTTPS and - # sent us a TLS stream where we were expecting plain HTTP given all - # versions of TLS so far start handshake with a 0x16 message type code. - def is_next_line_obviously_invalid_request_line(self) -> bool: - try: - # HTTP header line must not contain non-printable characters - # and should not start with a space - return self._data[0] < 0x21 - except IndexError: - return False diff --git a/server/venv/Lib/site-packages/h11/_state.py b/server/venv/Lib/site-packages/h11/_state.py deleted file mode 100644 index 3ad444b..0000000 --- a/server/venv/Lib/site-packages/h11/_state.py +++ /dev/null @@ -1,365 +0,0 @@ -################################################################ -# The core state machine -################################################################ -# -# Rule 1: everything that affects the state machine and state transitions must -# live here in this file. As much as possible goes into the table-based -# representation, but for the bits that don't quite fit, the actual code and -# state must nonetheless live here. -# -# Rule 2: this file does not know about what role we're playing; it only knows -# about HTTP request/response cycles in the abstract. This ensures that we -# don't cheat and apply different rules to local and remote parties. -# -# -# Theory of operation -# =================== -# -# Possibly the simplest way to think about this is that we actually have 5 -# different state machines here. Yes, 5. These are: -# -# 1) The client state, with its complicated automaton (see the docs) -# 2) The server state, with its complicated automaton (see the docs) -# 3) The keep-alive state, with possible states {True, False} -# 4) The SWITCH_CONNECT state, with possible states {False, True} -# 5) The SWITCH_UPGRADE state, with possible states {False, True} -# -# For (3)-(5), the first state listed is the initial state. -# -# (1)-(3) are stored explicitly in member variables. The last -# two are stored implicitly in the pending_switch_proposals set as: -# (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals) -# (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals) -# -# And each of these machines has two different kinds of transitions: -# -# a) Event-triggered -# b) State-triggered -# -# Event triggered is the obvious thing that you'd think it is: some event -# happens, and if it's the right event at the right time then a transition -# happens. But there are somewhat complicated rules for which machines can -# "see" which events. (As a rule of thumb, if a machine "sees" an event, this -# means two things: the event can affect the machine, and if the machine is -# not in a state where it expects that event then it's an error.) These rules -# are: -# -# 1) The client machine sees all h11.events objects emitted by the client. -# -# 2) The server machine sees all h11.events objects emitted by the server. -# -# It also sees the client's Request event. -# -# And sometimes, server events are annotated with a _SWITCH_* event. For -# example, we can have a (Response, _SWITCH_CONNECT) event, which is -# different from a regular Response event. -# -# 3) The keep-alive machine sees the process_keep_alive_disabled() event -# (which is derived from Request/Response events), and this event -# transitions it from True -> False, or from False -> False. There's no way -# to transition back. -# -# 4&5) The _SWITCH_* machines transition from False->True when we get a -# Request that proposes the relevant type of switch (via -# process_client_switch_proposals), and they go from True->False when we -# get a Response that has no _SWITCH_* annotation. -# -# So that's event-triggered transitions. -# -# State-triggered transitions are less standard. What they do here is couple -# the machines together. The way this works is, when certain *joint* -# configurations of states are achieved, then we automatically transition to a -# new *joint* state. So, for example, if we're ever in a joint state with -# -# client: DONE -# keep-alive: False -# -# then the client state immediately transitions to: -# -# client: MUST_CLOSE -# -# This is fundamentally different from an event-based transition, because it -# doesn't matter how we arrived at the {client: DONE, keep-alive: False} state -# -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive -# transitioned True -> False. Either way, once this precondition is satisfied, -# this transition is immediately triggered. -# -# What if two conflicting state-based transitions get enabled at the same -# time? In practice there's only one case where this arises (client DONE -> -# MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by -# explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition. -# -# Implementation -# -------------- -# -# The event-triggered transitions for the server and client machines are all -# stored explicitly in a table. Ditto for the state-triggered transitions that -# involve just the server and client state. -# -# The transitions for the other machines, and the state-triggered transitions -# that involve the other machines, are written out as explicit Python code. -# -# It'd be nice if there were some cleaner way to do all this. This isn't -# *too* terrible, but I feel like it could probably be better. -# -# WARNING -# ------- -# -# The script that generates the state machine diagrams for the docs knows how -# to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS -# tables. But it can't automatically read the transitions that are written -# directly in Python code. So if you touch those, you need to also update the -# script to keep it in sync! -from typing import cast, Dict, Optional, Set, Tuple, Type, Union - -from ._events import * -from ._util import LocalProtocolError, Sentinel - -# Everything in __all__ gets re-exported as part of the h11 public API. -__all__ = [ - "CLIENT", - "SERVER", - "IDLE", - "SEND_RESPONSE", - "SEND_BODY", - "DONE", - "MUST_CLOSE", - "CLOSED", - "MIGHT_SWITCH_PROTOCOL", - "SWITCHED_PROTOCOL", - "ERROR", -] - - -class CLIENT(Sentinel, metaclass=Sentinel): - pass - - -class SERVER(Sentinel, metaclass=Sentinel): - pass - - -# States -class IDLE(Sentinel, metaclass=Sentinel): - pass - - -class SEND_RESPONSE(Sentinel, metaclass=Sentinel): - pass - - -class SEND_BODY(Sentinel, metaclass=Sentinel): - pass - - -class DONE(Sentinel, metaclass=Sentinel): - pass - - -class MUST_CLOSE(Sentinel, metaclass=Sentinel): - pass - - -class CLOSED(Sentinel, metaclass=Sentinel): - pass - - -class ERROR(Sentinel, metaclass=Sentinel): - pass - - -# Switch types -class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel): - pass - - -class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel): - pass - - -class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel): - pass - - -class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel): - pass - - -EventTransitionType = Dict[ - Type[Sentinel], - Dict[ - Type[Sentinel], - Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]], - ], -] - -EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = { - CLIENT: { - IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED}, - SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, - DONE: {ConnectionClosed: CLOSED}, - MUST_CLOSE: {ConnectionClosed: CLOSED}, - CLOSED: {ConnectionClosed: CLOSED}, - MIGHT_SWITCH_PROTOCOL: {}, - SWITCHED_PROTOCOL: {}, - ERROR: {}, - }, - SERVER: { - IDLE: { - ConnectionClosed: CLOSED, - Response: SEND_BODY, - # Special case: server sees client Request events, in this form - (Request, CLIENT): SEND_RESPONSE, - }, - SEND_RESPONSE: { - InformationalResponse: SEND_RESPONSE, - Response: SEND_BODY, - (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL, - (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL, - }, - SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, - DONE: {ConnectionClosed: CLOSED}, - MUST_CLOSE: {ConnectionClosed: CLOSED}, - CLOSED: {ConnectionClosed: CLOSED}, - SWITCHED_PROTOCOL: {}, - ERROR: {}, - }, -} - -StateTransitionType = Dict[ - Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]] -] - -# NB: there are also some special-case state-triggered transitions hard-coded -# into _fire_state_triggered_transitions below. -STATE_TRIGGERED_TRANSITIONS: StateTransitionType = { - # (Client state, Server state) -> new states - # Protocol negotiation - (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL}, - # Socket shutdown - (CLOSED, DONE): {SERVER: MUST_CLOSE}, - (CLOSED, IDLE): {SERVER: MUST_CLOSE}, - (ERROR, DONE): {SERVER: MUST_CLOSE}, - (DONE, CLOSED): {CLIENT: MUST_CLOSE}, - (IDLE, CLOSED): {CLIENT: MUST_CLOSE}, - (DONE, ERROR): {CLIENT: MUST_CLOSE}, -} - - -class ConnectionState: - def __init__(self) -> None: - # Extra bits of state that don't quite fit into the state model. - - # If this is False then it enables the automatic DONE -> MUST_CLOSE - # transition. Don't set this directly; call .keep_alive_disabled() - self.keep_alive = True - - # This is a subset of {UPGRADE, CONNECT}, containing the proposals - # made by the client for switching protocols. - self.pending_switch_proposals: Set[Type[Sentinel]] = set() - - self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE} - - def process_error(self, role: Type[Sentinel]) -> None: - self.states[role] = ERROR - self._fire_state_triggered_transitions() - - def process_keep_alive_disabled(self) -> None: - self.keep_alive = False - self._fire_state_triggered_transitions() - - def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None: - self.pending_switch_proposals.add(switch_event) - self._fire_state_triggered_transitions() - - def process_event( - self, - role: Type[Sentinel], - event_type: Type[Event], - server_switch_event: Optional[Type[Sentinel]] = None, - ) -> None: - _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type - if server_switch_event is not None: - assert role is SERVER - if server_switch_event not in self.pending_switch_proposals: - raise LocalProtocolError( - "Received server _SWITCH_UPGRADE event without a pending proposal" - ) - _event_type = (event_type, server_switch_event) - if server_switch_event is None and _event_type is Response: - self.pending_switch_proposals = set() - self._fire_event_triggered_transitions(role, _event_type) - # Special case: the server state does get to see Request - # events. - if _event_type is Request: - assert role is CLIENT - self._fire_event_triggered_transitions(SERVER, (Request, CLIENT)) - self._fire_state_triggered_transitions() - - def _fire_event_triggered_transitions( - self, - role: Type[Sentinel], - event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], - ) -> None: - state = self.states[role] - try: - new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type] - except KeyError: - event_type = cast(Type[Event], event_type) - raise LocalProtocolError( - "can't handle event type {} when role={} and state={}".format( - event_type.__name__, role, self.states[role] - ) - ) from None - self.states[role] = new_state - - def _fire_state_triggered_transitions(self) -> None: - # We apply these rules repeatedly until converging on a fixed point - while True: - start_states = dict(self.states) - - # It could happen that both these special-case transitions are - # enabled at the same time: - # - # DONE -> MIGHT_SWITCH_PROTOCOL - # DONE -> MUST_CLOSE - # - # For example, this will always be true of a HTTP/1.0 client - # requesting CONNECT. If this happens, the protocol switch takes - # priority. From there the client will either go to - # SWITCHED_PROTOCOL, in which case it's none of our business when - # they close the connection, or else the server will deny the - # request, in which case the client will go back to DONE and then - # from there to MUST_CLOSE. - if self.pending_switch_proposals: - if self.states[CLIENT] is DONE: - self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL - - if not self.pending_switch_proposals: - if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL: - self.states[CLIENT] = DONE - - if not self.keep_alive: - for role in (CLIENT, SERVER): - if self.states[role] is DONE: - self.states[role] = MUST_CLOSE - - # Tabular state-triggered transitions - joint_state = (self.states[CLIENT], self.states[SERVER]) - changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {}) - self.states.update(changes) - - if self.states == start_states: - # Fixed point reached - return - - def start_next_cycle(self) -> None: - if self.states != {CLIENT: DONE, SERVER: DONE}: - raise LocalProtocolError( - f"not in a reusable state. self.states={self.states}" - ) - # Can't reach DONE/DONE with any of these active, but still, let's be - # sure. - assert self.keep_alive - assert not self.pending_switch_proposals - self.states = {CLIENT: IDLE, SERVER: IDLE} diff --git a/server/venv/Lib/site-packages/h11/_util.py b/server/venv/Lib/site-packages/h11/_util.py deleted file mode 100644 index 6718445..0000000 --- a/server/venv/Lib/site-packages/h11/_util.py +++ /dev/null @@ -1,135 +0,0 @@ -from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union - -__all__ = [ - "ProtocolError", - "LocalProtocolError", - "RemoteProtocolError", - "validate", - "bytesify", -] - - -class ProtocolError(Exception): - """Exception indicating a violation of the HTTP/1.1 protocol. - - This as an abstract base class, with two concrete base classes: - :exc:`LocalProtocolError`, which indicates that you tried to do something - that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which - indicates that the remote peer tried to do something that HTTP/1.1 says is - illegal. See :ref:`error-handling` for details. - - In addition to the normal :exc:`Exception` features, it has one attribute: - - .. attribute:: error_status_hint - - This gives a suggestion as to what status code a server might use if - this error occurred as part of a request. - - For a :exc:`RemoteProtocolError`, this is useful as a suggestion for - how you might want to respond to a misbehaving peer, if you're - implementing a server. - - For a :exc:`LocalProtocolError`, this can be taken as a suggestion for - how your peer might have responded to *you* if h11 had allowed you to - continue. - - The default is 400 Bad Request, a generic catch-all for protocol - violations. - - """ - - def __init__(self, msg: str, error_status_hint: int = 400) -> None: - if type(self) is ProtocolError: - raise TypeError("tried to directly instantiate ProtocolError") - Exception.__init__(self, msg) - self.error_status_hint = error_status_hint - - -# Strategy: there are a number of public APIs where a LocalProtocolError can -# be raised (send(), all the different event constructors, ...), and only one -# public API where RemoteProtocolError can be raised -# (receive_data()). Therefore we always raise LocalProtocolError internally, -# and then receive_data will translate this into a RemoteProtocolError. -# -# Internally: -# LocalProtocolError is the generic "ProtocolError". -# Externally: -# LocalProtocolError is for local errors and RemoteProtocolError is for -# remote errors. -class LocalProtocolError(ProtocolError): - def _reraise_as_remote_protocol_error(self) -> NoReturn: - # After catching a LocalProtocolError, use this method to re-raise it - # as a RemoteProtocolError. This method must be called from inside an - # except: block. - # - # An easy way to get an equivalent RemoteProtocolError is just to - # modify 'self' in place. - self.__class__ = RemoteProtocolError # type: ignore - # But the re-raising is somewhat non-trivial -- you might think that - # now that we've modified the in-flight exception object, that just - # doing 'raise' to re-raise it would be enough. But it turns out that - # this doesn't work, because Python tracks the exception type - # (exc_info[0]) separately from the exception object (exc_info[1]), - # and we only modified the latter. So we really do need to re-raise - # the new type explicitly. - # On py3, the traceback is part of the exception object, so our - # in-place modification preserved it and we can just re-raise: - raise self - - -class RemoteProtocolError(ProtocolError): - pass - - -def validate( - regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any -) -> Dict[str, bytes]: - match = regex.fullmatch(data) - if not match: - if format_args: - msg = msg.format(*format_args) - raise LocalProtocolError(msg) - return match.groupdict() - - -# Sentinel values -# -# - Inherit identity-based comparison and hashing from object -# - Have a nice repr -# - Have a *bonus property*: type(sentinel) is sentinel -# -# The bonus property is useful if you want to take the return value from -# next_event() and do some sort of dispatch based on type(event). - -_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel") - - -class Sentinel(type): - def __new__( - cls: Type[_T_Sentinel], - name: str, - bases: Tuple[type, ...], - namespace: Dict[str, Any], - **kwds: Any - ) -> _T_Sentinel: - assert bases == (Sentinel,) - v = super().__new__(cls, name, bases, namespace, **kwds) - v.__class__ = v # type: ignore - return v - - def __repr__(self) -> str: - return self.__name__ - - -# Used for methods, request targets, HTTP versions, header names, and header -# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always -# returns bytes. -def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes: - # Fast-path: - if type(s) is bytes: - return s - if isinstance(s, str): - s = s.encode("ascii") - if isinstance(s, int): - raise TypeError("expected bytes-like object, not int") - return bytes(s) diff --git a/server/venv/Lib/site-packages/h11/_version.py b/server/venv/Lib/site-packages/h11/_version.py deleted file mode 100644 index 76e7327..0000000 --- a/server/venv/Lib/site-packages/h11/_version.py +++ /dev/null @@ -1,16 +0,0 @@ -# This file must be kept very simple, because it is consumed from several -# places -- it is imported by h11/__init__.py, execfile'd by setup.py, etc. - -# We use a simple scheme: -# 1.0.0 -> 1.0.0+dev -> 1.1.0 -> 1.1.0+dev -# where the +dev versions are never released into the wild, they're just what -# we stick into the VCS in between releases. -# -# This is compatible with PEP 440: -# http://legacy.python.org/dev/peps/pep-0440/ -# via the use of the "local suffix" "+dev", which is disallowed on index -# servers and causes 1.0.0+dev to sort after plain 1.0.0, which is what we -# want. (Contrast with the special suffix 1.0.0.dev, which sorts *before* -# 1.0.0.) - -__version__ = "0.16.0" diff --git a/server/venv/Lib/site-packages/h11/_writers.py b/server/venv/Lib/site-packages/h11/_writers.py deleted file mode 100644 index 939cdb9..0000000 --- a/server/venv/Lib/site-packages/h11/_writers.py +++ /dev/null @@ -1,145 +0,0 @@ -# Code to read HTTP data -# -# Strategy: each writer takes an event + a write-some-bytes function, which is -# calls. -# -# WRITERS is a dict describing how to pick a reader. It maps states to either: -# - a writer -# - or, for body writers, a dict of framin-dependent writer factories - -from typing import Any, Callable, Dict, List, Tuple, Type, Union - -from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response -from ._headers import Headers -from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER -from ._util import LocalProtocolError, Sentinel - -__all__ = ["WRITERS"] - -Writer = Callable[[bytes], Any] - - -def write_headers(headers: Headers, write: Writer) -> None: - # "Since the Host field-value is critical information for handling a - # request, a user agent SHOULD generate Host as the first header field - # following the request-line." - RFC 7230 - raw_items = headers._full_items - for raw_name, name, value in raw_items: - if name == b"host": - write(b"%s: %s\r\n" % (raw_name, value)) - for raw_name, name, value in raw_items: - if name != b"host": - write(b"%s: %s\r\n" % (raw_name, value)) - write(b"\r\n") - - -def write_request(request: Request, write: Writer) -> None: - if request.http_version != b"1.1": - raise LocalProtocolError("I only send HTTP/1.1") - write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target)) - write_headers(request.headers, write) - - -# Shared between InformationalResponse and Response -def write_any_response( - response: Union[InformationalResponse, Response], write: Writer -) -> None: - if response.http_version != b"1.1": - raise LocalProtocolError("I only send HTTP/1.1") - status_bytes = str(response.status_code).encode("ascii") - # We don't bother sending ascii status messages like "OK"; they're - # optional and ignored by the protocol. (But the space after the numeric - # status code is mandatory.) - # - # XX FIXME: could at least make an effort to pull out the status message - # from stdlib's http.HTTPStatus table. Or maybe just steal their enums - # (either by import or copy/paste). We already accept them as status codes - # since they're of type IntEnum < int. - write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason)) - write_headers(response.headers, write) - - -class BodyWriter: - def __call__(self, event: Event, write: Writer) -> None: - if type(event) is Data: - self.send_data(event.data, write) - elif type(event) is EndOfMessage: - self.send_eom(event.headers, write) - else: # pragma: no cover - assert False - - def send_data(self, data: bytes, write: Writer) -> None: - pass - - def send_eom(self, headers: Headers, write: Writer) -> None: - pass - - -# -# These are all careful not to do anything to 'data' except call len(data) and -# write(data). This allows us to transparently pass-through funny objects, -# like placeholder objects referring to files on disk that will be sent via -# sendfile(2). -# -class ContentLengthWriter(BodyWriter): - def __init__(self, length: int) -> None: - self._length = length - - def send_data(self, data: bytes, write: Writer) -> None: - self._length -= len(data) - if self._length < 0: - raise LocalProtocolError("Too much data for declared Content-Length") - write(data) - - def send_eom(self, headers: Headers, write: Writer) -> None: - if self._length != 0: - raise LocalProtocolError("Too little data for declared Content-Length") - if headers: - raise LocalProtocolError("Content-Length and trailers don't mix") - - -class ChunkedWriter(BodyWriter): - def send_data(self, data: bytes, write: Writer) -> None: - # if we encoded 0-length data in the naive way, it would look like an - # end-of-message. - if not data: - return - write(b"%x\r\n" % len(data)) - write(data) - write(b"\r\n") - - def send_eom(self, headers: Headers, write: Writer) -> None: - write(b"0\r\n") - write_headers(headers, write) - - -class Http10Writer(BodyWriter): - def send_data(self, data: bytes, write: Writer) -> None: - write(data) - - def send_eom(self, headers: Headers, write: Writer) -> None: - if headers: - raise LocalProtocolError("can't send trailers to HTTP/1.0 client") - # no need to close the socket ourselves, that will be taken care of by - # Connection: close machinery - - -WritersType = Dict[ - Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]], - Union[ - Dict[str, Type[BodyWriter]], - Callable[[Union[InformationalResponse, Response], Writer], None], - Callable[[Request, Writer], None], - ], -] - -WRITERS: WritersType = { - (CLIENT, IDLE): write_request, - (SERVER, IDLE): write_any_response, - (SERVER, SEND_RESPONSE): write_any_response, - SEND_BODY: { - "chunked": ChunkedWriter, - "content-length": ContentLengthWriter, - "http/1.0": Http10Writer, - }, -} diff --git a/server/venv/Lib/site-packages/h11/py.typed b/server/venv/Lib/site-packages/h11/py.typed deleted file mode 100644 index f5642f7..0000000 --- a/server/venv/Lib/site-packages/h11/py.typed +++ /dev/null @@ -1 +0,0 @@ -Marker diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/INSTALLER b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/METADATA b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/METADATA deleted file mode 100644 index 5a74ddb..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/METADATA +++ /dev/null @@ -1,131 +0,0 @@ -Metadata-Version: 2.4 -Name: httptools -Version: 0.8.0 -Summary: A collection of framework independent HTTP protocol utils. -Author-email: Yury Selivanov -License-Expression: MIT -Project-URL: Homepage, https://github.com/MagicStack/httptools -Platform: macOS -Platform: POSIX -Platform: Windows -Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python :: 3 -Classifier: Operating System :: POSIX -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Environment :: Web Environment -Classifier: Development Status :: 5 - Production/Stable -Requires-Python: >=3.9 -Description-Content-Type: text/markdown -License-File: LICENSE -License-File: vendor/http-parser/LICENSE-MIT -License-File: vendor/llhttp/LICENSE -Dynamic: license-file -Dynamic: platform - -![Tests](https://github.com/MagicStack/httptools/workflows/Tests/badge.svg) - -httptools is a Python binding for the nodejs HTTP parser. - -The package is available on PyPI: `pip install httptools`. - - -# APIs - -httptools contains two classes `httptools.HttpRequestParser`, -`httptools.HttpResponseParser` (fulfilled through -[llhttp](https://github.com/nodejs/llhttp)) and a function for -parsing URLs `httptools.parse_url` (through -[http-parse](https://github.com/nodejs/http-parser) for now). -See unittests for examples. - - -```python - -class HttpRequestParser: - - def __init__(self, protocol): - """HttpRequestParser - - protocol -- a Python object with the following methods - (all optional): - - - on_message_begin() - - on_url(url: bytes) - - on_header(name: bytes, value: bytes) - - on_headers_complete() - - on_body(body: bytes) - - on_message_complete() - - on_chunk_header() - - on_chunk_complete() - - on_status(status: bytes) - """ - - def get_http_version(self) -> str: - """Return an HTTP protocol version.""" - - def should_keep_alive(self) -> bool: - """Return ``True`` if keep-alive mode is preferred.""" - - def should_upgrade(self) -> bool: - """Return ``True`` if the parsed request is a valid Upgrade request. - The method exposes a flag set just before on_headers_complete. - Calling this method earlier will only yield `False`. - """ - - def feed_data(self, data: bytes): - """Feed data to the parser. - - Will eventually trigger callbacks on the ``protocol`` - object. - - On HTTP upgrade, this method will raise an - ``HttpParserUpgrade`` exception, with its sole argument - set to the offset of the non-HTTP data in ``data``. - """ - - def get_method(self) -> bytes: - """Return HTTP request method (GET, HEAD, etc)""" - - -class HttpResponseParser: - - """Has all methods except ``get_method()`` that - HttpRequestParser has.""" - - def get_status_code(self) -> int: - """Return the status code of the HTTP response""" - - -def parse_url(url: bytes): - """Parse URL strings into a structured Python object. - - Returns an instance of ``httptools.URL`` class with the - following attributes: - - - schema: bytes - - host: bytes - - port: int - - path: bytes - - query: bytes - - fragment: bytes - - userinfo: bytes - """ -``` - - -# Development - -1. Clone this repository with - `git clone --recursive git@github.com:MagicStack/httptools.git` - -2. Create a virtual environment with Python 3: - `python3 -m venv envname` - -3. Activate the environment with `source envname/bin/activate` - -4. Run `make` and `make test`. - - -# License - -MIT. diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/RECORD b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/RECORD deleted file mode 100644 index 995a598..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/RECORD +++ /dev/null @@ -1,28 +0,0 @@ -httptools-0.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -httptools-0.8.0.dist-info/METADATA,sha256=4rBEKetR3NrzjzwCpmMX5YI0eTFD7h6tybgZiCEEAOY,3663 -httptools-0.8.0.dist-info/RECORD,, -httptools-0.8.0.dist-info/WHEEL,sha256=x5Wpw_tLx5PQKiWdxpqvs0e7Sg-SO0mTWdEADYDGPGA,101 -httptools-0.8.0.dist-info/licenses/LICENSE,sha256=HAKLBZ16WOFMzaOMq87WmMWR_mFP--T3IUMERuhsjWk,1114 -httptools-0.8.0.dist-info/licenses/vendor/http-parser/LICENSE-MIT,sha256=XNG2gsDMusDLMbhqmhEEw-LnsTd6KCuBadJpS0ANt8I,1096 -httptools-0.8.0.dist-info/licenses/vendor/llhttp/LICENSE,sha256=J5AS4CoQrP1Zo_LY8TpJczJTXYccKyfImYiYWwajpDg,1091 -httptools-0.8.0.dist-info/top_level.txt,sha256=APjJKTbZcj0OQ4fdgf2eTCk82nK1n2BFXOD7ky41MPY,10 -httptools/__init__.py,sha256=4jrSwXhVO8VMsFde5pRFIRc9IZgT7IqtRHlMJtc1jJw,759 -httptools/__pycache__/__init__.cpython-313.pyc,, -httptools/__pycache__/_version.cpython-313.pyc,, -httptools/_version.py,sha256=N1JaBh8KG5nLIetvXAKqMVo3dslEx-oJoHf65BywhrU,588 -httptools/parser/__init__.py,sha256=XounAcwcwAKDNLWlEdcPv5EKAgMXgpXV7o7A0-twbp8,704 -httptools/parser/__pycache__/__init__.cpython-313.pyc,, -httptools/parser/__pycache__/errors.cpython-313.pyc,, -httptools/parser/__pycache__/protocol.cpython-313.pyc,, -httptools/parser/cparser.pxd,sha256=XOczyt3PFtYc1r-GVKecEkN5zvBD78NXfd2JhyKOKBg,5144 -httptools/parser/errors.py,sha256=fQeEGiZ6AQH7SIa-7uzMs5QggoXr0A8YBPVHmafSw5Q,596 -httptools/parser/parser.cp313-win_amd64.pyd,sha256=nQCFUQZUoH6krlrR_54ArkC86Grx9d68nHx4xCXtQ1I,123904 -httptools/parser/parser.pyi,sha256=ZRhM6dEoqyJNTdMfgw9TVbkUOQCgkgVBlA3bfApV3dE,2215 -httptools/parser/parser.pyx,sha256=jgDH58PbHrXpT4wGDptP02tGsceOo9qHYSJBdpK5ZEs,15576 -httptools/parser/protocol.py,sha256=_NTcrPAY9tFe4tE--Noa9-mEGc8aBnWamFkPDGGKNiU,601 -httptools/parser/python.pxd,sha256=-pIXNLe1u3u3X51ORUUoHRBMsntzRlQa3z2iBqljTHU,144 -httptools/parser/url_cparser.pxd,sha256=rvPJV7S96tTqKMT0Bk4jHgMy2U0rNY5_NsPa7KG5lio,810 -httptools/parser/url_parser.cp313-win_amd64.pyd,sha256=w_f6JYhVyY72JO3GUPDf2ipx58kQ2d2NBj53QRDvZ0k,50688 -httptools/parser/url_parser.pyi,sha256=hu_X-VBNWIoxf6kOnF0B7Cju0n1VdPwxdkY-Jr4vPRM,305 -httptools/parser/url_parser.pyx,sha256=jzmxvNHzzKTyEgzQZwtx_hhjPXPoENkri-08zwXTkCA,4353 -httptools/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/WHEEL b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/WHEEL deleted file mode 100644 index f787649..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: setuptools (82.0.1) -Root-Is-Purelib: false -Tag: cp313-cp313-win_amd64 - diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/LICENSE b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/LICENSE deleted file mode 100644 index 79a03ca..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2015 MagicStack Inc. http://magic.io - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/vendor/http-parser/LICENSE-MIT b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/vendor/http-parser/LICENSE-MIT deleted file mode 100644 index 1ec0ab4..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/vendor/http-parser/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -Copyright Joyent, Inc. and other Node contributors. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/vendor/llhttp/LICENSE b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/vendor/llhttp/LICENSE deleted file mode 100644 index 23682c0..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/licenses/vendor/llhttp/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright © 2018 Fedor Indutny - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the -following conditions: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/top_level.txt b/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/top_level.txt deleted file mode 100644 index bef3b40..0000000 --- a/server/venv/Lib/site-packages/httptools-0.8.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -httptools diff --git a/server/venv/Lib/site-packages/httptools/__init__.py b/server/venv/Lib/site-packages/httptools/__init__.py deleted file mode 100644 index 8b7b327..0000000 --- a/server/venv/Lib/site-packages/httptools/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -from . import parser -from .parser import ( - HTTPProtocol, - HttpRequestParser, - HttpResponseParser, - HttpParserError, - HttpParserCallbackError, - HttpParserInvalidStatusError, - HttpParserInvalidMethodError, - HttpParserInvalidURLError, - HttpParserUpgrade, - parse_url, -) - -from ._version import __version__ - -__all__ = ( - "parser", - # protocol - "HTTPProtocol", - # parser - "HttpRequestParser", - "HttpResponseParser", - # errors - "HttpParserError", - "HttpParserCallbackError", - "HttpParserInvalidStatusError", - "HttpParserInvalidMethodError", - "HttpParserInvalidURLError", - "HttpParserUpgrade", - # url parser - "parse_url", - # version - "__version__", -) diff --git a/server/venv/Lib/site-packages/httptools/_version.py b/server/venv/Lib/site-packages/httptools/_version.py deleted file mode 100644 index b8b0ad2..0000000 --- a/server/venv/Lib/site-packages/httptools/_version.py +++ /dev/null @@ -1,13 +0,0 @@ -# This file MUST NOT contain anything but the __version__ assignment. -# -# When making a release, change the value of __version__ -# to an appropriate value, and open a pull request against -# the correct branch (master if making a new feature release). -# The commit message MUST contain a properly formatted release -# log, and the commit must be signed. -# -# The release automation will: build and test the packages for the -# supported platforms, publish the packages on PyPI, merge the PR -# to the target branch, create a Git tag pointing to the commit. - -__version__ = '0.8.0' diff --git a/server/venv/Lib/site-packages/httptools/parser/__init__.py b/server/venv/Lib/site-packages/httptools/parser/__init__.py deleted file mode 100644 index 6f57517..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -from .protocol import HTTPProtocol -from .parser import HttpRequestParser, HttpResponseParser # NoQA -from .errors import ( - HttpParserError, - HttpParserCallbackError, - HttpParserInvalidStatusError, - HttpParserInvalidMethodError, - HttpParserInvalidURLError, - HttpParserUpgrade, -) -from .url_parser import parse_url - -__all__ = ( - # protocol - "HTTPProtocol", - # parser - "HttpRequestParser", - "HttpResponseParser", - # errors - "HttpParserError", - "HttpParserCallbackError", - "HttpParserInvalidStatusError", - "HttpParserInvalidMethodError", - "HttpParserInvalidURLError", - "HttpParserUpgrade", - # url_parser - "parse_url", -) diff --git a/server/venv/Lib/site-packages/httptools/parser/cparser.pxd b/server/venv/Lib/site-packages/httptools/parser/cparser.pxd deleted file mode 100644 index 3281864..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/cparser.pxd +++ /dev/null @@ -1,167 +0,0 @@ -from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t - - -cdef extern from "llhttp.h": - struct llhttp__internal_s: - int32_t _index - void *_span_pos0 - void *_span_cb0 - int32_t error - const char *reason - const char *error_pos - void *data - void *_current - uint64_t content_length - uint8_t type - uint8_t method - uint8_t http_major - uint8_t http_minor - uint8_t header_state - uint16_t flags - uint8_t upgrade - uint16_t status_code - uint8_t finish - void *settings - ctypedef llhttp__internal_s llhttp__internal_t - ctypedef llhttp__internal_t llhttp_t - - ctypedef int (*llhttp_data_cb) (llhttp_t*, - const char *at, - size_t length) except -1 - - ctypedef int (*llhttp_cb) (llhttp_t*) except -1 - - struct llhttp_settings_s: - llhttp_cb on_message_begin - llhttp_data_cb on_url - llhttp_data_cb on_status - llhttp_data_cb on_header_field - llhttp_data_cb on_header_value - llhttp_cb on_headers_complete - llhttp_data_cb on_body - llhttp_cb on_message_complete - llhttp_cb on_chunk_header - llhttp_cb on_chunk_complete - ctypedef llhttp_settings_s llhttp_settings_t - - enum llhttp_type: - HTTP_BOTH, - HTTP_REQUEST, - HTTP_RESPONSE - ctypedef llhttp_type llhttp_type_t - - enum llhttp_errno: - HPE_OK, - HPE_INTERNAL, - HPE_STRICT, - HPE_LF_EXPECTED, - HPE_UNEXPECTED_CONTENT_LENGTH, - HPE_CLOSED_CONNECTION, - HPE_INVALID_METHOD, - HPE_INVALID_URL, - HPE_INVALID_CONSTANT, - HPE_INVALID_VERSION, - HPE_INVALID_HEADER_TOKEN, - HPE_INVALID_CONTENT_LENGTH, - HPE_INVALID_CHUNK_SIZE, - HPE_INVALID_STATUS, - HPE_INVALID_EOF_STATE, - HPE_INVALID_TRANSFER_ENCODING, - HPE_CB_MESSAGE_BEGIN, - HPE_CB_HEADERS_COMPLETE, - HPE_CB_MESSAGE_COMPLETE, - HPE_CB_CHUNK_HEADER, - HPE_CB_CHUNK_COMPLETE, - HPE_PAUSED, - HPE_PAUSED_UPGRADE, - HPE_USER - ctypedef llhttp_errno llhttp_errno_t - - enum llhttp_flags: - F_CONNECTION_KEEP_ALIVE, - F_CONNECTION_CLOSE, - F_CONNECTION_UPGRADE, - F_CHUNKED, - F_UPGRADE, - F_CONTENT_LENGTH, - F_SKIPBODY, - F_TRAILING, - F_LENIENT, - F_TRANSFER_ENCODING - ctypedef llhttp_flags llhttp_flags_t - - enum llhttp_method: - HTTP_DELETE, - HTTP_GET, - HTTP_HEAD, - HTTP_POST, - HTTP_PUT, - HTTP_CONNECT, - HTTP_OPTIONS, - HTTP_TRACE, - HTTP_COPY, - HTTP_LOCK, - HTTP_MKCOL, - HTTP_MOVE, - HTTP_PROPFIND, - HTTP_PROPPATCH, - HTTP_SEARCH, - HTTP_UNLOCK, - HTTP_BIND, - HTTP_REBIND, - HTTP_UNBIND, - HTTP_ACL, - HTTP_REPORT, - HTTP_MKACTIVITY, - HTTP_CHECKOUT, - HTTP_MERGE, - HTTP_MSEARCH, - HTTP_NOTIFY, - HTTP_SUBSCRIBE, - HTTP_UNSUBSCRIBE, - HTTP_PATCH, - HTTP_PURGE, - HTTP_MKCALENDAR, - HTTP_LINK, - HTTP_UNLINK, - HTTP_SOURCE, - HTTP_PRI, - HTTP_DESCRIBE, - HTTP_ANNOUNCE, - HTTP_SETUP, - HTTP_PLAY, - HTTP_PAUSE, - HTTP_TEARDOWN, - HTTP_GET_PARAMETER, - HTTP_SET_PARAMETER, - HTTP_REDIRECT, - HTTP_RECORD, - HTTP_FLUSH - ctypedef llhttp_method llhttp_method_t - - void llhttp_init(llhttp_t* parser, llhttp_type_t type, const llhttp_settings_t* settings) - - void llhttp_settings_init(llhttp_settings_t* settings) - - llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) - - void llhttp_resume_after_upgrade(llhttp_t* parser) - - int llhttp_should_keep_alive(const llhttp_t* parser) - - const char* llhttp_get_error_pos(const llhttp_t* parser) - const char* llhttp_get_error_reason(const llhttp_t* parser) - const char* llhttp_method_name(llhttp_method_t method) - - void llhttp_set_error_reason(llhttp_t* parser, const char* reason); - - void llhttp_set_lenient_headers(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_chunked_length(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_keep_alive(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_version(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_data_after_close(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, bint enabled); - void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, bint enabled); diff --git a/server/venv/Lib/site-packages/httptools/parser/errors.py b/server/venv/Lib/site-packages/httptools/parser/errors.py deleted file mode 100644 index bc24c46..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/errors.py +++ /dev/null @@ -1,30 +0,0 @@ -__all__ = ('HttpParserError', - 'HttpParserCallbackError', - 'HttpParserInvalidStatusError', - 'HttpParserInvalidMethodError', - 'HttpParserInvalidURLError', - 'HttpParserUpgrade') - - -class HttpParserError(Exception): - pass - - -class HttpParserCallbackError(HttpParserError): - pass - - -class HttpParserInvalidStatusError(HttpParserError): - pass - - -class HttpParserInvalidMethodError(HttpParserError): - pass - - -class HttpParserInvalidURLError(HttpParserError): - pass - - -class HttpParserUpgrade(Exception): - pass diff --git a/server/venv/Lib/site-packages/httptools/parser/parser.cp313-win_amd64.pyd b/server/venv/Lib/site-packages/httptools/parser/parser.cp313-win_amd64.pyd deleted file mode 100644 index d17bfea..0000000 Binary files a/server/venv/Lib/site-packages/httptools/parser/parser.cp313-win_amd64.pyd and /dev/null differ diff --git a/server/venv/Lib/site-packages/httptools/parser/parser.pyi b/server/venv/Lib/site-packages/httptools/parser/parser.pyi deleted file mode 100644 index 8afd006..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/parser.pyi +++ /dev/null @@ -1,58 +0,0 @@ -from array import array -from .protocol import HTTPProtocol - -class HttpParser: - def __init__(self, protocol: HTTPProtocol | object) -> None: - """The HTTP parser. - - Args: - protocol (HTTPProtocol): Callback interface for the parser. - """ - - def set_dangerous_leniencies( - self, - lenient_headers: bool | None = None, - lenient_chunked_length: bool | None = None, - lenient_keep_alive: bool | None = None, - lenient_transfer_encoding: bool | None = None, - lenient_version: bool | None = None, - lenient_data_after_close: bool | None = None, - lenient_optional_lf_after_cr: bool | None = None, - lenient_optional_cr_before_lf: bool | None = None, - lenient_optional_crlf_after_chunk: bool | None = None, - lenient_spaces_after_chunk_size: bool | None = None, - ) -> None: - """Set dangerous leniencies for the parser.""" - - def get_http_version(self) -> str: - """Retrieve the HTTP protocol version e.g. "1.1".""" - - def should_keep_alive(self) -> bool: - """Return `True` if keep-alive mode is preferred.""" - - def should_upgrade(self) -> bool: - """Return `True` if the parsed request is a valid Upgrade request. - The method exposes a flag set just before on_headers_complete. - Calling this method earlier will only yield `False`.""" - - def feed_data(self, data: bytes | bytearray | memoryview | array[int]) -> None: - """Feed data to the parser. - - Will eventually trigger callbacks on the ``protocol`` object. - - On HTTP upgrade, this method will raise an - ``HttpParserUpgrade`` exception, with its sole argument - set to the offset of the non-HTTP data in ``data``. - """ - -class HttpRequestParser(HttpParser): - """Used for parsing http requests from the server side.""" - - def get_method(self) -> bytes: - """Retrieve the HTTP method of the request.""" - -class HttpResponseParser(HttpParser): - """Used for parsing http responses from the client side.""" - - def get_status_code(self) -> int: - """Retrieve the status code of the HTTP response.""" diff --git a/server/venv/Lib/site-packages/httptools/parser/parser.pyx b/server/venv/Lib/site-packages/httptools/parser/parser.pyx deleted file mode 100644 index 2fa5026..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/parser.pyx +++ /dev/null @@ -1,436 +0,0 @@ -#cython: language_level=3 - -from __future__ import print_function -from typing import Optional - -from cpython.mem cimport PyMem_Malloc, PyMem_Free -from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \ - Py_buffer, PyBytes_AsString - -from .python cimport PyMemoryView_Check, PyMemoryView_GET_BUFFER - - -from .errors import (HttpParserError, - HttpParserCallbackError, - HttpParserInvalidStatusError, - HttpParserInvalidMethodError, - HttpParserInvalidURLError, - HttpParserUpgrade) - -cimport cython -from . cimport cparser - - -__all__ = ('HttpRequestParser', 'HttpResponseParser') - - -@cython.internal -cdef class HttpParser: - - cdef: - cparser.llhttp_t* _cparser - cparser.llhttp_settings_t* _csettings - - bytes _current_header_name - bytes _current_header_value - - _proto_on_url, _proto_on_status, _proto_on_body, \ - _proto_on_header, _proto_on_headers_complete, \ - _proto_on_message_complete, _proto_on_chunk_header, \ - _proto_on_chunk_complete, _proto_on_message_begin - - object _last_error - - Py_buffer py_buf - - def __cinit__(self): - self._cparser = \ - PyMem_Malloc(sizeof(cparser.llhttp_t)) - if self._cparser is NULL: - raise MemoryError() - - self._csettings = \ - PyMem_Malloc(sizeof(cparser.llhttp_settings_t)) - if self._csettings is NULL: - raise MemoryError() - - def __dealloc__(self): - PyMem_Free(self._cparser) - PyMem_Free(self._csettings) - - cdef _init(self, protocol, cparser.llhttp_type_t mode): - cparser.llhttp_settings_init(self._csettings) - - cparser.llhttp_init(self._cparser, mode, self._csettings) - self._cparser.data = self - - self._current_header_name = None - self._current_header_value = None - - self._proto_on_header = getattr(protocol, 'on_header', None) - if self._proto_on_header is not None: - self._csettings.on_header_field = cb_on_header_field - self._csettings.on_header_value = cb_on_header_value - self._proto_on_headers_complete = getattr( - protocol, 'on_headers_complete', None) - self._csettings.on_headers_complete = cb_on_headers_complete - - self._proto_on_body = getattr(protocol, 'on_body', None) - if self._proto_on_body is not None: - self._csettings.on_body = cb_on_body - - self._proto_on_message_begin = getattr( - protocol, 'on_message_begin', None) - if self._proto_on_message_begin is not None: - self._csettings.on_message_begin = cb_on_message_begin - - self._proto_on_message_complete = getattr( - protocol, 'on_message_complete', None) - if self._proto_on_message_complete is not None: - self._csettings.on_message_complete = cb_on_message_complete - - self._proto_on_chunk_header = getattr( - protocol, 'on_chunk_header', None) - self._csettings.on_chunk_header = cb_on_chunk_header - - self._proto_on_chunk_complete = getattr( - protocol, 'on_chunk_complete', None) - self._csettings.on_chunk_complete = cb_on_chunk_complete - - self._last_error = None - - cdef _maybe_call_on_header(self): - if self._current_header_value is not None: - current_header_name = self._current_header_name - current_header_value = self._current_header_value - - self._current_header_name = self._current_header_value = None - - if self._proto_on_header is not None: - self._proto_on_header(current_header_name, - current_header_value) - - cdef _on_header_field(self, bytes field): - self._maybe_call_on_header() - if self._current_header_name is None: - self._current_header_name = field - else: - self._current_header_name += field - - cdef _on_header_value(self, bytes val): - if self._current_header_value is None: - self._current_header_value = val - else: - # This is unlikely, as mostly HTTP headers are one-line - self._current_header_value += val - - cdef _on_headers_complete(self): - self._maybe_call_on_header() - - if self._proto_on_headers_complete is not None: - self._proto_on_headers_complete() - - cdef _on_chunk_header(self): - if (self._current_header_value is not None or - self._current_header_name is not None): - raise HttpParserError('invalid headers state') - - if self._proto_on_chunk_header is not None: - self._proto_on_chunk_header() - - cdef _on_chunk_complete(self): - self._maybe_call_on_header() - - if self._proto_on_chunk_complete is not None: - self._proto_on_chunk_complete() - - ### Public API ### - - def set_dangerous_leniencies( - self, - lenient_headers: Optional[bool] = None, - lenient_chunked_length: Optional[bool] = None, - lenient_keep_alive: Optional[bool] = None, - lenient_transfer_encoding: Optional[bool] = None, - lenient_version: Optional[bool] = None, - lenient_data_after_close: Optional[bool] = None, - lenient_optional_lf_after_cr: Optional[bool] = None, - lenient_optional_cr_before_lf: Optional[bool] = None, - lenient_optional_crlf_after_chunk: Optional[bool] = None, - lenient_spaces_after_chunk_size: Optional[bool] = None, - ): - cdef cparser.llhttp_t* parser = self._cparser - if lenient_headers is not None: - cparser.llhttp_set_lenient_headers( - parser, lenient_headers) - if lenient_chunked_length is not None: - cparser.llhttp_set_lenient_chunked_length( - parser, lenient_chunked_length) - if lenient_keep_alive is not None: - cparser.llhttp_set_lenient_keep_alive( - parser, lenient_keep_alive) - if lenient_transfer_encoding is not None: - cparser.llhttp_set_lenient_transfer_encoding( - parser, lenient_transfer_encoding) - if lenient_version is not None: - cparser.llhttp_set_lenient_version( - parser, lenient_version) - if lenient_data_after_close is not None: - cparser.llhttp_set_lenient_data_after_close( - parser, lenient_data_after_close) - if lenient_optional_lf_after_cr is not None: - cparser.llhttp_set_lenient_optional_lf_after_cr( - parser, lenient_optional_lf_after_cr) - if lenient_optional_cr_before_lf is not None: - cparser.llhttp_set_lenient_optional_cr_before_lf( - parser, lenient_optional_cr_before_lf) - if lenient_optional_crlf_after_chunk is not None: - cparser.llhttp_set_lenient_optional_crlf_after_chunk( - parser, lenient_optional_crlf_after_chunk) - if lenient_spaces_after_chunk_size is not None: - cparser.llhttp_set_lenient_spaces_after_chunk_size( - parser, lenient_spaces_after_chunk_size) - - def get_http_version(self): - cdef cparser.llhttp_t* parser = self._cparser - return '{}.{}'.format(parser.http_major, parser.http_minor) - - def should_keep_alive(self): - return bool(cparser.llhttp_should_keep_alive(self._cparser)) - - def should_upgrade(self): - cdef cparser.llhttp_t* parser = self._cparser - return bool(parser.upgrade) - - def feed_data(self, data): - cdef: - size_t data_len - cparser.llhttp_errno_t err - Py_buffer *buf - bint owning_buf = False - const char* err_pos - - if PyMemoryView_Check(data): - buf = PyMemoryView_GET_BUFFER(data) - data_len = buf.len - err = cparser.llhttp_execute( - self._cparser, - buf.buf, - data_len) - - else: - buf = &self.py_buf - PyObject_GetBuffer(data, buf, PyBUF_SIMPLE) - owning_buf = True - data_len = buf.len - - err = cparser.llhttp_execute( - self._cparser, - buf.buf, - data_len) - - try: - if self._cparser.upgrade == 1 and err == cparser.HPE_PAUSED_UPGRADE: - err_pos = cparser.llhttp_get_error_pos(self._cparser) - - # Immediately free the parser from "error" state, simulating - # http-parser behavior here because 1) we never had the API to - # allow users manually "resume after upgrade", and 2) the use - # case for resuming parsing is very rare. - cparser.llhttp_resume_after_upgrade(self._cparser) - - # The err_pos here is specific for the input buf. So if we ever - # switch to the llhttp behavior (re-raise HttpParserUpgrade for - # successive calls to feed_data() until resume_after_upgrade is - # called), we have to store the result and keep our own state. - raise HttpParserUpgrade(err_pos - buf.buf) - finally: - if owning_buf: - PyBuffer_Release(buf) - - if err != cparser.HPE_OK: - ex = parser_error_from_errno( - self._cparser, - self._cparser.error) - if isinstance(ex, HttpParserCallbackError): - if self._last_error is not None: - ex.__context__ = self._last_error - self._last_error = None - raise ex - - -cdef class HttpRequestParser(HttpParser): - - def __init__(self, protocol): - self._init(protocol, cparser.HTTP_REQUEST) - - self._proto_on_url = getattr(protocol, 'on_url', None) - if self._proto_on_url is not None: - self._csettings.on_url = cb_on_url - - def get_method(self): - cdef cparser.llhttp_t* parser = self._cparser - return cparser.llhttp_method_name( parser.method) - - -cdef class HttpResponseParser(HttpParser): - - def __init__(self, protocol): - self._init(protocol, cparser.HTTP_RESPONSE) - - self._proto_on_status = getattr(protocol, 'on_status', None) - if self._proto_on_status is not None: - self._csettings.on_status = cb_on_status - - def get_status_code(self): - cdef cparser.llhttp_t* parser = self._cparser - return parser.status_code - - -cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._proto_on_message_begin() - except BaseException as ex: - pyparser._last_error = ex - return -1 - else: - return 0 - - -cdef int cb_on_url(cparser.llhttp_t* parser, - const char *at, size_t length) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._proto_on_url(at[:length]) - except BaseException as ex: - cparser.llhttp_set_error_reason(parser, "`on_url` callback error") - pyparser._last_error = ex - return cparser.HPE_USER - else: - return 0 - - -cdef int cb_on_status(cparser.llhttp_t* parser, - const char *at, size_t length) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._proto_on_status(at[:length]) - except BaseException as ex: - cparser.llhttp_set_error_reason(parser, "`on_status` callback error") - pyparser._last_error = ex - return cparser.HPE_USER - else: - return 0 - - -cdef int cb_on_header_field(cparser.llhttp_t* parser, - const char *at, size_t length) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._on_header_field(at[:length]) - except BaseException as ex: - cparser.llhttp_set_error_reason(parser, "`on_header_field` callback error") - pyparser._last_error = ex - return cparser.HPE_USER - else: - return 0 - - -cdef int cb_on_header_value(cparser.llhttp_t* parser, - const char *at, size_t length) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._on_header_value(at[:length]) - except BaseException as ex: - cparser.llhttp_set_error_reason(parser, "`on_header_value` callback error") - pyparser._last_error = ex - return cparser.HPE_USER - else: - return 0 - - -cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._on_headers_complete() - except BaseException as ex: - pyparser._last_error = ex - return -1 - else: - if pyparser._cparser.upgrade: - return 1 - else: - return 0 - - -cdef int cb_on_body(cparser.llhttp_t* parser, - const char *at, size_t length) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._proto_on_body(at[:length]) - except BaseException as ex: - cparser.llhttp_set_error_reason(parser, "`on_body` callback error") - pyparser._last_error = ex - return cparser.HPE_USER - else: - return 0 - - -cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._proto_on_message_complete() - except BaseException as ex: - pyparser._last_error = ex - return -1 - else: - return 0 - - -cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._on_chunk_header() - except BaseException as ex: - pyparser._last_error = ex - return -1 - else: - return 0 - - -cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1: - cdef HttpParser pyparser = parser.data - try: - pyparser._on_chunk_complete() - except BaseException as ex: - pyparser._last_error = ex - return -1 - else: - return 0 - - -cdef parser_error_from_errno(cparser.llhttp_t* parser, cparser.llhttp_errno_t errno): - cdef bytes reason = cparser.llhttp_get_error_reason(parser) - - if errno in (cparser.HPE_CB_MESSAGE_BEGIN, - cparser.HPE_CB_HEADERS_COMPLETE, - cparser.HPE_CB_MESSAGE_COMPLETE, - cparser.HPE_CB_CHUNK_HEADER, - cparser.HPE_CB_CHUNK_COMPLETE, - cparser.HPE_USER): - cls = HttpParserCallbackError - - elif errno == cparser.HPE_INVALID_STATUS: - cls = HttpParserInvalidStatusError - - elif errno == cparser.HPE_INVALID_METHOD: - cls = HttpParserInvalidMethodError - - elif errno == cparser.HPE_INVALID_URL: - cls = HttpParserInvalidURLError - - else: - cls = HttpParserError - - return cls(reason.decode('latin-1')) diff --git a/server/venv/Lib/site-packages/httptools/parser/protocol.py b/server/venv/Lib/site-packages/httptools/parser/protocol.py deleted file mode 100644 index ae00523..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/protocol.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Protocol - - -class HTTPProtocol(Protocol): - """Used for providing static type-checking when parsing through the http protocol""" - - def on_message_begin(self) -> None: ... - def on_url(self, url: bytes) -> None: ... - def on_header(self, name: bytes, value: bytes) -> None: ... - def on_headers_complete(self) -> None: ... - def on_body(self, body: bytes) -> None: ... - def on_message_complete(self) -> None: ... - def on_chunk_header(self) -> None: ... - def on_chunk_complete(self) -> None: ... - def on_status(self, status: bytes) -> None: ... diff --git a/server/venv/Lib/site-packages/httptools/parser/python.pxd b/server/venv/Lib/site-packages/httptools/parser/python.pxd deleted file mode 100644 index 8e95925..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/python.pxd +++ /dev/null @@ -1,6 +0,0 @@ -cimport cpython - - -cdef extern from "Python.h": - cpython.Py_buffer* PyMemoryView_GET_BUFFER(object) - bint PyMemoryView_Check(object) diff --git a/server/venv/Lib/site-packages/httptools/parser/url_cparser.pxd b/server/venv/Lib/site-packages/httptools/parser/url_cparser.pxd deleted file mode 100644 index ab9265a..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/url_cparser.pxd +++ /dev/null @@ -1,31 +0,0 @@ -from libc.stdint cimport uint16_t - - -cdef extern from "http_parser.h": - # URL Parser - - enum http_parser_url_fields: - UF_SCHEMA = 0, - UF_HOST = 1, - UF_PORT = 2, - UF_PATH = 3, - UF_QUERY = 4, - UF_FRAGMENT = 5, - UF_USERINFO = 6, - UF_MAX = 7 - - struct http_parser_url_field_data: - uint16_t off - uint16_t len - - struct http_parser_url: - uint16_t field_set - uint16_t port - http_parser_url_field_data[UF_MAX] field_data - - void http_parser_url_init(http_parser_url *u) - - int http_parser_parse_url(const char *buf, - size_t buflen, - int is_connect, - http_parser_url *u) diff --git a/server/venv/Lib/site-packages/httptools/parser/url_parser.cp313-win_amd64.pyd b/server/venv/Lib/site-packages/httptools/parser/url_parser.cp313-win_amd64.pyd deleted file mode 100644 index 20812ed..0000000 Binary files a/server/venv/Lib/site-packages/httptools/parser/url_parser.cp313-win_amd64.pyd and /dev/null differ diff --git a/server/venv/Lib/site-packages/httptools/parser/url_parser.pyi b/server/venv/Lib/site-packages/httptools/parser/url_parser.pyi deleted file mode 100644 index 5f04847..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/url_parser.pyi +++ /dev/null @@ -1,13 +0,0 @@ -from array import array - -class URL: - schema: bytes - host: bytes - port: int - path: bytes - query: bytes - fragment: bytes - userinfo: bytes - -def parse_url(url: bytes | bytearray | memoryview | array[int]) -> URL: - """Parse a URL string into a structured Python object.""" diff --git a/server/venv/Lib/site-packages/httptools/parser/url_parser.pyx b/server/venv/Lib/site-packages/httptools/parser/url_parser.pyx deleted file mode 100644 index e855b5f..0000000 --- a/server/venv/Lib/site-packages/httptools/parser/url_parser.pyx +++ /dev/null @@ -1,118 +0,0 @@ -#cython: language_level=3 - -from __future__ import print_function -from cpython.mem cimport PyMem_Malloc, PyMem_Free -from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \ - Py_buffer - -from .errors import HttpParserInvalidURLError - -cimport cython -from . cimport url_cparser as uparser - -__all__ = ('parse_url',) - -DEF MAX_URL_LENGTH = (1 << 16) - 1 - -@cython.freelist(250) -cdef class URL: - cdef readonly bytes schema - cdef readonly bytes host - cdef readonly object port - cdef readonly bytes path - cdef readonly bytes query - cdef readonly bytes fragment - cdef readonly bytes userinfo - - def __cinit__(self, bytes schema, bytes host, object port, bytes path, - bytes query, bytes fragment, bytes userinfo): - - self.schema = schema - self.host = host - self.port = port - self.path = path - self.query = query - self.fragment = fragment - self.userinfo = userinfo - - def __repr__(self): - return ('' - .format(self.schema, self.host, self.port, self.path, - self.query, self.fragment, self.userinfo)) - - -def parse_url(url): - cdef: - Py_buffer py_buf - char* buf_data - uparser.http_parser_url* parsed - int res - bytes schema = None - bytes host = None - object port = None - bytes path = None - bytes query = None - bytes fragment = None - bytes userinfo = None - object result = None - int off - int ln - - parsed = \ - PyMem_Malloc(sizeof(uparser.http_parser_url)) - uparser.http_parser_url_init(parsed) - - PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE) - try: - if py_buf.len > MAX_URL_LENGTH: - # http_parser stores URL field offsets/lengths as uint16_t, - # so URLs longer than this will cause silent truncation. - # See https://github.com/MagicStack/httptools/issues/142 - raise HttpParserInvalidURLError( - "url is too long: url length of {} bytes exceeds the " - "maximum of {} bytes".format(py_buf.len, MAX_URL_LENGTH)) - - buf_data = py_buf.buf - res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed) - - if res == 0: - if parsed.field_set & (1 << uparser.UF_SCHEMA): - off = parsed.field_data[uparser.UF_SCHEMA].off - ln = parsed.field_data[uparser.UF_SCHEMA].len - schema = buf_data[off:off+ln] - - if parsed.field_set & (1 << uparser.UF_HOST): - off = parsed.field_data[uparser.UF_HOST].off - ln = parsed.field_data[uparser.UF_HOST].len - host = buf_data[off:off+ln] - - if parsed.field_set & (1 << uparser.UF_PORT): - port = parsed.port - - if parsed.field_set & (1 << uparser.UF_PATH): - off = parsed.field_data[uparser.UF_PATH].off - ln = parsed.field_data[uparser.UF_PATH].len - path = buf_data[off:off+ln] - - if parsed.field_set & (1 << uparser.UF_QUERY): - off = parsed.field_data[uparser.UF_QUERY].off - ln = parsed.field_data[uparser.UF_QUERY].len - query = buf_data[off:off+ln] - - if parsed.field_set & (1 << uparser.UF_FRAGMENT): - off = parsed.field_data[uparser.UF_FRAGMENT].off - ln = parsed.field_data[uparser.UF_FRAGMENT].len - fragment = buf_data[off:off+ln] - - if parsed.field_set & (1 << uparser.UF_USERINFO): - off = parsed.field_data[uparser.UF_USERINFO].off - ln = parsed.field_data[uparser.UF_USERINFO].len - userinfo = buf_data[off:off+ln] - - return URL(schema, host, port, path, query, fragment, userinfo) - else: - raise HttpParserInvalidURLError("invalid url {!r}".format(url)) - finally: - PyBuffer_Release(&py_buf) - PyMem_Free(parsed) diff --git a/server/venv/Lib/site-packages/httptools/py.typed b/server/venv/Lib/site-packages/httptools/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/idna-3.18.dist-info/INSTALLER b/server/venv/Lib/site-packages/idna-3.18.dist-info/INSTALLER deleted file mode 100644 index a1b589e..0000000 --- a/server/venv/Lib/site-packages/idna-3.18.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/server/venv/Lib/site-packages/idna-3.18.dist-info/METADATA b/server/venv/Lib/site-packages/idna-3.18.dist-info/METADATA deleted file mode 100644 index 6c4bf89..0000000 --- a/server/venv/Lib/site-packages/idna-3.18.dist-info/METADATA +++ /dev/null @@ -1,155 +0,0 @@ -Metadata-Version: 2.4 -Name: idna -Version: 3.18 -Summary: Internationalized Domain Names in Applications (IDNA) -Author-email: Kim Davies -Requires-Python: >=3.9 -Description-Content-Type: text/markdown -License-Expression: BSD-3-Clause -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: System Administrators -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: 3.14 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: Name Service (DNS) -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Utilities -License-File: LICENSE.md -Requires-Dist: ruff >= 0.6.2 ; extra == "all" -Requires-Dist: mypy >= 1.11.2 ; extra == "all" -Requires-Dist: pytest >= 8.3.2 ; extra == "all" -Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.md -Project-URL: Issue tracker, https://github.com/kjd/idna/issues -Project-URL: Source, https://github.com/kjd/idna -Provides-Extra: all - -# Internationalized Domain Names in Applications (IDNA) - -Support for [Internationalized Domain Names in Applications -(IDNA)](https://tools.ietf.org/html/rfc5891) and [Unicode IDNA -Compatibility Processing](https://unicode.org/reports/tr46/). It -supersedes the standard library's `encodings.idna`, which only -implements the 2003 specification, offering broader script coverage and -limiting domains with known security vulnerabilities. - -## Usage - -Package may be installed from [PyPI](https://pypi.org/project/idna/) via -the typical methods (e.g. `python3 -m pip install idna`) - -For typical usage, the `encode` and `decode` functions will take a -domain name argument and perform a conversion to ASCII-compatible encoding -(known as A-labels), or to Unicode strings (known as U-labels) -respectively. - -```pycon ->>> import idna ->>> idna.encode('ドメイン.テスト') -b'xn--eckwd4c7c.xn--zckzah' ->>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) -ドメイン.テスト -``` - -Conversions can be applied at a per-label basis using the `ulabel` or -`alabel` functions for specialized use cases. - - -### Compatibility Mapping (UTS #46) - -This library provides support for [Unicode IDNA Compatibility -Processing](https://unicode.org/reports/tr46/) which normalizes input from -different potential ways a user may input a domain prior to performing the IDNA -conversion operations. This functionality, known as a -[mapping](https://tools.ietf.org/html/rfc5895), is considered by the -specification to be a local user-interface issue distinct from IDNA -conversion functionality. - -For example, "Königsgäßchen" is not a permissible label as capital letters -are not allowed. UTS 46 will convert this into lower case prior to applying -the IDNA conversion. - -```pycon ->>> import idna ->>> idna.encode('Königsgäßchen') -... -idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed ->>> idna.encode('Königsgäßchen', uts46=True) -b'xn--knigsgchen-b4a3dun' ->>> idna.decode('xn--knigsgchen-b4a3dun') -'königsgäßchen' -``` - -When performing a decode operation for display purposes, `decode()` -accepts a `display=True` argument that leaves any `xn--` label that -fails to decode unchanged. This is useful for user interface display -where a domain is in use, the A-label form can be presented when it -is not a valid IDN. - - -## Exceptions - -All errors raised during conversion derive from the `idna.IDNAError` -base class. The more specific exceptions are: - -* `idna.IDNABidiError` — raised when a label contains an illegal - combination of left-to-right and right-to-left characters. -* `idna.InvalidCodepoint` — raised when a label contains a codepoint - that is INVALID for IDNA. -* `idna.InvalidCodepointContext` — raised when a CONTEXTO or CONTEXTJ - codepoint appears in a position whose contextual requirements are - not satisfied. - - -## Command-line tool - -The package supports command-line usage to convert domain names -between their Unicode and ASCII-compatible forms. It can be run either -as a module (`python3 -m idna`) or, once installed (such as with `uv -tool` or `pipx`), via the `idna` script: - -```bash -$ uv tool install idna -$ idna xn--e1afmkfd.xn--p1ai -пример.рф -$ idna пример.рф -xn--e1afmkfd.xn--p1ai -``` - -Mode can be specified with `-e`/`--encode` or `-d`/`--decode`, otherwise -it will be chosen automatically based on the first input. Multiple -domains can be supplied either as arguments or through standard input. -UTS #46 mapping is applied by default, which lets the tool accept -inputs that aren't strictly valid IDNA 2008 by normalising them first, -pass `--strict` to disable UTS #46. - -Conversion failures are reported on stderr together with the -offending input; processing continues with the remaining domains and -the tool exits with a non-zero status if any conversion failed. - - -## Additional Notes - -* **Version support**. This library supports Python 3.9 and higher. - As this library serves as a low-level toolkit for a variety of - applications, we strive to support all versions of Python that are - not beyond end-of-life. - -* **Emoji**. It is an occasional request to support emoji domains in - this library. Encoding of symbols like emoji is expressly prohibited by - the IDNA technical standard, and emoji domains are broadly phased - out across the domain industry due to associated security risks. - -* **Regenerating lookup tables**. The IDNA and UTS 46 functionality - relies upon pre-calculated lookup tables, generated using the - `idna-data` script in [`tools/`](tools/README.md). - diff --git a/server/venv/Lib/site-packages/idna-3.18.dist-info/RECORD b/server/venv/Lib/site-packages/idna-3.18.dist-info/RECORD deleted file mode 100644 index 65173f1..0000000 --- a/server/venv/Lib/site-packages/idna-3.18.dist-info/RECORD +++ /dev/null @@ -1,28 +0,0 @@ -../../Scripts/idna.exe,sha256=b-QI5CoT6rEnYZqQDLY0kAPswE2RSMumI2L1O1f4n8Y,108456 -idna-3.18.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -idna-3.18.dist-info/METADATA,sha256=Rt_m5axGLQ9oDs2avPZugptqIzSCS02eOXmzETXK8oE,6119 -idna-3.18.dist-info/RECORD,, -idna-3.18.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 -idna-3.18.dist-info/entry_points.txt,sha256=7H3nGOHap3jnLE5e7q7Ywr9Vq8axB7WIj5-C_4N2vhw,38 -idna-3.18.dist-info/licenses/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541 -idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868 -idna/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83 -idna/__pycache__/__init__.cpython-313.pyc,, -idna/__pycache__/__main__.cpython-313.pyc,, -idna/__pycache__/cli.cpython-313.pyc,, -idna/__pycache__/codec.cpython-313.pyc,, -idna/__pycache__/compat.cpython-313.pyc,, -idna/__pycache__/core.cpython-313.pyc,, -idna/__pycache__/idnadata.cpython-313.pyc,, -idna/__pycache__/intranges.cpython-313.pyc,, -idna/__pycache__/package_data.cpython-313.pyc,, -idna/__pycache__/uts46data.cpython-313.pyc,, -idna/cli.py,sha256=swqJLMNc8Uzs60KziNpbWnHuqlG3WRQwJSbo4n8xDAo,4139 -idna/codec.py,sha256=JRbo-f7pEkLdWeiH89Z72UR4VBYhvKDFrQBeNX6sRDE,5040 -idna/compat.py,sha256=AepA39ceRHxkfHP41-FvKW5Ki-f4PfUZ90RUMlCNdmo,1353 -idna/core.py,sha256=SfOr1xO3PoE0RDYx7bMciAnjiyjJPbPw_93AB5IUYOw,24685 -idna/idnadata.py,sha256=Af-mo8WBmkhAK6TyXKOQH88OX0mQNDKtdL7UWtQpppk,44862 -idna/intranges.py,sha256=g49scLSkqJtAhLmOODa7hVHriSjmb60tiTsEoocJdBI,1851 -idna/package_data.py,sha256=TeI94EqAFAFaXfBJwsOPUMLn2969uirPa-DaeaceAyU,21 -idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -idna/uts46data.py,sha256=jujNz5QqWMcJf-XYLv4X1jBvb5FlI0t6-e1mILsgbPk,234325 diff --git a/server/venv/Lib/site-packages/idna-3.18.dist-info/WHEEL b/server/venv/Lib/site-packages/idna-3.18.dist-info/WHEEL deleted file mode 100644 index d8b9936..0000000 --- a/server/venv/Lib/site-packages/idna-3.18.dist-info/WHEEL +++ /dev/null @@ -1,4 +0,0 @@ -Wheel-Version: 1.0 -Generator: flit 3.12.0 -Root-Is-Purelib: true -Tag: py3-none-any diff --git a/server/venv/Lib/site-packages/idna-3.18.dist-info/entry_points.txt b/server/venv/Lib/site-packages/idna-3.18.dist-info/entry_points.txt deleted file mode 100644 index 59ca7ac..0000000 --- a/server/venv/Lib/site-packages/idna-3.18.dist-info/entry_points.txt +++ /dev/null @@ -1,3 +0,0 @@ -[console_scripts] -idna=idna.cli:main - diff --git a/server/venv/Lib/site-packages/idna-3.18.dist-info/licenses/LICENSE.md b/server/venv/Lib/site-packages/idna-3.18.dist-info/licenses/LICENSE.md deleted file mode 100644 index f706835..0000000 --- a/server/venv/Lib/site-packages/idna-3.18.dist-info/licenses/LICENSE.md +++ /dev/null @@ -1,31 +0,0 @@ -BSD 3-Clause License - -Copyright (c) 2013-2026, Kim Davies and contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/server/venv/Lib/site-packages/idna/__init__.py b/server/venv/Lib/site-packages/idna/__init__.py deleted file mode 100644 index cfdc030..0000000 --- a/server/venv/Lib/site-packages/idna/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -from .core import ( - IDNABidiError, - IDNAError, - InvalidCodepoint, - InvalidCodepointContext, - alabel, - check_bidi, - check_hyphen_ok, - check_initial_combiner, - check_label, - check_nfc, - decode, - encode, - ulabel, - uts46_remap, - valid_contextj, - valid_contexto, - valid_label_length, - valid_string_length, -) -from .intranges import intranges_contain -from .package_data import __version__ - -__all__ = [ - "__version__", - "IDNABidiError", - "IDNAError", - "InvalidCodepoint", - "InvalidCodepointContext", - "alabel", - "check_bidi", - "check_hyphen_ok", - "check_initial_combiner", - "check_label", - "check_nfc", - "decode", - "encode", - "intranges_contain", - "ulabel", - "uts46_remap", - "valid_contextj", - "valid_contexto", - "valid_label_length", - "valid_string_length", -] diff --git a/server/venv/Lib/site-packages/idna/__main__.py b/server/venv/Lib/site-packages/idna/__main__.py deleted file mode 100644 index dbdd066..0000000 --- a/server/venv/Lib/site-packages/idna/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -import sys - -from .cli import main - -if __name__ == "__main__": - sys.exit(main()) diff --git a/server/venv/Lib/site-packages/idna/cli.py b/server/venv/Lib/site-packages/idna/cli.py deleted file mode 100644 index 4acda2c..0000000 --- a/server/venv/Lib/site-packages/idna/cli.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Command-line interface for the :mod:`idna` package. - -Invoked via ``python -m idna``. See :func:`main` for the entry point. -""" - -import argparse -import sys -from collections.abc import Iterable -from itertools import chain -from typing import IO, Optional - -from . import IDNAError, decode, encode -from .core import _alabel_prefix, _unicode_dots_re -from .package_data import __version__ - - -def _looks_like_alabel(s: str) -> bool: - """Return True if any label in ``s`` carries the ``xn--`` ACE prefix.""" - prefix = _alabel_prefix.decode("ascii") - return any(label.lower().startswith(prefix) for label in _unicode_dots_re.split(s)) - - -def _build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser( - prog="python -m idna", - description=( - "Convert a domain name between its Unicode (U-label) and " - "ASCII-compatible (A-label) forms. With no mode flag, the " - "direction is chosen from the first input — if it contains " - "an xn-- label the stream is decoded, otherwise it is " - "encoded — and the same mode is applied to every remaining " - "input. UTS #46 mapping is applied by default; pass " - "--strict to disable it. When no domains are given on the " - "command line and stdin is piped, one domain per line is " - "read from stdin." - ), - ) - mode = parser.add_mutually_exclusive_group() - mode.add_argument( - "-e", - "--encode", - dest="mode", - action="store_const", - const="encode", - help="Encode the input to its ASCII A-label form.", - ) - mode.add_argument( - "-d", - "--decode", - dest="mode", - action="store_const", - const="decode", - help="Decode the input from its ASCII A-label form.", - ) - parser.add_argument( - "--strict", - action="store_true", - help="Disable the default UTS #46 mapping and apply IDNA 2008 rules verbatim.", - ) - parser.add_argument( - "--version", - action="version", - version=f"idna {__version__}", - ) - parser.add_argument( - "domain", - nargs="*", - help="One or more domain names to convert. Omit to read from stdin.", - ) - return parser - - -def _iter_stdin(stream: IO[str]) -> Iterable[str]: - """Yield non-empty stripped lines from ``stream``, ignoring blanks.""" - for line in stream: - stripped = line.strip() - if stripped: - yield stripped - - -def _convert_one(domain: str, mode: str, uts46: bool) -> bool: - """Convert ``domain`` and write the result; return ``False`` on failure.""" - try: - if mode == "decode": - print(decode(domain, uts46=uts46)) - else: - print(encode(domain, uts46=uts46).decode("ascii")) - except IDNAError as err: - print(f"idna: {mode} failed for {domain!r}: {err}", file=sys.stderr) - return False - return True - - -def main(argv: Optional[list[str]] = None) -> int: - """Entry point for ``python -m idna``. - - When more than one domain is supplied (via positional arguments or - piped stdin) and no mode flag is given, the first input determines - the direction and that mode is applied uniformly to the rest. - - :param argv: Argument list excluding the program name. Defaults to - :data:`sys.argv` when ``None``. - :returns: ``0`` on success, ``1`` if any conversion fails. - """ - parser = _build_parser() - args = parser.parse_args(argv) - uts46 = not args.strict - - if args.domain: - domains: Iterable[str] = args.domain - elif not sys.stdin.isatty(): - domains = _iter_stdin(sys.stdin) - else: - parser.error("a domain argument is required when stdin is a terminal") - - iterator = iter(domains) - first = next(iterator, None) - if first is None: - return 0 - - mode = args.mode or ("decode" if _looks_like_alabel(first) else "encode") - - results = [_convert_one(domain, mode, uts46) for domain in chain([first], iterator)] - return 0 if all(results) else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/server/venv/Lib/site-packages/idna/codec.py b/server/venv/Lib/site-packages/idna/codec.py deleted file mode 100644 index 83b42fe..0000000 --- a/server/venv/Lib/site-packages/idna/codec.py +++ /dev/null @@ -1,159 +0,0 @@ -import codecs -from typing import Any, Optional - -from .core import IDNAError, _unicode_dots_re, alabel, decode, encode, ulabel - - -class Codec(codecs.Codec): - """Stateless IDNA 2008 codec. - - Implements the :class:`codecs.Codec` protocol so that the whole-domain - encoder (:func:`idna.encode`) and decoder (:func:`idna.decode`) are - accessible through the standard codec machinery as ``"idna2008"``. - - Only the ``"strict"`` error handler is supported; any other handler - raises :exc:`~idna.IDNAError`. - """ - - def encode(self, data: str, errors: str = "strict") -> tuple[bytes, int]: # ty: ignore[invalid-method-override] - if errors != "strict": - raise IDNAError(f'Unsupported error handling "{errors}"') - - if not data: - return b"", 0 - - return encode(data), len(data) - - def decode(self, data: bytes, errors: str = "strict") -> tuple[str, int]: # ty: ignore[invalid-method-override] - if errors != "strict": - raise IDNAError(f'Unsupported error handling "{errors}"') - - if not data: - return "", 0 - - return decode(data), len(data) - - -class IncrementalEncoder(codecs.BufferedIncrementalEncoder): - """Incremental IDNA 2008 encoder. - - Buffers a partial trailing label across calls until either the next - label separator is seen or ``final=True``, so that streamed input is - encoded one whole label at a time. Any of the four Unicode label - separators (``U+002E``, ``U+3002``, ``U+FF0E``, ``U+FF61``) ends a - label; the result always uses ``U+002E`` as the separator. - - Only the ``"strict"`` error handler is supported. - """ - - def _buffer_encode(self, data: str, errors: str, final: bool) -> tuple[bytes, int]: # ty: ignore[invalid-method-override] - if errors != "strict": - raise IDNAError(f'Unsupported error handling "{errors}"') - - if not data: - return b"", 0 - - labels = _unicode_dots_re.split(data) - trailing_dot = b"" - if labels: - if not labels[-1]: - trailing_dot = b"." - del labels[-1] - elif not final: - # Keep potentially unfinished label until the next call - del labels[-1] - if labels: - trailing_dot = b"." - - result = [] - size = 0 - for label in labels: - result.append(alabel(label)) - if size: - size += 1 - size += len(label) - - # Join with U+002E - result_bytes = b".".join(result) + trailing_dot - size += len(trailing_dot) - return result_bytes, size - - -class IncrementalDecoder(codecs.BufferedIncrementalDecoder): - """Incremental IDNA 2008 decoder. - - Buffers a partial trailing label across calls until either the next - label separator is seen or ``final=True``, so that streamed input is - decoded one whole label at a time. - - Only the ``"strict"`` error handler is supported. - """ - - def _buffer_decode(self, data: Any, errors: str, final: bool) -> tuple[str, int]: # ty: ignore[invalid-method-override] - if errors != "strict": - raise IDNAError(f'Unsupported error handling "{errors}"') - - if not data: - return ("", 0) - - if not isinstance(data, str): - data = str(data, "ascii") - - labels = _unicode_dots_re.split(data) - trailing_dot = "" - if labels: - if not labels[-1]: - trailing_dot = "." - del labels[-1] - elif not final: - # Keep potentially unfinished label until the next call - del labels[-1] - if labels: - trailing_dot = "." - - result = [] - size = 0 - for label in labels: - result.append(ulabel(label)) - if size: - size += 1 - size += len(label) - - result_str = ".".join(result) + trailing_dot - size += len(trailing_dot) - return (result_str, size) - - -class StreamWriter(Codec, codecs.StreamWriter): - pass - - -class StreamReader(Codec, codecs.StreamReader): - pass - - -def search_function(name: str) -> Optional[codecs.CodecInfo]: - """Codec search function registered with :mod:`codecs`. - - Returns a :class:`codecs.CodecInfo` for the ``"idna2008"`` codec name - so that ``str.encode("idna2008")`` and ``bytes.decode("idna2008")`` - invoke the IDNA 2008 codec defined in this module. - - :param name: The codec name being looked up. - :returns: A :class:`codecs.CodecInfo` instance if ``name`` is - ``"idna2008"``, otherwise ``None``. - """ - if name != "idna2008": - return None - return codecs.CodecInfo( - name=name, - encode=Codec().encode, - decode=Codec().decode, # type: ignore - incrementalencoder=IncrementalEncoder, - incrementaldecoder=IncrementalDecoder, - streamwriter=StreamWriter, - streamreader=StreamReader, - ) - - -codecs.register(search_function) diff --git a/server/venv/Lib/site-packages/idna/compat.py b/server/venv/Lib/site-packages/idna/compat.py deleted file mode 100644 index 1d01e3d..0000000 --- a/server/venv/Lib/site-packages/idna/compat.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any, Union - -from .core import decode, encode - - -def ToASCII(label: str) -> bytes: - """Compatibility shim for :rfc:`3490` ``ToASCII``. - - Delegates to :func:`idna.encode` (IDNA 2008). Provided to ease porting - of code written against the legacy :mod:`encodings.idna` API; new code - should call :func:`idna.encode` directly. - - :param label: The label or domain to encode. - :returns: The encoded form as ASCII :class:`bytes`. - """ - return encode(label) - - -def ToUnicode(label: Union[bytes, bytearray]) -> str: - """Compatibility shim for :rfc:`3490` ``ToUnicode``. - - Delegates to :func:`idna.decode` (IDNA 2008). Provided to ease porting - of code written against the legacy :mod:`encodings.idna` API; new code - should call :func:`idna.decode` directly. - - :param label: The label or domain to decode. - :returns: The decoded Unicode form. - """ - return decode(label) - - -def nameprep(s: Any) -> None: - """Stub for :rfc:`3491` Nameprep, which is not used by IDNA 2008. - - IDNA 2008 (:rfc:`5891`) replaces Nameprep with the per-codepoint - validity classes from :rfc:`5892`; this function exists only to - return a clear error if legacy code attempts to call it. - - :raises NotImplementedError: Always. - """ - raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") diff --git a/server/venv/Lib/site-packages/idna/core.py b/server/venv/Lib/site-packages/idna/core.py deleted file mode 100644 index 1ccbd1f..0000000 --- a/server/venv/Lib/site-packages/idna/core.py +++ /dev/null @@ -1,648 +0,0 @@ -import bisect -import re -import unicodedata -import warnings -from typing import Optional, Union - -from . import idnadata -from .intranges import intranges_contain - -_virama_combining_class = 9 -_alabel_prefix = b"xn--" -_max_input_length = 1024 -_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") - - -# Bidi category sets from RFC 5893, hoisted out of the per-codepoint loop -_bidi_rtl_first = frozenset({"R", "AL"}) -_bidi_rtl_categories = frozenset({"R", "AL", "AN"}) -_bidi_rtl_allowed = frozenset({"R", "AL", "AN", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"}) -_bidi_rtl_valid_ending = frozenset({"R", "AL", "EN", "AN"}) -_bidi_rtl_numeric = frozenset({"AN", "EN"}) -_bidi_ltr_allowed = frozenset({"L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"}) -_bidi_ltr_valid_ending = frozenset({"L", "EN"}) -_bidi_joiner_l_or_d = frozenset({"L", "D"}) -_bidi_joiner_r_or_d = frozenset({"R", "D"}) - - -def _joining_type(cp: int) -> Optional[str]: - for jt, ranges in idnadata.joining_types.items(): - if intranges_contain(cp, ranges): - return jt - return None - - -class IDNAError(UnicodeError): - """Base exception for all IDNA-encoding related problems""" - - -class IDNABidiError(IDNAError): - """Exception when bidirectional requirements are not satisfied""" - - -class InvalidCodepoint(IDNAError): - """Exception when a disallowed or unallocated codepoint is used""" - - -class InvalidCodepointContext(IDNAError): - """Exception when the codepoint is not valid in the context it is used""" - - -def _combining_class(cp: int) -> int: - v = unicodedata.combining(chr(cp)) - if v == 0 and not unicodedata.name(chr(cp)): - raise ValueError("Unknown character in unicodedata") - return v - - -def _is_script(cp: str, script: str) -> bool: - return intranges_contain(ord(cp), idnadata.scripts[script]) - - -def _punycode(s: str) -> bytes: - return s.encode("punycode") - - -def _unot(s: int) -> str: - return f"U+{s:04X}" - - -def valid_label_length(label: Union[bytes, str]) -> bool: - """Check that a label does not exceed the maximum permitted length. - - Per :rfc:`1035` (and :rfc:`5891` §4.2.4) a DNS label must not exceed - 63 octets. The argument may be either a :class:`str` (a U-label, where - length is measured in characters) or :class:`bytes` (an A-label, where - length is measured in octets). - - :param label: The label to check. - :returns: ``True`` if the label is within the length limit, otherwise - ``False``. - """ - return len(label) <= 63 - - -def valid_string_length(domain: Union[bytes, str], trailing_dot: bool) -> bool: - """Check that a full domain name does not exceed the maximum length. - - Per :rfc:`1035`, a domain name is limited to 253 octets when no trailing - dot is present, or 254 octets when one is included. - - :param domain: The full (possibly multi-label) domain name. - :param trailing_dot: ``True`` if ``domain`` includes a trailing ``.``. - :returns: ``True`` if the domain is within the length limit, otherwise - ``False``. - """ - return len(domain) <= (254 if trailing_dot else 253) - - -def check_bidi(label: str, check_ltr: bool = False) -> bool: - """Validate the Bidi Rule from :rfc:`5893` for a single label. - - The Bidi Rule constrains how bidirectional characters (Hebrew, Arabic, - etc.) may appear within a label. By default the check is only applied - when the label contains at least one right-to-left character (Unicode - bidirectional categories ``R``, ``AL``, or ``AN``); set ``check_ltr`` - to ``True`` to apply it to LTR-only labels as well. - - :param label: The label to validate, as a Unicode string. - :param check_ltr: If ``True``, apply the rules even when the label - contains no RTL characters. - :returns: ``True`` if the label satisfies the Bidi Rule. - :raises IDNABidiError: If any of Bidi Rule conditions 1-6 are violated, - or if the directional category of a codepoint cannot be determined. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - # Bidi rules should only be applied if string contains RTL characters - bidi_label = False - for idx, cp in enumerate(label, 1): - direction = unicodedata.bidirectional(cp) - if direction == "": - # String likely comes from a newer version of Unicode - raise IDNABidiError(f"Unknown directionality in label {label!r} at position {idx}") - if direction in _bidi_rtl_categories: - bidi_label = True - if not bidi_label and not check_ltr: - return True - - # Bidi rule 1 - direction = unicodedata.bidirectional(label[0]) - if direction in _bidi_rtl_first: - rtl = True - elif direction == "L": - rtl = False - else: - raise IDNABidiError(f"First codepoint in label {label!r} must be directionality L, R or AL") - - valid_ending = False - number_type: Optional[str] = None - for idx, cp in enumerate(label, 1): - direction = unicodedata.bidirectional(cp) - - if rtl: - # Bidi rule 2 - if direction not in _bidi_rtl_allowed: - raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a right-to-left label") - # Bidi rule 3 - if direction in _bidi_rtl_valid_ending: - valid_ending = True - elif direction != "NSM": - valid_ending = False - # Bidi rule 4 - if direction in _bidi_rtl_numeric: - if not number_type: - number_type = direction - elif number_type != direction: - raise IDNABidiError("Can not mix numeral types in a right-to-left label") - else: - # Bidi rule 5 - if direction not in _bidi_ltr_allowed: - raise IDNABidiError(f"Invalid direction for codepoint at position {idx} in a left-to-right label") - # Bidi rule 6 - if direction in _bidi_ltr_valid_ending: - valid_ending = True - elif direction != "NSM": - valid_ending = False - - if not valid_ending: - raise IDNABidiError("Label ends with illegal codepoint directionality") - - return True - - -def check_initial_combiner(label: str) -> bool: - """Reject labels that begin with a combining mark. - - Per :rfc:`5891` §4.2.3.2 a label must not start with a character of - Unicode general category ``M`` (Mark). - - :param label: The label to check. - :returns: ``True`` if the first character is not a combining mark. - :raises IDNAError: If the label begins with a combining character. - """ - if unicodedata.category(label[0])[0] == "M": - raise IDNAError("Label begins with an illegal combining character") - return True - - -def check_hyphen_ok(label: str) -> bool: - """Validate the hyphen restrictions for a label. - - Per :rfc:`5891` §4.2.3.1 a label must not start or end with a hyphen - (``U+002D``), and must not have hyphens in both the third and fourth - positions (the prefix reserved for A-labels). - - :param label: The label to check. - :returns: ``True`` if the hyphen restrictions are satisfied. - :raises IDNAError: If any of the hyphen restrictions are violated. - """ - if label[2:4] == "--": - raise IDNAError("Label has disallowed hyphens in 3rd and 4th position") - if label[0] == "-" or label[-1] == "-": - raise IDNAError("Label must not start or end with a hyphen") - return True - - -def check_nfc(label: str) -> None: - """Require that a label is in Unicode Normalization Form C. - - :param label: The label to check. - :raises IDNAError: If ``label`` differs from its NFC normalisation. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - if unicodedata.normalize("NFC", label) != label: - raise IDNAError("Label must be in Normalization Form C") - - -def valid_contextj(label: str, pos: int) -> bool: - """Validate the CONTEXTJ rules from :rfc:`5892` Appendix A. - - These rules govern the contextual use of the joiner codepoints - ``U+200C`` (ZERO WIDTH NON-JOINER, Appendix A.1) and ``U+200D`` - (ZERO WIDTH JOINER, Appendix A.2) within a label. - - :param label: The label containing the codepoint. - :param pos: Index of the joiner codepoint within ``label``. - :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTJ - rule, ``False`` otherwise (including when the codepoint at - ``pos`` is not a recognised joiner). - :raises ValueError: If an adjacent codepoint has no Unicode name when - determining its combining class. - :raises IDNAError: If ``label`` exceeds the defensive input length limit. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - cp_value = ord(label[pos]) - - if cp_value == 0x200C: - if pos > 0 and _combining_class(ord(label[pos - 1])) == _virama_combining_class: - return True - - ok = False - for i in range(pos - 1, -1, -1): - joining_type = _joining_type(ord(label[i])) - if joining_type == "T": - continue - if joining_type in _bidi_joiner_l_or_d: - ok = True - break - break - - if not ok: - return False - - ok = False - for i in range(pos + 1, len(label)): - joining_type = _joining_type(ord(label[i])) - if joining_type == "T": - continue - if joining_type in _bidi_joiner_r_or_d: - ok = True - break - break - return ok - - if cp_value == 0x200D: - return pos > 0 and _combining_class(ord(label[pos - 1])) == _virama_combining_class - - return False - - -def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: - """Validate the CONTEXTO rules from :rfc:`5892` Appendix A. - - Covers the contextual rules for codepoints such as MIDDLE DOT - (``U+00B7``), Greek lower numeral sign, Hebrew punctuation, Katakana - middle dot, and the Arabic-Indic / Extended Arabic-Indic digit ranges. - - :param label: The label containing the codepoint. - :param pos: Index of the codepoint within ``label``. - :param exception: Reserved for forward compatibility; currently unused. - :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTO - rule, ``False`` otherwise (including when the codepoint is not a - recognised CONTEXTO codepoint). - :raises IDNAError: If ``label`` exceeds the defensive input length limit. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - cp_value = ord(label[pos]) - - if cp_value == 0x00B7: - return 0 < pos < len(label) - 1 and ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C - - if cp_value == 0x0375: - if pos < len(label) - 1 and len(label) > 1: - return _is_script(label[pos + 1], "Greek") - return False - - if cp_value in {0x05F3, 0x05F4}: - if pos > 0: - return _is_script(label[pos - 1], "Hebrew") - return False - - if cp_value == 0x30FB: - for cp in label: - if cp == "\u30fb": - continue - if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"): - return True - return False - - if 0x660 <= cp_value <= 0x669: - return not any(0x6F0 <= ord(cp) <= 0x06F9 for cp in label) - - if 0x6F0 <= cp_value <= 0x6F9: - return not any(0x660 <= ord(cp) <= 0x0669 for cp in label) - - return False - - -def check_label(label: Union[str, bytes, bytearray]) -> None: - """Run the full set of IDNA 2008 validity checks on a single label. - - Applies, in order: NFC normalisation (:func:`check_nfc`), hyphen - restrictions (:func:`check_hyphen_ok`), the no-leading-combiner rule - (:func:`check_initial_combiner`), per-codepoint validity (PVALID, - CONTEXTJ, CONTEXTO classes from :rfc:`5892`), and the Bidi Rule - (:func:`check_bidi`). - - :param label: The label to validate. ``bytes`` or ``bytearray`` input - is decoded as UTF-8 first. - :raises IDNAError: If the label is empty or fails a structural rule. - :raises InvalidCodepoint: If the label contains a DISALLOWED or - UNASSIGNED codepoint. - :raises InvalidCodepointContext: If a CONTEXTJ or CONTEXTO codepoint - is not valid in its context. - :raises IDNABidiError: If the Bidi Rule is violated. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - if isinstance(label, (bytes, bytearray)): - label = label.decode("utf-8") - if len(label) == 0: - raise IDNAError("Empty Label") - - # Reject on domain length rather than label length so support some UTS 46 - # use cases, still reducing processing of label contextual rules - if not valid_string_length(label, trailing_dot=True): - raise IDNAError("Label too long") - - check_nfc(label) - check_hyphen_ok(label) - check_initial_combiner(label) - - for pos, cp in enumerate(label): - cp_value = ord(cp) - if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]): - continue - if intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]): - try: - if not valid_contextj(label, pos): - raise InvalidCodepointContext(f"Joiner {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}") - except ValueError as err: - raise IDNAError( - f"Unknown codepoint adjacent to joiner {_unot(cp_value)} at position {pos + 1} in {label!r}" - ) from err - elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]): - if not valid_contexto(label, pos): - raise InvalidCodepointContext(f"Codepoint {_unot(cp_value)} not allowed at position {pos + 1} in {label!r}") - else: - raise InvalidCodepoint(f"Codepoint {_unot(cp_value)} at position {pos + 1} of {label!r} not allowed") - - check_bidi(label) - - -def alabel(label: str) -> bytes: - """Convert a single U-label into its A-label form. - - The result is the ASCII-Compatible Encoding (ACE) form per :rfc:`5891` - §4: the label is validated, Punycode-encoded, and prefixed with - ``xn--``. Pure ASCII labels that are already valid IDNA labels are - returned unchanged (as :class:`bytes`). - - :param label: The label to convert, as a Unicode string. - :returns: The A-label as ASCII-encoded :class:`bytes`. - :raises IDNAError: If the label is invalid or the resulting A-label - exceeds 63 octets. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - try: - label_bytes = label.encode("ascii") - except UnicodeEncodeError: - pass - else: - ulabel(label_bytes) - if not valid_label_length(label_bytes): - raise IDNAError("Label too long") - return label_bytes - - check_label(label) - label_bytes = _alabel_prefix + _punycode(label) - - if not valid_label_length(label_bytes): - raise IDNAError("Label too long") - - return label_bytes - - -def ulabel(label: Union[str, bytes, bytearray]) -> str: - """Convert a single A-label into its U-label form. - - Performs the inverse of :func:`alabel`: an ``xn--``-prefixed label is - Punycode-decoded and validated. Labels that are already Unicode (or - plain ASCII without the ACE prefix) are validated and returned as a - Unicode string. - - :param label: The label to convert. ``bytes`` or ``bytearray`` input - is treated as ASCII. - :returns: The U-label as a Unicode string. - :raises IDNAError: If the label is malformed or fails validation. - """ - if len(label) > _max_input_length: - raise IDNAError("Label too long") - if not isinstance(label, (bytes, bytearray)): - try: - label_bytes = label.encode("ascii") - except UnicodeEncodeError: - check_label(label) - return label - else: - label_bytes = bytes(label) - - label_bytes = label_bytes.lower() - if label_bytes.startswith(_alabel_prefix): - label_bytes = label_bytes[len(_alabel_prefix) :] - if not label_bytes: - raise IDNAError("Malformed A-label, no Punycode eligible content found") - if label_bytes.endswith(b"-"): - raise IDNAError("A-label must not end with a hyphen") - else: - check_label(label_bytes) - return label_bytes.decode("ascii") - - try: - label = label_bytes.decode("punycode") - except UnicodeError as err: - raise IDNAError("Invalid A-label") from err - check_label(label) - return label - - -def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: - """Apply the UTS #46 character mapping to a domain string. - - Implements the mapping table from `UTS #46 §4 - `_: each character is kept, - replaced, or rejected based on its status (``V``, ``M``, ``D``, ``3``, - ``I``). The result is returned in Normalisation Form C. - - :param domain: The full domain name to remap. - :param std3_rules: If ``True``, apply the stricter STD3 ASCII rules - (status ``3`` codepoints raise instead of being kept or mapped). - :param transitional: If ``True``, use transitional processing (status - ``D`` codepoints are mapped instead of kept). Transitional - processing has been removed from UTS #46 and this option is - retained only for backwards compatibility. - :returns: The remapped domain, in Normalisation Form C. - :raises InvalidCodepoint: If the domain contains a disallowed - codepoint under the chosen rules. - :raises IDNAError: If ``domain`` exceeds the defensive input length limit. - """ - if len(domain) > _max_input_length: - raise IDNAError("Domain too long") - from .uts46data import uts46_replacements, uts46_starts, uts46_statuses - - output = "" - - for pos, char in enumerate(domain): - code_point = ord(char) - i = code_point if code_point < 256 else bisect.bisect_right(uts46_starts, code_point) - 1 - status = chr(uts46_statuses[i]) - replacement: Optional[str] = uts46_replacements[i] - - # UTS #46 §4: V is always valid, D is deviation (kept unless transitional), - # 3 is disallowed-STD3 (kept unmapped if std3_rules is off and no mapping). - keep_as_is = ( - status == "V" or (status == "D" and not transitional) or (status == "3" and not std3_rules and replacement is None) - ) - # M is mapped, 3-with-replacement and transitional D fall through to the - # same replacement output path. - use_replacement = replacement is not None and ( - status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional) - ) - - if keep_as_is: - output += char - elif use_replacement: - assert replacement is not None # narrowed by use_replacement - output += replacement - elif status == "I": - continue - else: - raise InvalidCodepoint(f"Codepoint {_unot(code_point)} not allowed at position {pos + 1} in {domain!r}") - - return unicodedata.normalize("NFC", output) - - -def encode( - s: Union[str, bytes, bytearray], - strict: bool = False, - uts46: bool = False, - std3_rules: bool = False, - transitional: bool = False, -) -> bytes: - """Encode a Unicode domain name into its ASCII (A-label) form. - - Splits the input on label separators (only ``U+002E`` if ``strict`` is - set; otherwise also IDEOGRAPHIC FULL STOP ``U+3002``, FULLWIDTH FULL - STOP ``U+FF0E``, and HALFWIDTH IDEOGRAPHIC FULL STOP ``U+FF61``), - encodes each label with :func:`alabel`, and rejoins them with ``.``. - Optionally pre-processes the input through :func:`uts46_remap`. - - :param s: The domain name to encode. - :param strict: If ``True``, only ``U+002E`` is recognised as a label - separator. - :param uts46: If ``True``, apply UTS #46 mapping before encoding. - :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is - ``True``. - :param transitional: Forwarded to :func:`uts46_remap` when ``uts46`` - is ``True``. Deprecated: emits a :class:`DeprecationWarning` and - will be removed in a future version. - :returns: The encoded domain as ASCII :class:`bytes`. - :raises IDNAError: If the domain is empty, contains an invalid label, - or exceeds the maximum domain length. - """ - if transitional: - warnings.warn( - "Transitional processing has been removed from UTS #46. " - "The transitional argument will be removed in a future version.", - DeprecationWarning, - stacklevel=2, - ) - if not isinstance(s, str): - try: - s = str(s, "ascii") - except (UnicodeDecodeError, TypeError) as err: - raise IDNAError("should pass a unicode string to the function rather than a byte string.") from err - if len(s) > _max_input_length: - raise IDNAError("Domain too long") - if uts46: - s = uts46_remap(s, std3_rules, transitional) - - # Reject inputs that exceed the maximum DNS domain length up-front - # to avoid expensive computation on long inputs. - if not valid_string_length(s, trailing_dot=True): - raise IDNAError("Domain too long") - - trailing_dot = False - result = [] - labels = s.split(".") if strict else _unicode_dots_re.split(s) - if not labels or labels == [""]: - raise IDNAError("Empty domain") - if labels[-1] == "": - del labels[-1] - trailing_dot = True - for label in labels: - s = alabel(label) - if s: - result.append(s) - else: - raise IDNAError("Empty label") - if trailing_dot: - result.append(b"") - s = b".".join(result) - if not valid_string_length(s, trailing_dot): - raise IDNAError("Domain too long") - return s - - -def decode( - s: Union[str, bytes, bytearray], - strict: bool = False, - uts46: bool = False, - std3_rules: bool = False, - display: bool = False, -) -> str: - """Decode an A-label-encoded domain name back to Unicode. - - Splits the input on label separators (see :func:`encode` for the - rules), decodes each label with :func:`ulabel`, and rejoins them - with ``.``. Optionally pre-processes the input through - :func:`uts46_remap`. - - :param s: The domain name to decode. - :param strict: If ``True``, only ``U+002E`` is recognised as a label - separator. - :param uts46: If ``True``, apply UTS #46 mapping before decoding. - :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is - ``True``. - :param display: If ``True``, any ``xn--`` label that fails IDNA - validation is passed through unchanged (lowercased) rather than - aborting the whole call. Intended for "decode for display" - consumers (e.g. URL libraries, HTTP clients) that want to show - the user the label as it appears on the wire when it cannot be - rendered as Unicode. Matches the per-label recovery prescribed - by UTS #46 §4 and the WHATWG URL "domain to Unicode" algorithm. - :returns: The decoded domain as a Unicode string. - :raises IDNAError: If the input is not valid ASCII, contains an - invalid label, or is empty. - """ - if not isinstance(s, str): - try: - s = str(s, "ascii") - except (UnicodeDecodeError, TypeError) as err: - raise IDNAError("Invalid ASCII in A-label") from err - if len(s) > _max_input_length: - raise IDNAError("Domain too long") - if uts46: - s = uts46_remap(s, std3_rules, False) - # Reject inputs that exceed the maximum DNS domain length up-front - # to avoid expensive computation on long inputs. - if not valid_string_length(s, trailing_dot=True): - raise IDNAError("Domain too long") - trailing_dot = False - result = [] - labels = s.split(".") if strict else _unicode_dots_re.split(s) - if not labels or labels == [""]: - raise IDNAError("Empty domain") - if not labels[-1]: - del labels[-1] - trailing_dot = True - for label in labels: - try: - u = ulabel(label) - except IDNAError: - if display and label[:4].lower() == "xn--": - u = label.lower() - else: - raise - if u: - result.append(u) - else: - raise IDNAError("Empty label") - if trailing_dot: - result.append("") - return ".".join(result) diff --git a/server/venv/Lib/site-packages/idna/idnadata.py b/server/venv/Lib/site-packages/idna/idnadata.py deleted file mode 100644 index f2ab388..0000000 --- a/server/venv/Lib/site-packages/idna/idnadata.py +++ /dev/null @@ -1,1897 +0,0 @@ -# This file is automatically generated by tools/idna-data - -__version__ = "17.0.0" - -scripts = { - "Greek": ( - 0x37000000374, - 0x37500000378, - 0x37A0000037E, - 0x37F00000380, - 0x38400000385, - 0x38600000387, - 0x3880000038B, - 0x38C0000038D, - 0x38E000003A2, - 0x3A3000003E2, - 0x3F000000400, - 0x1D2600001D2B, - 0x1D5D00001D62, - 0x1D6600001D6B, - 0x1DBF00001DC0, - 0x1F0000001F16, - 0x1F1800001F1E, - 0x1F2000001F46, - 0x1F4800001F4E, - 0x1F5000001F58, - 0x1F5900001F5A, - 0x1F5B00001F5C, - 0x1F5D00001F5E, - 0x1F5F00001F7E, - 0x1F8000001FB5, - 0x1FB600001FC5, - 0x1FC600001FD4, - 0x1FD600001FDC, - 0x1FDD00001FF0, - 0x1FF200001FF5, - 0x1FF600001FFF, - 0x212600002127, - 0xAB650000AB66, - 0x101400001018F, - 0x101A0000101A1, - 0x1D2000001D246, - ), - "Han": ( - 0x2E8000002E9A, - 0x2E9B00002EF4, - 0x2F0000002FD6, - 0x300500003006, - 0x300700003008, - 0x30210000302A, - 0x30380000303C, - 0x340000004DC0, - 0x4E000000A000, - 0xF9000000FA6E, - 0xFA700000FADA, - 0x16FE200016FE4, - 0x16FF000016FF7, - 0x200000002A6E0, - 0x2A7000002B81E, - 0x2B8200002CEAE, - 0x2CEB00002EBE1, - 0x2EBF00002EE5E, - 0x2F8000002FA1E, - 0x300000003134B, - 0x313500003347A, - ), - "Hebrew": ( - 0x591000005C8, - 0x5D0000005EB, - 0x5EF000005F5, - 0xFB1D0000FB37, - 0xFB380000FB3D, - 0xFB3E0000FB3F, - 0xFB400000FB42, - 0xFB430000FB45, - 0xFB460000FB50, - ), - "Hiragana": ( - 0x304100003097, - 0x309D000030A0, - 0x1B0010001B120, - 0x1B1320001B133, - 0x1B1500001B153, - 0x1F2000001F201, - ), - "Katakana": ( - 0x30A1000030FB, - 0x30FD00003100, - 0x31F000003200, - 0x32D0000032FF, - 0x330000003358, - 0xFF660000FF70, - 0xFF710000FF9E, - 0x1AFF00001AFF4, - 0x1AFF50001AFFC, - 0x1AFFD0001AFFF, - 0x1B0000001B001, - 0x1B1200001B123, - 0x1B1550001B156, - 0x1B1640001B168, - ), -} - - -joining_types = { - "C": ( - 0x64000000641, - 0x7FA000007FB, - 0x88300000886, - 0x180A0000180B, - 0x200D0000200E, - ), - "D": ( - 0x62000000621, - 0x62600000627, - 0x62800000629, - 0x62A0000062F, - 0x63300000640, - 0x64100000648, - 0x6490000064B, - 0x66E00000670, - 0x67800000688, - 0x69A000006C0, - 0x6C1000006C3, - 0x6CC000006CD, - 0x6CE000006CF, - 0x6D0000006D2, - 0x6FA000006FD, - 0x6FF00000700, - 0x71200000715, - 0x71A0000071E, - 0x71F00000728, - 0x7290000072A, - 0x72B0000072C, - 0x72D0000072F, - 0x74E00000759, - 0x75C0000076B, - 0x76D00000771, - 0x77200000773, - 0x77500000778, - 0x77A00000780, - 0x7CA000007EB, - 0x84100000846, - 0x84800000849, - 0x84A00000854, - 0x85500000856, - 0x86000000861, - 0x86200000866, - 0x86800000869, - 0x88600000887, - 0x8890000088E, - 0x88F00000890, - 0x8A0000008AA, - 0x8AF000008B1, - 0x8B3000008B9, - 0x8BA000008C9, - 0x180700001808, - 0x182000001879, - 0x1887000018A9, - 0x18AA000018AB, - 0xA8400000A872, - 0x10AC000010AC5, - 0x10AD300010AD7, - 0x10AD800010ADD, - 0x10ADE00010AE1, - 0x10AEB00010AEF, - 0x10B8000010B81, - 0x10B8200010B83, - 0x10B8600010B89, - 0x10B8A00010B8C, - 0x10B8D00010B8E, - 0x10B9000010B91, - 0x10BAD00010BAF, - 0x10D0100010D22, - 0x10D2300010D24, - 0x10EC300010EC5, - 0x10EC600010EC8, - 0x10F3000010F33, - 0x10F3400010F45, - 0x10F5100010F54, - 0x10F7000010F74, - 0x10F7600010F82, - 0x10FB000010FB1, - 0x10FB200010FB4, - 0x10FB800010FB9, - 0x10FBB00010FBD, - 0x10FBE00010FC0, - 0x10FC100010FC2, - 0x10FC400010FC5, - 0x10FCA00010FCB, - 0x1E9000001E944, - ), - "L": ( - 0xA8720000A873, - 0x10ACD00010ACE, - 0x10AD700010AD8, - 0x10D0000010D01, - 0x10FCB00010FCC, - ), - "R": ( - 0x62200000626, - 0x62700000628, - 0x6290000062A, - 0x62F00000633, - 0x64800000649, - 0x67100000674, - 0x67500000678, - 0x6880000069A, - 0x6C0000006C1, - 0x6C3000006CC, - 0x6CD000006CE, - 0x6CF000006D0, - 0x6D2000006D4, - 0x6D5000006D6, - 0x6EE000006F0, - 0x71000000711, - 0x7150000071A, - 0x71E0000071F, - 0x72800000729, - 0x72A0000072B, - 0x72C0000072D, - 0x72F00000730, - 0x74D0000074E, - 0x7590000075C, - 0x76B0000076D, - 0x77100000772, - 0x77300000775, - 0x7780000077A, - 0x84000000841, - 0x84600000848, - 0x8490000084A, - 0x85400000855, - 0x85600000859, - 0x86700000868, - 0x8690000086B, - 0x87000000883, - 0x88E0000088F, - 0x8AA000008AD, - 0x8AE000008AF, - 0x8B1000008B3, - 0x8B9000008BA, - 0x10AC500010AC6, - 0x10AC700010AC8, - 0x10AC900010ACB, - 0x10ACE00010AD3, - 0x10ADD00010ADE, - 0x10AE100010AE2, - 0x10AE400010AE5, - 0x10AEF00010AF0, - 0x10B8100010B82, - 0x10B8300010B86, - 0x10B8900010B8A, - 0x10B8C00010B8D, - 0x10B8E00010B90, - 0x10B9100010B92, - 0x10BA900010BAD, - 0x10D2200010D23, - 0x10EC200010EC3, - 0x10F3300010F34, - 0x10F5400010F55, - 0x10F7400010F76, - 0x10FB400010FB7, - 0x10FB900010FBB, - 0x10FBD00010FBE, - 0x10FC200010FC4, - 0x10FC900010FCA, - ), - "T": ( - 0xAD000000AE, - 0x30000000370, - 0x4830000048A, - 0x591000005BE, - 0x5BF000005C0, - 0x5C1000005C3, - 0x5C4000005C6, - 0x5C7000005C8, - 0x6100000061B, - 0x61C0000061D, - 0x64B00000660, - 0x67000000671, - 0x6D6000006DD, - 0x6DF000006E5, - 0x6E7000006E9, - 0x6EA000006EE, - 0x70F00000710, - 0x71100000712, - 0x7300000074B, - 0x7A6000007B1, - 0x7EB000007F4, - 0x7FD000007FE, - 0x8160000081A, - 0x81B00000824, - 0x82500000828, - 0x8290000082E, - 0x8590000085C, - 0x897000008A0, - 0x8CA000008E2, - 0x8E300000903, - 0x93A0000093B, - 0x93C0000093D, - 0x94100000949, - 0x94D0000094E, - 0x95100000958, - 0x96200000964, - 0x98100000982, - 0x9BC000009BD, - 0x9C1000009C5, - 0x9CD000009CE, - 0x9E2000009E4, - 0x9FE000009FF, - 0xA0100000A03, - 0xA3C00000A3D, - 0xA4100000A43, - 0xA4700000A49, - 0xA4B00000A4E, - 0xA5100000A52, - 0xA7000000A72, - 0xA7500000A76, - 0xA8100000A83, - 0xABC00000ABD, - 0xAC100000AC6, - 0xAC700000AC9, - 0xACD00000ACE, - 0xAE200000AE4, - 0xAFA00000B00, - 0xB0100000B02, - 0xB3C00000B3D, - 0xB3F00000B40, - 0xB4100000B45, - 0xB4D00000B4E, - 0xB5500000B57, - 0xB6200000B64, - 0xB8200000B83, - 0xBC000000BC1, - 0xBCD00000BCE, - 0xC0000000C01, - 0xC0400000C05, - 0xC3C00000C3D, - 0xC3E00000C41, - 0xC4600000C49, - 0xC4A00000C4E, - 0xC5500000C57, - 0xC6200000C64, - 0xC8100000C82, - 0xCBC00000CBD, - 0xCBF00000CC0, - 0xCC600000CC7, - 0xCCC00000CCE, - 0xCE200000CE4, - 0xD0000000D02, - 0xD3B00000D3D, - 0xD4100000D45, - 0xD4D00000D4E, - 0xD6200000D64, - 0xD8100000D82, - 0xDCA00000DCB, - 0xDD200000DD5, - 0xDD600000DD7, - 0xE3100000E32, - 0xE3400000E3B, - 0xE4700000E4F, - 0xEB100000EB2, - 0xEB400000EBD, - 0xEC800000ECF, - 0xF1800000F1A, - 0xF3500000F36, - 0xF3700000F38, - 0xF3900000F3A, - 0xF7100000F7F, - 0xF8000000F85, - 0xF8600000F88, - 0xF8D00000F98, - 0xF9900000FBD, - 0xFC600000FC7, - 0x102D00001031, - 0x103200001038, - 0x10390000103B, - 0x103D0000103F, - 0x10580000105A, - 0x105E00001061, - 0x107100001075, - 0x108200001083, - 0x108500001087, - 0x108D0000108E, - 0x109D0000109E, - 0x135D00001360, - 0x171200001715, - 0x173200001734, - 0x175200001754, - 0x177200001774, - 0x17B4000017B6, - 0x17B7000017BE, - 0x17C6000017C7, - 0x17C9000017D4, - 0x17DD000017DE, - 0x180B0000180E, - 0x180F00001810, - 0x188500001887, - 0x18A9000018AA, - 0x192000001923, - 0x192700001929, - 0x193200001933, - 0x19390000193C, - 0x1A1700001A19, - 0x1A1B00001A1C, - 0x1A5600001A57, - 0x1A5800001A5F, - 0x1A6000001A61, - 0x1A6200001A63, - 0x1A6500001A6D, - 0x1A7300001A7D, - 0x1A7F00001A80, - 0x1AB000001ADE, - 0x1AE000001AEC, - 0x1B0000001B04, - 0x1B3400001B35, - 0x1B3600001B3B, - 0x1B3C00001B3D, - 0x1B4200001B43, - 0x1B6B00001B74, - 0x1B8000001B82, - 0x1BA200001BA6, - 0x1BA800001BAA, - 0x1BAB00001BAE, - 0x1BE600001BE7, - 0x1BE800001BEA, - 0x1BED00001BEE, - 0x1BEF00001BF2, - 0x1C2C00001C34, - 0x1C3600001C38, - 0x1CD000001CD3, - 0x1CD400001CE1, - 0x1CE200001CE9, - 0x1CED00001CEE, - 0x1CF400001CF5, - 0x1CF800001CFA, - 0x1DC000001E00, - 0x200B0000200C, - 0x200E00002010, - 0x202A0000202F, - 0x206000002065, - 0x206A00002070, - 0x20D0000020F1, - 0x2CEF00002CF2, - 0x2D7F00002D80, - 0x2DE000002E00, - 0x302A0000302E, - 0x30990000309B, - 0xA66F0000A673, - 0xA6740000A67E, - 0xA69E0000A6A0, - 0xA6F00000A6F2, - 0xA8020000A803, - 0xA8060000A807, - 0xA80B0000A80C, - 0xA8250000A827, - 0xA82C0000A82D, - 0xA8C40000A8C6, - 0xA8E00000A8F2, - 0xA8FF0000A900, - 0xA9260000A92E, - 0xA9470000A952, - 0xA9800000A983, - 0xA9B30000A9B4, - 0xA9B60000A9BA, - 0xA9BC0000A9BE, - 0xA9E50000A9E6, - 0xAA290000AA2F, - 0xAA310000AA33, - 0xAA350000AA37, - 0xAA430000AA44, - 0xAA4C0000AA4D, - 0xAA7C0000AA7D, - 0xAAB00000AAB1, - 0xAAB20000AAB5, - 0xAAB70000AAB9, - 0xAABE0000AAC0, - 0xAAC10000AAC2, - 0xAAEC0000AAEE, - 0xAAF60000AAF7, - 0xABE50000ABE6, - 0xABE80000ABE9, - 0xABED0000ABEE, - 0xFB1E0000FB1F, - 0xFE000000FE10, - 0xFE200000FE30, - 0xFEFF0000FF00, - 0xFFF90000FFFC, - 0x101FD000101FE, - 0x102E0000102E1, - 0x103760001037B, - 0x10A0100010A04, - 0x10A0500010A07, - 0x10A0C00010A10, - 0x10A3800010A3B, - 0x10A3F00010A40, - 0x10AE500010AE7, - 0x10D2400010D28, - 0x10D6900010D6E, - 0x10EAB00010EAD, - 0x10EFA00010F00, - 0x10F4600010F51, - 0x10F8200010F86, - 0x1100100011002, - 0x1103800011047, - 0x1107000011071, - 0x1107300011075, - 0x1107F00011082, - 0x110B3000110B7, - 0x110B9000110BB, - 0x110C2000110C3, - 0x1110000011103, - 0x111270001112C, - 0x1112D00011135, - 0x1117300011174, - 0x1118000011182, - 0x111B6000111BF, - 0x111C9000111CD, - 0x111CF000111D0, - 0x1122F00011232, - 0x1123400011235, - 0x1123600011238, - 0x1123E0001123F, - 0x1124100011242, - 0x112DF000112E0, - 0x112E3000112EB, - 0x1130000011302, - 0x1133B0001133D, - 0x1134000011341, - 0x113660001136D, - 0x1137000011375, - 0x113BB000113C1, - 0x113CE000113CF, - 0x113D0000113D1, - 0x113D2000113D3, - 0x113E1000113E3, - 0x1143800011440, - 0x1144200011445, - 0x1144600011447, - 0x1145E0001145F, - 0x114B3000114B9, - 0x114BA000114BB, - 0x114BF000114C1, - 0x114C2000114C4, - 0x115B2000115B6, - 0x115BC000115BE, - 0x115BF000115C1, - 0x115DC000115DE, - 0x116330001163B, - 0x1163D0001163E, - 0x1163F00011641, - 0x116AB000116AC, - 0x116AD000116AE, - 0x116B0000116B6, - 0x116B7000116B8, - 0x1171D0001171E, - 0x1171F00011720, - 0x1172200011726, - 0x117270001172C, - 0x1182F00011838, - 0x118390001183B, - 0x1193B0001193D, - 0x1193E0001193F, - 0x1194300011944, - 0x119D4000119D8, - 0x119DA000119DC, - 0x119E0000119E1, - 0x11A0100011A0B, - 0x11A3300011A39, - 0x11A3B00011A3F, - 0x11A4700011A48, - 0x11A5100011A57, - 0x11A5900011A5C, - 0x11A8A00011A97, - 0x11A9800011A9A, - 0x11B6000011B61, - 0x11B6200011B65, - 0x11B6600011B67, - 0x11C3000011C37, - 0x11C3800011C3E, - 0x11C3F00011C40, - 0x11C9200011CA8, - 0x11CAA00011CB1, - 0x11CB200011CB4, - 0x11CB500011CB7, - 0x11D3100011D37, - 0x11D3A00011D3B, - 0x11D3C00011D3E, - 0x11D3F00011D46, - 0x11D4700011D48, - 0x11D9000011D92, - 0x11D9500011D96, - 0x11D9700011D98, - 0x11EF300011EF5, - 0x11F0000011F02, - 0x11F3600011F3B, - 0x11F4000011F41, - 0x11F4200011F43, - 0x11F5A00011F5B, - 0x1343000013441, - 0x1344700013456, - 0x1611E0001612A, - 0x1612D00016130, - 0x16AF000016AF5, - 0x16B3000016B37, - 0x16F4F00016F50, - 0x16F8F00016F93, - 0x16FE400016FE5, - 0x1BC9D0001BC9F, - 0x1BCA00001BCA4, - 0x1CF000001CF2E, - 0x1CF300001CF47, - 0x1D1670001D16A, - 0x1D1730001D183, - 0x1D1850001D18C, - 0x1D1AA0001D1AE, - 0x1D2420001D245, - 0x1DA000001DA37, - 0x1DA3B0001DA6D, - 0x1DA750001DA76, - 0x1DA840001DA85, - 0x1DA9B0001DAA0, - 0x1DAA10001DAB0, - 0x1E0000001E007, - 0x1E0080001E019, - 0x1E01B0001E022, - 0x1E0230001E025, - 0x1E0260001E02B, - 0x1E08F0001E090, - 0x1E1300001E137, - 0x1E2AE0001E2AF, - 0x1E2EC0001E2F0, - 0x1E4EC0001E4F0, - 0x1E5EE0001E5F0, - 0x1E6E30001E6E4, - 0x1E6E60001E6E7, - 0x1E6EE0001E6F0, - 0x1E6F50001E6F6, - 0x1E8D00001E8D7, - 0x1E9440001E94C, - 0xE0001000E0002, - 0xE0020000E0080, - 0xE0100000E01F0, - ), -} - - -codepoint_classes = { - "PVALID": ( - 0x2D0000002E, - 0x300000003A, - 0x610000007B, - 0xDF000000F7, - 0xF800000100, - 0x10100000102, - 0x10300000104, - 0x10500000106, - 0x10700000108, - 0x1090000010A, - 0x10B0000010C, - 0x10D0000010E, - 0x10F00000110, - 0x11100000112, - 0x11300000114, - 0x11500000116, - 0x11700000118, - 0x1190000011A, - 0x11B0000011C, - 0x11D0000011E, - 0x11F00000120, - 0x12100000122, - 0x12300000124, - 0x12500000126, - 0x12700000128, - 0x1290000012A, - 0x12B0000012C, - 0x12D0000012E, - 0x12F00000130, - 0x13100000132, - 0x13500000136, - 0x13700000139, - 0x13A0000013B, - 0x13C0000013D, - 0x13E0000013F, - 0x14200000143, - 0x14400000145, - 0x14600000147, - 0x14800000149, - 0x14B0000014C, - 0x14D0000014E, - 0x14F00000150, - 0x15100000152, - 0x15300000154, - 0x15500000156, - 0x15700000158, - 0x1590000015A, - 0x15B0000015C, - 0x15D0000015E, - 0x15F00000160, - 0x16100000162, - 0x16300000164, - 0x16500000166, - 0x16700000168, - 0x1690000016A, - 0x16B0000016C, - 0x16D0000016E, - 0x16F00000170, - 0x17100000172, - 0x17300000174, - 0x17500000176, - 0x17700000178, - 0x17A0000017B, - 0x17C0000017D, - 0x17E0000017F, - 0x18000000181, - 0x18300000184, - 0x18500000186, - 0x18800000189, - 0x18C0000018E, - 0x19200000193, - 0x19500000196, - 0x1990000019C, - 0x19E0000019F, - 0x1A1000001A2, - 0x1A3000001A4, - 0x1A5000001A6, - 0x1A8000001A9, - 0x1AA000001AC, - 0x1AD000001AE, - 0x1B0000001B1, - 0x1B4000001B5, - 0x1B6000001B7, - 0x1B9000001BC, - 0x1BD000001C4, - 0x1CE000001CF, - 0x1D0000001D1, - 0x1D2000001D3, - 0x1D4000001D5, - 0x1D6000001D7, - 0x1D8000001D9, - 0x1DA000001DB, - 0x1DC000001DE, - 0x1DF000001E0, - 0x1E1000001E2, - 0x1E3000001E4, - 0x1E5000001E6, - 0x1E7000001E8, - 0x1E9000001EA, - 0x1EB000001EC, - 0x1ED000001EE, - 0x1EF000001F1, - 0x1F5000001F6, - 0x1F9000001FA, - 0x1FB000001FC, - 0x1FD000001FE, - 0x1FF00000200, - 0x20100000202, - 0x20300000204, - 0x20500000206, - 0x20700000208, - 0x2090000020A, - 0x20B0000020C, - 0x20D0000020E, - 0x20F00000210, - 0x21100000212, - 0x21300000214, - 0x21500000216, - 0x21700000218, - 0x2190000021A, - 0x21B0000021C, - 0x21D0000021E, - 0x21F00000220, - 0x22100000222, - 0x22300000224, - 0x22500000226, - 0x22700000228, - 0x2290000022A, - 0x22B0000022C, - 0x22D0000022E, - 0x22F00000230, - 0x23100000232, - 0x2330000023A, - 0x23C0000023D, - 0x23F00000241, - 0x24200000243, - 0x24700000248, - 0x2490000024A, - 0x24B0000024C, - 0x24D0000024E, - 0x24F000002B0, - 0x2B9000002C2, - 0x2C6000002D2, - 0x2EC000002ED, - 0x2EE000002EF, - 0x30000000340, - 0x34200000343, - 0x3460000034F, - 0x35000000370, - 0x37100000372, - 0x37300000374, - 0x37700000378, - 0x37B0000037E, - 0x39000000391, - 0x3AC000003CF, - 0x3D7000003D8, - 0x3D9000003DA, - 0x3DB000003DC, - 0x3DD000003DE, - 0x3DF000003E0, - 0x3E1000003E2, - 0x3E3000003E4, - 0x3E5000003E6, - 0x3E7000003E8, - 0x3E9000003EA, - 0x3EB000003EC, - 0x3ED000003EE, - 0x3EF000003F0, - 0x3F3000003F4, - 0x3F8000003F9, - 0x3FB000003FD, - 0x43000000460, - 0x46100000462, - 0x46300000464, - 0x46500000466, - 0x46700000468, - 0x4690000046A, - 0x46B0000046C, - 0x46D0000046E, - 0x46F00000470, - 0x47100000472, - 0x47300000474, - 0x47500000476, - 0x47700000478, - 0x4790000047A, - 0x47B0000047C, - 0x47D0000047E, - 0x47F00000480, - 0x48100000482, - 0x48300000488, - 0x48B0000048C, - 0x48D0000048E, - 0x48F00000490, - 0x49100000492, - 0x49300000494, - 0x49500000496, - 0x49700000498, - 0x4990000049A, - 0x49B0000049C, - 0x49D0000049E, - 0x49F000004A0, - 0x4A1000004A2, - 0x4A3000004A4, - 0x4A5000004A6, - 0x4A7000004A8, - 0x4A9000004AA, - 0x4AB000004AC, - 0x4AD000004AE, - 0x4AF000004B0, - 0x4B1000004B2, - 0x4B3000004B4, - 0x4B5000004B6, - 0x4B7000004B8, - 0x4B9000004BA, - 0x4BB000004BC, - 0x4BD000004BE, - 0x4BF000004C0, - 0x4C2000004C3, - 0x4C4000004C5, - 0x4C6000004C7, - 0x4C8000004C9, - 0x4CA000004CB, - 0x4CC000004CD, - 0x4CE000004D0, - 0x4D1000004D2, - 0x4D3000004D4, - 0x4D5000004D6, - 0x4D7000004D8, - 0x4D9000004DA, - 0x4DB000004DC, - 0x4DD000004DE, - 0x4DF000004E0, - 0x4E1000004E2, - 0x4E3000004E4, - 0x4E5000004E6, - 0x4E7000004E8, - 0x4E9000004EA, - 0x4EB000004EC, - 0x4ED000004EE, - 0x4EF000004F0, - 0x4F1000004F2, - 0x4F3000004F4, - 0x4F5000004F6, - 0x4F7000004F8, - 0x4F9000004FA, - 0x4FB000004FC, - 0x4FD000004FE, - 0x4FF00000500, - 0x50100000502, - 0x50300000504, - 0x50500000506, - 0x50700000508, - 0x5090000050A, - 0x50B0000050C, - 0x50D0000050E, - 0x50F00000510, - 0x51100000512, - 0x51300000514, - 0x51500000516, - 0x51700000518, - 0x5190000051A, - 0x51B0000051C, - 0x51D0000051E, - 0x51F00000520, - 0x52100000522, - 0x52300000524, - 0x52500000526, - 0x52700000528, - 0x5290000052A, - 0x52B0000052C, - 0x52D0000052E, - 0x52F00000530, - 0x5590000055A, - 0x56000000587, - 0x58800000589, - 0x591000005BE, - 0x5BF000005C0, - 0x5C1000005C3, - 0x5C4000005C6, - 0x5C7000005C8, - 0x5D0000005EB, - 0x5EF000005F3, - 0x6100000061B, - 0x62000000640, - 0x64100000660, - 0x66E00000675, - 0x679000006D4, - 0x6D5000006DD, - 0x6DF000006E9, - 0x6EA000006F0, - 0x6FA00000700, - 0x7100000074B, - 0x74D000007B2, - 0x7C0000007F6, - 0x7FD000007FE, - 0x8000000082E, - 0x8400000085C, - 0x8600000086B, - 0x87000000888, - 0x88900000890, - 0x897000008E2, - 0x8E300000958, - 0x96000000964, - 0x96600000970, - 0x97100000984, - 0x9850000098D, - 0x98F00000991, - 0x993000009A9, - 0x9AA000009B1, - 0x9B2000009B3, - 0x9B6000009BA, - 0x9BC000009C5, - 0x9C7000009C9, - 0x9CB000009CF, - 0x9D7000009D8, - 0x9E0000009E4, - 0x9E6000009F2, - 0x9FC000009FD, - 0x9FE000009FF, - 0xA0100000A04, - 0xA0500000A0B, - 0xA0F00000A11, - 0xA1300000A29, - 0xA2A00000A31, - 0xA3200000A33, - 0xA3500000A36, - 0xA3800000A3A, - 0xA3C00000A3D, - 0xA3E00000A43, - 0xA4700000A49, - 0xA4B00000A4E, - 0xA5100000A52, - 0xA5C00000A5D, - 0xA6600000A76, - 0xA8100000A84, - 0xA8500000A8E, - 0xA8F00000A92, - 0xA9300000AA9, - 0xAAA00000AB1, - 0xAB200000AB4, - 0xAB500000ABA, - 0xABC00000AC6, - 0xAC700000ACA, - 0xACB00000ACE, - 0xAD000000AD1, - 0xAE000000AE4, - 0xAE600000AF0, - 0xAF900000B00, - 0xB0100000B04, - 0xB0500000B0D, - 0xB0F00000B11, - 0xB1300000B29, - 0xB2A00000B31, - 0xB3200000B34, - 0xB3500000B3A, - 0xB3C00000B45, - 0xB4700000B49, - 0xB4B00000B4E, - 0xB5500000B58, - 0xB5F00000B64, - 0xB6600000B70, - 0xB7100000B72, - 0xB8200000B84, - 0xB8500000B8B, - 0xB8E00000B91, - 0xB9200000B96, - 0xB9900000B9B, - 0xB9C00000B9D, - 0xB9E00000BA0, - 0xBA300000BA5, - 0xBA800000BAB, - 0xBAE00000BBA, - 0xBBE00000BC3, - 0xBC600000BC9, - 0xBCA00000BCE, - 0xBD000000BD1, - 0xBD700000BD8, - 0xBE600000BF0, - 0xC0000000C0D, - 0xC0E00000C11, - 0xC1200000C29, - 0xC2A00000C3A, - 0xC3C00000C45, - 0xC4600000C49, - 0xC4A00000C4E, - 0xC5500000C57, - 0xC5800000C5B, - 0xC5C00000C5E, - 0xC6000000C64, - 0xC6600000C70, - 0xC8000000C84, - 0xC8500000C8D, - 0xC8E00000C91, - 0xC9200000CA9, - 0xCAA00000CB4, - 0xCB500000CBA, - 0xCBC00000CC5, - 0xCC600000CC9, - 0xCCA00000CCE, - 0xCD500000CD7, - 0xCDC00000CDF, - 0xCE000000CE4, - 0xCE600000CF0, - 0xCF100000CF4, - 0xD0000000D0D, - 0xD0E00000D11, - 0xD1200000D45, - 0xD4600000D49, - 0xD4A00000D4F, - 0xD5400000D58, - 0xD5F00000D64, - 0xD6600000D70, - 0xD7A00000D80, - 0xD8100000D84, - 0xD8500000D97, - 0xD9A00000DB2, - 0xDB300000DBC, - 0xDBD00000DBE, - 0xDC000000DC7, - 0xDCA00000DCB, - 0xDCF00000DD5, - 0xDD600000DD7, - 0xDD800000DE0, - 0xDE600000DF0, - 0xDF200000DF4, - 0xE0100000E33, - 0xE3400000E3B, - 0xE4000000E4F, - 0xE5000000E5A, - 0xE8100000E83, - 0xE8400000E85, - 0xE8600000E8B, - 0xE8C00000EA4, - 0xEA500000EA6, - 0xEA700000EB3, - 0xEB400000EBE, - 0xEC000000EC5, - 0xEC600000EC7, - 0xEC800000ECF, - 0xED000000EDA, - 0xEDE00000EE0, - 0xF0000000F01, - 0xF0B00000F0C, - 0xF1800000F1A, - 0xF2000000F2A, - 0xF3500000F36, - 0xF3700000F38, - 0xF3900000F3A, - 0xF3E00000F43, - 0xF4400000F48, - 0xF4900000F4D, - 0xF4E00000F52, - 0xF5300000F57, - 0xF5800000F5C, - 0xF5D00000F69, - 0xF6A00000F6D, - 0xF7100000F73, - 0xF7400000F75, - 0xF7A00000F81, - 0xF8200000F85, - 0xF8600000F93, - 0xF9400000F98, - 0xF9900000F9D, - 0xF9E00000FA2, - 0xFA300000FA7, - 0xFA800000FAC, - 0xFAD00000FB9, - 0xFBA00000FBD, - 0xFC600000FC7, - 0x10000000104A, - 0x10500000109E, - 0x10D0000010FB, - 0x10FD00001100, - 0x120000001249, - 0x124A0000124E, - 0x125000001257, - 0x125800001259, - 0x125A0000125E, - 0x126000001289, - 0x128A0000128E, - 0x1290000012B1, - 0x12B2000012B6, - 0x12B8000012BF, - 0x12C0000012C1, - 0x12C2000012C6, - 0x12C8000012D7, - 0x12D800001311, - 0x131200001316, - 0x13180000135B, - 0x135D00001360, - 0x138000001390, - 0x13A0000013F6, - 0x14010000166D, - 0x166F00001680, - 0x16810000169B, - 0x16A0000016EB, - 0x16F1000016F9, - 0x170000001716, - 0x171F00001735, - 0x174000001754, - 0x17600000176D, - 0x176E00001771, - 0x177200001774, - 0x1780000017B4, - 0x17B6000017D4, - 0x17D7000017D8, - 0x17DC000017DE, - 0x17E0000017EA, - 0x18100000181A, - 0x182000001879, - 0x1880000018AB, - 0x18B0000018F6, - 0x19000000191F, - 0x19200000192C, - 0x19300000193C, - 0x19460000196E, - 0x197000001975, - 0x1980000019AC, - 0x19B0000019CA, - 0x19D0000019DA, - 0x1A0000001A1C, - 0x1A2000001A5F, - 0x1A6000001A7D, - 0x1A7F00001A8A, - 0x1A9000001A9A, - 0x1AA700001AA8, - 0x1AB000001ABE, - 0x1ABF00001ADE, - 0x1AE000001AEC, - 0x1B0000001B4D, - 0x1B5000001B5A, - 0x1B6B00001B74, - 0x1B8000001BF4, - 0x1C0000001C38, - 0x1C4000001C4A, - 0x1C4D00001C7E, - 0x1C8A00001C8B, - 0x1CD000001CD3, - 0x1CD400001CFB, - 0x1D0000001D2C, - 0x1D2F00001D30, - 0x1D3B00001D3C, - 0x1D4E00001D4F, - 0x1D6B00001D78, - 0x1D7900001D9B, - 0x1DC000001E00, - 0x1E0100001E02, - 0x1E0300001E04, - 0x1E0500001E06, - 0x1E0700001E08, - 0x1E0900001E0A, - 0x1E0B00001E0C, - 0x1E0D00001E0E, - 0x1E0F00001E10, - 0x1E1100001E12, - 0x1E1300001E14, - 0x1E1500001E16, - 0x1E1700001E18, - 0x1E1900001E1A, - 0x1E1B00001E1C, - 0x1E1D00001E1E, - 0x1E1F00001E20, - 0x1E2100001E22, - 0x1E2300001E24, - 0x1E2500001E26, - 0x1E2700001E28, - 0x1E2900001E2A, - 0x1E2B00001E2C, - 0x1E2D00001E2E, - 0x1E2F00001E30, - 0x1E3100001E32, - 0x1E3300001E34, - 0x1E3500001E36, - 0x1E3700001E38, - 0x1E3900001E3A, - 0x1E3B00001E3C, - 0x1E3D00001E3E, - 0x1E3F00001E40, - 0x1E4100001E42, - 0x1E4300001E44, - 0x1E4500001E46, - 0x1E4700001E48, - 0x1E4900001E4A, - 0x1E4B00001E4C, - 0x1E4D00001E4E, - 0x1E4F00001E50, - 0x1E5100001E52, - 0x1E5300001E54, - 0x1E5500001E56, - 0x1E5700001E58, - 0x1E5900001E5A, - 0x1E5B00001E5C, - 0x1E5D00001E5E, - 0x1E5F00001E60, - 0x1E6100001E62, - 0x1E6300001E64, - 0x1E6500001E66, - 0x1E6700001E68, - 0x1E6900001E6A, - 0x1E6B00001E6C, - 0x1E6D00001E6E, - 0x1E6F00001E70, - 0x1E7100001E72, - 0x1E7300001E74, - 0x1E7500001E76, - 0x1E7700001E78, - 0x1E7900001E7A, - 0x1E7B00001E7C, - 0x1E7D00001E7E, - 0x1E7F00001E80, - 0x1E8100001E82, - 0x1E8300001E84, - 0x1E8500001E86, - 0x1E8700001E88, - 0x1E8900001E8A, - 0x1E8B00001E8C, - 0x1E8D00001E8E, - 0x1E8F00001E90, - 0x1E9100001E92, - 0x1E9300001E94, - 0x1E9500001E9A, - 0x1E9C00001E9E, - 0x1E9F00001EA0, - 0x1EA100001EA2, - 0x1EA300001EA4, - 0x1EA500001EA6, - 0x1EA700001EA8, - 0x1EA900001EAA, - 0x1EAB00001EAC, - 0x1EAD00001EAE, - 0x1EAF00001EB0, - 0x1EB100001EB2, - 0x1EB300001EB4, - 0x1EB500001EB6, - 0x1EB700001EB8, - 0x1EB900001EBA, - 0x1EBB00001EBC, - 0x1EBD00001EBE, - 0x1EBF00001EC0, - 0x1EC100001EC2, - 0x1EC300001EC4, - 0x1EC500001EC6, - 0x1EC700001EC8, - 0x1EC900001ECA, - 0x1ECB00001ECC, - 0x1ECD00001ECE, - 0x1ECF00001ED0, - 0x1ED100001ED2, - 0x1ED300001ED4, - 0x1ED500001ED6, - 0x1ED700001ED8, - 0x1ED900001EDA, - 0x1EDB00001EDC, - 0x1EDD00001EDE, - 0x1EDF00001EE0, - 0x1EE100001EE2, - 0x1EE300001EE4, - 0x1EE500001EE6, - 0x1EE700001EE8, - 0x1EE900001EEA, - 0x1EEB00001EEC, - 0x1EED00001EEE, - 0x1EEF00001EF0, - 0x1EF100001EF2, - 0x1EF300001EF4, - 0x1EF500001EF6, - 0x1EF700001EF8, - 0x1EF900001EFA, - 0x1EFB00001EFC, - 0x1EFD00001EFE, - 0x1EFF00001F08, - 0x1F1000001F16, - 0x1F2000001F28, - 0x1F3000001F38, - 0x1F4000001F46, - 0x1F5000001F58, - 0x1F6000001F68, - 0x1F7000001F71, - 0x1F7200001F73, - 0x1F7400001F75, - 0x1F7600001F77, - 0x1F7800001F79, - 0x1F7A00001F7B, - 0x1F7C00001F7D, - 0x1FB000001FB2, - 0x1FB600001FB7, - 0x1FC600001FC7, - 0x1FD000001FD3, - 0x1FD600001FD8, - 0x1FE000001FE3, - 0x1FE400001FE8, - 0x1FF600001FF7, - 0x214E0000214F, - 0x218400002185, - 0x2C3000002C60, - 0x2C6100002C62, - 0x2C6500002C67, - 0x2C6800002C69, - 0x2C6A00002C6B, - 0x2C6C00002C6D, - 0x2C7100002C72, - 0x2C7300002C75, - 0x2C7600002C7C, - 0x2C8100002C82, - 0x2C8300002C84, - 0x2C8500002C86, - 0x2C8700002C88, - 0x2C8900002C8A, - 0x2C8B00002C8C, - 0x2C8D00002C8E, - 0x2C8F00002C90, - 0x2C9100002C92, - 0x2C9300002C94, - 0x2C9500002C96, - 0x2C9700002C98, - 0x2C9900002C9A, - 0x2C9B00002C9C, - 0x2C9D00002C9E, - 0x2C9F00002CA0, - 0x2CA100002CA2, - 0x2CA300002CA4, - 0x2CA500002CA6, - 0x2CA700002CA8, - 0x2CA900002CAA, - 0x2CAB00002CAC, - 0x2CAD00002CAE, - 0x2CAF00002CB0, - 0x2CB100002CB2, - 0x2CB300002CB4, - 0x2CB500002CB6, - 0x2CB700002CB8, - 0x2CB900002CBA, - 0x2CBB00002CBC, - 0x2CBD00002CBE, - 0x2CBF00002CC0, - 0x2CC100002CC2, - 0x2CC300002CC4, - 0x2CC500002CC6, - 0x2CC700002CC8, - 0x2CC900002CCA, - 0x2CCB00002CCC, - 0x2CCD00002CCE, - 0x2CCF00002CD0, - 0x2CD100002CD2, - 0x2CD300002CD4, - 0x2CD500002CD6, - 0x2CD700002CD8, - 0x2CD900002CDA, - 0x2CDB00002CDC, - 0x2CDD00002CDE, - 0x2CDF00002CE0, - 0x2CE100002CE2, - 0x2CE300002CE5, - 0x2CEC00002CED, - 0x2CEE00002CF2, - 0x2CF300002CF4, - 0x2D0000002D26, - 0x2D2700002D28, - 0x2D2D00002D2E, - 0x2D3000002D68, - 0x2D7F00002D97, - 0x2DA000002DA7, - 0x2DA800002DAF, - 0x2DB000002DB7, - 0x2DB800002DBF, - 0x2DC000002DC7, - 0x2DC800002DCF, - 0x2DD000002DD7, - 0x2DD800002DDF, - 0x2DE000002E00, - 0x2E2F00002E30, - 0x300500003008, - 0x302A0000302E, - 0x303C0000303D, - 0x304100003097, - 0x30990000309B, - 0x309D0000309F, - 0x30A1000030FB, - 0x30FC000030FF, - 0x310500003130, - 0x31A0000031C0, - 0x31F000003200, - 0x340000004DC0, - 0x4E000000A48D, - 0xA4D00000A4FE, - 0xA5000000A60D, - 0xA6100000A62C, - 0xA6410000A642, - 0xA6430000A644, - 0xA6450000A646, - 0xA6470000A648, - 0xA6490000A64A, - 0xA64B0000A64C, - 0xA64D0000A64E, - 0xA64F0000A650, - 0xA6510000A652, - 0xA6530000A654, - 0xA6550000A656, - 0xA6570000A658, - 0xA6590000A65A, - 0xA65B0000A65C, - 0xA65D0000A65E, - 0xA65F0000A660, - 0xA6610000A662, - 0xA6630000A664, - 0xA6650000A666, - 0xA6670000A668, - 0xA6690000A66A, - 0xA66B0000A66C, - 0xA66D0000A670, - 0xA6740000A67E, - 0xA67F0000A680, - 0xA6810000A682, - 0xA6830000A684, - 0xA6850000A686, - 0xA6870000A688, - 0xA6890000A68A, - 0xA68B0000A68C, - 0xA68D0000A68E, - 0xA68F0000A690, - 0xA6910000A692, - 0xA6930000A694, - 0xA6950000A696, - 0xA6970000A698, - 0xA6990000A69A, - 0xA69B0000A69C, - 0xA69E0000A6E6, - 0xA6F00000A6F2, - 0xA7170000A720, - 0xA7230000A724, - 0xA7250000A726, - 0xA7270000A728, - 0xA7290000A72A, - 0xA72B0000A72C, - 0xA72D0000A72E, - 0xA72F0000A732, - 0xA7330000A734, - 0xA7350000A736, - 0xA7370000A738, - 0xA7390000A73A, - 0xA73B0000A73C, - 0xA73D0000A73E, - 0xA73F0000A740, - 0xA7410000A742, - 0xA7430000A744, - 0xA7450000A746, - 0xA7470000A748, - 0xA7490000A74A, - 0xA74B0000A74C, - 0xA74D0000A74E, - 0xA74F0000A750, - 0xA7510000A752, - 0xA7530000A754, - 0xA7550000A756, - 0xA7570000A758, - 0xA7590000A75A, - 0xA75B0000A75C, - 0xA75D0000A75E, - 0xA75F0000A760, - 0xA7610000A762, - 0xA7630000A764, - 0xA7650000A766, - 0xA7670000A768, - 0xA7690000A76A, - 0xA76B0000A76C, - 0xA76D0000A76E, - 0xA76F0000A770, - 0xA7710000A779, - 0xA77A0000A77B, - 0xA77C0000A77D, - 0xA77F0000A780, - 0xA7810000A782, - 0xA7830000A784, - 0xA7850000A786, - 0xA7870000A789, - 0xA78C0000A78D, - 0xA78E0000A790, - 0xA7910000A792, - 0xA7930000A796, - 0xA7970000A798, - 0xA7990000A79A, - 0xA79B0000A79C, - 0xA79D0000A79E, - 0xA79F0000A7A0, - 0xA7A10000A7A2, - 0xA7A30000A7A4, - 0xA7A50000A7A6, - 0xA7A70000A7A8, - 0xA7A90000A7AA, - 0xA7AF0000A7B0, - 0xA7B50000A7B6, - 0xA7B70000A7B8, - 0xA7B90000A7BA, - 0xA7BB0000A7BC, - 0xA7BD0000A7BE, - 0xA7BF0000A7C0, - 0xA7C10000A7C2, - 0xA7C30000A7C4, - 0xA7C80000A7C9, - 0xA7CA0000A7CB, - 0xA7CD0000A7CE, - 0xA7CF0000A7D0, - 0xA7D10000A7D2, - 0xA7D30000A7D4, - 0xA7D50000A7D6, - 0xA7D70000A7D8, - 0xA7D90000A7DA, - 0xA7DB0000A7DC, - 0xA7F60000A7F8, - 0xA7FA0000A828, - 0xA82C0000A82D, - 0xA8400000A874, - 0xA8800000A8C6, - 0xA8D00000A8DA, - 0xA8E00000A8F8, - 0xA8FB0000A8FC, - 0xA8FD0000A92E, - 0xA9300000A954, - 0xA9800000A9C1, - 0xA9CF0000A9DA, - 0xA9E00000A9FF, - 0xAA000000AA37, - 0xAA400000AA4E, - 0xAA500000AA5A, - 0xAA600000AA77, - 0xAA7A0000AAC3, - 0xAADB0000AADE, - 0xAAE00000AAF0, - 0xAAF20000AAF7, - 0xAB010000AB07, - 0xAB090000AB0F, - 0xAB110000AB17, - 0xAB200000AB27, - 0xAB280000AB2F, - 0xAB300000AB5B, - 0xAB600000AB69, - 0xABC00000ABEB, - 0xABEC0000ABEE, - 0xABF00000ABFA, - 0xAC000000D7A4, - 0xFA0E0000FA10, - 0xFA110000FA12, - 0xFA130000FA15, - 0xFA1F0000FA20, - 0xFA210000FA22, - 0xFA230000FA25, - 0xFA270000FA2A, - 0xFB1E0000FB1F, - 0xFE200000FE30, - 0xFE730000FE74, - 0x100000001000C, - 0x1000D00010027, - 0x100280001003B, - 0x1003C0001003E, - 0x1003F0001004E, - 0x100500001005E, - 0x10080000100FB, - 0x101FD000101FE, - 0x102800001029D, - 0x102A0000102D1, - 0x102E0000102E1, - 0x1030000010320, - 0x1032D00010341, - 0x103420001034A, - 0x103500001037B, - 0x103800001039E, - 0x103A0000103C4, - 0x103C8000103D0, - 0x104280001049E, - 0x104A0000104AA, - 0x104D8000104FC, - 0x1050000010528, - 0x1053000010564, - 0x10597000105A2, - 0x105A3000105B2, - 0x105B3000105BA, - 0x105BB000105BD, - 0x105C0000105F4, - 0x1060000010737, - 0x1074000010756, - 0x1076000010768, - 0x1078000010781, - 0x1080000010806, - 0x1080800010809, - 0x1080A00010836, - 0x1083700010839, - 0x1083C0001083D, - 0x1083F00010856, - 0x1086000010877, - 0x108800001089F, - 0x108E0000108F3, - 0x108F4000108F6, - 0x1090000010916, - 0x109200001093A, - 0x109400001095A, - 0x10980000109B8, - 0x109BE000109C0, - 0x10A0000010A04, - 0x10A0500010A07, - 0x10A0C00010A14, - 0x10A1500010A18, - 0x10A1900010A36, - 0x10A3800010A3B, - 0x10A3F00010A40, - 0x10A6000010A7D, - 0x10A8000010A9D, - 0x10AC000010AC8, - 0x10AC900010AE7, - 0x10B0000010B36, - 0x10B4000010B56, - 0x10B6000010B73, - 0x10B8000010B92, - 0x10C0000010C49, - 0x10CC000010CF3, - 0x10D0000010D28, - 0x10D3000010D3A, - 0x10D4000010D50, - 0x10D6900010D6E, - 0x10D6F00010D86, - 0x10E8000010EAA, - 0x10EAB00010EAD, - 0x10EB000010EB2, - 0x10EC200010EC8, - 0x10EFA00010F1D, - 0x10F2700010F28, - 0x10F3000010F51, - 0x10F7000010F86, - 0x10FB000010FC5, - 0x10FE000010FF7, - 0x1100000011047, - 0x1106600011076, - 0x1107F000110BB, - 0x110C2000110C3, - 0x110D0000110E9, - 0x110F0000110FA, - 0x1110000011135, - 0x1113600011140, - 0x1114400011148, - 0x1115000011174, - 0x1117600011177, - 0x11180000111C5, - 0x111C9000111CD, - 0x111CE000111DB, - 0x111DC000111DD, - 0x1120000011212, - 0x1121300011238, - 0x1123E00011242, - 0x1128000011287, - 0x1128800011289, - 0x1128A0001128E, - 0x1128F0001129E, - 0x1129F000112A9, - 0x112B0000112EB, - 0x112F0000112FA, - 0x1130000011304, - 0x113050001130D, - 0x1130F00011311, - 0x1131300011329, - 0x1132A00011331, - 0x1133200011334, - 0x113350001133A, - 0x1133B00011345, - 0x1134700011349, - 0x1134B0001134E, - 0x1135000011351, - 0x1135700011358, - 0x1135D00011364, - 0x113660001136D, - 0x1137000011375, - 0x113800001138A, - 0x1138B0001138C, - 0x1138E0001138F, - 0x11390000113B6, - 0x113B7000113C1, - 0x113C2000113C3, - 0x113C5000113C6, - 0x113C7000113CB, - 0x113CC000113D4, - 0x113E1000113E3, - 0x114000001144B, - 0x114500001145A, - 0x1145E00011462, - 0x11480000114C6, - 0x114C7000114C8, - 0x114D0000114DA, - 0x11580000115B6, - 0x115B8000115C1, - 0x115D8000115DE, - 0x1160000011641, - 0x1164400011645, - 0x116500001165A, - 0x11680000116B9, - 0x116C0000116CA, - 0x116D0000116E4, - 0x117000001171B, - 0x1171D0001172C, - 0x117300001173A, - 0x1174000011747, - 0x118000001183B, - 0x118C0000118EA, - 0x118FF00011907, - 0x119090001190A, - 0x1190C00011914, - 0x1191500011917, - 0x1191800011936, - 0x1193700011939, - 0x1193B00011944, - 0x119500001195A, - 0x119A0000119A8, - 0x119AA000119D8, - 0x119DA000119E2, - 0x119E3000119E5, - 0x11A0000011A3F, - 0x11A4700011A48, - 0x11A5000011A9A, - 0x11A9D00011A9E, - 0x11AB000011AF9, - 0x11B6000011B68, - 0x11BC000011BE1, - 0x11BF000011BFA, - 0x11C0000011C09, - 0x11C0A00011C37, - 0x11C3800011C41, - 0x11C5000011C5A, - 0x11C7200011C90, - 0x11C9200011CA8, - 0x11CA900011CB7, - 0x11D0000011D07, - 0x11D0800011D0A, - 0x11D0B00011D37, - 0x11D3A00011D3B, - 0x11D3C00011D3E, - 0x11D3F00011D48, - 0x11D5000011D5A, - 0x11D6000011D66, - 0x11D6700011D69, - 0x11D6A00011D8F, - 0x11D9000011D92, - 0x11D9300011D99, - 0x11DA000011DAA, - 0x11DB000011DDC, - 0x11DE000011DEA, - 0x11EE000011EF7, - 0x11F0000011F11, - 0x11F1200011F3B, - 0x11F3E00011F43, - 0x11F5000011F5B, - 0x11FB000011FB1, - 0x120000001239A, - 0x1248000012544, - 0x12F9000012FF1, - 0x1300000013430, - 0x1344000013456, - 0x13460000143FB, - 0x1440000014647, - 0x161000001613A, - 0x1680000016A39, - 0x16A4000016A5F, - 0x16A6000016A6A, - 0x16A7000016ABF, - 0x16AC000016ACA, - 0x16AD000016AEE, - 0x16AF000016AF5, - 0x16B0000016B37, - 0x16B4000016B44, - 0x16B5000016B5A, - 0x16B6300016B78, - 0x16B7D00016B90, - 0x16D4000016D6D, - 0x16D7000016D7A, - 0x16E6000016E80, - 0x16EBB00016ED4, - 0x16F0000016F4B, - 0x16F4F00016F88, - 0x16F8F00016FA0, - 0x16FE000016FE2, - 0x16FE300016FE5, - 0x16FF000016FF4, - 0x1700000018CD6, - 0x18CFF00018D1F, - 0x18D8000018DF3, - 0x1AFF00001AFF4, - 0x1AFF50001AFFC, - 0x1AFFD0001AFFF, - 0x1B0000001B123, - 0x1B1320001B133, - 0x1B1500001B153, - 0x1B1550001B156, - 0x1B1640001B168, - 0x1B1700001B2FC, - 0x1BC000001BC6B, - 0x1BC700001BC7D, - 0x1BC800001BC89, - 0x1BC900001BC9A, - 0x1BC9D0001BC9F, - 0x1CF000001CF2E, - 0x1CF300001CF47, - 0x1DA000001DA37, - 0x1DA3B0001DA6D, - 0x1DA750001DA76, - 0x1DA840001DA85, - 0x1DA9B0001DAA0, - 0x1DAA10001DAB0, - 0x1DF000001DF1F, - 0x1DF250001DF2B, - 0x1E0000001E007, - 0x1E0080001E019, - 0x1E01B0001E022, - 0x1E0230001E025, - 0x1E0260001E02B, - 0x1E08F0001E090, - 0x1E1000001E12D, - 0x1E1300001E13E, - 0x1E1400001E14A, - 0x1E14E0001E14F, - 0x1E2900001E2AF, - 0x1E2C00001E2FA, - 0x1E4D00001E4FA, - 0x1E5D00001E5FB, - 0x1E6C00001E6DF, - 0x1E6E00001E6F6, - 0x1E6FE0001E700, - 0x1E7E00001E7E7, - 0x1E7E80001E7EC, - 0x1E7ED0001E7EF, - 0x1E7F00001E7FF, - 0x1E8000001E8C5, - 0x1E8D00001E8D7, - 0x1E9220001E94C, - 0x1E9500001E95A, - 0x200000002A6E0, - 0x2A7000002B81E, - 0x2B8200002CEAE, - 0x2CEB00002EBE1, - 0x2EBF00002EE5E, - 0x300000003134B, - 0x313500003347A, - ), - "CONTEXTJ": (0x200C0000200E,), - "CONTEXTO": ( - 0xB7000000B8, - 0x37500000376, - 0x5F3000005F5, - 0x6600000066A, - 0x6F0000006FA, - 0x30FB000030FC, - ), -} diff --git a/server/venv/Lib/site-packages/idna/intranges.py b/server/venv/Lib/site-packages/idna/intranges.py deleted file mode 100644 index 19d7781..0000000 --- a/server/venv/Lib/site-packages/idna/intranges.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Given a list of integers, made up of (hopefully) a small number of long runs -of consecutive integers, compute a representation of the form -((start1, end1), (start2, end2) ...). Then answer the question "was x present -in the original list?" in time O(log(# runs)). -""" - -import bisect - - -def intranges_from_list(list_: list[int]) -> tuple[int, ...]: - """Represent a list of integers as a sequence of ranges: - ((start_0, end_0), (start_1, end_1), ...), such that the original - integers are exactly those x such that start_i <= x < end_i for some i. - - Ranges are encoded as single integers (start << 32 | end), not as tuples. - """ - - sorted_list = sorted(list_) - ranges = [] - last_write = -1 - for i in range(len(sorted_list)): - if i + 1 < len(sorted_list) and sorted_list[i] == sorted_list[i + 1] - 1: - continue - current_range = sorted_list[last_write + 1 : i + 1] - ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) - last_write = i - - return tuple(ranges) - - -def _encode_range(start: int, end: int) -> int: - return (start << 32) | end - - -def _decode_range(r: int) -> tuple[int, int]: - return (r >> 32), (r & ((1 << 32) - 1)) - - -def intranges_contain(int_: int, ranges: tuple[int, ...]) -> bool: - """Determine if `int_` falls into one of the ranges in `ranges`.""" - tuple_ = _encode_range(int_, 0) - pos = bisect.bisect_left(ranges, tuple_) - # we could be immediately ahead of a tuple (start, end) - # with start < int_ <= end - if pos > 0: - left, right = _decode_range(ranges[pos - 1]) - if left <= int_ < right: - return True - # or we could be immediately behind a tuple (int_, end) - if pos < len(ranges): - left, _ = _decode_range(ranges[pos]) - if left == int_: - return True - return False diff --git a/server/venv/Lib/site-packages/idna/package_data.py b/server/venv/Lib/site-packages/idna/package_data.py deleted file mode 100644 index 94e4039..0000000 --- a/server/venv/Lib/site-packages/idna/package_data.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "3.18" diff --git a/server/venv/Lib/site-packages/idna/py.typed b/server/venv/Lib/site-packages/idna/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/server/venv/Lib/site-packages/idna/uts46data.py b/server/venv/Lib/site-packages/idna/uts46data.py deleted file mode 100644 index f2d931f..0000000 --- a/server/venv/Lib/site-packages/idna/uts46data.py +++ /dev/null @@ -1,16896 +0,0 @@ -# This file is automatically generated by tools/idna-data - -from array import array -from typing import Optional - -"""IDNA Mapping Table from UTS46.""" - - -__version__ = "17.0.0" - -uts46_starts: "array[int]" = array( - "I", - ( - 0x0, - 0x1, - 0x2, - 0x3, - 0x4, - 0x5, - 0x6, - 0x7, - 0x8, - 0x9, - 0xA, - 0xB, - 0xC, - 0xD, - 0xE, - 0xF, - 0x10, - 0x11, - 0x12, - 0x13, - 0x14, - 0x15, - 0x16, - 0x17, - 0x18, - 0x19, - 0x1A, - 0x1B, - 0x1C, - 0x1D, - 0x1E, - 0x1F, - 0x20, - 0x21, - 0x22, - 0x23, - 0x24, - 0x25, - 0x26, - 0x27, - 0x28, - 0x29, - 0x2A, - 0x2B, - 0x2C, - 0x2D, - 0x2E, - 0x2F, - 0x30, - 0x31, - 0x32, - 0x33, - 0x34, - 0x35, - 0x36, - 0x37, - 0x38, - 0x39, - 0x3A, - 0x3B, - 0x3C, - 0x3D, - 0x3E, - 0x3F, - 0x40, - 0x41, - 0x42, - 0x43, - 0x44, - 0x45, - 0x46, - 0x47, - 0x48, - 0x49, - 0x4A, - 0x4B, - 0x4C, - 0x4D, - 0x4E, - 0x4F, - 0x50, - 0x51, - 0x52, - 0x53, - 0x54, - 0x55, - 0x56, - 0x57, - 0x58, - 0x59, - 0x5A, - 0x5B, - 0x5C, - 0x5D, - 0x5E, - 0x5F, - 0x60, - 0x61, - 0x62, - 0x63, - 0x64, - 0x65, - 0x66, - 0x67, - 0x68, - 0x69, - 0x6A, - 0x6B, - 0x6C, - 0x6D, - 0x6E, - 0x6F, - 0x70, - 0x71, - 0x72, - 0x73, - 0x74, - 0x75, - 0x76, - 0x77, - 0x78, - 0x79, - 0x7A, - 0x7B, - 0x7C, - 0x7D, - 0x7E, - 0x7F, - 0x80, - 0x81, - 0x82, - 0x83, - 0x84, - 0x85, - 0x86, - 0x87, - 0x88, - 0x89, - 0x8A, - 0x8B, - 0x8C, - 0x8D, - 0x8E, - 0x8F, - 0x90, - 0x91, - 0x92, - 0x93, - 0x94, - 0x95, - 0x96, - 0x97, - 0x98, - 0x99, - 0x9A, - 0x9B, - 0x9C, - 0x9D, - 0x9E, - 0x9F, - 0xA0, - 0xA1, - 0xA2, - 0xA3, - 0xA4, - 0xA5, - 0xA6, - 0xA7, - 0xA8, - 0xA9, - 0xAA, - 0xAB, - 0xAC, - 0xAD, - 0xAE, - 0xAF, - 0xB0, - 0xB1, - 0xB2, - 0xB3, - 0xB4, - 0xB5, - 0xB6, - 0xB7, - 0xB8, - 0xB9, - 0xBA, - 0xBB, - 0xBC, - 0xBD, - 0xBE, - 0xBF, - 0xC0, - 0xC1, - 0xC2, - 0xC3, - 0xC4, - 0xC5, - 0xC6, - 0xC7, - 0xC8, - 0xC9, - 0xCA, - 0xCB, - 0xCC, - 0xCD, - 0xCE, - 0xCF, - 0xD0, - 0xD1, - 0xD2, - 0xD3, - 0xD4, - 0xD5, - 0xD6, - 0xD7, - 0xD8, - 0xD9, - 0xDA, - 0xDB, - 0xDC, - 0xDD, - 0xDE, - 0xDF, - 0xE0, - 0xE1, - 0xE2, - 0xE3, - 0xE4, - 0xE5, - 0xE6, - 0xE7, - 0xE8, - 0xE9, - 0xEA, - 0xEB, - 0xEC, - 0xED, - 0xEE, - 0xEF, - 0xF0, - 0xF1, - 0xF2, - 0xF3, - 0xF4, - 0xF5, - 0xF6, - 0xF7, - 0xF8, - 0xF9, - 0xFA, - 0xFB, - 0xFC, - 0xFD, - 0xFE, - 0xFF, - 0x100, - 0x101, - 0x102, - 0x103, - 0x104, - 0x105, - 0x106, - 0x107, - 0x108, - 0x109, - 0x10A, - 0x10B, - 0x10C, - 0x10D, - 0x10E, - 0x10F, - 0x110, - 0x111, - 0x112, - 0x113, - 0x114, - 0x115, - 0x116, - 0x117, - 0x118, - 0x119, - 0x11A, - 0x11B, - 0x11C, - 0x11D, - 0x11E, - 0x11F, - 0x120, - 0x121, - 0x122, - 0x123, - 0x124, - 0x125, - 0x126, - 0x127, - 0x128, - 0x129, - 0x12A, - 0x12B, - 0x12C, - 0x12D, - 0x12E, - 0x12F, - 0x130, - 0x131, - 0x132, - 0x134, - 0x135, - 0x136, - 0x137, - 0x139, - 0x13A, - 0x13B, - 0x13C, - 0x13D, - 0x13E, - 0x13F, - 0x141, - 0x142, - 0x143, - 0x144, - 0x145, - 0x146, - 0x147, - 0x148, - 0x149, - 0x14A, - 0x14B, - 0x14C, - 0x14D, - 0x14E, - 0x14F, - 0x150, - 0x151, - 0x152, - 0x153, - 0x154, - 0x155, - 0x156, - 0x157, - 0x158, - 0x159, - 0x15A, - 0x15B, - 0x15C, - 0x15D, - 0x15E, - 0x15F, - 0x160, - 0x161, - 0x162, - 0x163, - 0x164, - 0x165, - 0x166, - 0x167, - 0x168, - 0x169, - 0x16A, - 0x16B, - 0x16C, - 0x16D, - 0x16E, - 0x16F, - 0x170, - 0x171, - 0x172, - 0x173, - 0x174, - 0x175, - 0x176, - 0x177, - 0x178, - 0x179, - 0x17A, - 0x17B, - 0x17C, - 0x17D, - 0x17E, - 0x17F, - 0x180, - 0x181, - 0x182, - 0x183, - 0x184, - 0x185, - 0x186, - 0x187, - 0x188, - 0x189, - 0x18A, - 0x18B, - 0x18C, - 0x18E, - 0x18F, - 0x190, - 0x191, - 0x192, - 0x193, - 0x194, - 0x195, - 0x196, - 0x197, - 0x198, - 0x199, - 0x19C, - 0x19D, - 0x19E, - 0x19F, - 0x1A0, - 0x1A1, - 0x1A2, - 0x1A3, - 0x1A4, - 0x1A5, - 0x1A6, - 0x1A7, - 0x1A8, - 0x1A9, - 0x1AA, - 0x1AC, - 0x1AD, - 0x1AE, - 0x1AF, - 0x1B0, - 0x1B1, - 0x1B2, - 0x1B3, - 0x1B4, - 0x1B5, - 0x1B6, - 0x1B7, - 0x1B8, - 0x1B9, - 0x1BC, - 0x1BD, - 0x1C4, - 0x1C7, - 0x1CA, - 0x1CD, - 0x1CE, - 0x1CF, - 0x1D0, - 0x1D1, - 0x1D2, - 0x1D3, - 0x1D4, - 0x1D5, - 0x1D6, - 0x1D7, - 0x1D8, - 0x1D9, - 0x1DA, - 0x1DB, - 0x1DC, - 0x1DE, - 0x1DF, - 0x1E0, - 0x1E1, - 0x1E2, - 0x1E3, - 0x1E4, - 0x1E5, - 0x1E6, - 0x1E7, - 0x1E8, - 0x1E9, - 0x1EA, - 0x1EB, - 0x1EC, - 0x1ED, - 0x1EE, - 0x1EF, - 0x1F1, - 0x1F4, - 0x1F5, - 0x1F6, - 0x1F7, - 0x1F8, - 0x1F9, - 0x1FA, - 0x1FB, - 0x1FC, - 0x1FD, - 0x1FE, - 0x1FF, - 0x200, - 0x201, - 0x202, - 0x203, - 0x204, - 0x205, - 0x206, - 0x207, - 0x208, - 0x209, - 0x20A, - 0x20B, - 0x20C, - 0x20D, - 0x20E, - 0x20F, - 0x210, - 0x211, - 0x212, - 0x213, - 0x214, - 0x215, - 0x216, - 0x217, - 0x218, - 0x219, - 0x21A, - 0x21B, - 0x21C, - 0x21D, - 0x21E, - 0x21F, - 0x220, - 0x221, - 0x222, - 0x223, - 0x224, - 0x225, - 0x226, - 0x227, - 0x228, - 0x229, - 0x22A, - 0x22B, - 0x22C, - 0x22D, - 0x22E, - 0x22F, - 0x230, - 0x231, - 0x232, - 0x233, - 0x23A, - 0x23B, - 0x23C, - 0x23D, - 0x23E, - 0x23F, - 0x241, - 0x242, - 0x243, - 0x244, - 0x245, - 0x246, - 0x247, - 0x248, - 0x249, - 0x24A, - 0x24B, - 0x24C, - 0x24D, - 0x24E, - 0x24F, - 0x2B0, - 0x2B1, - 0x2B2, - 0x2B3, - 0x2B4, - 0x2B5, - 0x2B6, - 0x2B7, - 0x2B8, - 0x2B9, - 0x2D8, - 0x2D9, - 0x2DA, - 0x2DB, - 0x2DC, - 0x2DD, - 0x2DE, - 0x2E0, - 0x2E1, - 0x2E2, - 0x2E3, - 0x2E4, - 0x2E5, - 0x340, - 0x341, - 0x342, - 0x343, - 0x344, - 0x345, - 0x346, - 0x34F, - 0x350, - 0x370, - 0x371, - 0x372, - 0x373, - 0x374, - 0x375, - 0x376, - 0x377, - 0x378, - 0x37A, - 0x37B, - 0x37E, - 0x37F, - 0x380, - 0x384, - 0x385, - 0x386, - 0x387, - 0x388, - 0x389, - 0x38A, - 0x38B, - 0x38C, - 0x38D, - 0x38E, - 0x38F, - 0x390, - 0x391, - 0x392, - 0x393, - 0x394, - 0x395, - 0x396, - 0x397, - 0x398, - 0x399, - 0x39A, - 0x39B, - 0x39C, - 0x39D, - 0x39E, - 0x39F, - 0x3A0, - 0x3A1, - 0x3A2, - 0x3A3, - 0x3A4, - 0x3A5, - 0x3A6, - 0x3A7, - 0x3A8, - 0x3A9, - 0x3AA, - 0x3AB, - 0x3AC, - 0x3C2, - 0x3C3, - 0x3CF, - 0x3D0, - 0x3D1, - 0x3D2, - 0x3D3, - 0x3D4, - 0x3D5, - 0x3D6, - 0x3D7, - 0x3D8, - 0x3D9, - 0x3DA, - 0x3DB, - 0x3DC, - 0x3DD, - 0x3DE, - 0x3DF, - 0x3E0, - 0x3E1, - 0x3E2, - 0x3E3, - 0x3E4, - 0x3E5, - 0x3E6, - 0x3E7, - 0x3E8, - 0x3E9, - 0x3EA, - 0x3EB, - 0x3EC, - 0x3ED, - 0x3EE, - 0x3EF, - 0x3F0, - 0x3F1, - 0x3F2, - 0x3F3, - 0x3F4, - 0x3F5, - 0x3F6, - 0x3F7, - 0x3F8, - 0x3F9, - 0x3FA, - 0x3FB, - 0x3FD, - 0x3FE, - 0x3FF, - 0x400, - 0x401, - 0x402, - 0x403, - 0x404, - 0x405, - 0x406, - 0x407, - 0x408, - 0x409, - 0x40A, - 0x40B, - 0x40C, - 0x40D, - 0x40E, - 0x40F, - 0x410, - 0x411, - 0x412, - 0x413, - 0x414, - 0x415, - 0x416, - 0x417, - 0x418, - 0x419, - 0x41A, - 0x41B, - 0x41C, - 0x41D, - 0x41E, - 0x41F, - 0x420, - 0x421, - 0x422, - 0x423, - 0x424, - 0x425, - 0x426, - 0x427, - 0x428, - 0x429, - 0x42A, - 0x42B, - 0x42C, - 0x42D, - 0x42E, - 0x42F, - 0x430, - 0x460, - 0x461, - 0x462, - 0x463, - 0x464, - 0x465, - 0x466, - 0x467, - 0x468, - 0x469, - 0x46A, - 0x46B, - 0x46C, - 0x46D, - 0x46E, - 0x46F, - 0x470, - 0x471, - 0x472, - 0x473, - 0x474, - 0x475, - 0x476, - 0x477, - 0x478, - 0x479, - 0x47A, - 0x47B, - 0x47C, - 0x47D, - 0x47E, - 0x47F, - 0x480, - 0x481, - 0x48A, - 0x48B, - 0x48C, - 0x48D, - 0x48E, - 0x48F, - 0x490, - 0x491, - 0x492, - 0x493, - 0x494, - 0x495, - 0x496, - 0x497, - 0x498, - 0x499, - 0x49A, - 0x49B, - 0x49C, - 0x49D, - 0x49E, - 0x49F, - 0x4A0, - 0x4A1, - 0x4A2, - 0x4A3, - 0x4A4, - 0x4A5, - 0x4A6, - 0x4A7, - 0x4A8, - 0x4A9, - 0x4AA, - 0x4AB, - 0x4AC, - 0x4AD, - 0x4AE, - 0x4AF, - 0x4B0, - 0x4B1, - 0x4B2, - 0x4B3, - 0x4B4, - 0x4B5, - 0x4B6, - 0x4B7, - 0x4B8, - 0x4B9, - 0x4BA, - 0x4BB, - 0x4BC, - 0x4BD, - 0x4BE, - 0x4BF, - 0x4C0, - 0x4C1, - 0x4C2, - 0x4C3, - 0x4C4, - 0x4C5, - 0x4C6, - 0x4C7, - 0x4C8, - 0x4C9, - 0x4CA, - 0x4CB, - 0x4CC, - 0x4CD, - 0x4CE, - 0x4D0, - 0x4D1, - 0x4D2, - 0x4D3, - 0x4D4, - 0x4D5, - 0x4D6, - 0x4D7, - 0x4D8, - 0x4D9, - 0x4DA, - 0x4DB, - 0x4DC, - 0x4DD, - 0x4DE, - 0x4DF, - 0x4E0, - 0x4E1, - 0x4E2, - 0x4E3, - 0x4E4, - 0x4E5, - 0x4E6, - 0x4E7, - 0x4E8, - 0x4E9, - 0x4EA, - 0x4EB, - 0x4EC, - 0x4ED, - 0x4EE, - 0x4EF, - 0x4F0, - 0x4F1, - 0x4F2, - 0x4F3, - 0x4F4, - 0x4F5, - 0x4F6, - 0x4F7, - 0x4F8, - 0x4F9, - 0x4FA, - 0x4FB, - 0x4FC, - 0x4FD, - 0x4FE, - 0x4FF, - 0x500, - 0x501, - 0x502, - 0x503, - 0x504, - 0x505, - 0x506, - 0x507, - 0x508, - 0x509, - 0x50A, - 0x50B, - 0x50C, - 0x50D, - 0x50E, - 0x50F, - 0x510, - 0x511, - 0x512, - 0x513, - 0x514, - 0x515, - 0x516, - 0x517, - 0x518, - 0x519, - 0x51A, - 0x51B, - 0x51C, - 0x51D, - 0x51E, - 0x51F, - 0x520, - 0x521, - 0x522, - 0x523, - 0x524, - 0x525, - 0x526, - 0x527, - 0x528, - 0x529, - 0x52A, - 0x52B, - 0x52C, - 0x52D, - 0x52E, - 0x52F, - 0x530, - 0x531, - 0x532, - 0x533, - 0x534, - 0x535, - 0x536, - 0x537, - 0x538, - 0x539, - 0x53A, - 0x53B, - 0x53C, - 0x53D, - 0x53E, - 0x53F, - 0x540, - 0x541, - 0x542, - 0x543, - 0x544, - 0x545, - 0x546, - 0x547, - 0x548, - 0x549, - 0x54A, - 0x54B, - 0x54C, - 0x54D, - 0x54E, - 0x54F, - 0x550, - 0x551, - 0x552, - 0x553, - 0x554, - 0x555, - 0x556, - 0x557, - 0x559, - 0x587, - 0x588, - 0x58B, - 0x58D, - 0x590, - 0x591, - 0x5C8, - 0x5D0, - 0x5EB, - 0x5EF, - 0x5F5, - 0x606, - 0x61C, - 0x61D, - 0x675, - 0x676, - 0x677, - 0x678, - 0x679, - 0x6DD, - 0x6DE, - 0x70E, - 0x710, - 0x74B, - 0x74D, - 0x7B2, - 0x7C0, - 0x7FB, - 0x7FD, - 0x82E, - 0x830, - 0x83F, - 0x840, - 0x85C, - 0x85E, - 0x85F, - 0x860, - 0x86B, - 0x870, - 0x890, - 0x897, - 0x8E2, - 0x8E3, - 0x958, - 0x959, - 0x95A, - 0x95B, - 0x95C, - 0x95D, - 0x95E, - 0x95F, - 0x960, - 0x984, - 0x985, - 0x98D, - 0x98F, - 0x991, - 0x993, - 0x9A9, - 0x9AA, - 0x9B1, - 0x9B2, - 0x9B3, - 0x9B6, - 0x9BA, - 0x9BC, - 0x9C5, - 0x9C7, - 0x9C9, - 0x9CB, - 0x9CF, - 0x9D7, - 0x9D8, - 0x9DC, - 0x9DD, - 0x9DE, - 0x9DF, - 0x9E0, - 0x9E4, - 0x9E6, - 0x9FF, - 0xA01, - 0xA04, - 0xA05, - 0xA0B, - 0xA0F, - 0xA11, - 0xA13, - 0xA29, - 0xA2A, - 0xA31, - 0xA32, - 0xA33, - 0xA34, - 0xA35, - 0xA36, - 0xA37, - 0xA38, - 0xA3A, - 0xA3C, - 0xA3D, - 0xA3E, - 0xA43, - 0xA47, - 0xA49, - 0xA4B, - 0xA4E, - 0xA51, - 0xA52, - 0xA59, - 0xA5A, - 0xA5B, - 0xA5C, - 0xA5D, - 0xA5E, - 0xA5F, - 0xA66, - 0xA77, - 0xA81, - 0xA84, - 0xA85, - 0xA8E, - 0xA8F, - 0xA92, - 0xA93, - 0xAA9, - 0xAAA, - 0xAB1, - 0xAB2, - 0xAB4, - 0xAB5, - 0xABA, - 0xABC, - 0xAC6, - 0xAC7, - 0xACA, - 0xACB, - 0xACE, - 0xAD0, - 0xAD1, - 0xAE0, - 0xAE4, - 0xAE6, - 0xAF2, - 0xAF9, - 0xB00, - 0xB01, - 0xB04, - 0xB05, - 0xB0D, - 0xB0F, - 0xB11, - 0xB13, - 0xB29, - 0xB2A, - 0xB31, - 0xB32, - 0xB34, - 0xB35, - 0xB3A, - 0xB3C, - 0xB45, - 0xB47, - 0xB49, - 0xB4B, - 0xB4E, - 0xB55, - 0xB58, - 0xB5C, - 0xB5D, - 0xB5E, - 0xB5F, - 0xB64, - 0xB66, - 0xB78, - 0xB82, - 0xB84, - 0xB85, - 0xB8B, - 0xB8E, - 0xB91, - 0xB92, - 0xB96, - 0xB99, - 0xB9B, - 0xB9C, - 0xB9D, - 0xB9E, - 0xBA0, - 0xBA3, - 0xBA5, - 0xBA8, - 0xBAB, - 0xBAE, - 0xBBA, - 0xBBE, - 0xBC3, - 0xBC6, - 0xBC9, - 0xBCA, - 0xBCE, - 0xBD0, - 0xBD1, - 0xBD7, - 0xBD8, - 0xBE6, - 0xBFB, - 0xC00, - 0xC0D, - 0xC0E, - 0xC11, - 0xC12, - 0xC29, - 0xC2A, - 0xC3A, - 0xC3C, - 0xC45, - 0xC46, - 0xC49, - 0xC4A, - 0xC4E, - 0xC55, - 0xC57, - 0xC58, - 0xC5B, - 0xC5C, - 0xC5E, - 0xC60, - 0xC64, - 0xC66, - 0xC70, - 0xC77, - 0xC8D, - 0xC8E, - 0xC91, - 0xC92, - 0xCA9, - 0xCAA, - 0xCB4, - 0xCB5, - 0xCBA, - 0xCBC, - 0xCC5, - 0xCC6, - 0xCC9, - 0xCCA, - 0xCCE, - 0xCD5, - 0xCD7, - 0xCDC, - 0xCDF, - 0xCE0, - 0xCE4, - 0xCE6, - 0xCF0, - 0xCF1, - 0xCF4, - 0xD00, - 0xD0D, - 0xD0E, - 0xD11, - 0xD12, - 0xD45, - 0xD46, - 0xD49, - 0xD4A, - 0xD50, - 0xD54, - 0xD64, - 0xD66, - 0xD80, - 0xD81, - 0xD84, - 0xD85, - 0xD97, - 0xD9A, - 0xDB2, - 0xDB3, - 0xDBC, - 0xDBD, - 0xDBE, - 0xDC0, - 0xDC7, - 0xDCA, - 0xDCB, - 0xDCF, - 0xDD5, - 0xDD6, - 0xDD7, - 0xDD8, - 0xDE0, - 0xDE6, - 0xDF0, - 0xDF2, - 0xDF5, - 0xE01, - 0xE33, - 0xE34, - 0xE3B, - 0xE3F, - 0xE5C, - 0xE81, - 0xE83, - 0xE84, - 0xE85, - 0xE86, - 0xE8B, - 0xE8C, - 0xEA4, - 0xEA5, - 0xEA6, - 0xEA7, - 0xEB3, - 0xEB4, - 0xEBE, - 0xEC0, - 0xEC5, - 0xEC6, - 0xEC7, - 0xEC8, - 0xECF, - 0xED0, - 0xEDA, - 0xEDC, - 0xEDD, - 0xEDE, - 0xEE0, - 0xF00, - 0xF0C, - 0xF0D, - 0xF43, - 0xF44, - 0xF48, - 0xF49, - 0xF4D, - 0xF4E, - 0xF52, - 0xF53, - 0xF57, - 0xF58, - 0xF5C, - 0xF5D, - 0xF69, - 0xF6A, - 0xF6D, - 0xF71, - 0xF73, - 0xF74, - 0xF75, - 0xF76, - 0xF77, - 0xF78, - 0xF79, - 0xF7A, - 0xF81, - 0xF82, - 0xF93, - 0xF94, - 0xF98, - 0xF99, - 0xF9D, - 0xF9E, - 0xFA2, - 0xFA3, - 0xFA7, - 0xFA8, - 0xFAC, - 0xFAD, - 0xFB9, - 0xFBA, - 0xFBD, - 0xFBE, - 0xFCD, - 0xFCE, - 0xFDB, - 0x1000, - 0x10A0, - 0x10A1, - 0x10A2, - 0x10A3, - 0x10A4, - 0x10A5, - 0x10A6, - 0x10A7, - 0x10A8, - 0x10A9, - 0x10AA, - 0x10AB, - 0x10AC, - 0x10AD, - 0x10AE, - 0x10AF, - 0x10B0, - 0x10B1, - 0x10B2, - 0x10B3, - 0x10B4, - 0x10B5, - 0x10B6, - 0x10B7, - 0x10B8, - 0x10B9, - 0x10BA, - 0x10BB, - 0x10BC, - 0x10BD, - 0x10BE, - 0x10BF, - 0x10C0, - 0x10C1, - 0x10C2, - 0x10C3, - 0x10C4, - 0x10C5, - 0x10C6, - 0x10C7, - 0x10C8, - 0x10CD, - 0x10CE, - 0x10D0, - 0x10FC, - 0x10FD, - 0x115F, - 0x1161, - 0x1249, - 0x124A, - 0x124E, - 0x1250, - 0x1257, - 0x1258, - 0x1259, - 0x125A, - 0x125E, - 0x1260, - 0x1289, - 0x128A, - 0x128E, - 0x1290, - 0x12B1, - 0x12B2, - 0x12B6, - 0x12B8, - 0x12BF, - 0x12C0, - 0x12C1, - 0x12C2, - 0x12C6, - 0x12C8, - 0x12D7, - 0x12D8, - 0x1311, - 0x1312, - 0x1316, - 0x1318, - 0x135B, - 0x135D, - 0x137D, - 0x1380, - 0x139A, - 0x13A0, - 0x13F6, - 0x13F8, - 0x13F9, - 0x13FA, - 0x13FB, - 0x13FC, - 0x13FD, - 0x13FE, - 0x1400, - 0x1680, - 0x1681, - 0x169D, - 0x16A0, - 0x16F9, - 0x1700, - 0x1716, - 0x171F, - 0x1737, - 0x1740, - 0x1754, - 0x1760, - 0x176D, - 0x176E, - 0x1771, - 0x1772, - 0x1774, - 0x1780, - 0x17B4, - 0x17B6, - 0x17DE, - 0x17E0, - 0x17EA, - 0x17F0, - 0x17FA, - 0x1800, - 0x180B, - 0x1810, - 0x181A, - 0x1820, - 0x1879, - 0x1880, - 0x18AB, - 0x18B0, - 0x18F6, - 0x1900, - 0x191F, - 0x1920, - 0x192C, - 0x1930, - 0x193C, - 0x1940, - 0x1941, - 0x1944, - 0x196E, - 0x1970, - 0x1975, - 0x1980, - 0x19AC, - 0x19B0, - 0x19CA, - 0x19D0, - 0x19DB, - 0x19DE, - 0x1A1C, - 0x1A1E, - 0x1A5F, - 0x1A60, - 0x1A7D, - 0x1A7F, - 0x1A8A, - 0x1A90, - 0x1A9A, - 0x1AA0, - 0x1AAE, - 0x1AB0, - 0x1ADE, - 0x1AE0, - 0x1AEC, - 0x1B00, - 0x1B4D, - 0x1B4E, - 0x1BF4, - 0x1BFC, - 0x1C38, - 0x1C3B, - 0x1C4A, - 0x1C4D, - 0x1C80, - 0x1C81, - 0x1C82, - 0x1C83, - 0x1C84, - 0x1C86, - 0x1C87, - 0x1C88, - 0x1C89, - 0x1C8A, - 0x1C8B, - 0x1C90, - 0x1C91, - 0x1C92, - 0x1C93, - 0x1C94, - 0x1C95, - 0x1C96, - 0x1C97, - 0x1C98, - 0x1C99, - 0x1C9A, - 0x1C9B, - 0x1C9C, - 0x1C9D, - 0x1C9E, - 0x1C9F, - 0x1CA0, - 0x1CA1, - 0x1CA2, - 0x1CA3, - 0x1CA4, - 0x1CA5, - 0x1CA6, - 0x1CA7, - 0x1CA8, - 0x1CA9, - 0x1CAA, - 0x1CAB, - 0x1CAC, - 0x1CAD, - 0x1CAE, - 0x1CAF, - 0x1CB0, - 0x1CB1, - 0x1CB2, - 0x1CB3, - 0x1CB4, - 0x1CB5, - 0x1CB6, - 0x1CB7, - 0x1CB8, - 0x1CB9, - 0x1CBA, - 0x1CBB, - 0x1CBD, - 0x1CBE, - 0x1CBF, - 0x1CC0, - 0x1CC8, - 0x1CD0, - 0x1CFB, - 0x1D00, - 0x1D2C, - 0x1D2D, - 0x1D2E, - 0x1D2F, - 0x1D30, - 0x1D31, - 0x1D32, - 0x1D33, - 0x1D34, - 0x1D35, - 0x1D36, - 0x1D37, - 0x1D38, - 0x1D39, - 0x1D3A, - 0x1D3B, - 0x1D3C, - 0x1D3D, - 0x1D3E, - 0x1D3F, - 0x1D40, - 0x1D41, - 0x1D42, - 0x1D43, - 0x1D44, - 0x1D45, - 0x1D46, - 0x1D47, - 0x1D48, - 0x1D49, - 0x1D4A, - 0x1D4B, - 0x1D4C, - 0x1D4D, - 0x1D4E, - 0x1D4F, - 0x1D50, - 0x1D51, - 0x1D52, - 0x1D53, - 0x1D54, - 0x1D55, - 0x1D56, - 0x1D57, - 0x1D58, - 0x1D59, - 0x1D5A, - 0x1D5B, - 0x1D5C, - 0x1D5D, - 0x1D5E, - 0x1D5F, - 0x1D60, - 0x1D61, - 0x1D62, - 0x1D63, - 0x1D64, - 0x1D65, - 0x1D66, - 0x1D67, - 0x1D68, - 0x1D69, - 0x1D6A, - 0x1D6B, - 0x1D78, - 0x1D79, - 0x1D9B, - 0x1D9C, - 0x1D9D, - 0x1D9E, - 0x1D9F, - 0x1DA0, - 0x1DA1, - 0x1DA2, - 0x1DA3, - 0x1DA4, - 0x1DA5, - 0x1DA6, - 0x1DA7, - 0x1DA8, - 0x1DA9, - 0x1DAA, - 0x1DAB, - 0x1DAC, - 0x1DAD, - 0x1DAE, - 0x1DAF, - 0x1DB0, - 0x1DB1, - 0x1DB2, - 0x1DB3, - 0x1DB4, - 0x1DB5, - 0x1DB6, - 0x1DB7, - 0x1DB8, - 0x1DB9, - 0x1DBA, - 0x1DBB, - 0x1DBC, - 0x1DBD, - 0x1DBE, - 0x1DBF, - 0x1DC0, - 0x1E00, - 0x1E01, - 0x1E02, - 0x1E03, - 0x1E04, - 0x1E05, - 0x1E06, - 0x1E07, - 0x1E08, - 0x1E09, - 0x1E0A, - 0x1E0B, - 0x1E0C, - 0x1E0D, - 0x1E0E, - 0x1E0F, - 0x1E10, - 0x1E11, - 0x1E12, - 0x1E13, - 0x1E14, - 0x1E15, - 0x1E16, - 0x1E17, - 0x1E18, - 0x1E19, - 0x1E1A, - 0x1E1B, - 0x1E1C, - 0x1E1D, - 0x1E1E, - 0x1E1F, - 0x1E20, - 0x1E21, - 0x1E22, - 0x1E23, - 0x1E24, - 0x1E25, - 0x1E26, - 0x1E27, - 0x1E28, - 0x1E29, - 0x1E2A, - 0x1E2B, - 0x1E2C, - 0x1E2D, - 0x1E2E, - 0x1E2F, - 0x1E30, - 0x1E31, - 0x1E32, - 0x1E33, - 0x1E34, - 0x1E35, - 0x1E36, - 0x1E37, - 0x1E38, - 0x1E39, - 0x1E3A, - 0x1E3B, - 0x1E3C, - 0x1E3D, - 0x1E3E, - 0x1E3F, - 0x1E40, - 0x1E41, - 0x1E42, - 0x1E43, - 0x1E44, - 0x1E45, - 0x1E46, - 0x1E47, - 0x1E48, - 0x1E49, - 0x1E4A, - 0x1E4B, - 0x1E4C, - 0x1E4D, - 0x1E4E, - 0x1E4F, - 0x1E50, - 0x1E51, - 0x1E52, - 0x1E53, - 0x1E54, - 0x1E55, - 0x1E56, - 0x1E57, - 0x1E58, - 0x1E59, - 0x1E5A, - 0x1E5B, - 0x1E5C, - 0x1E5D, - 0x1E5E, - 0x1E5F, - 0x1E60, - 0x1E61, - 0x1E62, - 0x1E63, - 0x1E64, - 0x1E65, - 0x1E66, - 0x1E67, - 0x1E68, - 0x1E69, - 0x1E6A, - 0x1E6B, - 0x1E6C, - 0x1E6D, - 0x1E6E, - 0x1E6F, - 0x1E70, - 0x1E71, - 0x1E72, - 0x1E73, - 0x1E74, - 0x1E75, - 0x1E76, - 0x1E77, - 0x1E78, - 0x1E79, - 0x1E7A, - 0x1E7B, - 0x1E7C, - 0x1E7D, - 0x1E7E, - 0x1E7F, - 0x1E80, - 0x1E81, - 0x1E82, - 0x1E83, - 0x1E84, - 0x1E85, - 0x1E86, - 0x1E87, - 0x1E88, - 0x1E89, - 0x1E8A, - 0x1E8B, - 0x1E8C, - 0x1E8D, - 0x1E8E, - 0x1E8F, - 0x1E90, - 0x1E91, - 0x1E92, - 0x1E93, - 0x1E94, - 0x1E95, - 0x1E9A, - 0x1E9B, - 0x1E9C, - 0x1E9E, - 0x1E9F, - 0x1EA0, - 0x1EA1, - 0x1EA2, - 0x1EA3, - 0x1EA4, - 0x1EA5, - 0x1EA6, - 0x1EA7, - 0x1EA8, - 0x1EA9, - 0x1EAA, - 0x1EAB, - 0x1EAC, - 0x1EAD, - 0x1EAE, - 0x1EAF, - 0x1EB0, - 0x1EB1, - 0x1EB2, - 0x1EB3, - 0x1EB4, - 0x1EB5, - 0x1EB6, - 0x1EB7, - 0x1EB8, - 0x1EB9, - 0x1EBA, - 0x1EBB, - 0x1EBC, - 0x1EBD, - 0x1EBE, - 0x1EBF, - 0x1EC0, - 0x1EC1, - 0x1EC2, - 0x1EC3, - 0x1EC4, - 0x1EC5, - 0x1EC6, - 0x1EC7, - 0x1EC8, - 0x1EC9, - 0x1ECA, - 0x1ECB, - 0x1ECC, - 0x1ECD, - 0x1ECE, - 0x1ECF, - 0x1ED0, - 0x1ED1, - 0x1ED2, - 0x1ED3, - 0x1ED4, - 0x1ED5, - 0x1ED6, - 0x1ED7, - 0x1ED8, - 0x1ED9, - 0x1EDA, - 0x1EDB, - 0x1EDC, - 0x1EDD, - 0x1EDE, - 0x1EDF, - 0x1EE0, - 0x1EE1, - 0x1EE2, - 0x1EE3, - 0x1EE4, - 0x1EE5, - 0x1EE6, - 0x1EE7, - 0x1EE8, - 0x1EE9, - 0x1EEA, - 0x1EEB, - 0x1EEC, - 0x1EED, - 0x1EEE, - 0x1EEF, - 0x1EF0, - 0x1EF1, - 0x1EF2, - 0x1EF3, - 0x1EF4, - 0x1EF5, - 0x1EF6, - 0x1EF7, - 0x1EF8, - 0x1EF9, - 0x1EFA, - 0x1EFB, - 0x1EFC, - 0x1EFD, - 0x1EFE, - 0x1EFF, - 0x1F08, - 0x1F09, - 0x1F0A, - 0x1F0B, - 0x1F0C, - 0x1F0D, - 0x1F0E, - 0x1F0F, - 0x1F10, - 0x1F16, - 0x1F18, - 0x1F19, - 0x1F1A, - 0x1F1B, - 0x1F1C, - 0x1F1D, - 0x1F1E, - 0x1F20, - 0x1F28, - 0x1F29, - 0x1F2A, - 0x1F2B, - 0x1F2C, - 0x1F2D, - 0x1F2E, - 0x1F2F, - 0x1F30, - 0x1F38, - 0x1F39, - 0x1F3A, - 0x1F3B, - 0x1F3C, - 0x1F3D, - 0x1F3E, - 0x1F3F, - 0x1F40, - 0x1F46, - 0x1F48, - 0x1F49, - 0x1F4A, - 0x1F4B, - 0x1F4C, - 0x1F4D, - 0x1F4E, - 0x1F50, - 0x1F58, - 0x1F59, - 0x1F5A, - 0x1F5B, - 0x1F5C, - 0x1F5D, - 0x1F5E, - 0x1F5F, - 0x1F60, - 0x1F68, - 0x1F69, - 0x1F6A, - 0x1F6B, - 0x1F6C, - 0x1F6D, - 0x1F6E, - 0x1F6F, - 0x1F70, - 0x1F71, - 0x1F72, - 0x1F73, - 0x1F74, - 0x1F75, - 0x1F76, - 0x1F77, - 0x1F78, - 0x1F79, - 0x1F7A, - 0x1F7B, - 0x1F7C, - 0x1F7D, - 0x1F7E, - 0x1F80, - 0x1F81, - 0x1F82, - 0x1F83, - 0x1F84, - 0x1F85, - 0x1F86, - 0x1F87, - 0x1F88, - 0x1F89, - 0x1F8A, - 0x1F8B, - 0x1F8C, - 0x1F8D, - 0x1F8E, - 0x1F8F, - 0x1F90, - 0x1F91, - 0x1F92, - 0x1F93, - 0x1F94, - 0x1F95, - 0x1F96, - 0x1F97, - 0x1F98, - 0x1F99, - 0x1F9A, - 0x1F9B, - 0x1F9C, - 0x1F9D, - 0x1F9E, - 0x1F9F, - 0x1FA0, - 0x1FA1, - 0x1FA2, - 0x1FA3, - 0x1FA4, - 0x1FA5, - 0x1FA6, - 0x1FA7, - 0x1FA8, - 0x1FA9, - 0x1FAA, - 0x1FAB, - 0x1FAC, - 0x1FAD, - 0x1FAE, - 0x1FAF, - 0x1FB0, - 0x1FB2, - 0x1FB3, - 0x1FB4, - 0x1FB5, - 0x1FB6, - 0x1FB7, - 0x1FB8, - 0x1FB9, - 0x1FBA, - 0x1FBB, - 0x1FBC, - 0x1FBD, - 0x1FBE, - 0x1FBF, - 0x1FC0, - 0x1FC1, - 0x1FC2, - 0x1FC3, - 0x1FC4, - 0x1FC5, - 0x1FC6, - 0x1FC7, - 0x1FC8, - 0x1FC9, - 0x1FCA, - 0x1FCB, - 0x1FCC, - 0x1FCD, - 0x1FCE, - 0x1FCF, - 0x1FD0, - 0x1FD3, - 0x1FD4, - 0x1FD6, - 0x1FD8, - 0x1FD9, - 0x1FDA, - 0x1FDB, - 0x1FDC, - 0x1FDD, - 0x1FDE, - 0x1FDF, - 0x1FE0, - 0x1FE3, - 0x1FE4, - 0x1FE8, - 0x1FE9, - 0x1FEA, - 0x1FEB, - 0x1FEC, - 0x1FED, - 0x1FEE, - 0x1FEF, - 0x1FF0, - 0x1FF2, - 0x1FF3, - 0x1FF4, - 0x1FF5, - 0x1FF6, - 0x1FF7, - 0x1FF8, - 0x1FF9, - 0x1FFA, - 0x1FFB, - 0x1FFC, - 0x1FFD, - 0x1FFE, - 0x1FFF, - 0x2000, - 0x200B, - 0x200C, - 0x200E, - 0x2010, - 0x2011, - 0x2012, - 0x2017, - 0x2018, - 0x2024, - 0x2027, - 0x2028, - 0x202F, - 0x2030, - 0x2033, - 0x2034, - 0x2035, - 0x2036, - 0x2037, - 0x2038, - 0x203C, - 0x203D, - 0x203E, - 0x203F, - 0x2047, - 0x2048, - 0x2049, - 0x204A, - 0x2057, - 0x2058, - 0x205F, - 0x2060, - 0x2065, - 0x206A, - 0x2070, - 0x2071, - 0x2072, - 0x2074, - 0x2075, - 0x2076, - 0x2077, - 0x2078, - 0x2079, - 0x207A, - 0x207B, - 0x207C, - 0x207D, - 0x207E, - 0x207F, - 0x2080, - 0x2081, - 0x2082, - 0x2083, - 0x2084, - 0x2085, - 0x2086, - 0x2087, - 0x2088, - 0x2089, - 0x208A, - 0x208B, - 0x208C, - 0x208D, - 0x208E, - 0x208F, - 0x2090, - 0x2091, - 0x2092, - 0x2093, - 0x2094, - 0x2095, - 0x2096, - 0x2097, - 0x2098, - 0x2099, - 0x209A, - 0x209B, - 0x209C, - 0x209D, - 0x20A0, - 0x20A8, - 0x20A9, - 0x20C2, - 0x20D0, - 0x20F1, - 0x2100, - 0x2101, - 0x2102, - 0x2103, - 0x2104, - 0x2105, - 0x2106, - 0x2107, - 0x2108, - 0x2109, - 0x210A, - 0x210B, - 0x210F, - 0x2110, - 0x2112, - 0x2114, - 0x2115, - 0x2116, - 0x2117, - 0x2119, - 0x211A, - 0x211B, - 0x211E, - 0x2120, - 0x2121, - 0x2122, - 0x2123, - 0x2124, - 0x2125, - 0x2126, - 0x2127, - 0x2128, - 0x2129, - 0x212A, - 0x212B, - 0x212C, - 0x212D, - 0x212E, - 0x212F, - 0x2131, - 0x2132, - 0x2133, - 0x2134, - 0x2135, - 0x2136, - 0x2137, - 0x2138, - 0x2139, - 0x213A, - 0x213B, - 0x213C, - 0x213D, - 0x213F, - 0x2140, - 0x2141, - 0x2145, - 0x2147, - 0x2148, - 0x2149, - 0x214A, - 0x2150, - 0x2151, - 0x2152, - 0x2153, - 0x2154, - 0x2155, - 0x2156, - 0x2157, - 0x2158, - 0x2159, - 0x215A, - 0x215B, - 0x215C, - 0x215D, - 0x215E, - 0x215F, - 0x2160, - 0x2161, - 0x2162, - 0x2163, - 0x2164, - 0x2165, - 0x2166, - 0x2167, - 0x2168, - 0x2169, - 0x216A, - 0x216B, - 0x216C, - 0x216D, - 0x216E, - 0x216F, - 0x2170, - 0x2171, - 0x2172, - 0x2173, - 0x2174, - 0x2175, - 0x2176, - 0x2177, - 0x2178, - 0x2179, - 0x217A, - 0x217B, - 0x217C, - 0x217D, - 0x217E, - 0x217F, - 0x2180, - 0x2183, - 0x2184, - 0x2189, - 0x218A, - 0x218C, - 0x2190, - 0x222C, - 0x222D, - 0x222E, - 0x222F, - 0x2230, - 0x2231, - 0x2329, - 0x232A, - 0x232B, - 0x242A, - 0x2440, - 0x244B, - 0x2460, - 0x2461, - 0x2462, - 0x2463, - 0x2464, - 0x2465, - 0x2466, - 0x2467, - 0x2468, - 0x2469, - 0x246A, - 0x246B, - 0x246C, - 0x246D, - 0x246E, - 0x246F, - 0x2470, - 0x2471, - 0x2472, - 0x2473, - 0x2474, - 0x2475, - 0x2476, - 0x2477, - 0x2478, - 0x2479, - 0x247A, - 0x247B, - 0x247C, - 0x247D, - 0x247E, - 0x247F, - 0x2480, - 0x2481, - 0x2482, - 0x2483, - 0x2484, - 0x2485, - 0x2486, - 0x2487, - 0x2488, - 0x249C, - 0x249D, - 0x249E, - 0x249F, - 0x24A0, - 0x24A1, - 0x24A2, - 0x24A3, - 0x24A4, - 0x24A5, - 0x24A6, - 0x24A7, - 0x24A8, - 0x24A9, - 0x24AA, - 0x24AB, - 0x24AC, - 0x24AD, - 0x24AE, - 0x24AF, - 0x24B0, - 0x24B1, - 0x24B2, - 0x24B3, - 0x24B4, - 0x24B5, - 0x24B6, - 0x24B7, - 0x24B8, - 0x24B9, - 0x24BA, - 0x24BB, - 0x24BC, - 0x24BD, - 0x24BE, - 0x24BF, - 0x24C0, - 0x24C1, - 0x24C2, - 0x24C3, - 0x24C4, - 0x24C5, - 0x24C6, - 0x24C7, - 0x24C8, - 0x24C9, - 0x24CA, - 0x24CB, - 0x24CC, - 0x24CD, - 0x24CE, - 0x24CF, - 0x24D0, - 0x24D1, - 0x24D2, - 0x24D3, - 0x24D4, - 0x24D5, - 0x24D6, - 0x24D7, - 0x24D8, - 0x24D9, - 0x24DA, - 0x24DB, - 0x24DC, - 0x24DD, - 0x24DE, - 0x24DF, - 0x24E0, - 0x24E1, - 0x24E2, - 0x24E3, - 0x24E4, - 0x24E5, - 0x24E6, - 0x24E7, - 0x24E8, - 0x24E9, - 0x24EA, - 0x24EB, - 0x2A0C, - 0x2A0D, - 0x2A74, - 0x2A75, - 0x2A76, - 0x2A77, - 0x2ADC, - 0x2ADD, - 0x2B74, - 0x2B76, - 0x2C00, - 0x2C01, - 0x2C02, - 0x2C03, - 0x2C04, - 0x2C05, - 0x2C06, - 0x2C07, - 0x2C08, - 0x2C09, - 0x2C0A, - 0x2C0B, - 0x2C0C, - 0x2C0D, - 0x2C0E, - 0x2C0F, - 0x2C10, - 0x2C11, - 0x2C12, - 0x2C13, - 0x2C14, - 0x2C15, - 0x2C16, - 0x2C17, - 0x2C18, - 0x2C19, - 0x2C1A, - 0x2C1B, - 0x2C1C, - 0x2C1D, - 0x2C1E, - 0x2C1F, - 0x2C20, - 0x2C21, - 0x2C22, - 0x2C23, - 0x2C24, - 0x2C25, - 0x2C26, - 0x2C27, - 0x2C28, - 0x2C29, - 0x2C2A, - 0x2C2B, - 0x2C2C, - 0x2C2D, - 0x2C2E, - 0x2C2F, - 0x2C30, - 0x2C60, - 0x2C61, - 0x2C62, - 0x2C63, - 0x2C64, - 0x2C65, - 0x2C67, - 0x2C68, - 0x2C69, - 0x2C6A, - 0x2C6B, - 0x2C6C, - 0x2C6D, - 0x2C6E, - 0x2C6F, - 0x2C70, - 0x2C71, - 0x2C72, - 0x2C73, - 0x2C75, - 0x2C76, - 0x2C7C, - 0x2C7D, - 0x2C7E, - 0x2C7F, - 0x2C80, - 0x2C81, - 0x2C82, - 0x2C83, - 0x2C84, - 0x2C85, - 0x2C86, - 0x2C87, - 0x2C88, - 0x2C89, - 0x2C8A, - 0x2C8B, - 0x2C8C, - 0x2C8D, - 0x2C8E, - 0x2C8F, - 0x2C90, - 0x2C91, - 0x2C92, - 0x2C93, - 0x2C94, - 0x2C95, - 0x2C96, - 0x2C97, - 0x2C98, - 0x2C99, - 0x2C9A, - 0x2C9B, - 0x2C9C, - 0x2C9D, - 0x2C9E, - 0x2C9F, - 0x2CA0, - 0x2CA1, - 0x2CA2, - 0x2CA3, - 0x2CA4, - 0x2CA5, - 0x2CA6, - 0x2CA7, - 0x2CA8, - 0x2CA9, - 0x2CAA, - 0x2CAB, - 0x2CAC, - 0x2CAD, - 0x2CAE, - 0x2CAF, - 0x2CB0, - 0x2CB1, - 0x2CB2, - 0x2CB3, - 0x2CB4, - 0x2CB5, - 0x2CB6, - 0x2CB7, - 0x2CB8, - 0x2CB9, - 0x2CBA, - 0x2CBB, - 0x2CBC, - 0x2CBD, - 0x2CBE, - 0x2CBF, - 0x2CC0, - 0x2CC1, - 0x2CC2, - 0x2CC3, - 0x2CC4, - 0x2CC5, - 0x2CC6, - 0x2CC7, - 0x2CC8, - 0x2CC9, - 0x2CCA, - 0x2CCB, - 0x2CCC, - 0x2CCD, - 0x2CCE, - 0x2CCF, - 0x2CD0, - 0x2CD1, - 0x2CD2, - 0x2CD3, - 0x2CD4, - 0x2CD5, - 0x2CD6, - 0x2CD7, - 0x2CD8, - 0x2CD9, - 0x2CDA, - 0x2CDB, - 0x2CDC, - 0x2CDD, - 0x2CDE, - 0x2CDF, - 0x2CE0, - 0x2CE1, - 0x2CE2, - 0x2CE3, - 0x2CEB, - 0x2CEC, - 0x2CED, - 0x2CEE, - 0x2CF2, - 0x2CF3, - 0x2CF4, - 0x2CF9, - 0x2D26, - 0x2D27, - 0x2D28, - 0x2D2D, - 0x2D2E, - 0x2D30, - 0x2D68, - 0x2D6F, - 0x2D70, - 0x2D71, - 0x2D7F, - 0x2D97, - 0x2DA0, - 0x2DA7, - 0x2DA8, - 0x2DAF, - 0x2DB0, - 0x2DB7, - 0x2DB8, - 0x2DBF, - 0x2DC0, - 0x2DC7, - 0x2DC8, - 0x2DCF, - 0x2DD0, - 0x2DD7, - 0x2DD8, - 0x2DDF, - 0x2DE0, - 0x2E5E, - 0x2E80, - 0x2E9A, - 0x2E9B, - 0x2E9F, - 0x2EA0, - 0x2EF3, - 0x2EF4, - 0x2F00, - 0x2F01, - 0x2F02, - 0x2F03, - 0x2F04, - 0x2F05, - 0x2F06, - 0x2F07, - 0x2F08, - 0x2F09, - 0x2F0A, - 0x2F0B, - 0x2F0C, - 0x2F0D, - 0x2F0E, - 0x2F0F, - 0x2F10, - 0x2F11, - 0x2F12, - 0x2F13, - 0x2F14, - 0x2F15, - 0x2F16, - 0x2F17, - 0x2F18, - 0x2F19, - 0x2F1A, - 0x2F1B, - 0x2F1C, - 0x2F1D, - 0x2F1E, - 0x2F1F, - 0x2F20, - 0x2F21, - 0x2F22, - 0x2F23, - 0x2F24, - 0x2F25, - 0x2F26, - 0x2F27, - 0x2F28, - 0x2F29, - 0x2F2A, - 0x2F2B, - 0x2F2C, - 0x2F2D, - 0x2F2E, - 0x2F2F, - 0x2F30, - 0x2F31, - 0x2F32, - 0x2F33, - 0x2F34, - 0x2F35, - 0x2F36, - 0x2F37, - 0x2F38, - 0x2F39, - 0x2F3A, - 0x2F3B, - 0x2F3C, - 0x2F3D, - 0x2F3E, - 0x2F3F, - 0x2F40, - 0x2F41, - 0x2F42, - 0x2F43, - 0x2F44, - 0x2F45, - 0x2F46, - 0x2F47, - 0x2F48, - 0x2F49, - 0x2F4A, - 0x2F4B, - 0x2F4C, - 0x2F4D, - 0x2F4E, - 0x2F4F, - 0x2F50, - 0x2F51, - 0x2F52, - 0x2F53, - 0x2F54, - 0x2F55, - 0x2F56, - 0x2F57, - 0x2F58, - 0x2F59, - 0x2F5A, - 0x2F5B, - 0x2F5C, - 0x2F5D, - 0x2F5E, - 0x2F5F, - 0x2F60, - 0x2F61, - 0x2F62, - 0x2F63, - 0x2F64, - 0x2F65, - 0x2F66, - 0x2F67, - 0x2F68, - 0x2F69, - 0x2F6A, - 0x2F6B, - 0x2F6C, - 0x2F6D, - 0x2F6E, - 0x2F6F, - 0x2F70, - 0x2F71, - 0x2F72, - 0x2F73, - 0x2F74, - 0x2F75, - 0x2F76, - 0x2F77, - 0x2F78, - 0x2F79, - 0x2F7A, - 0x2F7B, - 0x2F7C, - 0x2F7D, - 0x2F7E, - 0x2F7F, - 0x2F80, - 0x2F81, - 0x2F82, - 0x2F83, - 0x2F84, - 0x2F85, - 0x2F86, - 0x2F87, - 0x2F88, - 0x2F89, - 0x2F8A, - 0x2F8B, - 0x2F8C, - 0x2F8D, - 0x2F8E, - 0x2F8F, - 0x2F90, - 0x2F91, - 0x2F92, - 0x2F93, - 0x2F94, - 0x2F95, - 0x2F96, - 0x2F97, - 0x2F98, - 0x2F99, - 0x2F9A, - 0x2F9B, - 0x2F9C, - 0x2F9D, - 0x2F9E, - 0x2F9F, - 0x2FA0, - 0x2FA1, - 0x2FA2, - 0x2FA3, - 0x2FA4, - 0x2FA5, - 0x2FA6, - 0x2FA7, - 0x2FA8, - 0x2FA9, - 0x2FAA, - 0x2FAB, - 0x2FAC, - 0x2FAD, - 0x2FAE, - 0x2FAF, - 0x2FB0, - 0x2FB1, - 0x2FB2, - 0x2FB3, - 0x2FB4, - 0x2FB5, - 0x2FB6, - 0x2FB7, - 0x2FB8, - 0x2FB9, - 0x2FBA, - 0x2FBB, - 0x2FBC, - 0x2FBD, - 0x2FBE, - 0x2FBF, - 0x2FC0, - 0x2FC1, - 0x2FC2, - 0x2FC3, - 0x2FC4, - 0x2FC5, - 0x2FC6, - 0x2FC7, - 0x2FC8, - 0x2FC9, - 0x2FCA, - 0x2FCB, - 0x2FCC, - 0x2FCD, - 0x2FCE, - 0x2FCF, - 0x2FD0, - 0x2FD1, - 0x2FD2, - 0x2FD3, - 0x2FD4, - 0x2FD5, - 0x2FD6, - 0x3000, - 0x3001, - 0x3002, - 0x3003, - 0x3036, - 0x3037, - 0x3038, - 0x3039, - 0x303A, - 0x303B, - 0x3040, - 0x3041, - 0x3097, - 0x3099, - 0x309B, - 0x309C, - 0x309D, - 0x309F, - 0x30A0, - 0x30FF, - 0x3100, - 0x3105, - 0x3130, - 0x3131, - 0x3132, - 0x3133, - 0x3134, - 0x3135, - 0x3136, - 0x3137, - 0x3138, - 0x3139, - 0x313A, - 0x313B, - 0x313C, - 0x313D, - 0x313E, - 0x313F, - 0x3140, - 0x3141, - 0x3142, - 0x3143, - 0x3144, - 0x3145, - 0x3146, - 0x3147, - 0x3148, - 0x3149, - 0x314A, - 0x314B, - 0x314C, - 0x314D, - 0x314E, - 0x314F, - 0x3150, - 0x3151, - 0x3152, - 0x3153, - 0x3154, - 0x3155, - 0x3156, - 0x3157, - 0x3158, - 0x3159, - 0x315A, - 0x315B, - 0x315C, - 0x315D, - 0x315E, - 0x315F, - 0x3160, - 0x3161, - 0x3162, - 0x3163, - 0x3164, - 0x3165, - 0x3166, - 0x3167, - 0x3168, - 0x3169, - 0x316A, - 0x316B, - 0x316C, - 0x316D, - 0x316E, - 0x316F, - 0x3170, - 0x3171, - 0x3172, - 0x3173, - 0x3174, - 0x3175, - 0x3176, - 0x3177, - 0x3178, - 0x3179, - 0x317A, - 0x317B, - 0x317C, - 0x317D, - 0x317E, - 0x317F, - 0x3180, - 0x3181, - 0x3182, - 0x3183, - 0x3184, - 0x3185, - 0x3186, - 0x3187, - 0x3188, - 0x3189, - 0x318A, - 0x318B, - 0x318C, - 0x318D, - 0x318E, - 0x318F, - 0x3190, - 0x3192, - 0x3193, - 0x3194, - 0x3195, - 0x3196, - 0x3197, - 0x3198, - 0x3199, - 0x319A, - 0x319B, - 0x319C, - 0x319D, - 0x319E, - 0x319F, - 0x31A0, - 0x31E6, - 0x31F0, - 0x3200, - 0x3201, - 0x3202, - 0x3203, - 0x3204, - 0x3205, - 0x3206, - 0x3207, - 0x3208, - 0x3209, - 0x320A, - 0x320B, - 0x320C, - 0x320D, - 0x320E, - 0x320F, - 0x3210, - 0x3211, - 0x3212, - 0x3213, - 0x3214, - 0x3215, - 0x3216, - 0x3217, - 0x3218, - 0x3219, - 0x321A, - 0x321B, - 0x321C, - 0x321D, - 0x321E, - 0x321F, - 0x3220, - 0x3221, - 0x3222, - 0x3223, - 0x3224, - 0x3225, - 0x3226, - 0x3227, - 0x3228, - 0x3229, - 0x322A, - 0x322B, - 0x322C, - 0x322D, - 0x322E, - 0x322F, - 0x3230, - 0x3231, - 0x3232, - 0x3233, - 0x3234, - 0x3235, - 0x3236, - 0x3237, - 0x3238, - 0x3239, - 0x323A, - 0x323B, - 0x323C, - 0x323D, - 0x323E, - 0x323F, - 0x3240, - 0x3241, - 0x3242, - 0x3243, - 0x3244, - 0x3245, - 0x3246, - 0x3247, - 0x3248, - 0x3250, - 0x3251, - 0x3252, - 0x3253, - 0x3254, - 0x3255, - 0x3256, - 0x3257, - 0x3258, - 0x3259, - 0x325A, - 0x325B, - 0x325C, - 0x325D, - 0x325E, - 0x325F, - 0x3260, - 0x3261, - 0x3262, - 0x3263, - 0x3264, - 0x3265, - 0x3266, - 0x3267, - 0x3268, - 0x3269, - 0x326A, - 0x326B, - 0x326C, - 0x326D, - 0x326E, - 0x326F, - 0x3270, - 0x3271, - 0x3272, - 0x3273, - 0x3274, - 0x3275, - 0x3276, - 0x3277, - 0x3278, - 0x3279, - 0x327A, - 0x327B, - 0x327C, - 0x327D, - 0x327E, - 0x327F, - 0x3280, - 0x3281, - 0x3282, - 0x3283, - 0x3284, - 0x3285, - 0x3286, - 0x3287, - 0x3288, - 0x3289, - 0x328A, - 0x328B, - 0x328C, - 0x328D, - 0x328E, - 0x328F, - 0x3290, - 0x3291, - 0x3292, - 0x3293, - 0x3294, - 0x3295, - 0x3296, - 0x3297, - 0x3298, - 0x3299, - 0x329A, - 0x329B, - 0x329C, - 0x329D, - 0x329E, - 0x329F, - 0x32A0, - 0x32A1, - 0x32A2, - 0x32A3, - 0x32A4, - 0x32A5, - 0x32A6, - 0x32A7, - 0x32A8, - 0x32A9, - 0x32AA, - 0x32AB, - 0x32AC, - 0x32AD, - 0x32AE, - 0x32AF, - 0x32B0, - 0x32B1, - 0x32B2, - 0x32B3, - 0x32B4, - 0x32B5, - 0x32B6, - 0x32B7, - 0x32B8, - 0x32B9, - 0x32BA, - 0x32BB, - 0x32BC, - 0x32BD, - 0x32BE, - 0x32BF, - 0x32C0, - 0x32C1, - 0x32C2, - 0x32C3, - 0x32C4, - 0x32C5, - 0x32C6, - 0x32C7, - 0x32C8, - 0x32C9, - 0x32CA, - 0x32CB, - 0x32CC, - 0x32CD, - 0x32CE, - 0x32CF, - 0x32D0, - 0x32D1, - 0x32D2, - 0x32D3, - 0x32D4, - 0x32D5, - 0x32D6, - 0x32D7, - 0x32D8, - 0x32D9, - 0x32DA, - 0x32DB, - 0x32DC, - 0x32DD, - 0x32DE, - 0x32DF, - 0x32E0, - 0x32E1, - 0x32E2, - 0x32E3, - 0x32E4, - 0x32E5, - 0x32E6, - 0x32E7, - 0x32E8, - 0x32E9, - 0x32EA, - 0x32EB, - 0x32EC, - 0x32ED, - 0x32EE, - 0x32EF, - 0x32F0, - 0x32F1, - 0x32F2, - 0x32F3, - 0x32F4, - 0x32F5, - 0x32F6, - 0x32F7, - 0x32F8, - 0x32F9, - 0x32FA, - 0x32FB, - 0x32FC, - 0x32FD, - 0x32FE, - 0x32FF, - 0x3300, - 0x3301, - 0x3302, - 0x3303, - 0x3304, - 0x3305, - 0x3306, - 0x3307, - 0x3308, - 0x3309, - 0x330A, - 0x330B, - 0x330C, - 0x330D, - 0x330E, - 0x330F, - 0x3310, - 0x3311, - 0x3312, - 0x3313, - 0x3314, - 0x3315, - 0x3316, - 0x3317, - 0x3318, - 0x3319, - 0x331A, - 0x331B, - 0x331C, - 0x331D, - 0x331E, - 0x331F, - 0x3320, - 0x3321, - 0x3322, - 0x3323, - 0x3324, - 0x3325, - 0x3326, - 0x3327, - 0x3328, - 0x3329, - 0x332A, - 0x332B, - 0x332C, - 0x332D, - 0x332E, - 0x332F, - 0x3330, - 0x3331, - 0x3332, - 0x3333, - 0x3334, - 0x3335, - 0x3336, - 0x3337, - 0x3338, - 0x3339, - 0x333A, - 0x333B, - 0x333C, - 0x333D, - 0x333E, - 0x333F, - 0x3340, - 0x3341, - 0x3342, - 0x3343, - 0x3344, - 0x3345, - 0x3346, - 0x3347, - 0x3348, - 0x3349, - 0x334A, - 0x334B, - 0x334C, - 0x334D, - 0x334E, - 0x334F, - 0x3350, - 0x3351, - 0x3352, - 0x3353, - 0x3354, - 0x3355, - 0x3356, - 0x3357, - 0x3358, - 0x3359, - 0x335A, - 0x335B, - 0x335C, - 0x335D, - 0x335E, - 0x335F, - 0x3360, - 0x3361, - 0x3362, - 0x3363, - 0x3364, - 0x3365, - 0x3366, - 0x3367, - 0x3368, - 0x3369, - 0x336A, - 0x336B, - 0x336C, - 0x336D, - 0x336E, - 0x336F, - 0x3370, - 0x3371, - 0x3372, - 0x3373, - 0x3374, - 0x3375, - 0x3376, - 0x3377, - 0x3378, - 0x3379, - 0x337A, - 0x337B, - 0x337C, - 0x337D, - 0x337E, - 0x337F, - 0x3380, - 0x3381, - 0x3382, - 0x3383, - 0x3384, - 0x3385, - 0x3386, - 0x3387, - 0x3388, - 0x3389, - 0x338A, - 0x338B, - 0x338C, - 0x338D, - 0x338E, - 0x338F, - 0x3390, - 0x3391, - 0x3392, - 0x3393, - 0x3394, - 0x3395, - 0x3396, - 0x3397, - 0x3398, - 0x3399, - 0x339A, - 0x339B, - 0x339C, - 0x339D, - 0x339E, - 0x339F, - 0x33A0, - 0x33A1, - 0x33A2, - 0x33A3, - 0x33A4, - 0x33A5, - 0x33A6, - 0x33A7, - 0x33A8, - 0x33A9, - 0x33AA, - 0x33AB, - 0x33AC, - 0x33AD, - 0x33AE, - 0x33AF, - 0x33B0, - 0x33B1, - 0x33B2, - 0x33B3, - 0x33B4, - 0x33B5, - 0x33B6, - 0x33B7, - 0x33B8, - 0x33B9, - 0x33BA, - 0x33BB, - 0x33BC, - 0x33BD, - 0x33BE, - 0x33BF, - 0x33C0, - 0x33C1, - 0x33C2, - 0x33C3, - 0x33C4, - 0x33C5, - 0x33C6, - 0x33C7, - 0x33C8, - 0x33C9, - 0x33CA, - 0x33CB, - 0x33CC, - 0x33CD, - 0x33CE, - 0x33CF, - 0x33D0, - 0x33D1, - 0x33D2, - 0x33D3, - 0x33D4, - 0x33D5, - 0x33D6, - 0x33D7, - 0x33D8, - 0x33D9, - 0x33DA, - 0x33DB, - 0x33DC, - 0x33DD, - 0x33DE, - 0x33DF, - 0x33E0, - 0x33E1, - 0x33E2, - 0x33E3, - 0x33E4, - 0x33E5, - 0x33E6, - 0x33E7, - 0x33E8, - 0x33E9, - 0x33EA, - 0x33EB, - 0x33EC, - 0x33ED, - 0x33EE, - 0x33EF, - 0x33F0, - 0x33F1, - 0x33F2, - 0x33F3, - 0x33F4, - 0x33F5, - 0x33F6, - 0x33F7, - 0x33F8, - 0x33F9, - 0x33FA, - 0x33FB, - 0x33FC, - 0x33FD, - 0x33FE, - 0x33FF, - 0x3400, - 0xA48D, - 0xA490, - 0xA4C7, - 0xA4D0, - 0xA62C, - 0xA640, - 0xA641, - 0xA642, - 0xA643, - 0xA644, - 0xA645, - 0xA646, - 0xA647, - 0xA648, - 0xA649, - 0xA64A, - 0xA64B, - 0xA64C, - 0xA64D, - 0xA64E, - 0xA64F, - 0xA650, - 0xA651, - 0xA652, - 0xA653, - 0xA654, - 0xA655, - 0xA656, - 0xA657, - 0xA658, - 0xA659, - 0xA65A, - 0xA65B, - 0xA65C, - 0xA65D, - 0xA65E, - 0xA65F, - 0xA660, - 0xA661, - 0xA662, - 0xA663, - 0xA664, - 0xA665, - 0xA666, - 0xA667, - 0xA668, - 0xA669, - 0xA66A, - 0xA66B, - 0xA66C, - 0xA66D, - 0xA680, - 0xA681, - 0xA682, - 0xA683, - 0xA684, - 0xA685, - 0xA686, - 0xA687, - 0xA688, - 0xA689, - 0xA68A, - 0xA68B, - 0xA68C, - 0xA68D, - 0xA68E, - 0xA68F, - 0xA690, - 0xA691, - 0xA692, - 0xA693, - 0xA694, - 0xA695, - 0xA696, - 0xA697, - 0xA698, - 0xA699, - 0xA69A, - 0xA69B, - 0xA69C, - 0xA69D, - 0xA69E, - 0xA6F8, - 0xA700, - 0xA722, - 0xA723, - 0xA724, - 0xA725, - 0xA726, - 0xA727, - 0xA728, - 0xA729, - 0xA72A, - 0xA72B, - 0xA72C, - 0xA72D, - 0xA72E, - 0xA72F, - 0xA732, - 0xA733, - 0xA734, - 0xA735, - 0xA736, - 0xA737, - 0xA738, - 0xA739, - 0xA73A, - 0xA73B, - 0xA73C, - 0xA73D, - 0xA73E, - 0xA73F, - 0xA740, - 0xA741, - 0xA742, - 0xA743, - 0xA744, - 0xA745, - 0xA746, - 0xA747, - 0xA748, - 0xA749, - 0xA74A, - 0xA74B, - 0xA74C, - 0xA74D, - 0xA74E, - 0xA74F, - 0xA750, - 0xA751, - 0xA752, - 0xA753, - 0xA754, - 0xA755, - 0xA756, - 0xA757, - 0xA758, - 0xA759, - 0xA75A, - 0xA75B, - 0xA75C, - 0xA75D, - 0xA75E, - 0xA75F, - 0xA760, - 0xA761, - 0xA762, - 0xA763, - 0xA764, - 0xA765, - 0xA766, - 0xA767, - 0xA768, - 0xA769, - 0xA76A, - 0xA76B, - 0xA76C, - 0xA76D, - 0xA76E, - 0xA76F, - 0xA770, - 0xA771, - 0xA779, - 0xA77A, - 0xA77B, - 0xA77C, - 0xA77D, - 0xA77E, - 0xA77F, - 0xA780, - 0xA781, - 0xA782, - 0xA783, - 0xA784, - 0xA785, - 0xA786, - 0xA787, - 0xA78B, - 0xA78C, - 0xA78D, - 0xA78E, - 0xA790, - 0xA791, - 0xA792, - 0xA793, - 0xA796, - 0xA797, - 0xA798, - 0xA799, - 0xA79A, - 0xA79B, - 0xA79C, - 0xA79D, - 0xA79E, - 0xA79F, - 0xA7A0, - 0xA7A1, - 0xA7A2, - 0xA7A3, - 0xA7A4, - 0xA7A5, - 0xA7A6, - 0xA7A7, - 0xA7A8, - 0xA7A9, - 0xA7AA, - 0xA7AB, - 0xA7AC, - 0xA7AD, - 0xA7AE, - 0xA7AF, - 0xA7B0, - 0xA7B1, - 0xA7B2, - 0xA7B3, - 0xA7B4, - 0xA7B5, - 0xA7B6, - 0xA7B7, - 0xA7B8, - 0xA7B9, - 0xA7BA, - 0xA7BB, - 0xA7BC, - 0xA7BD, - 0xA7BE, - 0xA7BF, - 0xA7C0, - 0xA7C1, - 0xA7C2, - 0xA7C3, - 0xA7C4, - 0xA7C5, - 0xA7C6, - 0xA7C7, - 0xA7C8, - 0xA7C9, - 0xA7CA, - 0xA7CB, - 0xA7CC, - 0xA7CD, - 0xA7CE, - 0xA7CF, - 0xA7D0, - 0xA7D1, - 0xA7D2, - 0xA7D3, - 0xA7D4, - 0xA7D5, - 0xA7D6, - 0xA7D7, - 0xA7D8, - 0xA7D9, - 0xA7DA, - 0xA7DB, - 0xA7DC, - 0xA7DD, - 0xA7F1, - 0xA7F2, - 0xA7F3, - 0xA7F4, - 0xA7F5, - 0xA7F6, - 0xA7F8, - 0xA7F9, - 0xA7FA, - 0xA82D, - 0xA830, - 0xA83A, - 0xA840, - 0xA878, - 0xA880, - 0xA8C6, - 0xA8CE, - 0xA8DA, - 0xA8E0, - 0xA954, - 0xA95F, - 0xA97D, - 0xA980, - 0xA9CE, - 0xA9CF, - 0xA9DA, - 0xA9DE, - 0xA9FF, - 0xAA00, - 0xAA37, - 0xAA40, - 0xAA4E, - 0xAA50, - 0xAA5A, - 0xAA5C, - 0xAAC3, - 0xAADB, - 0xAAF7, - 0xAB01, - 0xAB07, - 0xAB09, - 0xAB0F, - 0xAB11, - 0xAB17, - 0xAB20, - 0xAB27, - 0xAB28, - 0xAB2F, - 0xAB30, - 0xAB5C, - 0xAB5D, - 0xAB5E, - 0xAB5F, - 0xAB60, - 0xAB69, - 0xAB6A, - 0xAB6C, - 0xAB70, - 0xAB71, - 0xAB72, - 0xAB73, - 0xAB74, - 0xAB75, - 0xAB76, - 0xAB77, - 0xAB78, - 0xAB79, - 0xAB7A, - 0xAB7B, - 0xAB7C, - 0xAB7D, - 0xAB7E, - 0xAB7F, - 0xAB80, - 0xAB81, - 0xAB82, - 0xAB83, - 0xAB84, - 0xAB85, - 0xAB86, - 0xAB87, - 0xAB88, - 0xAB89, - 0xAB8A, - 0xAB8B, - 0xAB8C, - 0xAB8D, - 0xAB8E, - 0xAB8F, - 0xAB90, - 0xAB91, - 0xAB92, - 0xAB93, - 0xAB94, - 0xAB95, - 0xAB96, - 0xAB97, - 0xAB98, - 0xAB99, - 0xAB9A, - 0xAB9B, - 0xAB9C, - 0xAB9D, - 0xAB9E, - 0xAB9F, - 0xABA0, - 0xABA1, - 0xABA2, - 0xABA3, - 0xABA4, - 0xABA5, - 0xABA6, - 0xABA7, - 0xABA8, - 0xABA9, - 0xABAA, - 0xABAB, - 0xABAC, - 0xABAD, - 0xABAE, - 0xABAF, - 0xABB0, - 0xABB1, - 0xABB2, - 0xABB3, - 0xABB4, - 0xABB5, - 0xABB6, - 0xABB7, - 0xABB8, - 0xABB9, - 0xABBA, - 0xABBB, - 0xABBC, - 0xABBD, - 0xABBE, - 0xABBF, - 0xABC0, - 0xABEE, - 0xABF0, - 0xABFA, - 0xAC00, - 0xD7A4, - 0xD7B0, - 0xD7C7, - 0xD7CB, - 0xD7FC, - 0xF900, - 0xF901, - 0xF902, - 0xF903, - 0xF904, - 0xF905, - 0xF906, - 0xF907, - 0xF909, - 0xF90A, - 0xF90B, - 0xF90C, - 0xF90D, - 0xF90E, - 0xF90F, - 0xF910, - 0xF911, - 0xF912, - 0xF913, - 0xF914, - 0xF915, - 0xF916, - 0xF917, - 0xF918, - 0xF919, - 0xF91A, - 0xF91B, - 0xF91C, - 0xF91D, - 0xF91E, - 0xF91F, - 0xF920, - 0xF921, - 0xF922, - 0xF923, - 0xF924, - 0xF925, - 0xF926, - 0xF927, - 0xF928, - 0xF929, - 0xF92A, - 0xF92B, - 0xF92C, - 0xF92D, - 0xF92E, - 0xF92F, - 0xF930, - 0xF931, - 0xF932, - 0xF933, - 0xF934, - 0xF935, - 0xF936, - 0xF937, - 0xF938, - 0xF939, - 0xF93A, - 0xF93B, - 0xF93C, - 0xF93D, - 0xF93E, - 0xF93F, - 0xF940, - 0xF941, - 0xF942, - 0xF943, - 0xF944, - 0xF945, - 0xF946, - 0xF947, - 0xF948, - 0xF949, - 0xF94A, - 0xF94B, - 0xF94C, - 0xF94D, - 0xF94E, - 0xF94F, - 0xF950, - 0xF951, - 0xF952, - 0xF953, - 0xF954, - 0xF955, - 0xF956, - 0xF957, - 0xF958, - 0xF959, - 0xF95A, - 0xF95B, - 0xF95C, - 0xF95D, - 0xF95E, - 0xF95F, - 0xF960, - 0xF961, - 0xF962, - 0xF963, - 0xF964, - 0xF965, - 0xF966, - 0xF967, - 0xF968, - 0xF969, - 0xF96A, - 0xF96B, - 0xF96C, - 0xF96D, - 0xF96E, - 0xF96F, - 0xF970, - 0xF971, - 0xF972, - 0xF973, - 0xF974, - 0xF975, - 0xF976, - 0xF977, - 0xF978, - 0xF979, - 0xF97A, - 0xF97B, - 0xF97C, - 0xF97D, - 0xF97E, - 0xF97F, - 0xF980, - 0xF981, - 0xF982, - 0xF983, - 0xF984, - 0xF985, - 0xF986, - 0xF987, - 0xF988, - 0xF989, - 0xF98A, - 0xF98B, - 0xF98C, - 0xF98D, - 0xF98E, - 0xF98F, - 0xF990, - 0xF991, - 0xF992, - 0xF993, - 0xF994, - 0xF995, - 0xF996, - 0xF997, - 0xF998, - 0xF999, - 0xF99A, - 0xF99B, - 0xF99C, - 0xF99D, - 0xF99E, - 0xF99F, - 0xF9A0, - 0xF9A1, - 0xF9A2, - 0xF9A3, - 0xF9A4, - 0xF9A5, - 0xF9A6, - 0xF9A7, - 0xF9A8, - 0xF9A9, - 0xF9AA, - 0xF9AB, - 0xF9AC, - 0xF9AD, - 0xF9AE, - 0xF9AF, - 0xF9B0, - 0xF9B1, - 0xF9B2, - 0xF9B3, - 0xF9B4, - 0xF9B5, - 0xF9B6, - 0xF9B7, - 0xF9B8, - 0xF9B9, - 0xF9BA, - 0xF9BB, - 0xF9BC, - 0xF9BD, - 0xF9BE, - 0xF9BF, - 0xF9C0, - 0xF9C1, - 0xF9C2, - 0xF9C3, - 0xF9C4, - 0xF9C5, - 0xF9C6, - 0xF9C7, - 0xF9C8, - 0xF9C9, - 0xF9CA, - 0xF9CB, - 0xF9CC, - 0xF9CD, - 0xF9CE, - 0xF9CF, - 0xF9D0, - 0xF9D1, - 0xF9D2, - 0xF9D3, - 0xF9D4, - 0xF9D5, - 0xF9D6, - 0xF9D7, - 0xF9D8, - 0xF9D9, - 0xF9DA, - 0xF9DB, - 0xF9DC, - 0xF9DD, - 0xF9DE, - 0xF9DF, - 0xF9E0, - 0xF9E1, - 0xF9E2, - 0xF9E3, - 0xF9E4, - 0xF9E5, - 0xF9E6, - 0xF9E7, - 0xF9E8, - 0xF9E9, - 0xF9EA, - 0xF9EB, - 0xF9EC, - 0xF9ED, - 0xF9EE, - 0xF9EF, - 0xF9F0, - 0xF9F1, - 0xF9F2, - 0xF9F3, - 0xF9F4, - 0xF9F5, - 0xF9F6, - 0xF9F7, - 0xF9F8, - 0xF9F9, - 0xF9FA, - 0xF9FB, - 0xF9FC, - 0xF9FD, - 0xF9FE, - 0xF9FF, - 0xFA00, - 0xFA01, - 0xFA02, - 0xFA03, - 0xFA04, - 0xFA05, - 0xFA06, - 0xFA07, - 0xFA08, - 0xFA09, - 0xFA0A, - 0xFA0B, - 0xFA0C, - 0xFA0D, - 0xFA0E, - 0xFA10, - 0xFA11, - 0xFA12, - 0xFA13, - 0xFA15, - 0xFA16, - 0xFA17, - 0xFA18, - 0xFA19, - 0xFA1A, - 0xFA1B, - 0xFA1C, - 0xFA1D, - 0xFA1E, - 0xFA1F, - 0xFA20, - 0xFA21, - 0xFA22, - 0xFA23, - 0xFA25, - 0xFA26, - 0xFA27, - 0xFA2A, - 0xFA2B, - 0xFA2C, - 0xFA2D, - 0xFA2E, - 0xFA2F, - 0xFA30, - 0xFA31, - 0xFA32, - 0xFA33, - 0xFA34, - 0xFA35, - 0xFA36, - 0xFA37, - 0xFA38, - 0xFA39, - 0xFA3A, - 0xFA3B, - 0xFA3C, - 0xFA3D, - 0xFA3E, - 0xFA3F, - 0xFA40, - 0xFA41, - 0xFA42, - 0xFA43, - 0xFA44, - 0xFA45, - 0xFA46, - 0xFA47, - 0xFA48, - 0xFA49, - 0xFA4A, - 0xFA4B, - 0xFA4C, - 0xFA4D, - 0xFA4E, - 0xFA4F, - 0xFA50, - 0xFA51, - 0xFA52, - 0xFA53, - 0xFA54, - 0xFA55, - 0xFA56, - 0xFA57, - 0xFA58, - 0xFA59, - 0xFA5A, - 0xFA5B, - 0xFA5C, - 0xFA5D, - 0xFA5F, - 0xFA60, - 0xFA61, - 0xFA62, - 0xFA63, - 0xFA64, - 0xFA65, - 0xFA66, - 0xFA67, - 0xFA68, - 0xFA69, - 0xFA6A, - 0xFA6B, - 0xFA6C, - 0xFA6D, - 0xFA6E, - 0xFA70, - 0xFA71, - 0xFA72, - 0xFA73, - 0xFA74, - 0xFA75, - 0xFA76, - 0xFA77, - 0xFA78, - 0xFA79, - 0xFA7A, - 0xFA7B, - 0xFA7C, - 0xFA7D, - 0xFA7E, - 0xFA7F, - 0xFA80, - 0xFA81, - 0xFA82, - 0xFA83, - 0xFA84, - 0xFA85, - 0xFA86, - 0xFA87, - 0xFA88, - 0xFA89, - 0xFA8A, - 0xFA8B, - 0xFA8C, - 0xFA8D, - 0xFA8E, - 0xFA8F, - 0xFA90, - 0xFA91, - 0xFA92, - 0xFA93, - 0xFA94, - 0xFA95, - 0xFA96, - 0xFA97, - 0xFA98, - 0xFA99, - 0xFA9A, - 0xFA9B, - 0xFA9C, - 0xFA9D, - 0xFA9E, - 0xFA9F, - 0xFAA0, - 0xFAA1, - 0xFAA2, - 0xFAA3, - 0xFAA4, - 0xFAA5, - 0xFAA6, - 0xFAA7, - 0xFAA8, - 0xFAA9, - 0xFAAA, - 0xFAAB, - 0xFAAC, - 0xFAAD, - 0xFAAE, - 0xFAAF, - 0xFAB0, - 0xFAB1, - 0xFAB2, - 0xFAB3, - 0xFAB4, - 0xFAB5, - 0xFAB6, - 0xFAB7, - 0xFAB8, - 0xFAB9, - 0xFABA, - 0xFABB, - 0xFABC, - 0xFABD, - 0xFABE, - 0xFABF, - 0xFAC0, - 0xFAC1, - 0xFAC2, - 0xFAC3, - 0xFAC4, - 0xFAC5, - 0xFAC6, - 0xFAC7, - 0xFAC8, - 0xFAC9, - 0xFACA, - 0xFACB, - 0xFACC, - 0xFACD, - 0xFACE, - 0xFACF, - 0xFAD0, - 0xFAD1, - 0xFAD2, - 0xFAD3, - 0xFAD4, - 0xFAD5, - 0xFAD6, - 0xFAD7, - 0xFAD8, - 0xFAD9, - 0xFADA, - 0xFB00, - 0xFB01, - 0xFB02, - 0xFB03, - 0xFB04, - 0xFB05, - 0xFB07, - 0xFB13, - 0xFB14, - 0xFB15, - 0xFB16, - 0xFB17, - 0xFB18, - 0xFB1D, - 0xFB1E, - 0xFB1F, - 0xFB20, - 0xFB21, - 0xFB22, - 0xFB23, - 0xFB24, - 0xFB25, - 0xFB26, - 0xFB27, - 0xFB28, - 0xFB29, - 0xFB2A, - 0xFB2B, - 0xFB2C, - 0xFB2D, - 0xFB2E, - 0xFB2F, - 0xFB30, - 0xFB31, - 0xFB32, - 0xFB33, - 0xFB34, - 0xFB35, - 0xFB36, - 0xFB37, - 0xFB38, - 0xFB39, - 0xFB3A, - 0xFB3B, - 0xFB3C, - 0xFB3D, - 0xFB3E, - 0xFB3F, - 0xFB40, - 0xFB41, - 0xFB42, - 0xFB43, - 0xFB44, - 0xFB45, - 0xFB46, - 0xFB47, - 0xFB48, - 0xFB49, - 0xFB4A, - 0xFB4B, - 0xFB4C, - 0xFB4D, - 0xFB4E, - 0xFB4F, - 0xFB50, - 0xFB52, - 0xFB56, - 0xFB5A, - 0xFB5E, - 0xFB62, - 0xFB66, - 0xFB6A, - 0xFB6E, - 0xFB72, - 0xFB76, - 0xFB7A, - 0xFB7E, - 0xFB82, - 0xFB84, - 0xFB86, - 0xFB88, - 0xFB8A, - 0xFB8C, - 0xFB8E, - 0xFB92, - 0xFB96, - 0xFB9A, - 0xFB9E, - 0xFBA0, - 0xFBA4, - 0xFBA6, - 0xFBAA, - 0xFBAE, - 0xFBB0, - 0xFBB2, - 0xFBD3, - 0xFBD7, - 0xFBD9, - 0xFBDB, - 0xFBDD, - 0xFBDE, - 0xFBE0, - 0xFBE2, - 0xFBE4, - 0xFBE8, - 0xFBEA, - 0xFBEC, - 0xFBEE, - 0xFBF0, - 0xFBF2, - 0xFBF4, - 0xFBF6, - 0xFBF9, - 0xFBFC, - 0xFC00, - 0xFC01, - 0xFC02, - 0xFC03, - 0xFC04, - 0xFC05, - 0xFC06, - 0xFC07, - 0xFC08, - 0xFC09, - 0xFC0A, - 0xFC0B, - 0xFC0C, - 0xFC0D, - 0xFC0E, - 0xFC0F, - 0xFC10, - 0xFC11, - 0xFC12, - 0xFC13, - 0xFC14, - 0xFC15, - 0xFC16, - 0xFC17, - 0xFC18, - 0xFC19, - 0xFC1A, - 0xFC1B, - 0xFC1C, - 0xFC1D, - 0xFC1E, - 0xFC1F, - 0xFC20, - 0xFC21, - 0xFC22, - 0xFC23, - 0xFC24, - 0xFC25, - 0xFC26, - 0xFC27, - 0xFC28, - 0xFC29, - 0xFC2A, - 0xFC2B, - 0xFC2C, - 0xFC2D, - 0xFC2E, - 0xFC2F, - 0xFC30, - 0xFC31, - 0xFC32, - 0xFC33, - 0xFC34, - 0xFC35, - 0xFC36, - 0xFC37, - 0xFC38, - 0xFC39, - 0xFC3A, - 0xFC3B, - 0xFC3C, - 0xFC3D, - 0xFC3E, - 0xFC3F, - 0xFC40, - 0xFC41, - 0xFC42, - 0xFC43, - 0xFC44, - 0xFC45, - 0xFC46, - 0xFC47, - 0xFC48, - 0xFC49, - 0xFC4A, - 0xFC4B, - 0xFC4C, - 0xFC4D, - 0xFC4E, - 0xFC4F, - 0xFC50, - 0xFC51, - 0xFC52, - 0xFC53, - 0xFC54, - 0xFC55, - 0xFC56, - 0xFC57, - 0xFC58, - 0xFC59, - 0xFC5A, - 0xFC5B, - 0xFC5C, - 0xFC5D, - 0xFC5E, - 0xFC5F, - 0xFC60, - 0xFC61, - 0xFC62, - 0xFC63, - 0xFC64, - 0xFC65, - 0xFC66, - 0xFC67, - 0xFC68, - 0xFC69, - 0xFC6A, - 0xFC6B, - 0xFC6C, - 0xFC6D, - 0xFC6E, - 0xFC6F, - 0xFC70, - 0xFC71, - 0xFC72, - 0xFC73, - 0xFC74, - 0xFC75, - 0xFC76, - 0xFC77, - 0xFC78, - 0xFC79, - 0xFC7A, - 0xFC7B, - 0xFC7C, - 0xFC7D, - 0xFC7E, - 0xFC7F, - 0xFC80, - 0xFC81, - 0xFC82, - 0xFC83, - 0xFC84, - 0xFC85, - 0xFC86, - 0xFC87, - 0xFC88, - 0xFC89, - 0xFC8A, - 0xFC8B, - 0xFC8C, - 0xFC8D, - 0xFC8E, - 0xFC8F, - 0xFC90, - 0xFC91, - 0xFC92, - 0xFC93, - 0xFC94, - 0xFC95, - 0xFC96, - 0xFC97, - 0xFC98, - 0xFC99, - 0xFC9A, - 0xFC9B, - 0xFC9C, - 0xFC9D, - 0xFC9E, - 0xFC9F, - 0xFCA0, - 0xFCA1, - 0xFCA2, - 0xFCA3, - 0xFCA4, - 0xFCA5, - 0xFCA6, - 0xFCA7, - 0xFCA8, - 0xFCA9, - 0xFCAA, - 0xFCAB, - 0xFCAC, - 0xFCAD, - 0xFCAE, - 0xFCAF, - 0xFCB0, - 0xFCB1, - 0xFCB2, - 0xFCB3, - 0xFCB4, - 0xFCB5, - 0xFCB6, - 0xFCB7, - 0xFCB8, - 0xFCB9, - 0xFCBA, - 0xFCBB, - 0xFCBC, - 0xFCBD, - 0xFCBE, - 0xFCBF, - 0xFCC0, - 0xFCC1, - 0xFCC2, - 0xFCC3, - 0xFCC4, - 0xFCC5, - 0xFCC6, - 0xFCC7, - 0xFCC8, - 0xFCC9, - 0xFCCA, - 0xFCCB, - 0xFCCC, - 0xFCCD, - 0xFCCE, - 0xFCCF, - 0xFCD0, - 0xFCD1, - 0xFCD2, - 0xFCD3, - 0xFCD4, - 0xFCD5, - 0xFCD6, - 0xFCD7, - 0xFCD8, - 0xFCD9, - 0xFCDA, - 0xFCDB, - 0xFCDC, - 0xFCDD, - 0xFCDE, - 0xFCDF, - 0xFCE0, - 0xFCE1, - 0xFCE2, - 0xFCE3, - 0xFCE4, - 0xFCE5, - 0xFCE6, - 0xFCE7, - 0xFCE8, - 0xFCE9, - 0xFCEA, - 0xFCEB, - 0xFCEC, - 0xFCED, - 0xFCEE, - 0xFCEF, - 0xFCF0, - 0xFCF1, - 0xFCF2, - 0xFCF3, - 0xFCF4, - 0xFCF5, - 0xFCF6, - 0xFCF7, - 0xFCF8, - 0xFCF9, - 0xFCFA, - 0xFCFB, - 0xFCFC, - 0xFCFD, - 0xFCFE, - 0xFCFF, - 0xFD00, - 0xFD01, - 0xFD02, - 0xFD03, - 0xFD04, - 0xFD05, - 0xFD06, - 0xFD07, - 0xFD08, - 0xFD09, - 0xFD0A, - 0xFD0B, - 0xFD0C, - 0xFD0D, - 0xFD0E, - 0xFD0F, - 0xFD10, - 0xFD11, - 0xFD12, - 0xFD13, - 0xFD14, - 0xFD15, - 0xFD16, - 0xFD17, - 0xFD18, - 0xFD19, - 0xFD1A, - 0xFD1B, - 0xFD1C, - 0xFD1D, - 0xFD1E, - 0xFD1F, - 0xFD20, - 0xFD21, - 0xFD22, - 0xFD23, - 0xFD24, - 0xFD25, - 0xFD26, - 0xFD27, - 0xFD28, - 0xFD29, - 0xFD2A, - 0xFD2B, - 0xFD2C, - 0xFD2D, - 0xFD2E, - 0xFD2F, - 0xFD30, - 0xFD31, - 0xFD32, - 0xFD33, - 0xFD34, - 0xFD35, - 0xFD36, - 0xFD37, - 0xFD38, - 0xFD39, - 0xFD3A, - 0xFD3B, - 0xFD3C, - 0xFD3E, - 0xFD50, - 0xFD51, - 0xFD53, - 0xFD54, - 0xFD55, - 0xFD56, - 0xFD57, - 0xFD58, - 0xFD5A, - 0xFD5B, - 0xFD5C, - 0xFD5D, - 0xFD5E, - 0xFD5F, - 0xFD61, - 0xFD62, - 0xFD64, - 0xFD66, - 0xFD67, - 0xFD69, - 0xFD6A, - 0xFD6C, - 0xFD6E, - 0xFD6F, - 0xFD71, - 0xFD73, - 0xFD74, - 0xFD75, - 0xFD76, - 0xFD78, - 0xFD79, - 0xFD7A, - 0xFD7B, - 0xFD7C, - 0xFD7E, - 0xFD7F, - 0xFD80, - 0xFD81, - 0xFD82, - 0xFD83, - 0xFD85, - 0xFD87, - 0xFD89, - 0xFD8A, - 0xFD8B, - 0xFD8C, - 0xFD8D, - 0xFD8E, - 0xFD8F, - 0xFD90, - 0xFD92, - 0xFD93, - 0xFD94, - 0xFD95, - 0xFD96, - 0xFD97, - 0xFD99, - 0xFD9A, - 0xFD9B, - 0xFD9C, - 0xFD9E, - 0xFD9F, - 0xFDA0, - 0xFDA1, - 0xFDA2, - 0xFDA3, - 0xFDA4, - 0xFDA5, - 0xFDA6, - 0xFDA7, - 0xFDA8, - 0xFDA9, - 0xFDAA, - 0xFDAB, - 0xFDAC, - 0xFDAD, - 0xFDAE, - 0xFDAF, - 0xFDB0, - 0xFDB1, - 0xFDB2, - 0xFDB3, - 0xFDB4, - 0xFDB5, - 0xFDB6, - 0xFDB7, - 0xFDB8, - 0xFDB9, - 0xFDBA, - 0xFDBB, - 0xFDBC, - 0xFDBD, - 0xFDBE, - 0xFDBF, - 0xFDC0, - 0xFDC1, - 0xFDC2, - 0xFDC3, - 0xFDC4, - 0xFDC5, - 0xFDC6, - 0xFDC7, - 0xFDC8, - 0xFDD0, - 0xFDF0, - 0xFDF1, - 0xFDF2, - 0xFDF3, - 0xFDF4, - 0xFDF5, - 0xFDF6, - 0xFDF7, - 0xFDF8, - 0xFDF9, - 0xFDFA, - 0xFDFB, - 0xFDFC, - 0xFDFD, - 0xFE00, - 0xFE10, - 0xFE11, - 0xFE12, - 0xFE13, - 0xFE14, - 0xFE15, - 0xFE16, - 0xFE17, - 0xFE18, - 0xFE19, - 0xFE20, - 0xFE30, - 0xFE31, - 0xFE32, - 0xFE33, - 0xFE35, - 0xFE36, - 0xFE37, - 0xFE38, - 0xFE39, - 0xFE3A, - 0xFE3B, - 0xFE3C, - 0xFE3D, - 0xFE3E, - 0xFE3F, - 0xFE40, - 0xFE41, - 0xFE42, - 0xFE43, - 0xFE44, - 0xFE45, - 0xFE47, - 0xFE48, - 0xFE49, - 0xFE4D, - 0xFE50, - 0xFE51, - 0xFE52, - 0xFE54, - 0xFE55, - 0xFE56, - 0xFE57, - 0xFE58, - 0xFE59, - 0xFE5A, - 0xFE5B, - 0xFE5C, - 0xFE5D, - 0xFE5E, - 0xFE5F, - 0xFE60, - 0xFE61, - 0xFE62, - 0xFE63, - 0xFE64, - 0xFE65, - 0xFE66, - 0xFE67, - 0xFE68, - 0xFE69, - 0xFE6A, - 0xFE6B, - 0xFE6C, - 0xFE70, - 0xFE71, - 0xFE72, - 0xFE73, - 0xFE74, - 0xFE75, - 0xFE76, - 0xFE77, - 0xFE78, - 0xFE79, - 0xFE7A, - 0xFE7B, - 0xFE7C, - 0xFE7D, - 0xFE7E, - 0xFE7F, - 0xFE80, - 0xFE81, - 0xFE83, - 0xFE85, - 0xFE87, - 0xFE89, - 0xFE8D, - 0xFE8F, - 0xFE93, - 0xFE95, - 0xFE99, - 0xFE9D, - 0xFEA1, - 0xFEA5, - 0xFEA9, - 0xFEAB, - 0xFEAD, - 0xFEAF, - 0xFEB1, - 0xFEB5, - 0xFEB9, - 0xFEBD, - 0xFEC1, - 0xFEC5, - 0xFEC9, - 0xFECD, - 0xFED1, - 0xFED5, - 0xFED9, - 0xFEDD, - 0xFEE1, - 0xFEE5, - 0xFEE9, - 0xFEED, - 0xFEEF, - 0xFEF1, - 0xFEF5, - 0xFEF7, - 0xFEF9, - 0xFEFB, - 0xFEFD, - 0xFEFF, - 0xFF00, - 0xFF01, - 0xFF02, - 0xFF03, - 0xFF04, - 0xFF05, - 0xFF06, - 0xFF07, - 0xFF08, - 0xFF09, - 0xFF0A, - 0xFF0B, - 0xFF0C, - 0xFF0D, - 0xFF0E, - 0xFF0F, - 0xFF10, - 0xFF11, - 0xFF12, - 0xFF13, - 0xFF14, - 0xFF15, - 0xFF16, - 0xFF17, - 0xFF18, - 0xFF19, - 0xFF1A, - 0xFF1B, - 0xFF1C, - 0xFF1D, - 0xFF1E, - 0xFF1F, - 0xFF20, - 0xFF21, - 0xFF22, - 0xFF23, - 0xFF24, - 0xFF25, - 0xFF26, - 0xFF27, - 0xFF28, - 0xFF29, - 0xFF2A, - 0xFF2B, - 0xFF2C, - 0xFF2D, - 0xFF2E, - 0xFF2F, - 0xFF30, - 0xFF31, - 0xFF32, - 0xFF33, - 0xFF34, - 0xFF35, - 0xFF36, - 0xFF37, - 0xFF38, - 0xFF39, - 0xFF3A, - 0xFF3B, - 0xFF3C, - 0xFF3D, - 0xFF3E, - 0xFF3F, - 0xFF40, - 0xFF41, - 0xFF42, - 0xFF43, - 0xFF44, - 0xFF45, - 0xFF46, - 0xFF47, - 0xFF48, - 0xFF49, - 0xFF4A, - 0xFF4B, - 0xFF4C, - 0xFF4D, - 0xFF4E, - 0xFF4F, - 0xFF50, - 0xFF51, - 0xFF52, - 0xFF53, - 0xFF54, - 0xFF55, - 0xFF56, - 0xFF57, - 0xFF58, - 0xFF59, - 0xFF5A, - 0xFF5B, - 0xFF5C, - 0xFF5D, - 0xFF5E, - 0xFF5F, - 0xFF60, - 0xFF61, - 0xFF62, - 0xFF63, - 0xFF64, - 0xFF65, - 0xFF66, - 0xFF67, - 0xFF68, - 0xFF69, - 0xFF6A, - 0xFF6B, - 0xFF6C, - 0xFF6D, - 0xFF6E, - 0xFF6F, - 0xFF70, - 0xFF71, - 0xFF72, - 0xFF73, - 0xFF74, - 0xFF75, - 0xFF76, - 0xFF77, - 0xFF78, - 0xFF79, - 0xFF7A, - 0xFF7B, - 0xFF7C, - 0xFF7D, - 0xFF7E, - 0xFF7F, - 0xFF80, - 0xFF81, - 0xFF82, - 0xFF83, - 0xFF84, - 0xFF85, - 0xFF86, - 0xFF87, - 0xFF88, - 0xFF89, - 0xFF8A, - 0xFF8B, - 0xFF8C, - 0xFF8D, - 0xFF8E, - 0xFF8F, - 0xFF90, - 0xFF91, - 0xFF92, - 0xFF93, - 0xFF94, - 0xFF95, - 0xFF96, - 0xFF97, - 0xFF98, - 0xFF99, - 0xFF9A, - 0xFF9B, - 0xFF9C, - 0xFF9D, - 0xFF9E, - 0xFF9F, - 0xFFA0, - 0xFFA1, - 0xFFA2, - 0xFFA3, - 0xFFA4, - 0xFFA5, - 0xFFA6, - 0xFFA7, - 0xFFA8, - 0xFFA9, - 0xFFAA, - 0xFFAB, - 0xFFAC, - 0xFFAD, - 0xFFAE, - 0xFFAF, - 0xFFB0, - 0xFFB1, - 0xFFB2, - 0xFFB3, - 0xFFB4, - 0xFFB5, - 0xFFB6, - 0xFFB7, - 0xFFB8, - 0xFFB9, - 0xFFBA, - 0xFFBB, - 0xFFBC, - 0xFFBD, - 0xFFBE, - 0xFFBF, - 0xFFC2, - 0xFFC3, - 0xFFC4, - 0xFFC5, - 0xFFC6, - 0xFFC7, - 0xFFC8, - 0xFFCA, - 0xFFCB, - 0xFFCC, - 0xFFCD, - 0xFFCE, - 0xFFCF, - 0xFFD0, - 0xFFD2, - 0xFFD3, - 0xFFD4, - 0xFFD5, - 0xFFD6, - 0xFFD7, - 0xFFD8, - 0xFFDA, - 0xFFDB, - 0xFFDC, - 0xFFDD, - 0xFFE0, - 0xFFE1, - 0xFFE2, - 0xFFE3, - 0xFFE4, - 0xFFE5, - 0xFFE6, - 0xFFE7, - 0xFFE8, - 0xFFE9, - 0xFFEA, - 0xFFEB, - 0xFFEC, - 0xFFED, - 0xFFEE, - 0xFFEF, - 0x10000, - 0x1000C, - 0x1000D, - 0x10027, - 0x10028, - 0x1003B, - 0x1003C, - 0x1003E, - 0x1003F, - 0x1004E, - 0x10050, - 0x1005E, - 0x10080, - 0x100FB, - 0x10100, - 0x10103, - 0x10107, - 0x10134, - 0x10137, - 0x1018F, - 0x10190, - 0x1019D, - 0x101A0, - 0x101A1, - 0x101D0, - 0x101FE, - 0x10280, - 0x1029D, - 0x102A0, - 0x102D1, - 0x102E0, - 0x102FC, - 0x10300, - 0x10324, - 0x1032D, - 0x1034B, - 0x10350, - 0x1037B, - 0x10380, - 0x1039E, - 0x1039F, - 0x103C4, - 0x103C8, - 0x103D6, - 0x10400, - 0x10401, - 0x10402, - 0x10403, - 0x10404, - 0x10405, - 0x10406, - 0x10407, - 0x10408, - 0x10409, - 0x1040A, - 0x1040B, - 0x1040C, - 0x1040D, - 0x1040E, - 0x1040F, - 0x10410, - 0x10411, - 0x10412, - 0x10413, - 0x10414, - 0x10415, - 0x10416, - 0x10417, - 0x10418, - 0x10419, - 0x1041A, - 0x1041B, - 0x1041C, - 0x1041D, - 0x1041E, - 0x1041F, - 0x10420, - 0x10421, - 0x10422, - 0x10423, - 0x10424, - 0x10425, - 0x10426, - 0x10427, - 0x10428, - 0x1049E, - 0x104A0, - 0x104AA, - 0x104B0, - 0x104B1, - 0x104B2, - 0x104B3, - 0x104B4, - 0x104B5, - 0x104B6, - 0x104B7, - 0x104B8, - 0x104B9, - 0x104BA, - 0x104BB, - 0x104BC, - 0x104BD, - 0x104BE, - 0x104BF, - 0x104C0, - 0x104C1, - 0x104C2, - 0x104C3, - 0x104C4, - 0x104C5, - 0x104C6, - 0x104C7, - 0x104C8, - 0x104C9, - 0x104CA, - 0x104CB, - 0x104CC, - 0x104CD, - 0x104CE, - 0x104CF, - 0x104D0, - 0x104D1, - 0x104D2, - 0x104D3, - 0x104D4, - 0x104D8, - 0x104FC, - 0x10500, - 0x10528, - 0x10530, - 0x10564, - 0x1056F, - 0x10570, - 0x10571, - 0x10572, - 0x10573, - 0x10574, - 0x10575, - 0x10576, - 0x10577, - 0x10578, - 0x10579, - 0x1057A, - 0x1057B, - 0x1057C, - 0x1057D, - 0x1057E, - 0x1057F, - 0x10580, - 0x10581, - 0x10582, - 0x10583, - 0x10584, - 0x10585, - 0x10586, - 0x10587, - 0x10588, - 0x10589, - 0x1058A, - 0x1058B, - 0x1058C, - 0x1058D, - 0x1058E, - 0x1058F, - 0x10590, - 0x10591, - 0x10592, - 0x10593, - 0x10594, - 0x10595, - 0x10596, - 0x10597, - 0x105A2, - 0x105A3, - 0x105B2, - 0x105B3, - 0x105BA, - 0x105BB, - 0x105BD, - 0x105C0, - 0x105F4, - 0x10600, - 0x10737, - 0x10740, - 0x10756, - 0x10760, - 0x10768, - 0x10780, - 0x10781, - 0x10782, - 0x10783, - 0x10784, - 0x10785, - 0x10786, - 0x10787, - 0x10788, - 0x10789, - 0x1078A, - 0x1078B, - 0x1078C, - 0x1078D, - 0x1078E, - 0x1078F, - 0x10790, - 0x10791, - 0x10792, - 0x10793, - 0x10794, - 0x10795, - 0x10796, - 0x10797, - 0x10798, - 0x10799, - 0x1079A, - 0x1079B, - 0x1079C, - 0x1079D, - 0x1079E, - 0x1079F, - 0x107A0, - 0x107A1, - 0x107A2, - 0x107A3, - 0x107A4, - 0x107A5, - 0x107A6, - 0x107A7, - 0x107A8, - 0x107A9, - 0x107AA, - 0x107AB, - 0x107AC, - 0x107AD, - 0x107AE, - 0x107AF, - 0x107B0, - 0x107B1, - 0x107B2, - 0x107B3, - 0x107B4, - 0x107B5, - 0x107B6, - 0x107B7, - 0x107B8, - 0x107B9, - 0x107BA, - 0x107BB, - 0x10800, - 0x10806, - 0x10808, - 0x10809, - 0x1080A, - 0x10836, - 0x10837, - 0x10839, - 0x1083C, - 0x1083D, - 0x1083F, - 0x10856, - 0x10857, - 0x1089F, - 0x108A7, - 0x108B0, - 0x108E0, - 0x108F3, - 0x108F4, - 0x108F6, - 0x108FB, - 0x1091C, - 0x1091F, - 0x1093A, - 0x1093F, - 0x1095A, - 0x10980, - 0x109B8, - 0x109BC, - 0x109D0, - 0x109D2, - 0x10A04, - 0x10A05, - 0x10A07, - 0x10A0C, - 0x10A14, - 0x10A15, - 0x10A18, - 0x10A19, - 0x10A36, - 0x10A38, - 0x10A3B, - 0x10A3F, - 0x10A49, - 0x10A50, - 0x10A59, - 0x10A60, - 0x10AA0, - 0x10AC0, - 0x10AE7, - 0x10AEB, - 0x10AF7, - 0x10B00, - 0x10B36, - 0x10B39, - 0x10B56, - 0x10B58, - 0x10B73, - 0x10B78, - 0x10B92, - 0x10B99, - 0x10B9D, - 0x10BA9, - 0x10BB0, - 0x10C00, - 0x10C49, - 0x10C80, - 0x10C81, - 0x10C82, - 0x10C83, - 0x10C84, - 0x10C85, - 0x10C86, - 0x10C87, - 0x10C88, - 0x10C89, - 0x10C8A, - 0x10C8B, - 0x10C8C, - 0x10C8D, - 0x10C8E, - 0x10C8F, - 0x10C90, - 0x10C91, - 0x10C92, - 0x10C93, - 0x10C94, - 0x10C95, - 0x10C96, - 0x10C97, - 0x10C98, - 0x10C99, - 0x10C9A, - 0x10C9B, - 0x10C9C, - 0x10C9D, - 0x10C9E, - 0x10C9F, - 0x10CA0, - 0x10CA1, - 0x10CA2, - 0x10CA3, - 0x10CA4, - 0x10CA5, - 0x10CA6, - 0x10CA7, - 0x10CA8, - 0x10CA9, - 0x10CAA, - 0x10CAB, - 0x10CAC, - 0x10CAD, - 0x10CAE, - 0x10CAF, - 0x10CB0, - 0x10CB1, - 0x10CB2, - 0x10CB3, - 0x10CC0, - 0x10CF3, - 0x10CFA, - 0x10D28, - 0x10D30, - 0x10D3A, - 0x10D40, - 0x10D50, - 0x10D51, - 0x10D52, - 0x10D53, - 0x10D54, - 0x10D55, - 0x10D56, - 0x10D57, - 0x10D58, - 0x10D59, - 0x10D5A, - 0x10D5B, - 0x10D5C, - 0x10D5D, - 0x10D5E, - 0x10D5F, - 0x10D60, - 0x10D61, - 0x10D62, - 0x10D63, - 0x10D64, - 0x10D65, - 0x10D66, - 0x10D69, - 0x10D86, - 0x10D8E, - 0x10D90, - 0x10E60, - 0x10E7F, - 0x10E80, - 0x10EAA, - 0x10EAB, - 0x10EAE, - 0x10EB0, - 0x10EB2, - 0x10EC2, - 0x10EC8, - 0x10ED0, - 0x10ED9, - 0x10EFA, - 0x10F28, - 0x10F30, - 0x10F5A, - 0x10F70, - 0x10F8A, - 0x10FB0, - 0x10FCC, - 0x10FE0, - 0x10FF7, - 0x11000, - 0x1104E, - 0x11052, - 0x11076, - 0x1107F, - 0x110BD, - 0x110BE, - 0x110C3, - 0x110D0, - 0x110E9, - 0x110F0, - 0x110FA, - 0x11100, - 0x11135, - 0x11136, - 0x11148, - 0x11150, - 0x11177, - 0x11180, - 0x111E0, - 0x111E1, - 0x111F5, - 0x11200, - 0x11212, - 0x11213, - 0x11242, - 0x11280, - 0x11287, - 0x11288, - 0x11289, - 0x1128A, - 0x1128E, - 0x1128F, - 0x1129E, - 0x1129F, - 0x112AA, - 0x112B0, - 0x112EB, - 0x112F0, - 0x112FA, - 0x11300, - 0x11304, - 0x11305, - 0x1130D, - 0x1130F, - 0x11311, - 0x11313, - 0x11329, - 0x1132A, - 0x11331, - 0x11332, - 0x11334, - 0x11335, - 0x1133A, - 0x1133B, - 0x11345, - 0x11347, - 0x11349, - 0x1134B, - 0x1134E, - 0x11350, - 0x11351, - 0x11357, - 0x11358, - 0x1135D, - 0x11364, - 0x11366, - 0x1136D, - 0x11370, - 0x11375, - 0x11380, - 0x1138A, - 0x1138B, - 0x1138C, - 0x1138E, - 0x1138F, - 0x11390, - 0x113B6, - 0x113B7, - 0x113C1, - 0x113C2, - 0x113C3, - 0x113C5, - 0x113C6, - 0x113C7, - 0x113CB, - 0x113CC, - 0x113D6, - 0x113D7, - 0x113D9, - 0x113E1, - 0x113E3, - 0x11400, - 0x1145C, - 0x1145D, - 0x11462, - 0x11480, - 0x114C8, - 0x114D0, - 0x114DA, - 0x11580, - 0x115B6, - 0x115B8, - 0x115DE, - 0x11600, - 0x11645, - 0x11650, - 0x1165A, - 0x11660, - 0x1166D, - 0x11680, - 0x116BA, - 0x116C0, - 0x116CA, - 0x116D0, - 0x116E4, - 0x11700, - 0x1171B, - 0x1171D, - 0x1172C, - 0x11730, - 0x11747, - 0x11800, - 0x1183C, - 0x118A0, - 0x118A1, - 0x118A2, - 0x118A3, - 0x118A4, - 0x118A5, - 0x118A6, - 0x118A7, - 0x118A8, - 0x118A9, - 0x118AA, - 0x118AB, - 0x118AC, - 0x118AD, - 0x118AE, - 0x118AF, - 0x118B0, - 0x118B1, - 0x118B2, - 0x118B3, - 0x118B4, - 0x118B5, - 0x118B6, - 0x118B7, - 0x118B8, - 0x118B9, - 0x118BA, - 0x118BB, - 0x118BC, - 0x118BD, - 0x118BE, - 0x118BF, - 0x118C0, - 0x118F3, - 0x118FF, - 0x11907, - 0x11909, - 0x1190A, - 0x1190C, - 0x11914, - 0x11915, - 0x11917, - 0x11918, - 0x11936, - 0x11937, - 0x11939, - 0x1193B, - 0x11947, - 0x11950, - 0x1195A, - 0x119A0, - 0x119A8, - 0x119AA, - 0x119D8, - 0x119DA, - 0x119E5, - 0x11A00, - 0x11A48, - 0x11A50, - 0x11AA3, - 0x11AB0, - 0x11AF9, - 0x11B00, - 0x11B0A, - 0x11B60, - 0x11B68, - 0x11BC0, - 0x11BE2, - 0x11BF0, - 0x11BFA, - 0x11C00, - 0x11C09, - 0x11C0A, - 0x11C37, - 0x11C38, - 0x11C46, - 0x11C50, - 0x11C6D, - 0x11C70, - 0x11C90, - 0x11C92, - 0x11CA8, - 0x11CA9, - 0x11CB7, - 0x11D00, - 0x11D07, - 0x11D08, - 0x11D0A, - 0x11D0B, - 0x11D37, - 0x11D3A, - 0x11D3B, - 0x11D3C, - 0x11D3E, - 0x11D3F, - 0x11D48, - 0x11D50, - 0x11D5A, - 0x11D60, - 0x11D66, - 0x11D67, - 0x11D69, - 0x11D6A, - 0x11D8F, - 0x11D90, - 0x11D92, - 0x11D93, - 0x11D99, - 0x11DA0, - 0x11DAA, - 0x11DB0, - 0x11DDC, - 0x11DE0, - 0x11DEA, - 0x11EE0, - 0x11EF9, - 0x11F00, - 0x11F11, - 0x11F12, - 0x11F3B, - 0x11F3E, - 0x11F5B, - 0x11FB0, - 0x11FB1, - 0x11FC0, - 0x11FF2, - 0x11FFF, - 0x1239A, - 0x12400, - 0x1246F, - 0x12470, - 0x12475, - 0x12480, - 0x12544, - 0x12F90, - 0x12FF3, - 0x13000, - 0x13430, - 0x13440, - 0x13456, - 0x13460, - 0x143FB, - 0x14400, - 0x14647, - 0x16100, - 0x1613A, - 0x16800, - 0x16A39, - 0x16A40, - 0x16A5F, - 0x16A60, - 0x16A6A, - 0x16A6E, - 0x16ABF, - 0x16AC0, - 0x16ACA, - 0x16AD0, - 0x16AEE, - 0x16AF0, - 0x16AF6, - 0x16B00, - 0x16B46, - 0x16B50, - 0x16B5A, - 0x16B5B, - 0x16B62, - 0x16B63, - 0x16B78, - 0x16B7D, - 0x16B90, - 0x16D40, - 0x16D7A, - 0x16E40, - 0x16E41, - 0x16E42, - 0x16E43, - 0x16E44, - 0x16E45, - 0x16E46, - 0x16E47, - 0x16E48, - 0x16E49, - 0x16E4A, - 0x16E4B, - 0x16E4C, - 0x16E4D, - 0x16E4E, - 0x16E4F, - 0x16E50, - 0x16E51, - 0x16E52, - 0x16E53, - 0x16E54, - 0x16E55, - 0x16E56, - 0x16E57, - 0x16E58, - 0x16E59, - 0x16E5A, - 0x16E5B, - 0x16E5C, - 0x16E5D, - 0x16E5E, - 0x16E5F, - 0x16E60, - 0x16E9B, - 0x16EA0, - 0x16EA1, - 0x16EA2, - 0x16EA3, - 0x16EA4, - 0x16EA5, - 0x16EA6, - 0x16EA7, - 0x16EA8, - 0x16EA9, - 0x16EAA, - 0x16EAB, - 0x16EAC, - 0x16EAD, - 0x16EAE, - 0x16EAF, - 0x16EB0, - 0x16EB1, - 0x16EB2, - 0x16EB3, - 0x16EB4, - 0x16EB5, - 0x16EB6, - 0x16EB7, - 0x16EB8, - 0x16EB9, - 0x16EBB, - 0x16ED4, - 0x16F00, - 0x16F4B, - 0x16F4F, - 0x16F88, - 0x16F8F, - 0x16FA0, - 0x16FE0, - 0x16FE5, - 0x16FF0, - 0x16FF7, - 0x17000, - 0x18CD6, - 0x18CFF, - 0x18D1F, - 0x18D80, - 0x18DF3, - 0x1AFF0, - 0x1AFF4, - 0x1AFF5, - 0x1AFFC, - 0x1AFFD, - 0x1AFFF, - 0x1B000, - 0x1B123, - 0x1B132, - 0x1B133, - 0x1B150, - 0x1B153, - 0x1B155, - 0x1B156, - 0x1B164, - 0x1B168, - 0x1B170, - 0x1B2FC, - 0x1BC00, - 0x1BC6B, - 0x1BC70, - 0x1BC7D, - 0x1BC80, - 0x1BC89, - 0x1BC90, - 0x1BC9A, - 0x1BC9C, - 0x1BCA0, - 0x1BCA4, - 0x1CC00, - 0x1CCD6, - 0x1CCD7, - 0x1CCD8, - 0x1CCD9, - 0x1CCDA, - 0x1CCDB, - 0x1CCDC, - 0x1CCDD, - 0x1CCDE, - 0x1CCDF, - 0x1CCE0, - 0x1CCE1, - 0x1CCE2, - 0x1CCE3, - 0x1CCE4, - 0x1CCE5, - 0x1CCE6, - 0x1CCE7, - 0x1CCE8, - 0x1CCE9, - 0x1CCEA, - 0x1CCEB, - 0x1CCEC, - 0x1CCED, - 0x1CCEE, - 0x1CCEF, - 0x1CCF0, - 0x1CCF1, - 0x1CCF2, - 0x1CCF3, - 0x1CCF4, - 0x1CCF5, - 0x1CCF6, - 0x1CCF7, - 0x1CCF8, - 0x1CCF9, - 0x1CCFA, - 0x1CCFD, - 0x1CD00, - 0x1CEB4, - 0x1CEBA, - 0x1CED1, - 0x1CEE0, - 0x1CEF1, - 0x1CF00, - 0x1CF2E, - 0x1CF30, - 0x1CF47, - 0x1CF50, - 0x1CFC4, - 0x1D000, - 0x1D0F6, - 0x1D100, - 0x1D127, - 0x1D129, - 0x1D15E, - 0x1D15F, - 0x1D160, - 0x1D161, - 0x1D162, - 0x1D163, - 0x1D164, - 0x1D165, - 0x1D173, - 0x1D17B, - 0x1D1BB, - 0x1D1BC, - 0x1D1BD, - 0x1D1BE, - 0x1D1BF, - 0x1D1C0, - 0x1D1C1, - 0x1D1EB, - 0x1D200, - 0x1D246, - 0x1D2C0, - 0x1D2D4, - 0x1D2E0, - 0x1D2F4, - 0x1D300, - 0x1D357, - 0x1D360, - 0x1D379, - 0x1D400, - 0x1D401, - 0x1D402, - 0x1D403, - 0x1D404, - 0x1D405, - 0x1D406, - 0x1D407, - 0x1D408, - 0x1D409, - 0x1D40A, - 0x1D40B, - 0x1D40C, - 0x1D40D, - 0x1D40E, - 0x1D40F, - 0x1D410, - 0x1D411, - 0x1D412, - 0x1D413, - 0x1D414, - 0x1D415, - 0x1D416, - 0x1D417, - 0x1D418, - 0x1D419, - 0x1D41A, - 0x1D41B, - 0x1D41C, - 0x1D41D, - 0x1D41E, - 0x1D41F, - 0x1D420, - 0x1D421, - 0x1D422, - 0x1D423, - 0x1D424, - 0x1D425, - 0x1D426, - 0x1D427, - 0x1D428, - 0x1D429, - 0x1D42A, - 0x1D42B, - 0x1D42C, - 0x1D42D, - 0x1D42E, - 0x1D42F, - 0x1D430, - 0x1D431, - 0x1D432, - 0x1D433, - 0x1D434, - 0x1D435, - 0x1D436, - 0x1D437, - 0x1D438, - 0x1D439, - 0x1D43A, - 0x1D43B, - 0x1D43C, - 0x1D43D, - 0x1D43E, - 0x1D43F, - 0x1D440, - 0x1D441, - 0x1D442, - 0x1D443, - 0x1D444, - 0x1D445, - 0x1D446, - 0x1D447, - 0x1D448, - 0x1D449, - 0x1D44A, - 0x1D44B, - 0x1D44C, - 0x1D44D, - 0x1D44E, - 0x1D44F, - 0x1D450, - 0x1D451, - 0x1D452, - 0x1D453, - 0x1D454, - 0x1D455, - 0x1D456, - 0x1D457, - 0x1D458, - 0x1D459, - 0x1D45A, - 0x1D45B, - 0x1D45C, - 0x1D45D, - 0x1D45E, - 0x1D45F, - 0x1D460, - 0x1D461, - 0x1D462, - 0x1D463, - 0x1D464, - 0x1D465, - 0x1D466, - 0x1D467, - 0x1D468, - 0x1D469, - 0x1D46A, - 0x1D46B, - 0x1D46C, - 0x1D46D, - 0x1D46E, - 0x1D46F, - 0x1D470, - 0x1D471, - 0x1D472, - 0x1D473, - 0x1D474, - 0x1D475, - 0x1D476, - 0x1D477, - 0x1D478, - 0x1D479, - 0x1D47A, - 0x1D47B, - 0x1D47C, - 0x1D47D, - 0x1D47E, - 0x1D47F, - 0x1D480, - 0x1D481, - 0x1D482, - 0x1D483, - 0x1D484, - 0x1D485, - 0x1D486, - 0x1D487, - 0x1D488, - 0x1D489, - 0x1D48A, - 0x1D48B, - 0x1D48C, - 0x1D48D, - 0x1D48E, - 0x1D48F, - 0x1D490, - 0x1D491, - 0x1D492, - 0x1D493, - 0x1D494, - 0x1D495, - 0x1D496, - 0x1D497, - 0x1D498, - 0x1D499, - 0x1D49A, - 0x1D49B, - 0x1D49C, - 0x1D49D, - 0x1D49E, - 0x1D49F, - 0x1D4A0, - 0x1D4A2, - 0x1D4A3, - 0x1D4A5, - 0x1D4A6, - 0x1D4A7, - 0x1D4A9, - 0x1D4AA, - 0x1D4AB, - 0x1D4AC, - 0x1D4AD, - 0x1D4AE, - 0x1D4AF, - 0x1D4B0, - 0x1D4B1, - 0x1D4B2, - 0x1D4B3, - 0x1D4B4, - 0x1D4B5, - 0x1D4B6, - 0x1D4B7, - 0x1D4B8, - 0x1D4B9, - 0x1D4BA, - 0x1D4BB, - 0x1D4BC, - 0x1D4BD, - 0x1D4BE, - 0x1D4BF, - 0x1D4C0, - 0x1D4C1, - 0x1D4C2, - 0x1D4C3, - 0x1D4C4, - 0x1D4C5, - 0x1D4C6, - 0x1D4C7, - 0x1D4C8, - 0x1D4C9, - 0x1D4CA, - 0x1D4CB, - 0x1D4CC, - 0x1D4CD, - 0x1D4CE, - 0x1D4CF, - 0x1D4D0, - 0x1D4D1, - 0x1D4D2, - 0x1D4D3, - 0x1D4D4, - 0x1D4D5, - 0x1D4D6, - 0x1D4D7, - 0x1D4D8, - 0x1D4D9, - 0x1D4DA, - 0x1D4DB, - 0x1D4DC, - 0x1D4DD, - 0x1D4DE, - 0x1D4DF, - 0x1D4E0, - 0x1D4E1, - 0x1D4E2, - 0x1D4E3, - 0x1D4E4, - 0x1D4E5, - 0x1D4E6, - 0x1D4E7, - 0x1D4E8, - 0x1D4E9, - 0x1D4EA, - 0x1D4EB, - 0x1D4EC, - 0x1D4ED, - 0x1D4EE, - 0x1D4EF, - 0x1D4F0, - 0x1D4F1, - 0x1D4F2, - 0x1D4F3, - 0x1D4F4, - 0x1D4F5, - 0x1D4F6, - 0x1D4F7, - 0x1D4F8, - 0x1D4F9, - 0x1D4FA, - 0x1D4FB, - 0x1D4FC, - 0x1D4FD, - 0x1D4FE, - 0x1D4FF, - 0x1D500, - 0x1D501, - 0x1D502, - 0x1D503, - 0x1D504, - 0x1D505, - 0x1D506, - 0x1D507, - 0x1D508, - 0x1D509, - 0x1D50A, - 0x1D50B, - 0x1D50D, - 0x1D50E, - 0x1D50F, - 0x1D510, - 0x1D511, - 0x1D512, - 0x1D513, - 0x1D514, - 0x1D515, - 0x1D516, - 0x1D517, - 0x1D518, - 0x1D519, - 0x1D51A, - 0x1D51B, - 0x1D51C, - 0x1D51D, - 0x1D51E, - 0x1D51F, - 0x1D520, - 0x1D521, - 0x1D522, - 0x1D523, - 0x1D524, - 0x1D525, - 0x1D526, - 0x1D527, - 0x1D528, - 0x1D529, - 0x1D52A, - 0x1D52B, - 0x1D52C, - 0x1D52D, - 0x1D52E, - 0x1D52F, - 0x1D530, - 0x1D531, - 0x1D532, - 0x1D533, - 0x1D534, - 0x1D535, - 0x1D536, - 0x1D537, - 0x1D538, - 0x1D539, - 0x1D53A, - 0x1D53B, - 0x1D53C, - 0x1D53D, - 0x1D53E, - 0x1D53F, - 0x1D540, - 0x1D541, - 0x1D542, - 0x1D543, - 0x1D544, - 0x1D545, - 0x1D546, - 0x1D547, - 0x1D54A, - 0x1D54B, - 0x1D54C, - 0x1D54D, - 0x1D54E, - 0x1D54F, - 0x1D550, - 0x1D551, - 0x1D552, - 0x1D553, - 0x1D554, - 0x1D555, - 0x1D556, - 0x1D557, - 0x1D558, - 0x1D559, - 0x1D55A, - 0x1D55B, - 0x1D55C, - 0x1D55D, - 0x1D55E, - 0x1D55F, - 0x1D560, - 0x1D561, - 0x1D562, - 0x1D563, - 0x1D564, - 0x1D565, - 0x1D566, - 0x1D567, - 0x1D568, - 0x1D569, - 0x1D56A, - 0x1D56B, - 0x1D56C, - 0x1D56D, - 0x1D56E, - 0x1D56F, - 0x1D570, - 0x1D571, - 0x1D572, - 0x1D573, - 0x1D574, - 0x1D575, - 0x1D576, - 0x1D577, - 0x1D578, - 0x1D579, - 0x1D57A, - 0x1D57B, - 0x1D57C, - 0x1D57D, - 0x1D57E, - 0x1D57F, - 0x1D580, - 0x1D581, - 0x1D582, - 0x1D583, - 0x1D584, - 0x1D585, - 0x1D586, - 0x1D587, - 0x1D588, - 0x1D589, - 0x1D58A, - 0x1D58B, - 0x1D58C, - 0x1D58D, - 0x1D58E, - 0x1D58F, - 0x1D590, - 0x1D591, - 0x1D592, - 0x1D593, - 0x1D594, - 0x1D595, - 0x1D596, - 0x1D597, - 0x1D598, - 0x1D599, - 0x1D59A, - 0x1D59B, - 0x1D59C, - 0x1D59D, - 0x1D59E, - 0x1D59F, - 0x1D5A0, - 0x1D5A1, - 0x1D5A2, - 0x1D5A3, - 0x1D5A4, - 0x1D5A5, - 0x1D5A6, - 0x1D5A7, - 0x1D5A8, - 0x1D5A9, - 0x1D5AA, - 0x1D5AB, - 0x1D5AC, - 0x1D5AD, - 0x1D5AE, - 0x1D5AF, - 0x1D5B0, - 0x1D5B1, - 0x1D5B2, - 0x1D5B3, - 0x1D5B4, - 0x1D5B5, - 0x1D5B6, - 0x1D5B7, - 0x1D5B8, - 0x1D5B9, - 0x1D5BA, - 0x1D5BB, - 0x1D5BC, - 0x1D5BD, - 0x1D5BE, - 0x1D5BF, - 0x1D5C0, - 0x1D5C1, - 0x1D5C2, - 0x1D5C3, - 0x1D5C4, - 0x1D5C5, - 0x1D5C6, - 0x1D5C7, - 0x1D5C8, - 0x1D5C9, - 0x1D5CA, - 0x1D5CB, - 0x1D5CC, - 0x1D5CD, - 0x1D5CE, - 0x1D5CF, - 0x1D5D0, - 0x1D5D1, - 0x1D5D2, - 0x1D5D3, - 0x1D5D4, - 0x1D5D5, - 0x1D5D6, - 0x1D5D7, - 0x1D5D8, - 0x1D5D9, - 0x1D5DA, - 0x1D5DB, - 0x1D5DC, - 0x1D5DD, - 0x1D5DE, - 0x1D5DF, - 0x1D5E0, - 0x1D5E1, - 0x1D5E2, - 0x1D5E3, - 0x1D5E4, - 0x1D5E5, - 0x1D5E6, - 0x1D5E7, - 0x1D5E8, - 0x1D5E9, - 0x1D5EA, - 0x1D5EB, - 0x1D5EC, - 0x1D5ED, - 0x1D5EE, - 0x1D5EF, - 0x1D5F0, - 0x1D5F1, - 0x1D5F2, - 0x1D5F3, - 0x1D5F4, - 0x1D5F5, - 0x1D5F6, - 0x1D5F7, - 0x1D5F8, - 0x1D5F9, - 0x1D5FA, - 0x1D5FB, - 0x1D5FC, - 0x1D5FD, - 0x1D5FE, - 0x1D5FF, - 0x1D600, - 0x1D601, - 0x1D602, - 0x1D603, - 0x1D604, - 0x1D605, - 0x1D606, - 0x1D607, - 0x1D608, - 0x1D609, - 0x1D60A, - 0x1D60B, - 0x1D60C, - 0x1D60D, - 0x1D60E, - 0x1D60F, - 0x1D610, - 0x1D611, - 0x1D612, - 0x1D613, - 0x1D614, - 0x1D615, - 0x1D616, - 0x1D617, - 0x1D618, - 0x1D619, - 0x1D61A, - 0x1D61B, - 0x1D61C, - 0x1D61D, - 0x1D61E, - 0x1D61F, - 0x1D620, - 0x1D621, - 0x1D622, - 0x1D623, - 0x1D624, - 0x1D625, - 0x1D626, - 0x1D627, - 0x1D628, - 0x1D629, - 0x1D62A, - 0x1D62B, - 0x1D62C, - 0x1D62D, - 0x1D62E, - 0x1D62F, - 0x1D630, - 0x1D631, - 0x1D632, - 0x1D633, - 0x1D634, - 0x1D635, - 0x1D636, - 0x1D637, - 0x1D638, - 0x1D639, - 0x1D63A, - 0x1D63B, - 0x1D63C, - 0x1D63D, - 0x1D63E, - 0x1D63F, - 0x1D640, - 0x1D641, - 0x1D642, - 0x1D643, - 0x1D644, - 0x1D645, - 0x1D646, - 0x1D647, - 0x1D648, - 0x1D649, - 0x1D64A, - 0x1D64B, - 0x1D64C, - 0x1D64D, - 0x1D64E, - 0x1D64F, - 0x1D650, - 0x1D651, - 0x1D652, - 0x1D653, - 0x1D654, - 0x1D655, - 0x1D656, - 0x1D657, - 0x1D658, - 0x1D659, - 0x1D65A, - 0x1D65B, - 0x1D65C, - 0x1D65D, - 0x1D65E, - 0x1D65F, - 0x1D660, - 0x1D661, - 0x1D662, - 0x1D663, - 0x1D664, - 0x1D665, - 0x1D666, - 0x1D667, - 0x1D668, - 0x1D669, - 0x1D66A, - 0x1D66B, - 0x1D66C, - 0x1D66D, - 0x1D66E, - 0x1D66F, - 0x1D670, - 0x1D671, - 0x1D672, - 0x1D673, - 0x1D674, - 0x1D675, - 0x1D676, - 0x1D677, - 0x1D678, - 0x1D679, - 0x1D67A, - 0x1D67B, - 0x1D67C, - 0x1D67D, - 0x1D67E, - 0x1D67F, - 0x1D680, - 0x1D681, - 0x1D682, - 0x1D683, - 0x1D684, - 0x1D685, - 0x1D686, - 0x1D687, - 0x1D688, - 0x1D689, - 0x1D68A, - 0x1D68B, - 0x1D68C, - 0x1D68D, - 0x1D68E, - 0x1D68F, - 0x1D690, - 0x1D691, - 0x1D692, - 0x1D693, - 0x1D694, - 0x1D695, - 0x1D696, - 0x1D697, - 0x1D698, - 0x1D699, - 0x1D69A, - 0x1D69B, - 0x1D69C, - 0x1D69D, - 0x1D69E, - 0x1D69F, - 0x1D6A0, - 0x1D6A1, - 0x1D6A2, - 0x1D6A3, - 0x1D6A4, - 0x1D6A5, - 0x1D6A6, - 0x1D6A8, - 0x1D6A9, - 0x1D6AA, - 0x1D6AB, - 0x1D6AC, - 0x1D6AD, - 0x1D6AE, - 0x1D6AF, - 0x1D6B0, - 0x1D6B1, - 0x1D6B2, - 0x1D6B3, - 0x1D6B4, - 0x1D6B5, - 0x1D6B6, - 0x1D6B7, - 0x1D6B8, - 0x1D6B9, - 0x1D6BA, - 0x1D6BB, - 0x1D6BC, - 0x1D6BD, - 0x1D6BE, - 0x1D6BF, - 0x1D6C0, - 0x1D6C1, - 0x1D6C2, - 0x1D6C3, - 0x1D6C4, - 0x1D6C5, - 0x1D6C6, - 0x1D6C7, - 0x1D6C8, - 0x1D6C9, - 0x1D6CA, - 0x1D6CB, - 0x1D6CC, - 0x1D6CD, - 0x1D6CE, - 0x1D6CF, - 0x1D6D0, - 0x1D6D1, - 0x1D6D2, - 0x1D6D3, - 0x1D6D5, - 0x1D6D6, - 0x1D6D7, - 0x1D6D8, - 0x1D6D9, - 0x1D6DA, - 0x1D6DB, - 0x1D6DC, - 0x1D6DD, - 0x1D6DE, - 0x1D6DF, - 0x1D6E0, - 0x1D6E1, - 0x1D6E2, - 0x1D6E3, - 0x1D6E4, - 0x1D6E5, - 0x1D6E6, - 0x1D6E7, - 0x1D6E8, - 0x1D6E9, - 0x1D6EA, - 0x1D6EB, - 0x1D6EC, - 0x1D6ED, - 0x1D6EE, - 0x1D6EF, - 0x1D6F0, - 0x1D6F1, - 0x1D6F2, - 0x1D6F3, - 0x1D6F4, - 0x1D6F5, - 0x1D6F6, - 0x1D6F7, - 0x1D6F8, - 0x1D6F9, - 0x1D6FA, - 0x1D6FB, - 0x1D6FC, - 0x1D6FD, - 0x1D6FE, - 0x1D6FF, - 0x1D700, - 0x1D701, - 0x1D702, - 0x1D703, - 0x1D704, - 0x1D705, - 0x1D706, - 0x1D707, - 0x1D708, - 0x1D709, - 0x1D70A, - 0x1D70B, - 0x1D70C, - 0x1D70D, - 0x1D70F, - 0x1D710, - 0x1D711, - 0x1D712, - 0x1D713, - 0x1D714, - 0x1D715, - 0x1D716, - 0x1D717, - 0x1D718, - 0x1D719, - 0x1D71A, - 0x1D71B, - 0x1D71C, - 0x1D71D, - 0x1D71E, - 0x1D71F, - 0x1D720, - 0x1D721, - 0x1D722, - 0x1D723, - 0x1D724, - 0x1D725, - 0x1D726, - 0x1D727, - 0x1D728, - 0x1D729, - 0x1D72A, - 0x1D72B, - 0x1D72C, - 0x1D72D, - 0x1D72E, - 0x1D72F, - 0x1D730, - 0x1D731, - 0x1D732, - 0x1D733, - 0x1D734, - 0x1D735, - 0x1D736, - 0x1D737, - 0x1D738, - 0x1D739, - 0x1D73A, - 0x1D73B, - 0x1D73C, - 0x1D73D, - 0x1D73E, - 0x1D73F, - 0x1D740, - 0x1D741, - 0x1D742, - 0x1D743, - 0x1D744, - 0x1D745, - 0x1D746, - 0x1D747, - 0x1D749, - 0x1D74A, - 0x1D74B, - 0x1D74C, - 0x1D74D, - 0x1D74E, - 0x1D74F, - 0x1D750, - 0x1D751, - 0x1D752, - 0x1D753, - 0x1D754, - 0x1D755, - 0x1D756, - 0x1D757, - 0x1D758, - 0x1D759, - 0x1D75A, - 0x1D75B, - 0x1D75C, - 0x1D75D, - 0x1D75E, - 0x1D75F, - 0x1D760, - 0x1D761, - 0x1D762, - 0x1D763, - 0x1D764, - 0x1D765, - 0x1D766, - 0x1D767, - 0x1D768, - 0x1D769, - 0x1D76A, - 0x1D76B, - 0x1D76C, - 0x1D76D, - 0x1D76E, - 0x1D76F, - 0x1D770, - 0x1D771, - 0x1D772, - 0x1D773, - 0x1D774, - 0x1D775, - 0x1D776, - 0x1D777, - 0x1D778, - 0x1D779, - 0x1D77A, - 0x1D77B, - 0x1D77C, - 0x1D77D, - 0x1D77E, - 0x1D77F, - 0x1D780, - 0x1D781, - 0x1D783, - 0x1D784, - 0x1D785, - 0x1D786, - 0x1D787, - 0x1D788, - 0x1D789, - 0x1D78A, - 0x1D78B, - 0x1D78C, - 0x1D78D, - 0x1D78E, - 0x1D78F, - 0x1D790, - 0x1D791, - 0x1D792, - 0x1D793, - 0x1D794, - 0x1D795, - 0x1D796, - 0x1D797, - 0x1D798, - 0x1D799, - 0x1D79A, - 0x1D79B, - 0x1D79C, - 0x1D79D, - 0x1D79E, - 0x1D79F, - 0x1D7A0, - 0x1D7A1, - 0x1D7A2, - 0x1D7A3, - 0x1D7A4, - 0x1D7A5, - 0x1D7A6, - 0x1D7A7, - 0x1D7A8, - 0x1D7A9, - 0x1D7AA, - 0x1D7AB, - 0x1D7AC, - 0x1D7AD, - 0x1D7AE, - 0x1D7AF, - 0x1D7B0, - 0x1D7B1, - 0x1D7B2, - 0x1D7B3, - 0x1D7B4, - 0x1D7B5, - 0x1D7B6, - 0x1D7B7, - 0x1D7B8, - 0x1D7B9, - 0x1D7BA, - 0x1D7BB, - 0x1D7BD, - 0x1D7BE, - 0x1D7BF, - 0x1D7C0, - 0x1D7C1, - 0x1D7C2, - 0x1D7C3, - 0x1D7C4, - 0x1D7C5, - 0x1D7C6, - 0x1D7C7, - 0x1D7C8, - 0x1D7C9, - 0x1D7CA, - 0x1D7CC, - 0x1D7CE, - 0x1D7CF, - 0x1D7D0, - 0x1D7D1, - 0x1D7D2, - 0x1D7D3, - 0x1D7D4, - 0x1D7D5, - 0x1D7D6, - 0x1D7D7, - 0x1D7D8, - 0x1D7D9, - 0x1D7DA, - 0x1D7DB, - 0x1D7DC, - 0x1D7DD, - 0x1D7DE, - 0x1D7DF, - 0x1D7E0, - 0x1D7E1, - 0x1D7E2, - 0x1D7E3, - 0x1D7E4, - 0x1D7E5, - 0x1D7E6, - 0x1D7E7, - 0x1D7E8, - 0x1D7E9, - 0x1D7EA, - 0x1D7EB, - 0x1D7EC, - 0x1D7ED, - 0x1D7EE, - 0x1D7EF, - 0x1D7F0, - 0x1D7F1, - 0x1D7F2, - 0x1D7F3, - 0x1D7F4, - 0x1D7F5, - 0x1D7F6, - 0x1D7F7, - 0x1D7F8, - 0x1D7F9, - 0x1D7FA, - 0x1D7FB, - 0x1D7FC, - 0x1D7FD, - 0x1D7FE, - 0x1D7FF, - 0x1D800, - 0x1DA8C, - 0x1DA9B, - 0x1DAA0, - 0x1DAA1, - 0x1DAB0, - 0x1DF00, - 0x1DF1F, - 0x1DF25, - 0x1DF2B, - 0x1E000, - 0x1E007, - 0x1E008, - 0x1E019, - 0x1E01B, - 0x1E022, - 0x1E023, - 0x1E025, - 0x1E026, - 0x1E02B, - 0x1E030, - 0x1E031, - 0x1E032, - 0x1E033, - 0x1E034, - 0x1E035, - 0x1E036, - 0x1E037, - 0x1E038, - 0x1E039, - 0x1E03A, - 0x1E03B, - 0x1E03C, - 0x1E03D, - 0x1E03E, - 0x1E03F, - 0x1E040, - 0x1E041, - 0x1E042, - 0x1E043, - 0x1E044, - 0x1E045, - 0x1E046, - 0x1E047, - 0x1E048, - 0x1E049, - 0x1E04A, - 0x1E04B, - 0x1E04C, - 0x1E04D, - 0x1E04E, - 0x1E04F, - 0x1E050, - 0x1E051, - 0x1E052, - 0x1E053, - 0x1E054, - 0x1E055, - 0x1E056, - 0x1E057, - 0x1E058, - 0x1E059, - 0x1E05A, - 0x1E05B, - 0x1E05C, - 0x1E05D, - 0x1E05E, - 0x1E05F, - 0x1E060, - 0x1E061, - 0x1E062, - 0x1E063, - 0x1E064, - 0x1E065, - 0x1E066, - 0x1E067, - 0x1E068, - 0x1E069, - 0x1E06A, - 0x1E06B, - 0x1E06C, - 0x1E06D, - 0x1E06E, - 0x1E08F, - 0x1E090, - 0x1E100, - 0x1E12D, - 0x1E130, - 0x1E13E, - 0x1E140, - 0x1E14A, - 0x1E14E, - 0x1E150, - 0x1E290, - 0x1E2AF, - 0x1E2C0, - 0x1E2FA, - 0x1E2FF, - 0x1E300, - 0x1E4D0, - 0x1E4FA, - 0x1E5D0, - 0x1E5FB, - 0x1E5FF, - 0x1E600, - 0x1E6C0, - 0x1E6DF, - 0x1E6E0, - 0x1E6F6, - 0x1E6FE, - 0x1E700, - 0x1E7E0, - 0x1E7E7, - 0x1E7E8, - 0x1E7EC, - 0x1E7ED, - 0x1E7EF, - 0x1E7F0, - 0x1E7FF, - 0x1E800, - 0x1E8C5, - 0x1E8C7, - 0x1E8D7, - 0x1E900, - 0x1E901, - 0x1E902, - 0x1E903, - 0x1E904, - 0x1E905, - 0x1E906, - 0x1E907, - 0x1E908, - 0x1E909, - 0x1E90A, - 0x1E90B, - 0x1E90C, - 0x1E90D, - 0x1E90E, - 0x1E90F, - 0x1E910, - 0x1E911, - 0x1E912, - 0x1E913, - 0x1E914, - 0x1E915, - 0x1E916, - 0x1E917, - 0x1E918, - 0x1E919, - 0x1E91A, - 0x1E91B, - 0x1E91C, - 0x1E91D, - 0x1E91E, - 0x1E91F, - 0x1E920, - 0x1E921, - 0x1E922, - 0x1E94C, - 0x1E950, - 0x1E95A, - 0x1E95E, - 0x1E960, - 0x1EC71, - 0x1ECB5, - 0x1ED01, - 0x1ED3E, - 0x1EE00, - 0x1EE01, - 0x1EE02, - 0x1EE03, - 0x1EE04, - 0x1EE05, - 0x1EE06, - 0x1EE07, - 0x1EE08, - 0x1EE09, - 0x1EE0A, - 0x1EE0B, - 0x1EE0C, - 0x1EE0D, - 0x1EE0E, - 0x1EE0F, - 0x1EE10, - 0x1EE11, - 0x1EE12, - 0x1EE13, - 0x1EE14, - 0x1EE15, - 0x1EE16, - 0x1EE17, - 0x1EE18, - 0x1EE19, - 0x1EE1A, - 0x1EE1B, - 0x1EE1C, - 0x1EE1D, - 0x1EE1E, - 0x1EE1F, - 0x1EE20, - 0x1EE21, - 0x1EE22, - 0x1EE23, - 0x1EE24, - 0x1EE25, - 0x1EE27, - 0x1EE28, - 0x1EE29, - 0x1EE2A, - 0x1EE2B, - 0x1EE2C, - 0x1EE2D, - 0x1EE2E, - 0x1EE2F, - 0x1EE30, - 0x1EE31, - 0x1EE32, - 0x1EE33, - 0x1EE34, - 0x1EE35, - 0x1EE36, - 0x1EE37, - 0x1EE38, - 0x1EE39, - 0x1EE3A, - 0x1EE3B, - 0x1EE3C, - 0x1EE42, - 0x1EE43, - 0x1EE47, - 0x1EE48, - 0x1EE49, - 0x1EE4A, - 0x1EE4B, - 0x1EE4C, - 0x1EE4D, - 0x1EE4E, - 0x1EE4F, - 0x1EE50, - 0x1EE51, - 0x1EE52, - 0x1EE53, - 0x1EE54, - 0x1EE55, - 0x1EE57, - 0x1EE58, - 0x1EE59, - 0x1EE5A, - 0x1EE5B, - 0x1EE5C, - 0x1EE5D, - 0x1EE5E, - 0x1EE5F, - 0x1EE60, - 0x1EE61, - 0x1EE62, - 0x1EE63, - 0x1EE64, - 0x1EE65, - 0x1EE67, - 0x1EE68, - 0x1EE69, - 0x1EE6A, - 0x1EE6B, - 0x1EE6C, - 0x1EE6D, - 0x1EE6E, - 0x1EE6F, - 0x1EE70, - 0x1EE71, - 0x1EE72, - 0x1EE73, - 0x1EE74, - 0x1EE75, - 0x1EE76, - 0x1EE77, - 0x1EE78, - 0x1EE79, - 0x1EE7A, - 0x1EE7B, - 0x1EE7C, - 0x1EE7D, - 0x1EE7E, - 0x1EE7F, - 0x1EE80, - 0x1EE81, - 0x1EE82, - 0x1EE83, - 0x1EE84, - 0x1EE85, - 0x1EE86, - 0x1EE87, - 0x1EE88, - 0x1EE89, - 0x1EE8A, - 0x1EE8B, - 0x1EE8C, - 0x1EE8D, - 0x1EE8E, - 0x1EE8F, - 0x1EE90, - 0x1EE91, - 0x1EE92, - 0x1EE93, - 0x1EE94, - 0x1EE95, - 0x1EE96, - 0x1EE97, - 0x1EE98, - 0x1EE99, - 0x1EE9A, - 0x1EE9B, - 0x1EE9C, - 0x1EEA1, - 0x1EEA2, - 0x1EEA3, - 0x1EEA4, - 0x1EEA5, - 0x1EEA6, - 0x1EEA7, - 0x1EEA8, - 0x1EEA9, - 0x1EEAA, - 0x1EEAB, - 0x1EEAC, - 0x1EEAD, - 0x1EEAE, - 0x1EEAF, - 0x1EEB0, - 0x1EEB1, - 0x1EEB2, - 0x1EEB3, - 0x1EEB4, - 0x1EEB5, - 0x1EEB6, - 0x1EEB7, - 0x1EEB8, - 0x1EEB9, - 0x1EEBA, - 0x1EEBB, - 0x1EEBC, - 0x1EEF0, - 0x1EEF2, - 0x1F000, - 0x1F02C, - 0x1F030, - 0x1F094, - 0x1F0A0, - 0x1F0AF, - 0x1F0B1, - 0x1F0C0, - 0x1F0C1, - 0x1F0D0, - 0x1F0D1, - 0x1F0F6, - 0x1F101, - 0x1F102, - 0x1F103, - 0x1F104, - 0x1F105, - 0x1F106, - 0x1F107, - 0x1F108, - 0x1F109, - 0x1F10A, - 0x1F10B, - 0x1F110, - 0x1F111, - 0x1F112, - 0x1F113, - 0x1F114, - 0x1F115, - 0x1F116, - 0x1F117, - 0x1F118, - 0x1F119, - 0x1F11A, - 0x1F11B, - 0x1F11C, - 0x1F11D, - 0x1F11E, - 0x1F11F, - 0x1F120, - 0x1F121, - 0x1F122, - 0x1F123, - 0x1F124, - 0x1F125, - 0x1F126, - 0x1F127, - 0x1F128, - 0x1F129, - 0x1F12A, - 0x1F12B, - 0x1F12C, - 0x1F12D, - 0x1F12E, - 0x1F12F, - 0x1F130, - 0x1F131, - 0x1F132, - 0x1F133, - 0x1F134, - 0x1F135, - 0x1F136, - 0x1F137, - 0x1F138, - 0x1F139, - 0x1F13A, - 0x1F13B, - 0x1F13C, - 0x1F13D, - 0x1F13E, - 0x1F13F, - 0x1F140, - 0x1F141, - 0x1F142, - 0x1F143, - 0x1F144, - 0x1F145, - 0x1F146, - 0x1F147, - 0x1F148, - 0x1F149, - 0x1F14A, - 0x1F14B, - 0x1F14C, - 0x1F14D, - 0x1F14E, - 0x1F14F, - 0x1F150, - 0x1F16A, - 0x1F16B, - 0x1F16C, - 0x1F16D, - 0x1F190, - 0x1F191, - 0x1F1AE, - 0x1F1E6, - 0x1F200, - 0x1F201, - 0x1F202, - 0x1F203, - 0x1F210, - 0x1F211, - 0x1F212, - 0x1F213, - 0x1F214, - 0x1F215, - 0x1F216, - 0x1F217, - 0x1F218, - 0x1F219, - 0x1F21A, - 0x1F21B, - 0x1F21C, - 0x1F21D, - 0x1F21E, - 0x1F21F, - 0x1F220, - 0x1F221, - 0x1F222, - 0x1F223, - 0x1F224, - 0x1F225, - 0x1F226, - 0x1F227, - 0x1F228, - 0x1F229, - 0x1F22A, - 0x1F22B, - 0x1F22C, - 0x1F22D, - 0x1F22E, - 0x1F22F, - 0x1F230, - 0x1F231, - 0x1F232, - 0x1F233, - 0x1F234, - 0x1F235, - 0x1F236, - 0x1F237, - 0x1F238, - 0x1F239, - 0x1F23A, - 0x1F23B, - 0x1F23C, - 0x1F240, - 0x1F241, - 0x1F242, - 0x1F243, - 0x1F244, - 0x1F245, - 0x1F246, - 0x1F247, - 0x1F248, - 0x1F249, - 0x1F250, - 0x1F251, - 0x1F252, - 0x1F260, - 0x1F266, - 0x1F300, - 0x1F6D9, - 0x1F6DC, - 0x1F6ED, - 0x1F6F0, - 0x1F6FD, - 0x1F700, - 0x1F7DA, - 0x1F7E0, - 0x1F7EC, - 0x1F7F0, - 0x1F7F1, - 0x1F800, - 0x1F80C, - 0x1F810, - 0x1F848, - 0x1F850, - 0x1F85A, - 0x1F860, - 0x1F888, - 0x1F890, - 0x1F8AE, - 0x1F8B0, - 0x1F8BC, - 0x1F8C0, - 0x1F8C2, - 0x1F8D0, - 0x1F8D9, - 0x1F900, - 0x1FA58, - 0x1FA60, - 0x1FA6E, - 0x1FA70, - 0x1FA7D, - 0x1FA80, - 0x1FA8B, - 0x1FA8E, - 0x1FAC7, - 0x1FAC8, - 0x1FAC9, - 0x1FACD, - 0x1FADD, - 0x1FADF, - 0x1FAEB, - 0x1FAEF, - 0x1FAF9, - 0x1FB00, - 0x1FB93, - 0x1FB94, - 0x1FBF0, - 0x1FBF1, - 0x1FBF2, - 0x1FBF3, - 0x1FBF4, - 0x1FBF5, - 0x1FBF6, - 0x1FBF7, - 0x1FBF8, - 0x1FBF9, - 0x1FBFA, - 0x1FBFB, - 0x20000, - 0x2A6E0, - 0x2A700, - 0x2B81E, - 0x2B820, - 0x2CEAE, - 0x2CEB0, - 0x2EBE1, - 0x2EBF0, - 0x2EE5E, - 0x2F800, - 0x2F801, - 0x2F802, - 0x2F803, - 0x2F804, - 0x2F805, - 0x2F806, - 0x2F807, - 0x2F808, - 0x2F809, - 0x2F80A, - 0x2F80B, - 0x2F80C, - 0x2F80D, - 0x2F80E, - 0x2F80F, - 0x2F810, - 0x2F811, - 0x2F812, - 0x2F813, - 0x2F814, - 0x2F815, - 0x2F816, - 0x2F817, - 0x2F818, - 0x2F819, - 0x2F81A, - 0x2F81B, - 0x2F81C, - 0x2F81D, - 0x2F81E, - 0x2F81F, - 0x2F820, - 0x2F821, - 0x2F822, - 0x2F823, - 0x2F824, - 0x2F825, - 0x2F826, - 0x2F827, - 0x2F828, - 0x2F829, - 0x2F82A, - 0x2F82B, - 0x2F82C, - 0x2F82D, - 0x2F82E, - 0x2F82F, - 0x2F830, - 0x2F831, - 0x2F834, - 0x2F835, - 0x2F836, - 0x2F837, - 0x2F838, - 0x2F839, - 0x2F83A, - 0x2F83B, - 0x2F83C, - 0x2F83D, - 0x2F83E, - 0x2F83F, - 0x2F840, - 0x2F841, - 0x2F842, - 0x2F843, - 0x2F844, - 0x2F845, - 0x2F847, - 0x2F848, - 0x2F849, - 0x2F84A, - 0x2F84B, - 0x2F84C, - 0x2F84D, - 0x2F84E, - 0x2F84F, - 0x2F850, - 0x2F851, - 0x2F852, - 0x2F853, - 0x2F854, - 0x2F855, - 0x2F856, - 0x2F857, - 0x2F858, - 0x2F859, - 0x2F85A, - 0x2F85B, - 0x2F85C, - 0x2F85D, - 0x2F85E, - 0x2F85F, - 0x2F860, - 0x2F861, - 0x2F862, - 0x2F863, - 0x2F864, - 0x2F865, - 0x2F866, - 0x2F867, - 0x2F868, - 0x2F869, - 0x2F86A, - 0x2F86C, - 0x2F86D, - 0x2F86E, - 0x2F86F, - 0x2F870, - 0x2F871, - 0x2F872, - 0x2F873, - 0x2F874, - 0x2F875, - 0x2F876, - 0x2F877, - 0x2F878, - 0x2F879, - 0x2F87A, - 0x2F87B, - 0x2F87C, - 0x2F87D, - 0x2F87E, - 0x2F87F, - 0x2F880, - 0x2F881, - 0x2F882, - 0x2F883, - 0x2F884, - 0x2F885, - 0x2F886, - 0x2F887, - 0x2F888, - 0x2F889, - 0x2F88A, - 0x2F88B, - 0x2F88C, - 0x2F88D, - 0x2F88E, - 0x2F88F, - 0x2F890, - 0x2F891, - 0x2F893, - 0x2F894, - 0x2F896, - 0x2F897, - 0x2F898, - 0x2F899, - 0x2F89A, - 0x2F89B, - 0x2F89C, - 0x2F89D, - 0x2F89E, - 0x2F89F, - 0x2F8A0, - 0x2F8A1, - 0x2F8A2, - 0x2F8A3, - 0x2F8A4, - 0x2F8A5, - 0x2F8A6, - 0x2F8A7, - 0x2F8A8, - 0x2F8A9, - 0x2F8AA, - 0x2F8AB, - 0x2F8AC, - 0x2F8AD, - 0x2F8AE, - 0x2F8AF, - 0x2F8B0, - 0x2F8B1, - 0x2F8B2, - 0x2F8B3, - 0x2F8B4, - 0x2F8B5, - 0x2F8B6, - 0x2F8B7, - 0x2F8B8, - 0x2F8B9, - 0x2F8BA, - 0x2F8BB, - 0x2F8BC, - 0x2F8BD, - 0x2F8BE, - 0x2F8BF, - 0x2F8C0, - 0x2F8C1, - 0x2F8C2, - 0x2F8C3, - 0x2F8C4, - 0x2F8C5, - 0x2F8C6, - 0x2F8C7, - 0x2F8C8, - 0x2F8C9, - 0x2F8CA, - 0x2F8CB, - 0x2F8CC, - 0x2F8CD, - 0x2F8CE, - 0x2F8CF, - 0x2F8D0, - 0x2F8D1, - 0x2F8D2, - 0x2F8D3, - 0x2F8D4, - 0x2F8D5, - 0x2F8D6, - 0x2F8D7, - 0x2F8D8, - 0x2F8D9, - 0x2F8DA, - 0x2F8DB, - 0x2F8DC, - 0x2F8DD, - 0x2F8DE, - 0x2F8DF, - 0x2F8E0, - 0x2F8E1, - 0x2F8E2, - 0x2F8E3, - 0x2F8E4, - 0x2F8E5, - 0x2F8E6, - 0x2F8E7, - 0x2F8E8, - 0x2F8E9, - 0x2F8EA, - 0x2F8EB, - 0x2F8EC, - 0x2F8ED, - 0x2F8EE, - 0x2F8EF, - 0x2F8F0, - 0x2F8F1, - 0x2F8F2, - 0x2F8F3, - 0x2F8F4, - 0x2F8F5, - 0x2F8F6, - 0x2F8F7, - 0x2F8F8, - 0x2F8F9, - 0x2F8FA, - 0x2F8FB, - 0x2F8FC, - 0x2F8FD, - 0x2F8FE, - 0x2F8FF, - 0x2F900, - 0x2F901, - 0x2F902, - 0x2F903, - 0x2F904, - 0x2F905, - 0x2F906, - 0x2F907, - 0x2F908, - 0x2F909, - 0x2F90A, - 0x2F90B, - 0x2F90C, - 0x2F90D, - 0x2F90E, - 0x2F90F, - 0x2F910, - 0x2F911, - 0x2F912, - 0x2F913, - 0x2F914, - 0x2F915, - 0x2F916, - 0x2F917, - 0x2F918, - 0x2F919, - 0x2F91A, - 0x2F91B, - 0x2F91C, - 0x2F91D, - 0x2F91E, - 0x2F91F, - 0x2F920, - 0x2F921, - 0x2F922, - 0x2F923, - 0x2F924, - 0x2F925, - 0x2F926, - 0x2F927, - 0x2F928, - 0x2F929, - 0x2F92A, - 0x2F92B, - 0x2F92C, - 0x2F92E, - 0x2F92F, - 0x2F930, - 0x2F931, - 0x2F932, - 0x2F933, - 0x2F934, - 0x2F935, - 0x2F936, - 0x2F937, - 0x2F938, - 0x2F939, - 0x2F93A, - 0x2F93B, - 0x2F93C, - 0x2F93D, - 0x2F93E, - 0x2F93F, - 0x2F940, - 0x2F941, - 0x2F942, - 0x2F943, - 0x2F944, - 0x2F945, - 0x2F946, - 0x2F948, - 0x2F949, - 0x2F94A, - 0x2F94B, - 0x2F94C, - 0x2F94D, - 0x2F94E, - 0x2F94F, - 0x2F950, - 0x2F951, - 0x2F952, - 0x2F953, - 0x2F954, - 0x2F955, - 0x2F956, - 0x2F957, - 0x2F958, - 0x2F959, - 0x2F95A, - 0x2F95B, - 0x2F95C, - 0x2F95D, - 0x2F95F, - 0x2F960, - 0x2F961, - 0x2F962, - 0x2F963, - 0x2F964, - 0x2F965, - 0x2F966, - 0x2F967, - 0x2F968, - 0x2F969, - 0x2F96A, - 0x2F96B, - 0x2F96C, - 0x2F96D, - 0x2F96E, - 0x2F96F, - 0x2F970, - 0x2F971, - 0x2F972, - 0x2F973, - 0x2F974, - 0x2F975, - 0x2F976, - 0x2F977, - 0x2F978, - 0x2F979, - 0x2F97A, - 0x2F97B, - 0x2F97C, - 0x2F97D, - 0x2F97E, - 0x2F97F, - 0x2F980, - 0x2F981, - 0x2F982, - 0x2F983, - 0x2F984, - 0x2F985, - 0x2F986, - 0x2F987, - 0x2F988, - 0x2F989, - 0x2F98A, - 0x2F98B, - 0x2F98C, - 0x2F98D, - 0x2F98E, - 0x2F98F, - 0x2F990, - 0x2F991, - 0x2F992, - 0x2F993, - 0x2F994, - 0x2F995, - 0x2F996, - 0x2F997, - 0x2F998, - 0x2F999, - 0x2F99A, - 0x2F99B, - 0x2F99C, - 0x2F99D, - 0x2F99E, - 0x2F99F, - 0x2F9A0, - 0x2F9A1, - 0x2F9A2, - 0x2F9A3, - 0x2F9A4, - 0x2F9A5, - 0x2F9A6, - 0x2F9A7, - 0x2F9A8, - 0x2F9A9, - 0x2F9AA, - 0x2F9AB, - 0x2F9AC, - 0x2F9AD, - 0x2F9AE, - 0x2F9AF, - 0x2F9B0, - 0x2F9B1, - 0x2F9B2, - 0x2F9B3, - 0x2F9B4, - 0x2F9B5, - 0x2F9B6, - 0x2F9B7, - 0x2F9B8, - 0x2F9B9, - 0x2F9BA, - 0x2F9BB, - 0x2F9BC, - 0x2F9BD, - 0x2F9BE, - 0x2F9BF, - 0x2F9C0, - 0x2F9C1, - 0x2F9C2, - 0x2F9C3, - 0x2F9C4, - 0x2F9C5, - 0x2F9C6, - 0x2F9C7, - 0x2F9C8, - 0x2F9C9, - 0x2F9CA, - 0x2F9CB, - 0x2F9CC, - 0x2F9CD, - 0x2F9CE, - 0x2F9CF, - 0x2F9D0, - 0x2F9D1, - 0x2F9D2, - 0x2F9D3, - 0x2F9D4, - 0x2F9D5, - 0x2F9D6, - 0x2F9D7, - 0x2F9D8, - 0x2F9D9, - 0x2F9DA, - 0x2F9DB, - 0x2F9DC, - 0x2F9DD, - 0x2F9DE, - 0x2F9DF, - 0x2F9E0, - 0x2F9E1, - 0x2F9E2, - 0x2F9E3, - 0x2F9E4, - 0x2F9E5, - 0x2F9E6, - 0x2F9E7, - 0x2F9E8, - 0x2F9E9, - 0x2F9EA, - 0x2F9EB, - 0x2F9EC, - 0x2F9ED, - 0x2F9EE, - 0x2F9EF, - 0x2F9F0, - 0x2F9F1, - 0x2F9F2, - 0x2F9F3, - 0x2F9F4, - 0x2F9F5, - 0x2F9F6, - 0x2F9F7, - 0x2F9F8, - 0x2F9F9, - 0x2F9FA, - 0x2F9FB, - 0x2F9FC, - 0x2F9FD, - 0x2F9FE, - 0x2FA00, - 0x2FA01, - 0x2FA02, - 0x2FA03, - 0x2FA04, - 0x2FA05, - 0x2FA06, - 0x2FA07, - 0x2FA08, - 0x2FA09, - 0x2FA0A, - 0x2FA0B, - 0x2FA0C, - 0x2FA0D, - 0x2FA0E, - 0x2FA0F, - 0x2FA10, - 0x2FA11, - 0x2FA12, - 0x2FA13, - 0x2FA14, - 0x2FA15, - 0x2FA16, - 0x2FA17, - 0x2FA18, - 0x2FA19, - 0x2FA1A, - 0x2FA1B, - 0x2FA1C, - 0x2FA1D, - 0x2FA1E, - 0x30000, - 0x3134B, - 0x31350, - 0x3347A, - 0xE0100, - 0xE01F0, - ), -) - -uts46_statuses: bytes = ( - b"VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" - b"VMMMMMMMMMMMMMMMMMMMMMMMMMMVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" - b"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXMVVVVVVVMVMVVIVMVVMMMMVVMMMVMMMV" - b"MMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMDVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMVMVMVMVMMV" - b"MVMVMVMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMVMVMVMM" - b"VMVMMVMMMVMMMMVMMVMMMVMMVMMVMVMVMMVMVMVMMVMMMVMVMMVMVMMMMVMVMVMV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMMVMMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" - b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMMVMVMMMMVMVMVMVMVMMMMMMMMMVMMMMMM" - b"VMMMMMVMMVMMMVIVMVMVMVMVXMVMMXMMMMMMMXMXMMVMMMMMMMMMMMMMMMMMXMMM" - b"MMMMMMVDVMMMMMMMMVMVMVMVMVMVMVMVMVMVMVMVMVMMMVMMVMVMMVMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMVMVMVMVMVMVMVMVMVMV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" - b"MVMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" - b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVXMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMXVMVXVXVXVXVXVXVMMMMVXVXVXVXVXVXVXVXVXVX" - b"VXVXVMMMMMMMMVXVXVXVXVXVXVXVXVXVXVXMMXMVXVXVXVXVXVXVXVMXVMXVXVXV" - b"XVXVXVXMMMVXMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXV" - b"XVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXV" - b"XVMVXVXVXVXVXVXVXVMVXVXVXVXVXMMVXVMVMVXVMVMVMVMVMVXVMVMMMMMVMVMV" - b"XVMVMVMVMVMVXVXVXVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMXMXVMV" - b"IVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXMMMMMMXVXVXVXVXVXVXVXVXVX" - b"VIVXVXVXVIVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVMMM" - b"MMMMMMVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMVXVXVMMMV" - b"MMMMMMMMMMMVMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVMVM" - b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVM" - b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMMMMMMMVXMMMMMMXVMMMMMMMMVMMMMMM" - b"MMVXMMMMMMXVXMXMXMXMVMMMMMMMMVMVMVMVMVMVMVMXMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMXVMMMMMMMMMMMMMMXVMMMMMMMMMVMXVM" - b"MMMXMMMVMVMMMMMMMMXMMMXVMMMMMMMMXMIDXVMVMVXVXMVMMVMMVMVMVMMMVMVM" - b"IXIMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMXVMVXVXMMMMVMMMVM" - b"MMMMMVMMVMMMVMMMVMVMVMVMMMMVMMMMMMMMMMVMMMMMVMMMMVMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMVXVMMVMMVMMVXVXMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMMMVMVXVMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMVMMMVMVMVMVMMMMVMVMVMM" - b"MMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVXVXVXVXVXMVXVXVXVXVX" - b"VXVXVXVXVXVXVXVMVMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMVMVMVMMMVXVXVMMVMVMXV" - b"XMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMIMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXVMMMMMMMMMMMMMMVXVMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMXMMMMMMMMMMMMM" - b"MMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXMVMVMVMVMVMVMVM" - b"VMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMVXV" - b"MVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMV" - b"MVMVMVMVMVMVMVMVMVMMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMVMMMMMVM" - b"MMMMVMVMVMVMVMVMVMVMMMMVMVMMVMVMVMVMVMVMVMVMXMMMMMVMMVXVXVXVXVXV" - b"XVXVXVXVXVXVXVXVXVXVXVXVXVXVXVMMMMVMVXMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVXVX" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMVMVMVMMMMMMMMMMVMVMVMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMXMMMMMMXMMMMMXMVMMMMMMMMMMMMMMMMMMMMMMMMXMMMMM" - b"XMXMMXMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXMMMMMMMMMMMMMVIMMXMMM" - b"MMMXVXMMMMMMMMMMMMMMMMMMMVMMMMMMXMMMMMMMMMMMMMMMMMMMXMMMMXMMMVMX" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXIXMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMIMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMXMMMMM" - b"MXMMMMMMXMMMXMMMMMMMXMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXV" - b"XVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVMMMMMMMMMMMXMMMMMMMMMMMMMMMXMMM" - b"MMMMXMMXVXVXVXVXVXVXVXVXVMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMXMMMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMXVXVXVXVMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMVXMMMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVIXVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVX" - b"VXVXVXVXVXVXVMMMMMMMVIVMMMMMMVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMXMMXMXMMXMMMMXMMMMMMMMMMMMXMXMMMMMMMXMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMXMMMMMMMMXMMMMM" - b"MMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMXMMMMMXMXMMMMMMMXMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVXVXVXVXVXVXVXMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXVXVXVXVXVXVXVXVXVX" - b"VXVXVXVXVXVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVXVXVXVX" - b"VXMMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMXMMXMXMXMMMMMMMMMMXMMMMXMXMXMX" - b"MXMXMXMMMXMMXMXMXMXMXMXMXMMXMXMMMMXMMMMMMMXMMMMXMMMMXMXMMMMMMMMM" - b"MXMMMMMMMMMMMMMMMMMXMMMXMMMMMXMMMMMMMMMMMMMMMMMXVXVXVXVXVXVXVXMM" - b"MMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMVMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMVMMMVMVXVMMMXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMXMMMMMMMMMXMMXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVXVX" - b"VXVMMMMMMMMMMVXVXVXVXVXVXMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" - b"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMXVXVXIX" -) - -uts46_replacements: tuple[Optional[str], ...] = ( - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - " ", - None, - None, - None, - None, - None, - None, - None, - " ̈", - None, - "a", - None, - None, - None, - None, - " ̄", - None, - None, - "2", - "3", - " ́", - "μ", - None, - None, - " ̧", - "1", - "o", - None, - "1⁄4", - "1⁄2", - "3⁄4", - None, - "à", - "á", - "â", - "ã", - "ä", - "å", - "æ", - "ç", - "è", - "é", - "ê", - "ë", - "ì", - "í", - "î", - "ï", - "ð", - "ñ", - "ò", - "ó", - "ô", - "õ", - "ö", - None, - "ø", - "ù", - "ú", - "û", - "ü", - "ý", - "þ", - "ss", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ā", - None, - "ă", - None, - "ą", - None, - "ć", - None, - "ĉ", - None, - "ċ", - None, - "č", - None, - "ď", - None, - "đ", - None, - "ē", - None, - "ĕ", - None, - "ė", - None, - "ę", - None, - "ě", - None, - "ĝ", - None, - "ğ", - None, - "ġ", - None, - "ģ", - None, - "ĥ", - None, - "ħ", - None, - "ĩ", - None, - "ī", - None, - "ĭ", - None, - "į", - None, - "i̇", - None, - "ij", - "ĵ", - None, - "ķ", - None, - "ĺ", - None, - "ļ", - None, - "ľ", - None, - "l·", - "ł", - None, - "ń", - None, - "ņ", - None, - "ň", - None, - "ʼn", - "ŋ", - None, - "ō", - None, - "ŏ", - None, - "ő", - None, - "œ", - None, - "ŕ", - None, - "ŗ", - None, - "ř", - None, - "ś", - None, - "ŝ", - None, - "ş", - None, - "š", - None, - "ţ", - None, - "ť", - None, - "ŧ", - None, - "ũ", - None, - "ū", - None, - "ŭ", - None, - "ů", - None, - "ű", - None, - "ų", - None, - "ŵ", - None, - "ŷ", - None, - "ÿ", - "ź", - None, - "ż", - None, - "ž", - None, - "s", - None, - "ɓ", - "ƃ", - None, - "ƅ", - None, - "ɔ", - "ƈ", - None, - "ɖ", - "ɗ", - "ƌ", - None, - "ǝ", - "ə", - "ɛ", - "ƒ", - None, - "ɠ", - "ɣ", - None, - "ɩ", - "ɨ", - "ƙ", - None, - "ɯ", - "ɲ", - None, - "ɵ", - "ơ", - None, - "ƣ", - None, - "ƥ", - None, - "ʀ", - "ƨ", - None, - "ʃ", - None, - "ƭ", - None, - "ʈ", - "ư", - None, - "ʊ", - "ʋ", - "ƴ", - None, - "ƶ", - None, - "ʒ", - "ƹ", - None, - "ƽ", - None, - "dž", - "lj", - "nj", - "ǎ", - None, - "ǐ", - None, - "ǒ", - None, - "ǔ", - None, - "ǖ", - None, - "ǘ", - None, - "ǚ", - None, - "ǜ", - None, - "ǟ", - None, - "ǡ", - None, - "ǣ", - None, - "ǥ", - None, - "ǧ", - None, - "ǩ", - None, - "ǫ", - None, - "ǭ", - None, - "ǯ", - None, - "dz", - "ǵ", - None, - "ƕ", - "ƿ", - "ǹ", - None, - "ǻ", - None, - "ǽ", - None, - "ǿ", - None, - "ȁ", - None, - "ȃ", - None, - "ȅ", - None, - "ȇ", - None, - "ȉ", - None, - "ȋ", - None, - "ȍ", - None, - "ȏ", - None, - "ȑ", - None, - "ȓ", - None, - "ȕ", - None, - "ȗ", - None, - "ș", - None, - "ț", - None, - "ȝ", - None, - "ȟ", - None, - "ƞ", - None, - "ȣ", - None, - "ȥ", - None, - "ȧ", - None, - "ȩ", - None, - "ȫ", - None, - "ȭ", - None, - "ȯ", - None, - "ȱ", - None, - "ȳ", - None, - "ⱥ", - "ȼ", - None, - "ƚ", - "ⱦ", - None, - "ɂ", - None, - "ƀ", - "ʉ", - "ʌ", - "ɇ", - None, - "ɉ", - None, - "ɋ", - None, - "ɍ", - None, - "ɏ", - None, - "h", - "ɦ", - "j", - "r", - "ɹ", - "ɻ", - "ʁ", - "w", - "y", - None, - " ̆", - " ̇", - " ̊", - " ̨", - " ̃", - " ̋", - None, - "ɣ", - "l", - "s", - "x", - "ʕ", - None, - "̀", - "́", - None, - "̓", - "̈́", - "ι", - None, - None, - None, - "ͱ", - None, - "ͳ", - None, - "ʹ", - None, - "ͷ", - None, - None, - " ι", - None, - ";", - "ϳ", - None, - " ́", - " ̈́", - "ά", - "·", - "έ", - "ή", - "ί", - None, - "ό", - None, - "ύ", - "ώ", - None, - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - None, - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "ϊ", - "ϋ", - None, - "σ", - None, - "ϗ", - "β", - "θ", - "υ", - "ύ", - "ϋ", - "φ", - "π", - None, - "ϙ", - None, - "ϛ", - None, - "ϝ", - None, - "ϟ", - None, - "ϡ", - None, - "ϣ", - None, - "ϥ", - None, - "ϧ", - None, - "ϩ", - None, - "ϫ", - None, - "ϭ", - None, - "ϯ", - None, - "κ", - "ρ", - "σ", - None, - "θ", - "ε", - None, - "ϸ", - None, - "σ", - "ϻ", - None, - "ͻ", - "ͼ", - "ͽ", - "ѐ", - "ё", - "ђ", - "ѓ", - "є", - "ѕ", - "і", - "ї", - "ј", - "љ", - "њ", - "ћ", - "ќ", - "ѝ", - "ў", - "џ", - "а", - "б", - "в", - "г", - "д", - "е", - "ж", - "з", - "и", - "й", - "к", - "л", - "м", - "н", - "о", - "п", - "р", - "с", - "т", - "у", - "ф", - "х", - "ц", - "ч", - "ш", - "щ", - "ъ", - "ы", - "ь", - "э", - "ю", - "я", - None, - "ѡ", - None, - "ѣ", - None, - "ѥ", - None, - "ѧ", - None, - "ѩ", - None, - "ѫ", - None, - "ѭ", - None, - "ѯ", - None, - "ѱ", - None, - "ѳ", - None, - "ѵ", - None, - "ѷ", - None, - "ѹ", - None, - "ѻ", - None, - "ѽ", - None, - "ѿ", - None, - "ҁ", - None, - "ҋ", - None, - "ҍ", - None, - "ҏ", - None, - "ґ", - None, - "ғ", - None, - "ҕ", - None, - "җ", - None, - "ҙ", - None, - "қ", - None, - "ҝ", - None, - "ҟ", - None, - "ҡ", - None, - "ң", - None, - "ҥ", - None, - "ҧ", - None, - "ҩ", - None, - "ҫ", - None, - "ҭ", - None, - "ү", - None, - "ұ", - None, - "ҳ", - None, - "ҵ", - None, - "ҷ", - None, - "ҹ", - None, - "һ", - None, - "ҽ", - None, - "ҿ", - None, - "ӏ", - "ӂ", - None, - "ӄ", - None, - "ӆ", - None, - "ӈ", - None, - "ӊ", - None, - "ӌ", - None, - "ӎ", - None, - "ӑ", - None, - "ӓ", - None, - "ӕ", - None, - "ӗ", - None, - "ә", - None, - "ӛ", - None, - "ӝ", - None, - "ӟ", - None, - "ӡ", - None, - "ӣ", - None, - "ӥ", - None, - "ӧ", - None, - "ө", - None, - "ӫ", - None, - "ӭ", - None, - "ӯ", - None, - "ӱ", - None, - "ӳ", - None, - "ӵ", - None, - "ӷ", - None, - "ӹ", - None, - "ӻ", - None, - "ӽ", - None, - "ӿ", - None, - "ԁ", - None, - "ԃ", - None, - "ԅ", - None, - "ԇ", - None, - "ԉ", - None, - "ԋ", - None, - "ԍ", - None, - "ԏ", - None, - "ԑ", - None, - "ԓ", - None, - "ԕ", - None, - "ԗ", - None, - "ԙ", - None, - "ԛ", - None, - "ԝ", - None, - "ԟ", - None, - "ԡ", - None, - "ԣ", - None, - "ԥ", - None, - "ԧ", - None, - "ԩ", - None, - "ԫ", - None, - "ԭ", - None, - "ԯ", - None, - None, - "ա", - "բ", - "գ", - "դ", - "ե", - "զ", - "է", - "ը", - "թ", - "ժ", - "ի", - "լ", - "խ", - "ծ", - "կ", - "հ", - "ձ", - "ղ", - "ճ", - "մ", - "յ", - "ն", - "շ", - "ո", - "չ", - "պ", - "ջ", - "ռ", - "ս", - "վ", - "տ", - "ր", - "ց", - "ւ", - "փ", - "ք", - "օ", - "ֆ", - None, - None, - "եւ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "اٴ", - "وٴ", - "ۇٴ", - "يٴ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "क़", - "ख़", - "ग़", - "ज़", - "ड़", - "ढ़", - "फ़", - "य़", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ড়", - "ঢ়", - None, - "য়", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ਲ਼", - None, - None, - "ਸ਼", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ਖ਼", - "ਗ਼", - "ਜ਼", - None, - None, - "ਫ਼", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ଡ଼", - "ଢ଼", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ํา", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ໍາ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ຫນ", - "ຫມ", - None, - None, - None, - "་", - None, - "གྷ", - None, - None, - None, - "ཌྷ", - None, - "དྷ", - None, - "བྷ", - None, - "ཛྷ", - None, - "ཀྵ", - None, - None, - None, - "ཱི", - None, - "ཱུ", - "ྲྀ", - "ྲཱྀ", - "ླྀ", - "ླཱྀ", - None, - "ཱྀ", - None, - "ྒྷ", - None, - None, - None, - "ྜྷ", - None, - "ྡྷ", - None, - "ྦྷ", - None, - "ྫྷ", - None, - "ྐྵ", - None, - None, - None, - None, - None, - None, - None, - "ⴀ", - "ⴁ", - "ⴂ", - "ⴃ", - "ⴄ", - "ⴅ", - "ⴆ", - "ⴇ", - "ⴈ", - "ⴉ", - "ⴊ", - "ⴋ", - "ⴌ", - "ⴍ", - "ⴎ", - "ⴏ", - "ⴐ", - "ⴑ", - "ⴒ", - "ⴓ", - "ⴔ", - "ⴕ", - "ⴖ", - "ⴗ", - "ⴘ", - "ⴙ", - "ⴚ", - "ⴛ", - "ⴜ", - "ⴝ", - "ⴞ", - "ⴟ", - "ⴠ", - "ⴡ", - "ⴢ", - "ⴣ", - "ⴤ", - "ⴥ", - None, - "ⴧ", - None, - "ⴭ", - None, - None, - "ნ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "Ᏸ", - "Ᏹ", - "Ᏺ", - "Ᏻ", - "Ᏼ", - "Ᏽ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "в", - "д", - "о", - "с", - "т", - "ъ", - "ѣ", - "ꙋ", - "\u1c8a", - None, - None, - "ა", - "ბ", - "გ", - "დ", - "ე", - "ვ", - "ზ", - "თ", - "ი", - "კ", - "ლ", - "მ", - "ნ", - "ო", - "პ", - "ჟ", - "რ", - "ს", - "ტ", - "უ", - "ფ", - "ქ", - "ღ", - "ყ", - "შ", - "ჩ", - "ც", - "ძ", - "წ", - "ჭ", - "ხ", - "ჯ", - "ჰ", - "ჱ", - "ჲ", - "ჳ", - "ჴ", - "ჵ", - "ჶ", - "ჷ", - "ჸ", - "ჹ", - "ჺ", - None, - "ჽ", - "ჾ", - "ჿ", - None, - None, - None, - None, - None, - "a", - "æ", - "b", - None, - "d", - "e", - "ǝ", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - None, - "o", - "ȣ", - "p", - "r", - "t", - "u", - "w", - "a", - "ɐ", - "ɑ", - "ᴂ", - "b", - "d", - "e", - "ə", - "ɛ", - "ɜ", - "g", - None, - "k", - "m", - "ŋ", - "o", - "ɔ", - "ᴖ", - "ᴗ", - "p", - "t", - "u", - "ᴝ", - "ɯ", - "v", - "ᴥ", - "β", - "γ", - "δ", - "φ", - "χ", - "i", - "r", - "u", - "v", - "β", - "γ", - "ρ", - "φ", - "χ", - None, - "н", - None, - "ɒ", - "c", - "ɕ", - "ð", - "ɜ", - "f", - "ɟ", - "ɡ", - "ɥ", - "ɨ", - "ɩ", - "ɪ", - "ᵻ", - "ʝ", - "ɭ", - "ᶅ", - "ʟ", - "ɱ", - "ɰ", - "ɲ", - "ɳ", - "ɴ", - "ɵ", - "ɸ", - "ʂ", - "ʃ", - "ƫ", - "ʉ", - "ʊ", - "ᴜ", - "ʋ", - "ʌ", - "z", - "ʐ", - "ʑ", - "ʒ", - "θ", - None, - "ḁ", - None, - "ḃ", - None, - "ḅ", - None, - "ḇ", - None, - "ḉ", - None, - "ḋ", - None, - "ḍ", - None, - "ḏ", - None, - "ḑ", - None, - "ḓ", - None, - "ḕ", - None, - "ḗ", - None, - "ḙ", - None, - "ḛ", - None, - "ḝ", - None, - "ḟ", - None, - "ḡ", - None, - "ḣ", - None, - "ḥ", - None, - "ḧ", - None, - "ḩ", - None, - "ḫ", - None, - "ḭ", - None, - "ḯ", - None, - "ḱ", - None, - "ḳ", - None, - "ḵ", - None, - "ḷ", - None, - "ḹ", - None, - "ḻ", - None, - "ḽ", - None, - "ḿ", - None, - "ṁ", - None, - "ṃ", - None, - "ṅ", - None, - "ṇ", - None, - "ṉ", - None, - "ṋ", - None, - "ṍ", - None, - "ṏ", - None, - "ṑ", - None, - "ṓ", - None, - "ṕ", - None, - "ṗ", - None, - "ṙ", - None, - "ṛ", - None, - "ṝ", - None, - "ṟ", - None, - "ṡ", - None, - "ṣ", - None, - "ṥ", - None, - "ṧ", - None, - "ṩ", - None, - "ṫ", - None, - "ṭ", - None, - "ṯ", - None, - "ṱ", - None, - "ṳ", - None, - "ṵ", - None, - "ṷ", - None, - "ṹ", - None, - "ṻ", - None, - "ṽ", - None, - "ṿ", - None, - "ẁ", - None, - "ẃ", - None, - "ẅ", - None, - "ẇ", - None, - "ẉ", - None, - "ẋ", - None, - "ẍ", - None, - "ẏ", - None, - "ẑ", - None, - "ẓ", - None, - "ẕ", - None, - "aʾ", - "ṡ", - None, - "ß", - None, - "ạ", - None, - "ả", - None, - "ấ", - None, - "ầ", - None, - "ẩ", - None, - "ẫ", - None, - "ậ", - None, - "ắ", - None, - "ằ", - None, - "ẳ", - None, - "ẵ", - None, - "ặ", - None, - "ẹ", - None, - "ẻ", - None, - "ẽ", - None, - "ế", - None, - "ề", - None, - "ể", - None, - "ễ", - None, - "ệ", - None, - "ỉ", - None, - "ị", - None, - "ọ", - None, - "ỏ", - None, - "ố", - None, - "ồ", - None, - "ổ", - None, - "ỗ", - None, - "ộ", - None, - "ớ", - None, - "ờ", - None, - "ở", - None, - "ỡ", - None, - "ợ", - None, - "ụ", - None, - "ủ", - None, - "ứ", - None, - "ừ", - None, - "ử", - None, - "ữ", - None, - "ự", - None, - "ỳ", - None, - "ỵ", - None, - "ỷ", - None, - "ỹ", - None, - "ỻ", - None, - "ỽ", - None, - "ỿ", - None, - "ἀ", - "ἁ", - "ἂ", - "ἃ", - "ἄ", - "ἅ", - "ἆ", - "ἇ", - None, - None, - "ἐ", - "ἑ", - "ἒ", - "ἓ", - "ἔ", - "ἕ", - None, - None, - "ἠ", - "ἡ", - "ἢ", - "ἣ", - "ἤ", - "ἥ", - "ἦ", - "ἧ", - None, - "ἰ", - "ἱ", - "ἲ", - "ἳ", - "ἴ", - "ἵ", - "ἶ", - "ἷ", - None, - None, - "ὀ", - "ὁ", - "ὂ", - "ὃ", - "ὄ", - "ὅ", - None, - None, - None, - "ὑ", - None, - "ὓ", - None, - "ὕ", - None, - "ὗ", - None, - "ὠ", - "ὡ", - "ὢ", - "ὣ", - "ὤ", - "ὥ", - "ὦ", - "ὧ", - None, - "ά", - None, - "έ", - None, - "ή", - None, - "ί", - None, - "ό", - None, - "ύ", - None, - "ώ", - None, - "ἀι", - "ἁι", - "ἂι", - "ἃι", - "ἄι", - "ἅι", - "ἆι", - "ἇι", - "ἀι", - "ἁι", - "ἂι", - "ἃι", - "ἄι", - "ἅι", - "ἆι", - "ἇι", - "ἠι", - "ἡι", - "ἢι", - "ἣι", - "ἤι", - "ἥι", - "ἦι", - "ἧι", - "ἠι", - "ἡι", - "ἢι", - "ἣι", - "ἤι", - "ἥι", - "ἦι", - "ἧι", - "ὠι", - "ὡι", - "ὢι", - "ὣι", - "ὤι", - "ὥι", - "ὦι", - "ὧι", - "ὠι", - "ὡι", - "ὢι", - "ὣι", - "ὤι", - "ὥι", - "ὦι", - "ὧι", - None, - "ὰι", - "αι", - "άι", - None, - None, - "ᾶι", - "ᾰ", - "ᾱ", - "ὰ", - "ά", - "αι", - " ̓", - "ι", - " ̓", - " ͂", - " ̈͂", - "ὴι", - "ηι", - "ήι", - None, - None, - "ῆι", - "ὲ", - "έ", - "ὴ", - "ή", - "ηι", - " ̓̀", - " ̓́", - " ̓͂", - None, - "ΐ", - None, - None, - "ῐ", - "ῑ", - "ὶ", - "ί", - None, - " ̔̀", - " ̔́", - " ̔͂", - None, - "ΰ", - None, - "ῠ", - "ῡ", - "ὺ", - "ύ", - "ῥ", - " ̈̀", - " ̈́", - "`", - None, - "ὼι", - "ωι", - "ώι", - None, - None, - "ῶι", - "ὸ", - "ό", - "ὼ", - "ώ", - "ωι", - " ́", - " ̔", - None, - " ", - None, - "", - None, - None, - "‐", - None, - " ̳", - None, - None, - None, - None, - " ", - None, - "′′", - "′′′", - None, - "‵‵", - "‵‵‵", - None, - "!!", - None, - " ̅", - None, - "??", - "?!", - "!?", - None, - "′′′′", - None, - " ", - None, - None, - None, - "0", - "i", - None, - "4", - "5", - "6", - "7", - "8", - "9", - "+", - "−", - "=", - "(", - ")", - "n", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "+", - "−", - "=", - "(", - ")", - None, - "a", - "e", - "o", - "x", - "ə", - "h", - "k", - "l", - "m", - "n", - "p", - "s", - "t", - None, - None, - "rs", - None, - None, - None, - None, - "a/c", - "a/s", - "c", - "°c", - None, - "c/o", - "c/u", - "ɛ", - None, - "°f", - "g", - "h", - "ħ", - "i", - "l", - None, - "n", - "no", - None, - "p", - "q", - "r", - None, - "sm", - "tel", - "tm", - None, - "z", - None, - "ω", - None, - "z", - None, - "k", - "å", - "b", - "c", - None, - "e", - "f", - "ⅎ", - "m", - "o", - "א", - "ב", - "ג", - "ד", - "i", - None, - "fax", - "π", - "γ", - "π", - "∑", - None, - "d", - "e", - "i", - "j", - None, - "1⁄7", - "1⁄9", - "1⁄10", - "1⁄3", - "2⁄3", - "1⁄5", - "2⁄5", - "3⁄5", - "4⁄5", - "1⁄6", - "5⁄6", - "1⁄8", - "3⁄8", - "5⁄8", - "7⁄8", - "1⁄", - "i", - "ii", - "iii", - "iv", - "v", - "vi", - "vii", - "viii", - "ix", - "x", - "xi", - "xii", - "l", - "c", - "d", - "m", - "i", - "ii", - "iii", - "iv", - "v", - "vi", - "vii", - "viii", - "ix", - "x", - "xi", - "xii", - "l", - "c", - "d", - "m", - None, - "ↄ", - None, - "0⁄3", - None, - None, - None, - "∫∫", - "∫∫∫", - None, - "∮∮", - "∮∮∮", - None, - "〈", - "〉", - None, - None, - None, - None, - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "20", - "(1)", - "(2)", - "(3)", - "(4)", - "(5)", - "(6)", - "(7)", - "(8)", - "(9)", - "(10)", - "(11)", - "(12)", - "(13)", - "(14)", - "(15)", - "(16)", - "(17)", - "(18)", - "(19)", - "(20)", - None, - "(a)", - "(b)", - "(c)", - "(d)", - "(e)", - "(f)", - "(g)", - "(h)", - "(i)", - "(j)", - "(k)", - "(l)", - "(m)", - "(n)", - "(o)", - "(p)", - "(q)", - "(r)", - "(s)", - "(t)", - "(u)", - "(v)", - "(w)", - "(x)", - "(y)", - "(z)", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "0", - None, - "∫∫∫∫", - None, - "::=", - "==", - "===", - None, - "⫝̸", - None, - None, - None, - "ⰰ", - "ⰱ", - "ⰲ", - "ⰳ", - "ⰴ", - "ⰵ", - "ⰶ", - "ⰷ", - "ⰸ", - "ⰹ", - "ⰺ", - "ⰻ", - "ⰼ", - "ⰽ", - "ⰾ", - "ⰿ", - "ⱀ", - "ⱁ", - "ⱂ", - "ⱃ", - "ⱄ", - "ⱅ", - "ⱆ", - "ⱇ", - "ⱈ", - "ⱉ", - "ⱊ", - "ⱋ", - "ⱌ", - "ⱍ", - "ⱎ", - "ⱏ", - "ⱐ", - "ⱑ", - "ⱒ", - "ⱓ", - "ⱔ", - "ⱕ", - "ⱖ", - "ⱗ", - "ⱘ", - "ⱙ", - "ⱚ", - "ⱛ", - "ⱜ", - "ⱝ", - "ⱞ", - "ⱟ", - None, - "ⱡ", - None, - "ɫ", - "ᵽ", - "ɽ", - None, - "ⱨ", - None, - "ⱪ", - None, - "ⱬ", - None, - "ɑ", - "ɱ", - "ɐ", - "ɒ", - None, - "ⱳ", - None, - "ⱶ", - None, - "j", - "v", - "ȿ", - "ɀ", - "ⲁ", - None, - "ⲃ", - None, - "ⲅ", - None, - "ⲇ", - None, - "ⲉ", - None, - "ⲋ", - None, - "ⲍ", - None, - "ⲏ", - None, - "ⲑ", - None, - "ⲓ", - None, - "ⲕ", - None, - "ⲗ", - None, - "ⲙ", - None, - "ⲛ", - None, - "ⲝ", - None, - "ⲟ", - None, - "ⲡ", - None, - "ⲣ", - None, - "ⲥ", - None, - "ⲧ", - None, - "ⲩ", - None, - "ⲫ", - None, - "ⲭ", - None, - "ⲯ", - None, - "ⲱ", - None, - "ⲳ", - None, - "ⲵ", - None, - "ⲷ", - None, - "ⲹ", - None, - "ⲻ", - None, - "ⲽ", - None, - "ⲿ", - None, - "ⳁ", - None, - "ⳃ", - None, - "ⳅ", - None, - "ⳇ", - None, - "ⳉ", - None, - "ⳋ", - None, - "ⳍ", - None, - "ⳏ", - None, - "ⳑ", - None, - "ⳓ", - None, - "ⳕ", - None, - "ⳗ", - None, - "ⳙ", - None, - "ⳛ", - None, - "ⳝ", - None, - "ⳟ", - None, - "ⳡ", - None, - "ⳣ", - None, - "ⳬ", - None, - "ⳮ", - None, - "ⳳ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ⵡ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "母", - None, - "龟", - None, - "一", - "丨", - "丶", - "丿", - "乙", - "亅", - "二", - "亠", - "人", - "儿", - "入", - "八", - "冂", - "冖", - "冫", - "几", - "凵", - "刀", - "力", - "勹", - "匕", - "匚", - "匸", - "十", - "卜", - "卩", - "厂", - "厶", - "又", - "口", - "囗", - "土", - "士", - "夂", - "夊", - "夕", - "大", - "女", - "子", - "宀", - "寸", - "小", - "尢", - "尸", - "屮", - "山", - "巛", - "工", - "己", - "巾", - "干", - "幺", - "广", - "廴", - "廾", - "弋", - "弓", - "彐", - "彡", - "彳", - "心", - "戈", - "戶", - "手", - "支", - "攴", - "文", - "斗", - "斤", - "方", - "无", - "日", - "曰", - "月", - "木", - "欠", - "止", - "歹", - "殳", - "毋", - "比", - "毛", - "氏", - "气", - "水", - "火", - "爪", - "父", - "爻", - "爿", - "片", - "牙", - "牛", - "犬", - "玄", - "玉", - "瓜", - "瓦", - "甘", - "生", - "用", - "田", - "疋", - "疒", - "癶", - "白", - "皮", - "皿", - "目", - "矛", - "矢", - "石", - "示", - "禸", - "禾", - "穴", - "立", - "竹", - "米", - "糸", - "缶", - "网", - "羊", - "羽", - "老", - "而", - "耒", - "耳", - "聿", - "肉", - "臣", - "自", - "至", - "臼", - "舌", - "舛", - "舟", - "艮", - "色", - "艸", - "虍", - "虫", - "血", - "行", - "衣", - "襾", - "見", - "角", - "言", - "谷", - "豆", - "豕", - "豸", - "貝", - "赤", - "走", - "足", - "身", - "車", - "辛", - "辰", - "辵", - "邑", - "酉", - "釆", - "里", - "金", - "長", - "門", - "阜", - "隶", - "隹", - "雨", - "靑", - "非", - "面", - "革", - "韋", - "韭", - "音", - "頁", - "風", - "飛", - "食", - "首", - "香", - "馬", - "骨", - "高", - "髟", - "鬥", - "鬯", - "鬲", - "鬼", - "魚", - "鳥", - "鹵", - "鹿", - "麥", - "麻", - "黃", - "黍", - "黑", - "黹", - "黽", - "鼎", - "鼓", - "鼠", - "鼻", - "齊", - "齒", - "龍", - "龜", - "龠", - None, - " ", - None, - ".", - None, - "〒", - None, - "十", - "卄", - "卅", - None, - None, - None, - None, - None, - " ゙", - " ゚", - None, - "より", - None, - "コト", - None, - None, - None, - "ᄀ", - "ᄁ", - "ᆪ", - "ᄂ", - "ᆬ", - "ᆭ", - "ᄃ", - "ᄄ", - "ᄅ", - "ᆰ", - "ᆱ", - "ᆲ", - "ᆳ", - "ᆴ", - "ᆵ", - "ᄚ", - "ᄆ", - "ᄇ", - "ᄈ", - "ᄡ", - "ᄉ", - "ᄊ", - "ᄋ", - "ᄌ", - "ᄍ", - "ᄎ", - "ᄏ", - "ᄐ", - "ᄑ", - "ᄒ", - "ᅡ", - "ᅢ", - "ᅣ", - "ᅤ", - "ᅥ", - "ᅦ", - "ᅧ", - "ᅨ", - "ᅩ", - "ᅪ", - "ᅫ", - "ᅬ", - "ᅭ", - "ᅮ", - "ᅯ", - "ᅰ", - "ᅱ", - "ᅲ", - "ᅳ", - "ᅴ", - "ᅵ", - None, - "ᄔ", - "ᄕ", - "ᇇ", - "ᇈ", - "ᇌ", - "ᇎ", - "ᇓ", - "ᇗ", - "ᇙ", - "ᄜ", - "ᇝ", - "ᇟ", - "ᄝ", - "ᄞ", - "ᄠ", - "ᄢ", - "ᄣ", - "ᄧ", - "ᄩ", - "ᄫ", - "ᄬ", - "ᄭ", - "ᄮ", - "ᄯ", - "ᄲ", - "ᄶ", - "ᅀ", - "ᅇ", - "ᅌ", - "ᇱ", - "ᇲ", - "ᅗ", - "ᅘ", - "ᅙ", - "ᆄ", - "ᆅ", - "ᆈ", - "ᆑ", - "ᆒ", - "ᆔ", - "ᆞ", - "ᆡ", - None, - None, - "一", - "二", - "三", - "四", - "上", - "中", - "下", - "甲", - "乙", - "丙", - "丁", - "天", - "地", - "人", - None, - None, - None, - "(ᄀ)", - "(ᄂ)", - "(ᄃ)", - "(ᄅ)", - "(ᄆ)", - "(ᄇ)", - "(ᄉ)", - "(ᄋ)", - "(ᄌ)", - "(ᄎ)", - "(ᄏ)", - "(ᄐ)", - "(ᄑ)", - "(ᄒ)", - "(가)", - "(나)", - "(다)", - "(라)", - "(마)", - "(바)", - "(사)", - "(아)", - "(자)", - "(차)", - "(카)", - "(타)", - "(파)", - "(하)", - "(주)", - "(오전)", - "(오후)", - None, - "(一)", - "(二)", - "(三)", - "(四)", - "(五)", - "(六)", - "(七)", - "(八)", - "(九)", - "(十)", - "(月)", - "(火)", - "(水)", - "(木)", - "(金)", - "(土)", - "(日)", - "(株)", - "(有)", - "(社)", - "(名)", - "(特)", - "(財)", - "(祝)", - "(労)", - "(代)", - "(呼)", - "(学)", - "(監)", - "(企)", - "(資)", - "(協)", - "(祭)", - "(休)", - "(自)", - "(至)", - "問", - "幼", - "文", - "箏", - None, - "pte", - "21", - "22", - "23", - "24", - "25", - "26", - "27", - "28", - "29", - "30", - "31", - "32", - "33", - "34", - "35", - "ᄀ", - "ᄂ", - "ᄃ", - "ᄅ", - "ᄆ", - "ᄇ", - "ᄉ", - "ᄋ", - "ᄌ", - "ᄎ", - "ᄏ", - "ᄐ", - "ᄑ", - "ᄒ", - "가", - "나", - "다", - "라", - "마", - "바", - "사", - "아", - "자", - "차", - "카", - "타", - "파", - "하", - "참고", - "주의", - "우", - None, - "一", - "二", - "三", - "四", - "五", - "六", - "七", - "八", - "九", - "十", - "月", - "火", - "水", - "木", - "金", - "土", - "日", - "株", - "有", - "社", - "名", - "特", - "財", - "祝", - "労", - "秘", - "男", - "女", - "適", - "優", - "印", - "注", - "項", - "休", - "写", - "正", - "上", - "中", - "下", - "左", - "右", - "医", - "宗", - "学", - "監", - "企", - "資", - "協", - "夜", - "36", - "37", - "38", - "39", - "40", - "41", - "42", - "43", - "44", - "45", - "46", - "47", - "48", - "49", - "50", - "1月", - "2月", - "3月", - "4月", - "5月", - "6月", - "7月", - "8月", - "9月", - "10月", - "11月", - "12月", - "hg", - "erg", - "ev", - "ltd", - "ア", - "イ", - "ウ", - "エ", - "オ", - "カ", - "キ", - "ク", - "ケ", - "コ", - "サ", - "シ", - "ス", - "セ", - "ソ", - "タ", - "チ", - "ツ", - "テ", - "ト", - "ナ", - "ニ", - "ヌ", - "ネ", - "ノ", - "ハ", - "ヒ", - "フ", - "ヘ", - "ホ", - "マ", - "ミ", - "ム", - "メ", - "モ", - "ヤ", - "ユ", - "ヨ", - "ラ", - "リ", - "ル", - "レ", - "ロ", - "ワ", - "ヰ", - "ヱ", - "ヲ", - "令和", - "アパート", - "アルファ", - "アンペア", - "アール", - "イニング", - "インチ", - "ウォン", - "エスクード", - "エーカー", - "オンス", - "オーム", - "カイリ", - "カラット", - "カロリー", - "ガロン", - "ガンマ", - "ギガ", - "ギニー", - "キュリー", - "ギルダー", - "キロ", - "キログラム", - "キロメートル", - "キロワット", - "グラム", - "グラムトン", - "クルゼイロ", - "クローネ", - "ケース", - "コルナ", - "コーポ", - "サイクル", - "サンチーム", - "シリング", - "センチ", - "セント", - "ダース", - "デシ", - "ドル", - "トン", - "ナノ", - "ノット", - "ハイツ", - "パーセント", - "パーツ", - "バーレル", - "ピアストル", - "ピクル", - "ピコ", - "ビル", - "ファラッド", - "フィート", - "ブッシェル", - "フラン", - "ヘクタール", - "ペソ", - "ペニヒ", - "ヘルツ", - "ペンス", - "ページ", - "ベータ", - "ポイント", - "ボルト", - "ホン", - "ポンド", - "ホール", - "ホーン", - "マイクロ", - "マイル", - "マッハ", - "マルク", - "マンション", - "ミクロン", - "ミリ", - "ミリバール", - "メガ", - "メガトン", - "メートル", - "ヤード", - "ヤール", - "ユアン", - "リットル", - "リラ", - "ルピー", - "ルーブル", - "レム", - "レントゲン", - "ワット", - "0点", - "1点", - "2点", - "3点", - "4点", - "5点", - "6点", - "7点", - "8点", - "9点", - "10点", - "11点", - "12点", - "13点", - "14点", - "15点", - "16点", - "17点", - "18点", - "19点", - "20点", - "21点", - "22点", - "23点", - "24点", - "hpa", - "da", - "au", - "bar", - "ov", - "pc", - "dm", - "dm2", - "dm3", - "iu", - "平成", - "昭和", - "大正", - "明治", - "株式会社", - "pa", - "na", - "μa", - "ma", - "ka", - "kb", - "mb", - "gb", - "cal", - "kcal", - "pf", - "nf", - "μf", - "μg", - "mg", - "kg", - "hz", - "khz", - "mhz", - "ghz", - "thz", - "μl", - "ml", - "dl", - "kl", - "fm", - "nm", - "μm", - "mm", - "cm", - "km", - "mm2", - "cm2", - "m2", - "km2", - "mm3", - "cm3", - "m3", - "km3", - "m∕s", - "m∕s2", - "pa", - "kpa", - "mpa", - "gpa", - "rad", - "rad∕s", - "rad∕s2", - "ps", - "ns", - "μs", - "ms", - "pv", - "nv", - "μv", - "mv", - "kv", - "mv", - "pw", - "nw", - "μw", - "mw", - "kw", - "mw", - "kω", - "mω", - None, - "bq", - "cc", - "cd", - "c∕kg", - None, - "db", - "gy", - "ha", - "hp", - "in", - "kk", - "km", - "kt", - "lm", - "ln", - "log", - "lx", - "mb", - "mil", - "mol", - "ph", - None, - "ppm", - "pr", - "sr", - "sv", - "wb", - "v∕m", - "a∕m", - "1日", - "2日", - "3日", - "4日", - "5日", - "6日", - "7日", - "8日", - "9日", - "10日", - "11日", - "12日", - "13日", - "14日", - "15日", - "16日", - "17日", - "18日", - "19日", - "20日", - "21日", - "22日", - "23日", - "24日", - "25日", - "26日", - "27日", - "28日", - "29日", - "30日", - "31日", - "gal", - None, - None, - None, - None, - None, - None, - "ꙁ", - None, - "ꙃ", - None, - "ꙅ", - None, - "ꙇ", - None, - "ꙉ", - None, - "ꙋ", - None, - "ꙍ", - None, - "ꙏ", - None, - "ꙑ", - None, - "ꙓ", - None, - "ꙕ", - None, - "ꙗ", - None, - "ꙙ", - None, - "ꙛ", - None, - "ꙝ", - None, - "ꙟ", - None, - "ꙡ", - None, - "ꙣ", - None, - "ꙥ", - None, - "ꙧ", - None, - "ꙩ", - None, - "ꙫ", - None, - "ꙭ", - None, - "ꚁ", - None, - "ꚃ", - None, - "ꚅ", - None, - "ꚇ", - None, - "ꚉ", - None, - "ꚋ", - None, - "ꚍ", - None, - "ꚏ", - None, - "ꚑ", - None, - "ꚓ", - None, - "ꚕ", - None, - "ꚗ", - None, - "ꚙ", - None, - "ꚛ", - None, - "ъ", - "ь", - None, - None, - None, - "ꜣ", - None, - "ꜥ", - None, - "ꜧ", - None, - "ꜩ", - None, - "ꜫ", - None, - "ꜭ", - None, - "ꜯ", - None, - "ꜳ", - None, - "ꜵ", - None, - "ꜷ", - None, - "ꜹ", - None, - "ꜻ", - None, - "ꜽ", - None, - "ꜿ", - None, - "ꝁ", - None, - "ꝃ", - None, - "ꝅ", - None, - "ꝇ", - None, - "ꝉ", - None, - "ꝋ", - None, - "ꝍ", - None, - "ꝏ", - None, - "ꝑ", - None, - "ꝓ", - None, - "ꝕ", - None, - "ꝗ", - None, - "ꝙ", - None, - "ꝛ", - None, - "ꝝ", - None, - "ꝟ", - None, - "ꝡ", - None, - "ꝣ", - None, - "ꝥ", - None, - "ꝧ", - None, - "ꝩ", - None, - "ꝫ", - None, - "ꝭ", - None, - "ꝯ", - None, - "ꝯ", - None, - "ꝺ", - None, - "ꝼ", - None, - "ᵹ", - "ꝿ", - None, - "ꞁ", - None, - "ꞃ", - None, - "ꞅ", - None, - "ꞇ", - None, - "ꞌ", - None, - "ɥ", - None, - "ꞑ", - None, - "ꞓ", - None, - "ꞗ", - None, - "ꞙ", - None, - "ꞛ", - None, - "ꞝ", - None, - "ꞟ", - None, - "ꞡ", - None, - "ꞣ", - None, - "ꞥ", - None, - "ꞧ", - None, - "ꞩ", - None, - "ɦ", - "ɜ", - "ɡ", - "ɬ", - "ɪ", - None, - "ʞ", - "ʇ", - "ʝ", - "ꭓ", - "ꞵ", - None, - "ꞷ", - None, - "ꞹ", - None, - "ꞻ", - None, - "ꞽ", - None, - "ꞿ", - None, - "ꟁ", - None, - "ꟃ", - None, - "ꞔ", - "ʂ", - "ᶎ", - "ꟈ", - None, - "ꟊ", - None, - "ɤ", - "\ua7cd", - None, - "\ua7cf", - None, - "ꟑ", - None, - "ꟓ", - None, - "ꟕ", - None, - "ꟗ", - None, - "ꟙ", - None, - "\ua7db", - None, - "ƛ", - None, - "s", - "c", - "f", - "q", - "ꟶ", - None, - "ħ", - "œ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ꜧ", - "ꬷ", - "ɫ", - "ꭒ", - None, - "ʍ", - None, - None, - "Ꭰ", - "Ꭱ", - "Ꭲ", - "Ꭳ", - "Ꭴ", - "Ꭵ", - "Ꭶ", - "Ꭷ", - "Ꭸ", - "Ꭹ", - "Ꭺ", - "Ꭻ", - "Ꭼ", - "Ꭽ", - "Ꭾ", - "Ꭿ", - "Ꮀ", - "Ꮁ", - "Ꮂ", - "Ꮃ", - "Ꮄ", - "Ꮅ", - "Ꮆ", - "Ꮇ", - "Ꮈ", - "Ꮉ", - "Ꮊ", - "Ꮋ", - "Ꮌ", - "Ꮍ", - "Ꮎ", - "Ꮏ", - "Ꮐ", - "Ꮑ", - "Ꮒ", - "Ꮓ", - "Ꮔ", - "Ꮕ", - "Ꮖ", - "Ꮗ", - "Ꮘ", - "Ꮙ", - "Ꮚ", - "Ꮛ", - "Ꮜ", - "Ꮝ", - "Ꮞ", - "Ꮟ", - "Ꮠ", - "Ꮡ", - "Ꮢ", - "Ꮣ", - "Ꮤ", - "Ꮥ", - "Ꮦ", - "Ꮧ", - "Ꮨ", - "Ꮩ", - "Ꮪ", - "Ꮫ", - "Ꮬ", - "Ꮭ", - "Ꮮ", - "Ꮯ", - "Ꮰ", - "Ꮱ", - "Ꮲ", - "Ꮳ", - "Ꮴ", - "Ꮵ", - "Ꮶ", - "Ꮷ", - "Ꮸ", - "Ꮹ", - "Ꮺ", - "Ꮻ", - "Ꮼ", - "Ꮽ", - "Ꮾ", - "Ꮿ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "豈", - "更", - "車", - "賈", - "滑", - "串", - "句", - "龜", - "契", - "金", - "喇", - "奈", - "懶", - "癩", - "羅", - "蘿", - "螺", - "裸", - "邏", - "樂", - "洛", - "烙", - "珞", - "落", - "酪", - "駱", - "亂", - "卵", - "欄", - "爛", - "蘭", - "鸞", - "嵐", - "濫", - "藍", - "襤", - "拉", - "臘", - "蠟", - "廊", - "朗", - "浪", - "狼", - "郎", - "來", - "冷", - "勞", - "擄", - "櫓", - "爐", - "盧", - "老", - "蘆", - "虜", - "路", - "露", - "魯", - "鷺", - "碌", - "祿", - "綠", - "菉", - "錄", - "鹿", - "論", - "壟", - "弄", - "籠", - "聾", - "牢", - "磊", - "賂", - "雷", - "壘", - "屢", - "樓", - "淚", - "漏", - "累", - "縷", - "陋", - "勒", - "肋", - "凜", - "凌", - "稜", - "綾", - "菱", - "陵", - "讀", - "拏", - "樂", - "諾", - "丹", - "寧", - "怒", - "率", - "異", - "北", - "磻", - "便", - "復", - "不", - "泌", - "數", - "索", - "參", - "塞", - "省", - "葉", - "說", - "殺", - "辰", - "沈", - "拾", - "若", - "掠", - "略", - "亮", - "兩", - "凉", - "梁", - "糧", - "良", - "諒", - "量", - "勵", - "呂", - "女", - "廬", - "旅", - "濾", - "礪", - "閭", - "驪", - "麗", - "黎", - "力", - "曆", - "歷", - "轢", - "年", - "憐", - "戀", - "撚", - "漣", - "煉", - "璉", - "秊", - "練", - "聯", - "輦", - "蓮", - "連", - "鍊", - "列", - "劣", - "咽", - "烈", - "裂", - "說", - "廉", - "念", - "捻", - "殮", - "簾", - "獵", - "令", - "囹", - "寧", - "嶺", - "怜", - "玲", - "瑩", - "羚", - "聆", - "鈴", - "零", - "靈", - "領", - "例", - "禮", - "醴", - "隸", - "惡", - "了", - "僚", - "寮", - "尿", - "料", - "樂", - "燎", - "療", - "蓼", - "遼", - "龍", - "暈", - "阮", - "劉", - "杻", - "柳", - "流", - "溜", - "琉", - "留", - "硫", - "紐", - "類", - "六", - "戮", - "陸", - "倫", - "崙", - "淪", - "輪", - "律", - "慄", - "栗", - "率", - "隆", - "利", - "吏", - "履", - "易", - "李", - "梨", - "泥", - "理", - "痢", - "罹", - "裏", - "裡", - "里", - "離", - "匿", - "溺", - "吝", - "燐", - "璘", - "藺", - "隣", - "鱗", - "麟", - "林", - "淋", - "臨", - "立", - "笠", - "粒", - "狀", - "炙", - "識", - "什", - "茶", - "刺", - "切", - "度", - "拓", - "糖", - "宅", - "洞", - "暴", - "輻", - "行", - "降", - "見", - "廓", - "兀", - "嗀", - None, - "塚", - None, - "晴", - None, - "凞", - "猪", - "益", - "礼", - "神", - "祥", - "福", - "靖", - "精", - "羽", - None, - "蘒", - None, - "諸", - None, - "逸", - "都", - None, - "飯", - "飼", - "館", - "鶴", - "郞", - "隷", - "侮", - "僧", - "免", - "勉", - "勤", - "卑", - "喝", - "嘆", - "器", - "塀", - "墨", - "層", - "屮", - "悔", - "慨", - "憎", - "懲", - "敏", - "既", - "暑", - "梅", - "海", - "渚", - "漢", - "煮", - "爫", - "琢", - "碑", - "社", - "祉", - "祈", - "祐", - "祖", - "祝", - "禍", - "禎", - "穀", - "突", - "節", - "練", - "縉", - "繁", - "署", - "者", - "臭", - "艹", - "著", - "褐", - "視", - "謁", - "謹", - "賓", - "贈", - "辶", - "逸", - "難", - "響", - "頻", - "恵", - "𤋮", - "舘", - None, - "並", - "况", - "全", - "侀", - "充", - "冀", - "勇", - "勺", - "喝", - "啕", - "喙", - "嗢", - "塚", - "墳", - "奄", - "奔", - "婢", - "嬨", - "廒", - "廙", - "彩", - "徭", - "惘", - "慎", - "愈", - "憎", - "慠", - "懲", - "戴", - "揄", - "搜", - "摒", - "敖", - "晴", - "朗", - "望", - "杖", - "歹", - "殺", - "流", - "滛", - "滋", - "漢", - "瀞", - "煮", - "瞧", - "爵", - "犯", - "猪", - "瑱", - "甆", - "画", - "瘝", - "瘟", - "益", - "盛", - "直", - "睊", - "着", - "磌", - "窱", - "節", - "类", - "絛", - "練", - "缾", - "者", - "荒", - "華", - "蝹", - "襁", - "覆", - "視", - "調", - "諸", - "請", - "謁", - "諾", - "諭", - "謹", - "變", - "贈", - "輸", - "遲", - "醙", - "鉶", - "陼", - "難", - "靖", - "韛", - "響", - "頋", - "頻", - "鬒", - "龜", - "𢡊", - "𢡄", - "𣏕", - "㮝", - "䀘", - "䀹", - "𥉉", - "𥳐", - "𧻓", - "齃", - "龎", - None, - "ff", - "fi", - "fl", - "ffi", - "ffl", - "st", - None, - "մն", - "մե", - "մի", - "վն", - "մխ", - None, - "יִ", - None, - "ײַ", - "ע", - "א", - "ד", - "ה", - "כ", - "ל", - "ם", - "ר", - "ת", - "+", - "שׁ", - "שׂ", - "שּׁ", - "שּׂ", - "אַ", - "אָ", - "אּ", - "בּ", - "גּ", - "דּ", - "הּ", - "וּ", - "זּ", - None, - "טּ", - "יּ", - "ךּ", - "כּ", - "לּ", - None, - "מּ", - None, - "נּ", - "סּ", - None, - "ףּ", - "פּ", - None, - "צּ", - "קּ", - "רּ", - "שּ", - "תּ", - "וֹ", - "בֿ", - "כֿ", - "פֿ", - "אל", - "ٱ", - "ٻ", - "پ", - "ڀ", - "ٺ", - "ٿ", - "ٹ", - "ڤ", - "ڦ", - "ڄ", - "ڃ", - "چ", - "ڇ", - "ڍ", - "ڌ", - "ڎ", - "ڈ", - "ژ", - "ڑ", - "ک", - "گ", - "ڳ", - "ڱ", - "ں", - "ڻ", - "ۀ", - "ہ", - "ھ", - "ے", - "ۓ", - None, - "ڭ", - "ۇ", - "ۆ", - "ۈ", - "ۇٴ", - "ۋ", - "ۅ", - "ۉ", - "ې", - "ى", - "ئا", - "ئە", - "ئو", - "ئۇ", - "ئۆ", - "ئۈ", - "ئې", - "ئى", - "ی", - "ئج", - "ئح", - "ئم", - "ئى", - "ئي", - "بج", - "بح", - "بخ", - "بم", - "بى", - "بي", - "تج", - "تح", - "تخ", - "تم", - "تى", - "تي", - "ثج", - "ثم", - "ثى", - "ثي", - "جح", - "جم", - "حج", - "حم", - "خج", - "خح", - "خم", - "سج", - "سح", - "سخ", - "سم", - "صح", - "صم", - "ضج", - "ضح", - "ضخ", - "ضم", - "طح", - "طم", - "ظم", - "عج", - "عم", - "غج", - "غم", - "فج", - "فح", - "فخ", - "فم", - "فى", - "في", - "قح", - "قم", - "قى", - "قي", - "كا", - "كج", - "كح", - "كخ", - "كل", - "كم", - "كى", - "كي", - "لج", - "لح", - "لخ", - "لم", - "لى", - "لي", - "مج", - "مح", - "مخ", - "مم", - "مى", - "مي", - "نج", - "نح", - "نخ", - "نم", - "نى", - "ني", - "هج", - "هم", - "هى", - "هي", - "يج", - "يح", - "يخ", - "يم", - "يى", - "يي", - "ذٰ", - "رٰ", - "ىٰ", - " ٌّ", - " ٍّ", - " َّ", - " ُّ", - " ِّ", - " ّٰ", - "ئر", - "ئز", - "ئم", - "ئن", - "ئى", - "ئي", - "بر", - "بز", - "بم", - "بن", - "بى", - "بي", - "تر", - "تز", - "تم", - "تن", - "تى", - "تي", - "ثر", - "ثز", - "ثم", - "ثن", - "ثى", - "ثي", - "فى", - "في", - "قى", - "قي", - "كا", - "كل", - "كم", - "كى", - "كي", - "لم", - "لى", - "لي", - "ما", - "مم", - "نر", - "نز", - "نم", - "نن", - "نى", - "ني", - "ىٰ", - "ير", - "يز", - "يم", - "ين", - "يى", - "يي", - "ئج", - "ئح", - "ئخ", - "ئم", - "ئه", - "بج", - "بح", - "بخ", - "بم", - "به", - "تج", - "تح", - "تخ", - "تم", - "ته", - "ثم", - "جح", - "جم", - "حج", - "حم", - "خج", - "خم", - "سج", - "سح", - "سخ", - "سم", - "صح", - "صخ", - "صم", - "ضج", - "ضح", - "ضخ", - "ضم", - "طح", - "ظم", - "عج", - "عم", - "غج", - "غم", - "فج", - "فح", - "فخ", - "فم", - "قح", - "قم", - "كج", - "كح", - "كخ", - "كل", - "كم", - "لج", - "لح", - "لخ", - "لم", - "له", - "مج", - "مح", - "مخ", - "مم", - "نج", - "نح", - "نخ", - "نم", - "نه", - "هج", - "هم", - "هٰ", - "يج", - "يح", - "يخ", - "يم", - "يه", - "ئم", - "ئه", - "بم", - "به", - "تم", - "ته", - "ثم", - "ثه", - "سم", - "سه", - "شم", - "شه", - "كل", - "كم", - "لم", - "نم", - "نه", - "يم", - "يه", - "ـَّ", - "ـُّ", - "ـِّ", - "طى", - "طي", - "عى", - "عي", - "غى", - "غي", - "سى", - "سي", - "شى", - "شي", - "حى", - "حي", - "جى", - "جي", - "خى", - "خي", - "صى", - "صي", - "ضى", - "ضي", - "شج", - "شح", - "شخ", - "شم", - "شر", - "سر", - "صر", - "ضر", - "طى", - "طي", - "عى", - "عي", - "غى", - "غي", - "سى", - "سي", - "شى", - "شي", - "حى", - "حي", - "جى", - "جي", - "خى", - "خي", - "صى", - "صي", - "ضى", - "ضي", - "شج", - "شح", - "شخ", - "شم", - "شر", - "سر", - "صر", - "ضر", - "شج", - "شح", - "شخ", - "شم", - "سه", - "شه", - "طم", - "سج", - "سح", - "سخ", - "شج", - "شح", - "شخ", - "طم", - "ظم", - "اً", - None, - "تجم", - "تحج", - "تحم", - "تخم", - "تمج", - "تمح", - "تمخ", - "جمح", - "حمي", - "حمى", - "سحج", - "سجح", - "سجى", - "سمح", - "سمج", - "سمم", - "صحح", - "صمم", - "شحم", - "شجي", - "شمخ", - "شمم", - "ضحى", - "ضخم", - "طمح", - "طمم", - "طمي", - "عجم", - "عمم", - "عمى", - "غمم", - "غمي", - "غمى", - "فخم", - "قمح", - "قمم", - "لحم", - "لحي", - "لحى", - "لجج", - "لخم", - "لمح", - "محج", - "محم", - "محي", - "مجح", - "مجم", - "مخج", - "مخم", - None, - "مجخ", - "همج", - "همم", - "نحم", - "نحى", - "نجم", - "نجى", - "نمي", - "نمى", - "يمم", - "بخي", - "تجي", - "تجى", - "تخي", - "تخى", - "تمي", - "تمى", - "جمي", - "جحى", - "جمى", - "سخى", - "صحي", - "شحي", - "ضحي", - "لجي", - "لمي", - "يحي", - "يجي", - "يمي", - "ممي", - "قمي", - "نحي", - "قمح", - "لحم", - "عمي", - "كمي", - "نجح", - "مخي", - "لجم", - "كمم", - "لجم", - "نجح", - "جحي", - "حجي", - "مجي", - "فمي", - "بحي", - "كمم", - "عجم", - "صمم", - "سخي", - "نجي", - None, - None, - "صلے", - "قلے", - "الله", - "اكبر", - "محمد", - "صلعم", - "رسول", - "عليه", - "وسلم", - "صلى", - "صلى الله عليه وسلم", - "جل جلاله", - "ریال", - None, - None, - ",", - "、", - None, - ":", - ";", - "!", - "?", - "〖", - "〗", - None, - None, - None, - "—", - "–", - "_", - "(", - ")", - "{", - "}", - "〔", - "〕", - "【", - "】", - "《", - "》", - "〈", - "〉", - "「", - "」", - "『", - "』", - None, - "[", - "]", - " ̅", - "_", - ",", - "、", - None, - ";", - ":", - "?", - "!", - "—", - "(", - ")", - "{", - "}", - "〔", - "〕", - "#", - "&", - "*", - "+", - "-", - "<", - ">", - "=", - None, - "\\", - "$", - "%", - "@", - None, - " ً", - "ـً", - " ٌ", - None, - " ٍ", - None, - " َ", - "ـَ", - " ُ", - "ـُ", - " ِ", - "ـِ", - " ّ", - "ـّ", - " ْ", - "ـْ", - "ء", - "آ", - "أ", - "ؤ", - "إ", - "ئ", - "ا", - "ب", - "ة", - "ت", - "ث", - "ج", - "ح", - "خ", - "د", - "ذ", - "ر", - "ز", - "س", - "ش", - "ص", - "ض", - "ط", - "ظ", - "ع", - "غ", - "ف", - "ق", - "ك", - "ل", - "م", - "ن", - "ه", - "و", - "ى", - "ي", - "لآ", - "لأ", - "لإ", - "لا", - None, - None, - None, - "!", - '"', - "#", - "$", - "%", - "&", - "'", - "(", - ")", - "*", - "+", - ",", - "-", - ".", - "/", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - ":", - ";", - "<", - "=", - ">", - "?", - "@", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "[", - "\\", - "]", - "^", - "_", - "`", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "{", - "|", - "}", - "~", - "⦅", - "⦆", - ".", - "「", - "」", - "、", - "・", - "ヲ", - "ァ", - "ィ", - "ゥ", - "ェ", - "ォ", - "ャ", - "ュ", - "ョ", - "ッ", - "ー", - "ア", - "イ", - "ウ", - "エ", - "オ", - "カ", - "キ", - "ク", - "ケ", - "コ", - "サ", - "シ", - "ス", - "セ", - "ソ", - "タ", - "チ", - "ツ", - "テ", - "ト", - "ナ", - "ニ", - "ヌ", - "ネ", - "ノ", - "ハ", - "ヒ", - "フ", - "ヘ", - "ホ", - "マ", - "ミ", - "ム", - "メ", - "モ", - "ヤ", - "ユ", - "ヨ", - "ラ", - "リ", - "ル", - "レ", - "ロ", - "ワ", - "ン", - "゙", - "゚", - None, - "ᄀ", - "ᄁ", - "ᆪ", - "ᄂ", - "ᆬ", - "ᆭ", - "ᄃ", - "ᄄ", - "ᄅ", - "ᆰ", - "ᆱ", - "ᆲ", - "ᆳ", - "ᆴ", - "ᆵ", - "ᄚ", - "ᄆ", - "ᄇ", - "ᄈ", - "ᄡ", - "ᄉ", - "ᄊ", - "ᄋ", - "ᄌ", - "ᄍ", - "ᄎ", - "ᄏ", - "ᄐ", - "ᄑ", - "ᄒ", - None, - "ᅡ", - "ᅢ", - "ᅣ", - "ᅤ", - "ᅥ", - "ᅦ", - None, - "ᅧ", - "ᅨ", - "ᅩ", - "ᅪ", - "ᅫ", - "ᅬ", - None, - "ᅭ", - "ᅮ", - "ᅯ", - "ᅰ", - "ᅱ", - "ᅲ", - None, - "ᅳ", - "ᅴ", - "ᅵ", - None, - "¢", - "£", - "¬", - " ̄", - "¦", - "¥", - "₩", - None, - "│", - "←", - "↑", - "→", - "↓", - "■", - "○", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "𐐨", - "𐐩", - "𐐪", - "𐐫", - "𐐬", - "𐐭", - "𐐮", - "𐐯", - "𐐰", - "𐐱", - "𐐲", - "𐐳", - "𐐴", - "𐐵", - "𐐶", - "𐐷", - "𐐸", - "𐐹", - "𐐺", - "𐐻", - "𐐼", - "𐐽", - "𐐾", - "𐐿", - "𐑀", - "𐑁", - "𐑂", - "𐑃", - "𐑄", - "𐑅", - "𐑆", - "𐑇", - "𐑈", - "𐑉", - "𐑊", - "𐑋", - "𐑌", - "𐑍", - "𐑎", - "𐑏", - None, - None, - None, - None, - "𐓘", - "𐓙", - "𐓚", - "𐓛", - "𐓜", - "𐓝", - "𐓞", - "𐓟", - "𐓠", - "𐓡", - "𐓢", - "𐓣", - "𐓤", - "𐓥", - "𐓦", - "𐓧", - "𐓨", - "𐓩", - "𐓪", - "𐓫", - "𐓬", - "𐓭", - "𐓮", - "𐓯", - "𐓰", - "𐓱", - "𐓲", - "𐓳", - "𐓴", - "𐓵", - "𐓶", - "𐓷", - "𐓸", - "𐓹", - "𐓺", - "𐓻", - None, - None, - None, - None, - None, - None, - None, - None, - "𐖗", - "𐖘", - "𐖙", - "𐖚", - "𐖛", - "𐖜", - "𐖝", - "𐖞", - "𐖟", - "𐖠", - "𐖡", - None, - "𐖣", - "𐖤", - "𐖥", - "𐖦", - "𐖧", - "𐖨", - "𐖩", - "𐖪", - "𐖫", - "𐖬", - "𐖭", - "𐖮", - "𐖯", - "𐖰", - "𐖱", - None, - "𐖳", - "𐖴", - "𐖵", - "𐖶", - "𐖷", - "𐖸", - "𐖹", - None, - "𐖻", - "𐖼", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ː", - "ˑ", - "æ", - "ʙ", - "ɓ", - None, - "ʣ", - "ꭦ", - "ʥ", - "ʤ", - "ɖ", - "ɗ", - "ᶑ", - "ɘ", - "ɞ", - "ʩ", - "ɤ", - "ɢ", - "ɠ", - "ʛ", - "ħ", - "ʜ", - "ɧ", - "ʄ", - "ʪ", - "ʫ", - "ɬ", - "𝼄", - "ꞎ", - "ɮ", - "𝼅", - "ʎ", - "𝼆", - "ø", - "ɶ", - "ɷ", - "q", - "ɺ", - "𝼈", - "ɽ", - "ɾ", - "ʀ", - "ʨ", - "ʦ", - "ꭧ", - "ʧ", - "ʈ", - "ⱱ", - None, - "ʏ", - "ʡ", - "ʢ", - "ʘ", - "ǀ", - "ǁ", - "ǂ", - "𝼊", - "𝼞", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "𐳀", - "𐳁", - "𐳂", - "𐳃", - "𐳄", - "𐳅", - "𐳆", - "𐳇", - "𐳈", - "𐳉", - "𐳊", - "𐳋", - "𐳌", - "𐳍", - "𐳎", - "𐳏", - "𐳐", - "𐳑", - "𐳒", - "𐳓", - "𐳔", - "𐳕", - "𐳖", - "𐳗", - "𐳘", - "𐳙", - "𐳚", - "𐳛", - "𐳜", - "𐳝", - "𐳞", - "𐳟", - "𐳠", - "𐳡", - "𐳢", - "𐳣", - "𐳤", - "𐳥", - "𐳦", - "𐳧", - "𐳨", - "𐳩", - "𐳪", - "𐳫", - "𐳬", - "𐳭", - "𐳮", - "𐳯", - "𐳰", - "𐳱", - "𐳲", - None, - None, - None, - None, - None, - None, - None, - None, - "\U00010d70", - "\U00010d71", - "\U00010d72", - "\U00010d73", - "\U00010d74", - "\U00010d75", - "\U00010d76", - "\U00010d77", - "\U00010d78", - "\U00010d79", - "\U00010d7a", - "\U00010d7b", - "\U00010d7c", - "\U00010d7d", - "\U00010d7e", - "\U00010d7f", - "\U00010d80", - "\U00010d81", - "\U00010d82", - "\U00010d83", - "\U00010d84", - "\U00010d85", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "𑣀", - "𑣁", - "𑣂", - "𑣃", - "𑣄", - "𑣅", - "𑣆", - "𑣇", - "𑣈", - "𑣉", - "𑣊", - "𑣋", - "𑣌", - "𑣍", - "𑣎", - "𑣏", - "𑣐", - "𑣑", - "𑣒", - "𑣓", - "𑣔", - "𑣕", - "𑣖", - "𑣗", - "𑣘", - "𑣙", - "𑣚", - "𑣛", - "𑣜", - "𑣝", - "𑣞", - "𑣟", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "𖹠", - "𖹡", - "𖹢", - "𖹣", - "𖹤", - "𖹥", - "𖹦", - "𖹧", - "𖹨", - "𖹩", - "𖹪", - "𖹫", - "𖹬", - "𖹭", - "𖹮", - "𖹯", - "𖹰", - "𖹱", - "𖹲", - "𖹳", - "𖹴", - "𖹵", - "𖹶", - "𖹷", - "𖹸", - "𖹹", - "𖹺", - "𖹻", - "𖹼", - "𖹽", - "𖹾", - "𖹿", - None, - None, - "\U00016ebb", - "\U00016ebc", - "\U00016ebd", - "\U00016ebe", - "\U00016ebf", - "\U00016ec0", - "\U00016ec1", - "\U00016ec2", - "\U00016ec3", - "\U00016ec4", - "\U00016ec5", - "\U00016ec6", - "\U00016ec7", - "\U00016ec8", - "\U00016ec9", - "\U00016eca", - "\U00016ecb", - "\U00016ecc", - "\U00016ecd", - "\U00016ece", - "\U00016ecf", - "\U00016ed0", - "\U00016ed1", - "\U00016ed2", - "\U00016ed3", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "𝅗𝅥", - "𝅘𝅥", - "𝅘𝅥𝅮", - "𝅘𝅥𝅯", - "𝅘𝅥𝅰", - "𝅘𝅥𝅱", - "𝅘𝅥𝅲", - None, - None, - None, - "𝆹𝅥", - "𝆺𝅥", - "𝆹𝅥𝅮", - "𝆺𝅥𝅮", - "𝆹𝅥𝅯", - "𝆺𝅥𝅯", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - None, - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - None, - "c", - "d", - None, - "g", - None, - "j", - "k", - None, - "n", - "o", - "p", - "q", - None, - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - None, - "f", - None, - "h", - "i", - "j", - "k", - "l", - "m", - "n", - None, - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - None, - "d", - "e", - "f", - "g", - None, - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - None, - "s", - "t", - "u", - "v", - "w", - "x", - "y", - None, - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - None, - "d", - "e", - "f", - "g", - None, - "i", - "j", - "k", - "l", - "m", - None, - "o", - None, - "s", - "t", - "u", - "v", - "w", - "x", - "y", - None, - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "ı", - "ȷ", - None, - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "θ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∇", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∂", - "ε", - "θ", - "κ", - "φ", - "ρ", - "π", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "θ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∇", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∂", - "ε", - "θ", - "κ", - "φ", - "ρ", - "π", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "θ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∇", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∂", - "ε", - "θ", - "κ", - "φ", - "ρ", - "π", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "θ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∇", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∂", - "ε", - "θ", - "κ", - "φ", - "ρ", - "π", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "θ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∇", - "α", - "β", - "γ", - "δ", - "ε", - "ζ", - "η", - "θ", - "ι", - "κ", - "λ", - "μ", - "ν", - "ξ", - "ο", - "π", - "ρ", - "σ", - "τ", - "υ", - "φ", - "χ", - "ψ", - "ω", - "∂", - "ε", - "θ", - "κ", - "φ", - "ρ", - "π", - "ϝ", - None, - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "а", - "б", - "в", - "г", - "д", - "е", - "ж", - "з", - "и", - "к", - "л", - "м", - "о", - "п", - "р", - "с", - "т", - "у", - "ф", - "х", - "ц", - "ч", - "ш", - "ы", - "э", - "ю", - "ꚉ", - "ә", - "і", - "ј", - "ө", - "ү", - "ӏ", - "а", - "б", - "в", - "г", - "д", - "е", - "ж", - "з", - "и", - "к", - "л", - "о", - "п", - "с", - "у", - "ф", - "х", - "ц", - "ч", - "ш", - "ъ", - "ы", - "ґ", - "і", - "ѕ", - "џ", - "ҫ", - "ꙑ", - "ұ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "𞤢", - "𞤣", - "𞤤", - "𞤥", - "𞤦", - "𞤧", - "𞤨", - "𞤩", - "𞤪", - "𞤫", - "𞤬", - "𞤭", - "𞤮", - "𞤯", - "𞤰", - "𞤱", - "𞤲", - "𞤳", - "𞤴", - "𞤵", - "𞤶", - "𞤷", - "𞤸", - "𞤹", - "𞤺", - "𞤻", - "𞤼", - "𞤽", - "𞤾", - "𞤿", - "𞥀", - "𞥁", - "𞥂", - "𞥃", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "ا", - "ب", - "ج", - "د", - None, - "و", - "ز", - "ح", - "ط", - "ي", - "ك", - "ل", - "م", - "ن", - "س", - "ع", - "ف", - "ص", - "ق", - "ر", - "ش", - "ت", - "ث", - "خ", - "ذ", - "ض", - "ظ", - "غ", - "ٮ", - "ں", - "ڡ", - "ٯ", - None, - "ب", - "ج", - None, - "ه", - None, - "ح", - None, - "ي", - "ك", - "ل", - "م", - "ن", - "س", - "ع", - "ف", - "ص", - "ق", - None, - "ش", - "ت", - "ث", - "خ", - None, - "ض", - None, - "غ", - None, - "ج", - None, - "ح", - None, - "ي", - None, - "ل", - None, - "ن", - "س", - "ع", - None, - "ص", - "ق", - None, - "ش", - None, - "خ", - None, - "ض", - None, - "غ", - None, - "ں", - None, - "ٯ", - None, - "ب", - "ج", - None, - "ه", - None, - "ح", - "ط", - "ي", - "ك", - None, - "م", - "ن", - "س", - "ع", - "ف", - "ص", - "ق", - None, - "ش", - "ت", - "ث", - "خ", - None, - "ض", - "ظ", - "غ", - "ٮ", - None, - "ڡ", - None, - "ا", - "ب", - "ج", - "د", - "ه", - "و", - "ز", - "ح", - "ط", - "ي", - None, - "ل", - "م", - "ن", - "س", - "ع", - "ف", - "ص", - "ق", - "ر", - "ش", - "ت", - "ث", - "خ", - "ذ", - "ض", - "ظ", - "غ", - None, - "ب", - "ج", - "د", - None, - "و", - "ز", - "ح", - "ط", - "ي", - None, - "ل", - "م", - "ن", - "س", - "ع", - "ف", - "ص", - "ق", - "ر", - "ش", - "ت", - "ث", - "خ", - "ذ", - "ض", - "ظ", - "غ", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "0,", - "1,", - "2,", - "3,", - "4,", - "5,", - "6,", - "7,", - "8,", - "9,", - None, - "(a)", - "(b)", - "(c)", - "(d)", - "(e)", - "(f)", - "(g)", - "(h)", - "(i)", - "(j)", - "(k)", - "(l)", - "(m)", - "(n)", - "(o)", - "(p)", - "(q)", - "(r)", - "(s)", - "(t)", - "(u)", - "(v)", - "(w)", - "(x)", - "(y)", - "(z)", - "〔s〕", - "c", - "r", - "cd", - "wz", - None, - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z", - "hv", - "mv", - "sd", - "ss", - "ppv", - "wc", - None, - "mc", - "md", - "mr", - None, - "dj", - None, - None, - None, - "ほか", - "ココ", - "サ", - None, - "手", - "字", - "双", - "デ", - "二", - "多", - "解", - "天", - "交", - "映", - "無", - "料", - "前", - "後", - "再", - "新", - "初", - "終", - "生", - "販", - "声", - "吹", - "演", - "投", - "捕", - "一", - "三", - "遊", - "左", - "中", - "右", - "指", - "走", - "打", - "禁", - "空", - "合", - "満", - "有", - "月", - "申", - "割", - "営", - "配", - None, - "〔本〕", - "〔三〕", - "〔二〕", - "〔安〕", - "〔点〕", - "〔打〕", - "〔盗〕", - "〔勝〕", - "〔敗〕", - None, - "得", - "可", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - "丽", - "丸", - "乁", - "𠄢", - "你", - "侮", - "侻", - "倂", - "偺", - "備", - "僧", - "像", - "㒞", - "𠘺", - "免", - "兔", - "兤", - "具", - "𠔜", - "㒹", - "內", - "再", - "𠕋", - "冗", - "冤", - "仌", - "冬", - "况", - "𩇟", - "凵", - "刃", - "㓟", - "刻", - "剆", - "割", - "剷", - "㔕", - "勇", - "勉", - "勤", - "勺", - "包", - "匆", - "北", - "卉", - "卑", - "博", - "即", - "卽", - "卿", - "𠨬", - "灰", - "及", - "叟", - "𠭣", - "叫", - "叱", - "吆", - "咞", - "吸", - "呈", - "周", - "咢", - "哶", - "唐", - "啓", - "啣", - "善", - "喙", - "喫", - "喳", - "嗂", - "圖", - "嘆", - "圗", - "噑", - "噴", - "切", - "壮", - "城", - "埴", - "堍", - "型", - "堲", - "報", - "墬", - "𡓤", - "売", - "壷", - "夆", - "多", - "夢", - "奢", - "𡚨", - "𡛪", - "姬", - "娛", - "娧", - "姘", - "婦", - "㛮", - "㛼", - "嬈", - "嬾", - "𡧈", - "寃", - "寘", - "寧", - "寳", - "𡬘", - "寿", - "将", - "当", - "尢", - "㞁", - "屠", - "屮", - "峀", - "岍", - "𡷤", - "嵃", - "𡷦", - "嵮", - "嵫", - "嵼", - "巡", - "巢", - "㠯", - "巽", - "帨", - "帽", - "幩", - "㡢", - "𢆃", - "㡼", - "庰", - "庳", - "庶", - "廊", - "𪎒", - "廾", - "𢌱", - "舁", - "弢", - "㣇", - "𣊸", - "𦇚", - "形", - "彫", - "㣣", - "徚", - "忍", - "志", - "忹", - "悁", - "㤺", - "㤜", - "悔", - "𢛔", - "惇", - "慈", - "慌", - "慎", - "慌", - "慺", - "憎", - "憲", - "憤", - "憯", - "懞", - "懲", - "懶", - "成", - "戛", - "扝", - "抱", - "拔", - "捐", - "𢬌", - "挽", - "拼", - "捨", - "掃", - "揤", - "𢯱", - "搢", - "揅", - "掩", - "㨮", - "摩", - "摾", - "撝", - "摷", - "㩬", - "敏", - "敬", - "𣀊", - "旣", - "書", - "晉", - "㬙", - "暑", - "㬈", - "㫤", - "冒", - "冕", - "最", - "暜", - "肭", - "䏙", - "朗", - "望", - "朡", - "杞", - "杓", - "𣏃", - "㭉", - "柺", - "枅", - "桒", - "梅", - "𣑭", - "梎", - "栟", - "椔", - "㮝", - "楂", - "榣", - "槪", - "檨", - "𣚣", - "櫛", - "㰘", - "次", - "𣢧", - "歔", - "㱎", - "歲", - "殟", - "殺", - "殻", - "𣪍", - "𡴋", - "𣫺", - "汎", - "𣲼", - "沿", - "泍", - "汧", - "洖", - "派", - "海", - "流", - "浩", - "浸", - "涅", - "𣴞", - "洴", - "港", - "湮", - "㴳", - "滋", - "滇", - "𣻑", - "淹", - "潮", - "𣽞", - "𣾎", - "濆", - "瀹", - "瀞", - "瀛", - "㶖", - "灊", - "災", - "灷", - "炭", - "𠔥", - "煅", - "𤉣", - "熜", - "𤎫", - "爨", - "爵", - "牐", - "𤘈", - "犀", - "犕", - "𤜵", - "𤠔", - "獺", - "王", - "㺬", - "玥", - "㺸", - "瑇", - "瑜", - "瑱", - "璅", - "瓊", - "㼛", - "甤", - "𤰶", - "甾", - "𤲒", - "異", - "𢆟", - "瘐", - "𤾡", - "𤾸", - "𥁄", - "㿼", - "䀈", - "直", - "𥃳", - "𥃲", - "𥄙", - "𥄳", - "眞", - "真", - "睊", - "䀹", - "瞋", - "䁆", - "䂖", - "𥐝", - "硎", - "碌", - "磌", - "䃣", - "𥘦", - "祖", - "𥚚", - "𥛅", - "福", - "秫", - "䄯", - "穀", - "穊", - "穏", - "𥥼", - "𥪧", - "竮", - "䈂", - "𥮫", - "篆", - "築", - "䈧", - "𥲀", - "糒", - "䊠", - "糨", - "糣", - "紀", - "𥾆", - "絣", - "䌁", - "緇", - "縂", - "繅", - "䌴", - "𦈨", - "𦉇", - "䍙", - "𦋙", - "罺", - "𦌾", - "羕", - "翺", - "者", - "𦓚", - "𦔣", - "聠", - "𦖨", - "聰", - "𣍟", - "䏕", - "育", - "脃", - "䐋", - "脾", - "媵", - "𦞧", - "𦞵", - "𣎓", - "𣎜", - "舁", - "舄", - "辞", - "䑫", - "芑", - "芋", - "芝", - "劳", - "花", - "芳", - "芽", - "苦", - "𦬼", - "若", - "茝", - "荣", - "莭", - "茣", - "莽", - "菧", - "著", - "荓", - "菊", - "菌", - "菜", - "𦰶", - "𦵫", - "𦳕", - "䔫", - "蓱", - "蓳", - "蔖", - "𧏊", - "蕤", - "𦼬", - "䕝", - "䕡", - "𦾱", - "𧃒", - "䕫", - "虐", - "虜", - "虧", - "虩", - "蚩", - "蚈", - "蜎", - "蛢", - "蝹", - "蜨", - "蝫", - "螆", - "䗗", - "蟡", - "蠁", - "䗹", - "衠", - "衣", - "𧙧", - "裗", - "裞", - "䘵", - "裺", - "㒻", - "𧢮", - "𧥦", - "䚾", - "䛇", - "誠", - "諭", - "變", - "豕", - "𧲨", - "貫", - "賁", - "贛", - "起", - "𧼯", - "𠠄", - "跋", - "趼", - "跰", - "𠣞", - "軔", - "輸", - "𨗒", - "𨗭", - "邔", - "郱", - "鄑", - "𨜮", - "鄛", - "鈸", - "鋗", - "鋘", - "鉼", - "鏹", - "鐕", - "𨯺", - "開", - "䦕", - "閷", - "𨵷", - "䧦", - "雃", - "嶲", - "霣", - "𩅅", - "𩈚", - "䩮", - "䩶", - "韠", - "𩐊", - "䪲", - "𩒖", - "頋", - "頩", - "𩖶", - "飢", - "䬳", - "餩", - "馧", - "駂", - "駾", - "䯎", - "𩬰", - "鬒", - "鱀", - "鳽", - "䳎", - "䳭", - "鵧", - "𪃎", - "䳸", - "𪄅", - "𪈎", - "𪊑", - "麻", - "䵖", - "黹", - "黾", - "鼅", - "鼏", - "鼖", - "鼻", - "𪘀", - None, - None, - None, - None, - None, - None, - None, -) diff --git a/server/venv/Lib/site-packages/k5sprt64.dll b/server/venv/Lib/site-packages/k5sprt64.dll deleted file mode 100644 index ec4eab3..0000000 Binary files a/server/venv/Lib/site-packages/k5sprt64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/krb5_64.dll b/server/venv/Lib/site-packages/krb5_64.dll deleted file mode 100644 index 8cdd0bf..0000000 Binary files a/server/venv/Lib/site-packages/krb5_64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/krbcc64.dll b/server/venv/Lib/site-packages/krbcc64.dll deleted file mode 100644 index 9cf617d..0000000 Binary files a/server/venv/Lib/site-packages/krbcc64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/libcrypto-3-x64.dll b/server/venv/Lib/site-packages/libcrypto-3-x64.dll deleted file mode 100644 index 8441fd3..0000000 Binary files a/server/venv/Lib/site-packages/libcrypto-3-x64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/libmysql.dll b/server/venv/Lib/site-packages/libmysql.dll deleted file mode 100644 index 1fffc7e..0000000 Binary files a/server/venv/Lib/site-packages/libmysql.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/libsasl.dll b/server/venv/Lib/site-packages/libsasl.dll deleted file mode 100644 index 4af1df6..0000000 Binary files a/server/venv/Lib/site-packages/libsasl.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/libssl-3-x64.dll b/server/venv/Lib/site-packages/libssl-3-x64.dll deleted file mode 100644 index 4134cee..0000000 Binary files a/server/venv/Lib/site-packages/libssl-3-x64.dll and /dev/null differ diff --git a/server/venv/Lib/site-packages/mysql/__init__.py b/server/venv/Lib/site-packages/mysql/__init__.py deleted file mode 100644 index 1f2326a..0000000 --- a/server/venv/Lib/site-packages/mysql/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/server/venv/Lib/site-packages/mysql/ai/__init__.py b/server/venv/Lib/site-packages/mysql/ai/__init__.py deleted file mode 100644 index 0efd1ec..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/server/venv/Lib/site-packages/mysql/ai/genai/__init__.py b/server/venv/Lib/site-packages/mysql/ai/genai/__init__.py deleted file mode 100644 index 700cb7f..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/genai/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""GenAI package for MySQL Connector/Python. - -Performs optional dependency checks and exposes public classes: -- MyEmbeddings -- MyLLM -- MyVectorStore -""" -from mysql.ai.utils import check_dependencies as _check_dependencies - -_check_dependencies(["GENAI"]) -del _check_dependencies - -from .embedding import MyEmbeddings -from .generation import MyLLM -from .vector_store import MyVectorStore diff --git a/server/venv/Lib/site-packages/mysql/ai/genai/embedding.py b/server/venv/Lib/site-packages/mysql/ai/genai/embedding.py deleted file mode 100644 index 1089484..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/genai/embedding.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Embeddings integration utilities for MySQL Connector/Python. - -Provides MyEmbeddings class to generate embeddings via MySQL HeatWave -using ML_EMBED_TABLE and ML_EMBED_ROW. -""" - -from typing import Dict, List, Optional - -import pandas as pd - -from langchain_core.embeddings import Embeddings -from pydantic import PrivateAttr - -from mysql.ai.utils import ( - atomic_transaction, - execute_sql, - format_value_sql, - source_schema, - sql_table_from_df, - sql_table_to_df, - temporary_sql_tables, -) -from mysql.connector.abstracts import MySQLConnectionAbstract - - -class MyEmbeddings(Embeddings): - """ - Embedding generator class that uses a MySQL database to compute embeddings for input text. - - This class batches input text into temporary SQL tables, invokes MySQL's ML_EMBED_TABLE - to generate embeddings, and retrieves the results as lists of floats. - - Attributes: - _db_connection (MySQLConnectionAbstract): MySQL connection used for all database operations. - schema_name (str): Name of the database schema to use. - options_placeholder (str): SQL-ready placeholder string for ML_EMBED_TABLE options. - options_params (dict): Dictionary of concrete option values to be passed as SQL parameters. - """ - - _db_connection: MySQLConnectionAbstract = PrivateAttr() - - def __init__( - self, db_connection: MySQLConnectionAbstract, options: Optional[Dict] = None - ): - """ - Initialize MyEmbeddings with a database connection and optional embedding parameters. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwgenai-ml-embed-row.html - A full list of supported options can be found under "options" - - NOTE: The supported "options" are the intersection of the options provided in - https://dev.mysql.com/doc/heatwave/en/mys-hwgenai-ml-embed-row.html - https://dev.mysql.com/doc/heatwave/en/mys-hwgenai-ml-embed-table.html - - Args: - db_connection: Active MySQL connector database connection. - options: Optional dictionary of options for embedding operations. - - Raises: - ValueError: If the schema name is not valid - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - super().__init__() - self._db_connection = db_connection - self.schema_name = source_schema(db_connection) - options = options or {} - self.options_placeholder, self.options_params = format_value_sql(options) - - def embed_documents(self, texts: List[str]) -> List[List[float]]: - """ - Generate embeddings for a list of input texts using the MySQL ML embedding procedure. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwgenai-ml-embed-table.html - - Args: - texts: List of input strings to embed. - - Returns: - List of lists of floats, with each inner list containing the embedding for a text. - - Raises: - DatabaseError: - If provided options are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: - If one or more text entries were unable to be embedded. - - Implementation notes: - - Creates a temporary table to pass input text to the MySQL embedding service. - - Adds a primary key to ensure results preserve input order. - - Calls ML_EMBED_TABLE and fetches the resulting embeddings. - - Deletes the temporary table after use to avoid polluting the database. - - Embedding vectors are extracted from the "embeddings" column of the result table. - """ - if not texts: - return [] - - df = pd.DataFrame({"id": range(len(texts)), "text": texts}) - - with ( - atomic_transaction(self._db_connection) as cursor, - temporary_sql_tables(self._db_connection) as temporary_tables, - ): - qualified_table_name, table_name = sql_table_from_df( - cursor, self.schema_name, df - ) - temporary_tables.append((self.schema_name, table_name)) - - # ML_EMBED_TABLE expects input/output columns and options as parameters - embed_query = ( - "CALL sys.ML_EMBED_TABLE(" - f"'{qualified_table_name}.text', " - f"'{qualified_table_name}.embeddings', " - f"{self.options_placeholder}" - ")" - ) - execute_sql(cursor, embed_query, params=self.options_params) - - # Read back all columns, including "embeddings" - df_embeddings = sql_table_to_df(cursor, self.schema_name, table_name) - - if df_embeddings["embeddings"].isnull().any() or any( - e is None for e in df_embeddings["embeddings"] - ): - raise ValueError( - "Failure to generate embeddings for one or more text entry." - ) - - # Convert fetched embeddings to lists of floats - embeddings = df_embeddings["embeddings"].tolist() - embeddings = [list(e) for e in embeddings] - - return embeddings - - def embed_query(self, text: str) -> List[float]: - """ - Generate an embedding for a single text string. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwgenai-ml-embed-row.html - - Args: - text: The input string to embed. - - Returns: - List of floats representing the embedding vector. - - Raises: - DatabaseError: - If provided options are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - - Example: - >>> MyEmbeddings(db_conn).embed_query("Hello world") - [0.1, 0.2, ...] - """ - with atomic_transaction(self._db_connection) as cursor: - execute_sql( - cursor, - f'SELECT sys.ML_EMBED_ROW("%s", {self.options_placeholder})', - params=(text, *self.options_params), - ) - return list(cursor.fetchone()[0]) diff --git a/server/venv/Lib/site-packages/mysql/ai/genai/generation.py b/server/venv/Lib/site-packages/mysql/ai/genai/generation.py deleted file mode 100644 index c837acb..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/genai/generation.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""GenAI LLM integration utilities for MySQL Connector/Python. - -Provides MyLLM wrapper that issues ML_GENERATE calls via SQL. -""" - -import json - -from typing import Any, List, Optional - -try: - from langchain_core.language_models.llms import LLM -except ImportError: - from langchain.llms.base import LLM -from pydantic import PrivateAttr - -from mysql.ai.utils import atomic_transaction, execute_sql, format_value_sql -from mysql.connector.abstracts import MySQLConnectionAbstract - - -class MyLLM(LLM): - """ - Custom Large Language Model (LLM) interface for MySQL HeatWave. - - This class wraps the generation functionality provided by HeatWave LLMs, - exposing an interface compatible with common LLM APIs for text generation. - It provides full support for generative queries and limited support for - agentic queries. - - Attributes: - _db_connection (MySQLConnectionAbstract): - Underlying MySQL connector database connection. - """ - - _db_connection: MySQLConnectionAbstract = PrivateAttr() - - class Config: - """ - Pydantic config for the model. - - By default, LangChain (through Pydantic BaseModel) does not allow - setting or storing undeclared attributes such as _db_connection. - Setting extra = "allow" makes it possible to store extra attributes - on the class instance, which is required for MyLLM. - """ - - extra = "allow" - - def __init__(self, db_connection: MySQLConnectionAbstract): - """ - Initialize the MyLLM instance with an active MySQL database connection. - - Args: - db_connection: A MySQL connection object used to run LLM queries. - - Notes: - The db_connection is stored as a private attribute via object.__setattr__, - which is compatible with Pydantic models. - """ - super().__init__() - - self._db_connection = db_connection - - def _call( - self, - prompt: str, - stop: Optional[List[str]] = None, - **kwargs: Any, - ) -> str: - """ - Generate a text completion from the LLM for a given input prompt. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwgenai-ml-generate.html - A full list of supported options (specified by kwargs) can be found under "options" - - Args: - prompt: The input prompt string for the language model. - stop: Optional list of stop strings to support agentic and chain-of-thought - reasoning workflows. - **kwargs: Additional keyword arguments providing generation options to - the LLM (these are serialized to JSON and passed to the HeatWave syscall). - - Returns: - The generated model output as a string. - (The actual completion does NOT include the input prompt.) - - Raises: - DatabaseError: - If provided options are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - - Implementation Notes: - - Serializes kwargs into a SQL-compatible JSON string. - - Calls the LLM stored procedure using a database cursor context. - - Uses `sys.ML_GENERATE` on the server to produce the model output. - - Expects the server response to be a JSON object with a 'text' key. - """ - options = kwargs.copy() - if stop is not None: - options["stop_sequences"] = stop - - options_placeholder, options_params = format_value_sql(options) - with atomic_transaction(self._db_connection) as cursor: - # The prompt is passed as a parameterized argument (avoids SQL injection). - generate_query = f"""SELECT sys.ML_GENERATE("%s", {options_placeholder});""" - execute_sql(cursor, generate_query, params=(prompt, *options_params)) - # Expect a JSON-encoded result from MySQL; parse to extract the output. - llm_response = json.loads(cursor.fetchone()[0])["text"] - - return llm_response - - @property - def _identifying_params(self) -> dict: - """ - Return a dictionary of params that uniquely identify this LLM instance. - - Returns: - dict: Dictionary of identifier parameters (should include - model_name for tracing/caching). - """ - return { - "model_name": "mysql_heatwave_llm", - } - - @property - def _llm_type(self) -> str: - """ - Get the type name of this LLM implementation. - - Returns: - A string identifying the LLM provider (used for logging or metrics). - """ - return "mysql_heatwave_llm" diff --git a/server/venv/Lib/site-packages/mysql/ai/genai/vector_store.py b/server/venv/Lib/site-packages/mysql/ai/genai/vector_store.py deleted file mode 100644 index bb02634..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/genai/vector_store.py +++ /dev/null @@ -1,520 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""MySQL-backed vector store for embeddings and semantic document retrieval. - -Provides a VectorStore implementation persisting documents, metadata, and -embeddings in MySQL, plus similarity search utilities. -""" - -import json - -from typing import Any, Iterable, List, Optional, Sequence, Union - -import pandas as pd - -from langchain_core.documents import Document -from langchain_core.embeddings import Embeddings -from langchain_core.vectorstores import VectorStore -from pydantic import PrivateAttr - -from mysql.ai.genai.embedding import MyEmbeddings -from mysql.ai.utils import ( - VAR_NAME_SPACE, - atomic_transaction, - delete_sql_table, - execute_sql, - extend_sql_table, - format_value_sql, - get_random_name, - is_table_empty, - source_schema, - table_exists, -) -from mysql.connector.abstracts import MySQLConnectionAbstract - -BASIC_EMBEDDING_QUERY = "Hello world!" -EMBEDDING_SOURCE = "external_source" - -VAR_EMBEDDING = f"{VAR_NAME_SPACE}.embedding" -VAR_CONTEXT = f"{VAR_NAME_SPACE}.context" -VAR_CONTEXT_MAP = f"{VAR_NAME_SPACE}.context_map" -VAR_RETRIEVAL_INFO = f"{VAR_NAME_SPACE}.retrieval_info" -VAR_OPTIONS = f"{VAR_NAME_SPACE}.options" - -ID_SPACE = "internal_ai_id_" - - -class MyVectorStore(VectorStore): - """ - MySQL-backed vector store for handling embeddings and semantic document retrieval. - - Supports adding, deleting, and searching high-dimensional vector representations - of documents using efficient storage and HeatWave ML similarity search procedures. - - Supports use as a context manager: when used in a `with` statement, all backing - tables/data are deleted automatically when the block exits (even on exception). - - Attributes: - db_connection (MySQLConnectionAbstract): Active MySQL database connection. - embedder (Embeddings): Embeddings generator for computing vector representations. - schema_name (str): SQL schema for table storage. - table_name (Optional[str]): Name of the active table backing the store - (or None until created). - embedding_dimension (int): Size of embedding vectors stored. - next_id (int): Internal counter for unique document ID generation. - """ - - _db_connection: MySQLConnectionAbstract = PrivateAttr() - _embedder: Embeddings = PrivateAttr() - _schema_name: str = PrivateAttr() - _table_name: Optional[str] = PrivateAttr() - _embedding_dimension: int = PrivateAttr() - _next_id: int = PrivateAttr() - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - embedder: Optional[Embeddings] = None, - ) -> None: - """ - Initialize a MyVectorStore with a database connection and embedding generator. - - Args: - db_connection: MySQL database connection for all vector operations. - embedder: Embeddings generator used for creating and querying embeddings. - - Raises: - ValueError: If the schema name is not valid - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - super().__init__() - self._next_id = 0 - - self._schema_name = source_schema(db_connection) - self._embedder = embedder or MyEmbeddings(db_connection) - self._db_connection = db_connection - self._table_name: Optional[str] = None - - # Embedding dimension determined using an example call. - # Assumes embeddings have fixed length. - self._embedding_dimension = len( - self._embedder.embed_query(BASIC_EMBEDDING_QUERY) - ) - - def _get_ids(self, num_ids: int) -> list[str]: - """ - Generate a batch of unique internal document IDs for vector storage. - - Args: - num_ids: Number of IDs to create. - - Returns: - List of sequentially numbered internal string IDs. - """ - ids = [ - f"internal_ai_id_{i}" for i in range(self._next_id, self._next_id + num_ids) - ] - self._next_id += num_ids - return ids - - def _make_vector_store(self) -> None: - """ - Create a backing SQL table for storing vectors if not already created. - - Returns: - None - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - The table name is randomized to avoid collisions. - Schema includes content, metadata, and embedding vector. - """ - if self._table_name is None: - with atomic_transaction(self._db_connection) as cursor: - table_name = get_random_name( - lambda table_name: not table_exists( - cursor, self._schema_name, table_name - ) - ) - - create_table_stmt = f""" - CREATE TABLE {self._schema_name}.{table_name} ( - `id` VARCHAR(128) NOT NULL, - `content` TEXT, - `metadata` JSON DEFAULT NULL, - `embed` vector(%s), - PRIMARY KEY (`id`) - ) ENGINE=InnoDB; - """ - execute_sql( - cursor, create_table_stmt, params=(self._embedding_dimension,) - ) - - self._table_name = table_name - - def delete(self, ids: Optional[Sequence[str]] = None, **_: Any) -> None: - """ - Delete documents by ID. Optionally deletes the vector table if empty after deletions. - - Args: - ids: Optional sequence of document IDs to delete. If None, no action is taken. - - Returns: - None - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - If the backing table is empty after deletions, the table is dropped and - table_name is set to None. - """ - with atomic_transaction(self._db_connection) as cursor: - if ids: - for _id in ids: - execute_sql( - cursor, - f"DELETE FROM {self._schema_name}.{self._table_name} WHERE id = %s", - params=(_id,), - ) - - if is_table_empty(cursor, self._schema_name, self._table_name): - self.delete_all() - - def delete_all(self) -> None: - """ - Delete and drop the entire vector store table. - - Returns: - None - """ - if self._table_name is not None: - with atomic_transaction(self._db_connection) as cursor: - delete_sql_table(cursor, self._schema_name, self._table_name) - self._table_name = None - - def add_texts( - self, - texts: Iterable[str], - metadatas: Optional[list[dict]] = None, - ids: Optional[List[str]] = None, - **_: dict, - ) -> List[str]: - """ - Add a batch of text strings and corresponding metadata to the vector store. - - Args: - texts: List of strings to embed and store. - metadatas: Optional list of metadata dicts (one per text). - ids: Optional custom document IDs. - - Returns: - List of document IDs corresponding to the added texts. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - If metadatas is None, an empty dict is assigned to each document. - """ - texts = list(texts) - - documents = [ - Document(page_content=text, metadata=meta) - for text, meta in zip(texts, metadatas or [{}] * len(texts)) - ] - return self.add_documents(documents, ids=ids) - - @classmethod - def from_texts( - cls, - texts: Iterable[str], - embedder: Embeddings, - metadatas: Optional[list[dict]] = None, - db_connection: MySQLConnectionAbstract = None, - ) -> VectorStore: - """ - Construct and populate a MyVectorStore instance from raw texts and metadata. - - Args: - texts: List of strings to vectorize and store. - embedder: Embeddings generator to use. - metadatas: Optional list of metadata dicts per text. - db_connection: Active MySQL connection. - - Returns: - Instance of MyVectorStore containing the added texts. - - Raises: - ValueError: If db_connection is not provided. - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - if db_connection is None: - raise ValueError( - "db_connection must be specified to create a MyVectorStore object" - ) - - texts = list(texts) - - instance = cls(db_connection=db_connection, embedder=embedder) - instance.add_texts(texts, metadatas=metadatas) - - return instance - - def add_documents( - self, documents: list[Document], ids: list[str] = None - ) -> list[str]: - """ - Embed and store Document objects as high-dimensional vectors with metadata. - - Args: - documents: List of Document objects (each with 'page_content' and 'metadata'). - ids: Optional list of explicit document IDs. Must match the length of documents. - - Returns: - List of document IDs stored. - - Raises: - ValueError: If provided IDs do not match the number of documents. - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Automatically creates the backing table if it does not exist. - """ - if ids and len(ids) != len(documents): - msg = ( - "ids must be the same length as documents. " - f"Got {len(ids)} ids and {len(documents)} documents." - ) - raise ValueError(msg) - - if len(documents) > 0: - self._make_vector_store() - else: - return [] - - if ids is None: - ids = self._get_ids(len(documents)) - - content = [doc.page_content for doc in documents] - vectors = self._embedder.embed_documents(content) - - df = pd.DataFrame() - df["id"] = ids - df["content"] = content - df["embed"] = vectors - df["metadata"] = [doc.metadata for doc in documents] - - with atomic_transaction(self._db_connection) as cursor: - extend_sql_table( - cursor, - self._schema_name, - self._table_name, - df, - col_name_to_placeholder_string={"embed": "string_to_vector(%s)"}, - ) - - return ids - - def similarity_search( - self, - query: str, - k: int = 3, - **kwargs: Any, - ) -> list[Document]: - """ - Search for and return the most similar documents in the store to the given query. - - Args: - query: String query to embed and use for similarity search. - k: Number of top documents to return. - kwargs: options to pass to ML_SIMILARITY_SEARCH. Currently supports - distance_metric, max_distance, percentage_distance, and segment_overlap - - Returns: - List of Document objects, ordered from most to least similar. - - Raises: - DatabaseError: - If provided kwargs are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - - Implementation Notes: - - Calls ML similarity search within MySQL using stored procedures. - - Retrieves IDs, content, and metadata for search matches. - - Parsing and retrieval for context results are handled via intermediate JSONs. - """ - if self._table_name is None: - return [] - - embedding = self._embedder.embed_query(query) - - with atomic_transaction(self._db_connection) as cursor: - # Set the embedding variable for the similarity search SP - execute_sql( - cursor, - f"SET @{VAR_EMBEDDING} = string_to_vector(%s)", - params=[str(embedding)], - ) - - distance_metric = kwargs.get("distance_metric", "COSINE") - retrieval_options = { - "max_distance": kwargs.get("max_distance", 0.6), - "percentage_distance": kwargs.get("percentage_distance", 20.0), - "segment_overlap": kwargs.get("segment_overlap", 0), - } - - retrieval_options_placeholder, retrieval_options_params = format_value_sql( - retrieval_options - ) - similarity_search_query = f""" - CALL sys.ML_SIMILARITY_SEARCH( - @{VAR_EMBEDDING}, - JSON_ARRAY( - '{self._schema_name}.{self._table_name}' - ), - JSON_OBJECT( - "segment", "content", - "segment_embedding", "embed", - "document_name", "id" - ), - {k}, - %s, - NULL, - NULL, - {retrieval_options_placeholder}, - @{VAR_CONTEXT}, - @{VAR_CONTEXT_MAP}, - @{VAR_RETRIEVAL_INFO} - ) - """ - - execute_sql( - cursor, - similarity_search_query, - params=[distance_metric, *retrieval_options_params], - ) - execute_sql(cursor, f"SELECT @{VAR_CONTEXT_MAP}") - - results = [] - - context_maps = json.loads(cursor.fetchone()[0]) - for context in context_maps: - execute_sql( - cursor, - ( - "SELECT id, content, metadata " - f"FROM {self._schema_name}.{self._table_name} " - "WHERE id = %s" - ), - params=(context["document_name"],), - ) - doc_id, content, metadata = cursor.fetchone() - - doc_args = { - "id": doc_id, - "page_content": content, - } - if metadata is not None: - doc_args["metadata"] = json.loads(metadata) - - doc = Document(**doc_args) - results.append(doc) - - return results - - def __enter__(self) -> "VectorStore": - """ - Enter the runtime context related to this vector store instance. - - Returns: - The current MyVectorStore object, allowing use within a `with` statement block. - - Usage Notes: - - Intended for use in a `with` statement to ensure automatic - cleanup of resources. - - No special initialization occurs during context entry, but enables - proper context-managed lifecycle. - - Example: - with MyVectorStore(db_connection, embedder) as vectorstore: - vectorstore.add_texts([...]) - # Vector store is active within this block. - # All storage and resources are now cleaned up. - """ - return self - - def __exit__( - self, - exc_type: Union[type, None], - exc_val: Union[BaseException, None], - exc_tb: Union[object, None], - ) -> None: - """ - Exit the runtime context for the vector store, ensuring all storage - resources are cleaned up. - - Args: - exc_type: The exception type, if any exception occurred in the context block. - exc_val: The exception value, if any exception occurred in the context block. - exc_tb: The traceback object, if any exception occurred in the context block. - - Returns: - None: Indicates that exceptions are never suppressed; they will propagate as normal. - - Implementation Notes: - - Automatically deletes all vector store data and backing tables via `delete_all()` - upon exiting the context. - - This cleanup occurs whether the block exits normally or due to an exception. - - Does not suppress exceptions; errors in the context block will continue to propagate. - - Use when the vector store lifecycle is intended to be temporary or scoped. - - Example: - with MyVectorStore(db_connection, embedder) as vectorstore: - vectorstore.add_texts([...]) - # Vector store is active within this block. - # All storage and resources are now cleaned up. - """ - self.delete_all() - # No return, so exceptions are never suppressed diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/__init__.py b/server/venv/Lib/site-packages/mysql/ai/ml/__init__.py deleted file mode 100644 index 4b45d0d..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/__init__.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""ML package for MySQL Connector/Python. - -Performs optional dependency checks and exposes ML utilities: -- ML_TASK, MyModel -- MyClassifier, MyRegressor, MyGenericTransformer -- MyAnomalyDetector -""" -from mysql.ai.utils import check_dependencies as _check_dependencies - -_check_dependencies(["ML"]) -del _check_dependencies - -# Sklearn models -from .classifier import MyClassifier - -# Minimal interface -from .model import ML_TASK, MyModel -from .outlier import MyAnomalyDetector -from .regressor import MyRegressor -from .transformer import MyGenericTransformer diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/base.py b/server/venv/Lib/site-packages/mysql/ai/ml/base.py deleted file mode 100644 index 811cb55..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/base.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Base classes for MySQL HeatWave ML estimators for Connector/Python. - -Implements a scikit-learn-compatible base estimator wrapping server-side ML. -""" -from typing import Optional, Union - -import pandas as pd - -from sklearn.base import BaseEstimator - -from mysql.connector.abstracts import MySQLConnectionAbstract -from mysql.ai.ml.model import ML_TASK, MyModel -from mysql.ai.utils import copy_dict - - -class MyBaseMLModel(BaseEstimator): - """ - Base class for MySQL HeatWave machine learning estimators. - - Implements the scikit-learn API and core model management logic, - including fit, explain, serialization, and dynamic option handling. - For use as a base class by classifiers, regressors, transformers, and outlier models. - - Args: - db_connection (MySQLConnectionAbstract): An active MySQL connector database connection. - task (str): ML task type, e.g. "classification" or "regression". - model_name (str, optional): Custom name for the deployed model. - fit_extra_options (dict, optional): Extra options for fitting. - - Attributes: - _model: Underlying database helper for fit/predict/explain. - fit_extra_options: User-provided options for fitting. - """ - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - task: Union[str, ML_TASK], - model_name: Optional[str] = None, - fit_extra_options: Optional[dict] = None, - ): - """ - Initialize a MyBaseMLModel with connection, task, and option parameters. - - Args: - db_connection: Active MySQL connector database connection. - task: String label of ML task (e.g. "classification"). - model_name: Optional custom model name. - fit_extra_options: Optional extra fit options. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - self._model = MyModel(db_connection, task=task, model_name=model_name) - self.fit_extra_options = copy_dict(fit_extra_options) - - def fit( - self, - X: pd.DataFrame, # pylint: disable=invalid-name - y: Optional[pd.DataFrame] = None, - ) -> "MyBaseMLModel": - """ - Fit the underlying ML model using pandas DataFrames. - Delegates to MyMLModelPandasHelper.fit. - - Args: - X: Features DataFrame. - y: (Optional) Target labels DataFrame or Series. - - Returns: - self - - Raises: - DatabaseError: - If provided options are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Additional temp SQL resources may be created and cleaned up during the operation. - """ - self._model.fit(X, y, self.fit_extra_options) - return self - - def _delete_model(self) -> bool: - """ - Deletes the model from the model catalog if present - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Returns: - Whether the model was deleted - """ - return self._model._delete_model() - - def get_model_info(self) -> Optional[dict]: - """ - Checks if the model name is available. Model info will only be present in the - catalog if the model has previously been fitted. - - Returns: - True if the model name is not part of the model catalog - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - return self._model.get_model_info() diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/classifier.py b/server/venv/Lib/site-packages/mysql/ai/ml/classifier.py deleted file mode 100644 index ffa624b..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/classifier.py +++ /dev/null @@ -1,194 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Classifier utilities for MySQL Connector/Python. - -Provides a scikit-learn compatible classifier backed by HeatWave ML. -""" -from typing import Optional, Union - -import numpy as np -import pandas as pd -from sklearn.base import ClassifierMixin - -from mysql.ai.ml.base import MyBaseMLModel -from mysql.ai.ml.model import ML_TASK -from mysql.ai.utils import copy_dict - -from mysql.connector.abstracts import MySQLConnectionAbstract - - -class MyClassifier(MyBaseMLModel, ClassifierMixin): - """ - MySQL HeatWave scikit-learn compatible classifier estimator. - - Provides prediction and probability output from a model deployed in MySQL, - and manages fit, explain, and prediction options as per HeatWave ML interface. - - Attributes: - predict_extra_options (dict): Dictionary of optional parameters passed through - to the MySQL backend for prediction and probability inference. - _model (MyModel): Underlying interface for database model operations. - fit_extra_options (dict): See MyBaseMLModel. - - Args: - db_connection (MySQLConnectionAbstract): Active MySQL connector DB connection. - model_name (str, optional): Custom name for the model. - fit_extra_options (dict, optional): Extra options for fitting. - explain_extra_options (dict, optional): Extra options for explanations. - predict_extra_options (dict, optional): Extra options for predict/predict_proba. - - Methods: - predict(X): Predict class labels. - predict_proba(X): Predict class probabilities. - """ - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - model_name: Optional[str] = None, - fit_extra_options: Optional[dict] = None, - explain_extra_options: Optional[dict] = None, - predict_extra_options: Optional[dict] = None, - ): - """ - Initialize a MyClassifier. - - Args: - db_connection: Active MySQL connector database connection. - model_name: Optional, custom model name. - fit_extra_options: Optional fit options. - explain_extra_options: Optional explain options. - predict_extra_options: Optional predict/predict_proba options. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - MyBaseMLModel.__init__( - self, - db_connection, - ML_TASK.CLASSIFICATION, - model_name=model_name, - fit_extra_options=fit_extra_options, - ) - self.predict_extra_options = copy_dict(predict_extra_options) - self.explain_extra_options = copy_dict(explain_extra_options) - - def predict( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> np.ndarray: # pylint: disable=invalid-name - """ - Predict class labels for the input features using the MySQL model. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-predict-table.html - A full list of supported options can be found under "ML_PREDICT_TABLE Options" - - Args: - X: Input samples as a numpy array or pandas DataFrame. - - Returns: - ndarray: Array of predicted class labels, shape (n_samples,). - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - result = self._model.predict(X, options=self.predict_extra_options) - return result["Prediction"].to_numpy() - - def predict_proba( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> np.ndarray: # pylint: disable=invalid-name - """ - Predict class probabilities for the input features using the MySQL model. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-predict-table.html - A full list of supported options can be found under "ML_PREDICT_TABLE Options" - - Args: - X: Input samples as a numpy array or pandas DataFrame. - - Returns: - ndarray: Array of shape (n_samples, n_classes) with class probabilities. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - result = self._model.predict(X, options=self.predict_extra_options) - - classes = sorted(result["ml_results"].iloc[0]["probabilities"].keys()) - - return np.stack( - result["ml_results"].map( - lambda ml_result: [ - ml_result["probabilities"][class_name] for class_name in classes - ] - ) - ) - - def explain_predictions( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> pd.DataFrame: # pylint: disable=invalid-name - """ - Explain model predictions using provided data. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-explain-table.html - A full list of supported options can be found under "ML_EXPLAIN_TABLE Options" - - Args: - X: DataFrame for which predictions should be explained. - - Returns: - DataFrame containing explanation details (feature attributions, etc.) - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Temporary input/output tables are cleaned up after explanation. - """ - self._model.explain_predictions(X, options=self.explain_extra_options) diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/model.py b/server/venv/Lib/site-packages/mysql/ai/ml/model.py deleted file mode 100644 index ea02f49..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/model.py +++ /dev/null @@ -1,780 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -"""HeatWave ML model utilities for MySQL Connector/Python. - -Provides classes to manage training, prediction, scoring, and explanations -via MySQL HeatWave stored procedures. -""" -import copy -import json - -from enum import Enum -from typing import Any, Dict, Optional, Union - -import numpy as np -import pandas as pd - -from mysql.ai.utils import ( - VAR_NAME_SPACE, - atomic_transaction, - convert_to_df, - execute_sql, - format_value_sql, - get_random_name, - source_schema, - sql_response_to_df, - sql_table_from_df, - sql_table_to_df, - table_exists, - temporary_sql_tables, - validate_name, -) -from mysql.connector.abstracts import MySQLConnectionAbstract - - -class ML_TASK(Enum): # pylint: disable=invalid-name - """Enumeration of supported ML tasks for HeatWave.""" - - CLASSIFICATION = "classification" - REGRESSION = "regression" - FORECASTING = "forecasting" - ANOMALY_DETECTION = "anomaly_detection" - LOG_ANOMALY_DETECTION = "log_anomaly_detection" - RECOMMENDATION = "recommendation" - TOPIC_MODELING = "topic_modeling" - - @staticmethod - def get_task_string(task: Union[str, "ML_TASK"]) -> str: - """ - Return the string representation of a machine learning task. - - Args: - task (Union[str, ML_TASK]): The task to convert. - Accepts either a task enum member (ML_TASK) or a string. - - Returns: - str: The string value of the ML task. - """ - - if isinstance(task, str): - return task - - return task.value - - -class _MyModelCommon: - """ - Common utilities and workflow for MySQL HeatWave ML models. - - This class handles model lifecycle steps such as loading, fitting, scoring, - making predictions, and explaining models or predictions. Not intended for - direct instantiation, but as a superclass for heatwave model wrappers. - - Attributes: - db_connection: MySQL connector database connection. - task: ML task, e.g., "classification" or "regression". - model_name: Identifier of model in MySQL. - schema_name: Database schema used for operations and temp tables. - """ - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - task: Union[str, ML_TASK] = ML_TASK.CLASSIFICATION, - model_name: Optional[str] = None, - ): - """ - Instantiate _MyMLModelCommon. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-train.html - A full list of supported tasks can be found under "Common ML_TRAIN Options" - - Args: - db_connection: MySQL database connection. - task: ML task type (default: "classification"). - model_name: Name to register the model within MySQL (default: None). - - Raises: - ValueError: If the schema name is not valid - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Returns: - None - """ - self.db_connection = db_connection - self.task = ML_TASK.get_task_string(task) - self.schema_name = source_schema(db_connection) - - with atomic_transaction(self.db_connection) as cursor: - execute_sql(cursor, "CALL sys.ML_CREATE_OR_UPGRADE_CATALOG();") - - if model_name is None: - model_name = get_random_name(self._is_model_name_available) - - self.model_var = f"{VAR_NAME_SPACE}.{model_name}" - self.model_var_score = f"{self.model_var}.score" - - self.model_name = model_name - validate_name(model_name) - - with atomic_transaction(self.db_connection) as cursor: - execute_sql(cursor, f"SET @{self.model_var} = %s;", params=(model_name,)) - - def _delete_model(self) -> bool: - """ - Deletes the model from the model catalog if present - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Returns: - Whether the model was deleted - """ - current_user = self._get_user() - - qualified_model_catalog = f"ML_SCHEMA_{current_user}.MODEL_CATALOG" - delete_model = ( - f"DELETE FROM {qualified_model_catalog} " - f"WHERE model_handle = @{self.model_var}" - ) - - with atomic_transaction(self.db_connection) as cursor: - execute_sql(cursor, delete_model) - return cursor.rowcount > 0 - - def _get_model_info(self, model_name: str) -> Optional[dict]: - """ - Retrieves the model info from the model_catalog - - Args: - model_var: The model alias to retrieve - - Returns: - The model info from the model_catalog (None if the model is not present in the catalog) - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - - def process_col(elem: Any) -> Any: - if isinstance(elem, str): - try: - elem = json.loads(elem) - except json.JSONDecodeError: - pass - return elem - - current_user = self._get_user() - - qualified_model_catalog = f"ML_SCHEMA_{current_user}.MODEL_CATALOG" - model_exists = ( - f"SELECT * FROM {qualified_model_catalog} WHERE model_handle = %s" - ) - - with atomic_transaction(self.db_connection) as cursor: - execute_sql(cursor, model_exists, params=(model_name,)) - model_info_df = sql_response_to_df(cursor) - - if model_info_df.empty: - result = None - else: - unprocessed_result = model_info_df.to_json(orient="records") - unprocessed_result_json = json.loads(unprocessed_result)[0] - result = { - key: process_col(elem) - for key, elem in unprocessed_result_json.items() - } - - return result - - def get_model_info(self) -> Optional[dict]: - """ - Checks if the model name is available. - Model info is present in the catalog only if the model was previously fitted. - - Returns: - True if the model name is not part of the model catalog - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - return self._get_model_info(self.model_name) - - def _is_model_name_available(self, model_name: str) -> bool: - """ - Checks if the model name is available - - Returns: - True if the model name is not part of the model catalog - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - return self._get_model_info(model_name) is None - - def _load_model(self) -> None: - """ - Loads the model specified by `self.model_name` into MySQL. - After loading, the model is ready to handle ML operations. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-model-load.html - - Raises: - DatabaseError: - If the model is not initialized, i.e., fit or import has not been called - If a database connection issue occurs. - If an operational error occurs during execution. - - Returns: - None - """ - with atomic_transaction(self.db_connection) as cursor: - load_model_query = f"CALL sys.ML_MODEL_LOAD(@{self.model_var}, NULL);" - execute_sql(cursor, load_model_query) - - def _get_user(self) -> str: - """ - Fetch the current database user (without host). - - Returns: - The username string associated with the connection. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the user name includes unsupported characters - """ - with atomic_transaction(self.db_connection) as cursor: - cursor.execute("SELECT CURRENT_USER()") - current_user = cursor.fetchone()[0].split("@")[0] - - return validate_name(current_user) - - def explain_model(self) -> dict: - """ - Get model explanations, such as detailed feature importances. - - Returns: - dict: Feature importances and model explainability data. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-model-explanations.html - - Raises: - DatabaseError: - If the model is not initialized, i.e., fit or import has not been called - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: - If the model does not exist in the model catalog. - Should only occur if model was not fitted or was deleted. - """ - self._load_model() - with atomic_transaction(self.db_connection) as cursor: - current_user = self._get_user() - - qualified_model_catalog = f"ML_SCHEMA_{current_user}.MODEL_CATALOG" - explain_query = ( - f"SELECT model_explanation FROM {qualified_model_catalog} " - f"WHERE model_handle = @{self.model_var}" - ) - - execute_sql(cursor, explain_query) - df = sql_response_to_df(cursor) - - return df.iloc[0, 0] - - def _fit( - self, - table_name: str, - target_column_name: Optional[str], - options: Optional[dict], - ) -> None: - """ - Fit an ML model using a referenced SQL table and target column. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-train.html - A full list of supported options can be found under "Common ML_TRAIN Options" - - Args: - table_name: Name of the training data table. - target_column_name: Name of the target/label column. - options: Additional fit/config options (may override defaults). - - Raises: - DatabaseError: - If provided options are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the table or target_column name is not valid - - Returns: - None - """ - validate_name(table_name) - if target_column_name is not None: - validate_name(target_column_name) - target_col_string = f"'{target_column_name}'" - else: - target_col_string = "NULL" - - if options is None: - options = {} - options = copy.deepcopy(options) - options["task"] = self.task - - self._delete_model() - - with atomic_transaction(self.db_connection) as cursor: - placeholders, parameters = format_value_sql(options) - execute_sql( - cursor, - ( - "CALL sys.ML_TRAIN(" - f"'{self.schema_name}.{table_name}', " - f"{target_col_string}, " - f"{placeholders}, " - f"@{self.model_var}" - ")" - ), - params=parameters, - ) - - def _predict( - self, table_name: str, output_table_name: str, options: Optional[dict] - ) -> None: - """ - Predict on a given data table and write results to an output table. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-predict-table.html - A full list of supported options can be found under "ML_PREDICT_TABLE Options" - - Args: - table_name: Name of the SQL table with input data. - output_table_name: Name for the SQL output table to contain predictions. - options: Optional prediction options. - - Returns: - None - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the table or output_table name is not valid - """ - validate_name(table_name) - validate_name(output_table_name) - - self._load_model() - with atomic_transaction(self.db_connection) as cursor: - placeholders, parameters = format_value_sql(options) - execute_sql( - cursor, - ( - "CALL sys.ML_PREDICT_TABLE(" - f"'{self.schema_name}.{table_name}', " - f"@{self.model_var}, " - f"'{self.schema_name}.{output_table_name}', " - f"{placeholders}" - ")" - ), - params=parameters, - ) - - def _score( - self, - table_name: str, - target_column_name: str, - metric: str, - options: Optional[dict], - ) -> float: - """ - Evaluate model performance with a scoring metric. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-score.html - A full list of supported options can be found under - "Options for Recommendation Models" and - "Options for Anomaly Detection Models" - - Args: - table_name: Table with features and ground truth. - target_column_name: Column of true target labels. - metric: String name of the metric to compute. - options: Optional dictionary of further scoring options. - - Returns: - float: Computed score from the ML system. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the table or target_column name or metric is not valid - """ - validate_name(table_name) - validate_name(target_column_name) - validate_name(metric) - - self._load_model() - with atomic_transaction(self.db_connection) as cursor: - placeholders, parameters = format_value_sql(options) - execute_sql( - cursor, - ( - "CALL sys.ML_SCORE(" - f"'{self.schema_name}.{table_name}', " - f"'{target_column_name}', " - f"@{self.model_var}, " - "%s, " - f"@{self.model_var_score}, " - f"{placeholders}" - ")" - ), - params=[metric, *parameters], - ) - execute_sql(cursor, f"SELECT @{self.model_var_score}") - df = sql_response_to_df(cursor) - - return df.iloc[0, 0] - - def _explain_predictions( - self, table_name: str, output_table_name: str, options: Optional[dict] - ) -> pd.DataFrame: - """ - Produce explanations for model predictions on provided data. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-explain-table.html - A full list of supported options can be found under "ML_EXPLAIN_TABLE Options" - - Args: - table_name: Name of the SQL table with input data. - output_table_name: Name for the SQL table to store explanations. - options: Optional dictionary (default: - {"prediction_explainer": "permutation_importance"}). - - Returns: - DataFrame: Prediction explanations from the output SQL table. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the table or output_table name is not valid - """ - validate_name(table_name) - validate_name(output_table_name) - - if options is None: - options = {"prediction_explainer": "permutation_importance"} - - self._load_model() - - with atomic_transaction(self.db_connection) as cursor: - placeholders, parameters = format_value_sql(options) - execute_sql( - cursor, - ( - "CALL sys.ML_EXPLAIN_TABLE(" - f"'{self.schema_name}.{table_name}', " - f"@{self.model_var}, " - f"'{self.schema_name}.{output_table_name}', " - f"{placeholders}" - ")" - ), - params=parameters, - ) - execute_sql(cursor, f"SELECT * FROM {self.schema_name}.{output_table_name}") - df = sql_response_to_df(cursor) - - return df - - -class MyModel(_MyModelCommon): - """ - Convenience class for managing the ML workflow using pandas DataFrames. - - Methods convert in-memory DataFrames into temp SQL tables before delegating to the - _MyMLModelCommon routines, and automatically clean up temp resources. - """ - - def fit( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - y: Optional[Union[pd.DataFrame, np.ndarray]], - options: Optional[dict] = None, - ) -> None: - """ - Fit a model using DataFrame inputs. - - If an 'id' column is defined in either dataframe, it will be used as the primary key. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-train.html - A full list of supported options can be found under "Common ML_TRAIN Options" - - Args: - X: Features DataFrame. - y: (Optional) Target labels DataFrame or Series. If None, only X is used. - options: Additional options to pass to training. - - Returns: - None - - Raises: - DatabaseError: - If provided options are invalid or unsupported. - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Combines X and y as necessary. Creates a temporary table in the schema for training, - and deletes it afterward. - """ - X, y = convert_to_df(X), convert_to_df(y) - - with ( - atomic_transaction(self.db_connection) as cursor, - temporary_sql_tables(self.db_connection) as temporary_tables, - ): - if y is not None: - if isinstance(y, pd.DataFrame): - # keep column name if it exists - target_column_name = y.columns[0] - else: - target_column_name = get_random_name( - lambda name: name not in X.columns - ) - - if target_column_name in X.columns: - raise ValueError( - f"Target column y with name {target_column_name} already present " - "in feature dataframe X" - ) - - df_combined = X.copy() - df_combined[target_column_name] = y - final_df = df_combined - else: - target_column_name = None - final_df = X - - _, table_name = sql_table_from_df(cursor, self.schema_name, final_df) - temporary_tables.append((self.schema_name, table_name)) - - self._fit(table_name, target_column_name, options) - - def predict( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - options: Optional[dict] = None, - ) -> pd.DataFrame: - """ - Generate model predictions using DataFrame input. - - If an 'id' column is defined in either dataframe, it will be used as the primary key. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-predict-table.html - A full list of supported options can be found under "ML_PREDICT_TABLE Options" - - Args: - X: DataFrame containing prediction features (no labels). - options: Additional prediction settings. - - Returns: - DataFrame with prediction results as returned by HeatWave. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Temporary SQL tables are created and deleted for input/output. - """ - X = convert_to_df(X) - - with ( - atomic_transaction(self.db_connection) as cursor, - temporary_sql_tables(self.db_connection) as temporary_tables, - ): - _, table_name = sql_table_from_df(cursor, self.schema_name, X) - temporary_tables.append((self.schema_name, table_name)) - - output_table_name = get_random_name( - lambda table_name: not table_exists( - cursor, self.schema_name, table_name - ) - ) - temporary_tables.append((self.schema_name, output_table_name)) - - self._predict(table_name, output_table_name, options) - predictions = sql_table_to_df(cursor, self.schema_name, output_table_name) - - # ml_results is text but known to always follow JSON format - predictions["ml_results"] = predictions["ml_results"].map(json.loads) - - return predictions - - def score( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - y: Union[pd.DataFrame, np.ndarray], - metric: str, - options: Optional[dict] = None, - ) -> float: - """ - Score the model using X/y data and a selected metric. - - If an 'id' column is defined in either dataframe, it will be used as the primary key. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-score.html - A full list of supported options can be found under - "Options for Recommendation Models" and - "Options for Anomaly Detection Models" - - Args: - X: DataFrame of features. - y: DataFrame or Series of labels. - metric: Metric name (e.g., "balanced_accuracy"). - options: Optional ml scoring options. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - - Returns: - float: Computed score. - """ - X, y = convert_to_df(X), convert_to_df(y) - - with ( - atomic_transaction(self.db_connection) as cursor, - temporary_sql_tables(self.db_connection) as temporary_tables, - ): - target_column_name = get_random_name(lambda name: name not in X.columns) - df_combined = X.copy() - df_combined[target_column_name] = y - final_df = df_combined - - _, table_name = sql_table_from_df(cursor, self.schema_name, final_df) - temporary_tables.append((self.schema_name, table_name)) - - score = self._score(table_name, target_column_name, metric, options) - - return score - - def explain_predictions( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - options: Dict = None, - ) -> pd.DataFrame: - """ - Explain model predictions using provided data. - - If an 'id' column is defined in either dataframe, it will be used as the primary key. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-explain-table.html - A full list of supported options can be found under - "ML_EXPLAIN_TABLE Options" - - Args: - X: DataFrame for which predictions should be explained. - options: Optional dictionary of explainability options. - - Returns: - DataFrame containing explanation details (feature attributions, etc.) - - Raises: - DatabaseError: - If provided options are invalid or unsupported, or if the model is not initialized, - i.e., fit or import has not been called - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Temporary input/output tables are cleaned up after explanation. - """ - X = convert_to_df(X) - - with ( - atomic_transaction(self.db_connection) as cursor, - temporary_sql_tables(self.db_connection) as temporary_tables, - ): - - _, table_name = sql_table_from_df(cursor, self.schema_name, X) - temporary_tables.append((self.schema_name, table_name)) - - output_table_name = get_random_name( - lambda table_name: not table_exists( - cursor, self.schema_name, table_name - ) - ) - temporary_tables.append((self.schema_name, output_table_name)) - - explanations = self._explain_predictions( - table_name, output_table_name, options - ) - - return explanations diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/outlier.py b/server/venv/Lib/site-packages/mysql/ai/ml/outlier.py deleted file mode 100644 index aab3cb5..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/outlier.py +++ /dev/null @@ -1,221 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Outlier/anomaly detection utilities for MySQL Connector/Python. - -Provides a scikit-learn compatible wrapper using HeatWave to score anomalies. -""" -from typing import Optional, Union - -import numpy as np -import pandas as pd -from sklearn.base import OutlierMixin - -from mysql.ai.ml.base import MyBaseMLModel -from mysql.ai.ml.model import ML_TASK -from mysql.ai.utils import copy_dict - -from mysql.connector.abstracts import MySQLConnectionAbstract - -EPS = 1e-5 - - -def _get_logits(prob: Union[float, np.ndarray]) -> Union[float, np.ndarray]: - """ - Compute logit (logodds) for a probability, clipping to avoid numerical overflow. - - Args: - prob: Scalar or array of probability values in (0,1). - - Returns: - logit-transformed probabilities. - """ - result = np.clip(prob, EPS, 1 - EPS) - return np.log(result / (1 - result)) - - -class MyAnomalyDetector(MyBaseMLModel, OutlierMixin): - """ - MySQL HeatWave scikit-learn compatible anomaly/outlier detector. - - Flags samples as outliers when the probability of being an anomaly - exceeds a user-tunable threshold. - Includes helpers to obtain decision scores and anomaly probabilities - for ranking. - - Args: - db_connection (MySQLConnectionAbstract): Active MySQL DB connection. - model_name (str, optional): Custom model name in the database. - fit_extra_options (dict, optional): Extra options for fitting. - score_extra_options (dict, optional): Extra options for scoring/prediction. - - Attributes: - boundary: Decision threshold boundary in logit space. Derived from - trained model's catalog info - - Methods: - predict(X): Predict outlier/inlier labels. - score_samples(X): Compute anomaly (normal class) logit scores. - decision_function(X): Compute signed score above/below threshold for ranking. - """ - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - model_name: Optional[str] = None, - fit_extra_options: Optional[dict] = None, - score_extra_options: Optional[dict] = None, - ): - """ - Initialize an anomaly detector instance with threshold and extra options. - - Args: - db_connection: Active MySQL DB connection. - model_name: Optional model name in DB. - fit_extra_options: Optional extra fit options. - score_extra_options: Optional extra scoring options. - - Raises: - ValueError: If outlier_threshold is not in (0,1). - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - MyBaseMLModel.__init__( - self, - db_connection, - ML_TASK.ANOMALY_DETECTION, - model_name=model_name, - fit_extra_options=fit_extra_options, - ) - self.score_extra_options = copy_dict(score_extra_options) - self.boundary: Optional[float] = None - - def predict( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - ) -> np.ndarray: - """ - Predict outlier/inlier binary labels for input samples. - - Args: - X: Samples to predict on. - - Returns: - ndarray: Values are -1 for outliers, +1 for inliers, as per scikit-learn convention. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - return np.where(self.decision_function(X) < 0.0, -1, 1) - - def decision_function( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - ) -> np.ndarray: - """ - Compute signed distance to the outlier threshold. - - Args: - X: Samples to predict on. - - Returns: - ndarray: Score > 0 means inlier, < 0 means outlier; |value| gives margin. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: - If the provided model info does not provide threshold - """ - sample_scores = self.score_samples(X) - - if self.boundary is None: - model_info = self.get_model_info() - if model_info is None: - raise ValueError("Model does not exist in catalog.") - - threshold = model_info["model_metadata"]["training_params"].get( - "anomaly_detection_threshold", None - ) - if threshold is None: - raise ValueError( - "Trained model is outdated and does not support threshold. " - "Try retraining or using an existing, trained model with MyModel." - ) - - # scikit-learn uses large positive values as inlier - # and negative as outlier, so we need to flip our threshold - self.boundary = _get_logits(1.0 - threshold) - - return sample_scores - self.boundary - - def score_samples( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - ) -> np.ndarray: - """ - Compute normal probability logit score for each sample. - Used for ranking, thresholding. - - Args: - X: Samples to score. - - Returns: - ndarray: Logit scores based on "normal" class probability. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - result = self._model.predict(X, options=self.score_extra_options) - - return _get_logits( - result["ml_results"] - .apply(lambda x: x["probabilities"]["normal"]) - .to_numpy() - ) diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/regressor.py b/server/venv/Lib/site-packages/mysql/ai/ml/regressor.py deleted file mode 100644 index cfc7e63..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/regressor.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Regressor utilities for MySQL Connector/Python. - -Provides a scikit-learn compatible regressor backed by HeatWave ML. -""" -from typing import Optional, Union - -import numpy as np -import pandas as pd -from sklearn.base import RegressorMixin - -from mysql.ai.ml.base import MyBaseMLModel -from mysql.ai.ml.model import ML_TASK -from mysql.ai.utils import copy_dict - -from mysql.connector.abstracts import MySQLConnectionAbstract - - -class MyRegressor(MyBaseMLModel, RegressorMixin): - """ - MySQL HeatWave scikit-learn compatible regressor estimator. - - Provides prediction output from a regression model deployed in MySQL, - and manages fit, explain, and prediction options as per HeatWave ML interface. - - Attributes: - predict_extra_options (dict): Optional parameter dict passed to the backend for prediction. - _model (MyModel): Underlying interface for database model operations. - fit_extra_options (dict): See MyBaseMLModel. - explain_extra_options (dict): See MyBaseMLModel. - - Args: - db_connection (MySQLConnectionAbstract): Active MySQL connector DB connection. - model_name (str, optional): Custom name for the model. - fit_extra_options (dict, optional): Extra options for fitting. - explain_extra_options (dict, optional): Extra options for explanations. - predict_extra_options (dict, optional): Extra options for predictions. - - Methods: - predict(X): Predict regression target. - """ - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - model_name: Optional[str] = None, - fit_extra_options: Optional[dict] = None, - explain_extra_options: Optional[dict] = None, - predict_extra_options: Optional[dict] = None, - ): - """ - Initialize a MyRegressor. - - Args: - db_connection: Active MySQL connector database connection. - model_name: Optional, custom model name. - fit_extra_options: Optional fit options. - explain_extra_options: Optional explain options. - predict_extra_options: Optional prediction options. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - MyBaseMLModel.__init__( - self, - db_connection, - ML_TASK.REGRESSION, - model_name=model_name, - fit_extra_options=fit_extra_options, - ) - - self.predict_extra_options = copy_dict(predict_extra_options) - self.explain_extra_options = copy_dict(explain_extra_options) - - def predict( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> np.ndarray: # pylint: disable=invalid-name - """ - Predict a continuous target for the input features using the MySQL model. - - Args: - X: Input samples as a numpy array or pandas DataFrame. - - Returns: - ndarray: Array of predicted target values, shape (n_samples,). - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - result = self._model.predict(X, options=self.predict_extra_options) - return result["Prediction"].to_numpy() - - def explain_predictions( - self, X: Union[pd.DataFrame, np.ndarray] - ) -> pd.DataFrame: # pylint: disable=invalid-name - """ - Explain model predictions using provided data. - - References: - https://dev.mysql.com/doc/heatwave/en/mys-hwaml-ml-explain-table.html - A full list of supported options can be found under "ML_EXPLAIN_TABLE Options" - - Args: - X: DataFrame for which predictions should be explained. - - Returns: - DataFrame containing explanation details (feature attributions, etc.) - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - - Notes: - Temporary input/output tables are cleaned up after explanation. - """ - self._model.explain_predictions(X, options=self.explain_extra_options) diff --git a/server/venv/Lib/site-packages/mysql/ai/ml/transformer.py b/server/venv/Lib/site-packages/mysql/ai/ml/transformer.py deleted file mode 100644 index ba07d4a..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/ml/transformer.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -"""Generic transformer utilities for MySQL Connector/Python. - -Provides a scikit-learn compatible Transformer using HeatWave for fit/transform -and scoring operations. -""" -from typing import Optional, Union - -import numpy as np -import pandas as pd -from sklearn.base import TransformerMixin - -from mysql.ai.ml.base import MyBaseMLModel -from mysql.ai.ml.model import ML_TASK -from mysql.ai.utils import copy_dict -from mysql.connector.abstracts import MySQLConnectionAbstract - - -class MyGenericTransformer(MyBaseMLModel, TransformerMixin): - """ - MySQL HeatWave scikit-learn compatible generic transformer. - - Can be used as the transformation step in an sklearn pipeline. Implements fit, transform, - explain, and scoring capability, passing options for server-side transform logic. - - Args: - db_connection (MySQLConnectionAbstract): Active MySQL connector database connection. - task (str): ML task type for transformer (default: "classification"). - score_metric (str): Scoring metric to request from backend (default: "balanced_accuracy"). - model_name (str, optional): Custom name for the deployed model. - fit_extra_options (dict, optional): Extra fit options. - transform_extra_options (dict, optional): Extra options for transformations. - score_extra_options (dict, optional): Extra options for scoring. - - Attributes: - score_metric (str): Name of the backend metric to use for scoring - (e.g. "balanced_accuracy"). - score_extra_options (dict): Dictionary of optional scoring parameters; - passed to backend score. - transform_extra_options (dict): Dictionary of inference (/predict) - parameters for the backend. - fit_extra_options (dict): See MyBaseMLModel. - _model (MyModel): Underlying interface for database model operations. - - Methods: - fit(X, y): Fit the underlying model using the provided features/targets. - transform(X): Transform features using the backend model. - score(X, y): Score data using backend metric and options. - """ - - def __init__( - self, - db_connection: MySQLConnectionAbstract, - task: Union[str, ML_TASK] = ML_TASK.CLASSIFICATION, - score_metric: str = "balanced_accuracy", - model_name: Optional[str] = None, - fit_extra_options: Optional[dict] = None, - transform_extra_options: Optional[dict] = None, - score_extra_options: Optional[dict] = None, - ): - """ - Initialize transformer with required and optional arguments. - - Args: - db_connection: Active MySQL backend database connection. - task: ML task type for transformer. - score_metric: Requested backend scoring metric. - model_name: Optional model name for storage. - fit_extra_options: Optional extra options for fitting. - transform_extra_options: Optional extra options for transformation/inference. - score_extra_options: Optional extra scoring options. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - MyBaseMLModel.__init__( - self, - db_connection, - task, - model_name=model_name, - fit_extra_options=fit_extra_options, - ) - - self.score_metric = score_metric - self.score_extra_options = copy_dict(score_extra_options) - - self.transform_extra_options = copy_dict(transform_extra_options) - - def transform( - self, X: pd.DataFrame - ) -> pd.DataFrame: # pylint: disable=invalid-name - """ - Transform input data to model predictions using the underlying helper. - - Args: - X: DataFrame of features to predict/transform. - - Returns: - pd.DataFrame: Results of transformation as returned by backend. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - return self._model.predict(X, options=self.transform_extra_options) - - def score( - self, - X: Union[pd.DataFrame, np.ndarray], # pylint: disable=invalid-name - y: Union[pd.DataFrame, np.ndarray], - ) -> float: - """ - Score the transformed data using the backend scoring interface. - - Args: - X: Transformed features. - y: Target labels or data for scoring. - - Returns: - float: Score based on backend metric. - - Raises: - DatabaseError: - If provided options are invalid or unsupported, - or if the model is not initialized, i.e., fit or import has not - been called - If a database connection issue occurs. - If an operational error occurs during execution. - """ - return self._model.score( - X, y, self.score_metric, options=self.score_extra_options - ) diff --git a/server/venv/Lib/site-packages/mysql/ai/utils/__init__.py b/server/venv/Lib/site-packages/mysql/ai/utils/__init__.py deleted file mode 100644 index c6e5f62..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/utils/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Utilities for AI-related helpers in MySQL Connector/Python. - -This package exposes: -- check_dependencies(): runtime dependency guard for optional AI features -- atomic_transaction(): context manager ensuring atomic DB transactions -- utils: general-purpose helpers used by AI integrations - -Importing this package validates base dependencies required for AI utilities. -""" - -from .dependencies import check_dependencies - -check_dependencies(["BASE"]) - -from .atomic_cursor import atomic_transaction -from .utils import * diff --git a/server/venv/Lib/site-packages/mysql/ai/utils/atomic_cursor.py b/server/venv/Lib/site-packages/mysql/ai/utils/atomic_cursor.py deleted file mode 100644 index be13553..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/utils/atomic_cursor.py +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Atomic transaction context manager utilities for MySQL Connector/Python. - -Provides context manager atomic_transaction() that ensures commit on success -and rollback on error without obscuring the original exception. -""" - -from contextlib import contextmanager -from typing import Iterator - -from mysql.connector.abstracts import MySQLConnectionAbstract -from mysql.connector.cursor import MySQLCursorAbstract - - -@contextmanager -def atomic_transaction( - conn: MySQLConnectionAbstract, -) -> Iterator[MySQLCursorAbstract]: - """ - Context manager that wraps a MySQL database cursor and ensures transaction - rollback in case of exception. - - NOTE: DDL statements such as CREATE TABLE cause implicit commits. These cannot - be managed by a cursor object. Changes made at or before a DDL statement will - be committed and not rolled back. Callers are responsible for any cleanup of - this type. - - This class acts as a robust, PEP 343-compliant context manager for handling - database cursor operations on a MySQL connection. It ensures that all operations - executed within the context block are part of the same transaction, and - automatically calls `connection.rollback()` if an exception occurs, helping - to maintain database integrity. On normal completion (no exception), it simply - closes the cursor after use. Exceptions are always propagated to the caller. - - Args: - conn: A MySQLConnectionAbstract instance. - """ - old_autocommit = conn.autocommit - cursor = conn.cursor() - - exception_raised = False - try: - if old_autocommit: - conn.autocommit = False - - yield cursor # provide cursor to block - - conn.commit() - except Exception: # pylint: disable=broad-exception-caught - exception_raised = True - try: - conn.rollback() - except Exception: # pylint: disable=broad-exception-caught - # Don't obscure original exception - pass - - # Raise original exception - raise - finally: - conn.autocommit = old_autocommit - - try: - cursor.close() - except Exception: # pylint: disable=broad-exception-caught - # don't obscure original exception if exists - if not exception_raised: - raise diff --git a/server/venv/Lib/site-packages/mysql/ai/utils/dependencies.py b/server/venv/Lib/site-packages/mysql/ai/utils/dependencies.py deleted file mode 100644 index 77a502d..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/utils/dependencies.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Dependency checking utilities for AI features in MySQL Connector/Python. - -Provides check_dependencies() to assert required optional packages are present -with acceptable minimum versions at runtime. -""" - -import importlib.metadata - -from typing import List - - -def check_dependencies(tasks: List[str]) -> None: - """ - Check required runtime dependencies and minimum versions; raise an error - if any are missing or version-incompatible. - - This verifies the presence and minimum version of essential Python packages. - Missing or insufficient versions cause an ImportError listing the packages - and a suggested install command. - - Args: - tasks (List[str]): Task types to check requirements for. - - Raises: - ImportError: If any required dependencies are missing or below the - minimum version. - """ - task_set = set(tasks) - task_set.add("BASE") - - # Requirements: (import_name, min_version) - task_to_requirement = { - "BASE": [("pandas", "1.5.0")], - "GENAI": [ - ("langchain", "0.1.11"), - ("langchain_core", "0.1.11"), - ("pydantic", "1.10.0"), - ], - "ML": [("scikit-learn", "1.3.0")], - } - requirements = [] - for task in task_set: - requirements.extend(task_to_requirement[task]) - requirements_set = set(requirements) - - problems = [] - for name, min_version in requirements_set: - try: - installed_version = importlib.metadata.version(name) - # Version comparison uses simple string comparison to avoid extra - # dependencies. This is valid for the dependencies defined above; - # reconsider if adding packages with version schemes that do not - # compare correctly as strings. - error = installed_version < min_version - except importlib.metadata.PackageNotFoundError: - error = True - if error: - problems.append(f"{name} v{min_version} (or later)") - if problems: - raise ImportError("Please install " + ", ".join(problems) + ".") diff --git a/server/venv/Lib/site-packages/mysql/ai/utils/utils.py b/server/venv/Lib/site-packages/mysql/ai/utils/utils.py deleted file mode 100644 index c679dec..0000000 --- a/server/venv/Lib/site-packages/mysql/ai/utils/utils.py +++ /dev/null @@ -1,573 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -"""General utilities for AI features in MySQL Connector/Python. - -Includes helpers for: -- defensive dict copying -- temporary table lifecycle management -- SQL execution and result conversions -- DataFrame to/from SQL table utilities -- schema/table/column name validation -- array-like to DataFrame conversion -""" - -import copy -import json -import random -import re -import string - -from contextlib import contextmanager -from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union - -import numpy as np -import pandas as pd - -from mysql.ai.utils.atomic_cursor import atomic_transaction - -from mysql.connector.abstracts import MySQLConnectionAbstract -from mysql.connector.cursor import MySQLCursorAbstract -from mysql.connector.types import ParamsSequenceOrDictType - -VAR_NAME_SPACE = "mysql_ai" -RANDOM_TABLE_NAME_LENGTH = 32 - -PD_TO_SQL_DTYPE_MAPPING = { - "int64": "BIGINT", - "float64": "DOUBLE", - "object": "LONGTEXT", - "bool": "BOOLEAN", - "datetime64[ns]": "DATETIME", -} - -DEFAULT_SCHEMA = "mysql_ai" - -# Misc Utilities - - -def copy_dict(options: Optional[dict]) -> dict: - """ - Make a defensive copy of a dictionary, or return an empty dict if None. - - Args: - options: param dict or None - - Returns: - dict - """ - if options is None: - return {} - - return copy.deepcopy(options) - - -@contextmanager -def temporary_sql_tables( - db_connection: MySQLConnectionAbstract, -) -> Iterator[list[tuple[str, str]]]: - """ - Context manager to track and automatically clean up temporary SQL tables. - - Args: - db_connection: Database connection object used to create and delete tables. - - Returns: - None - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - - Yields: - temporary_tables: List of (schema_name, table_name) tuples created during the - context. All tables in this list are deleted on context exit. - """ - temporary_tables: List[Tuple[str, str]] = [] - try: - yield temporary_tables - finally: - with atomic_transaction(db_connection) as cursor: - for schema_name, table_name in temporary_tables: - delete_sql_table(cursor, schema_name, table_name) - - -def execute_sql( - cursor: MySQLCursorAbstract, query: str, params: ParamsSequenceOrDictType = None -) -> None: - """ - Execute an SQL query with optional parameters using the given cursor. - - Args: - cursor: MySQLCursorAbstract object to execute the query. - query: SQL query string to execute. - params: Optional sequence or dict providing parameters for the query. - - Raises: - DatabaseError: - If the provided SQL query/params are invalid - If the query is valid but the sql raises as an exception - If a database connection issue occurs. - If an operational error occurs during execution. - - Returns: - None - """ - cursor.execute(query, params or ()) - - -def _get_name() -> str: - """ - Generate a random uppercase string of fixed length for table names. - - Returns: - Random string of length RANDOM_TABLE_NAME_LENGTH. - """ - char_set = string.ascii_uppercase - return "".join(random.choices(char_set, k=RANDOM_TABLE_NAME_LENGTH)) - - -def get_random_name(condition: Callable[[str], bool], max_calls: int = 100) -> str: - """ - Generate a random string name that satisfies a given condition. - - Args: - condition: Callable that takes a generated name and returns True if it is valid. - max_calls: Maximum number of attempts before giving up (default 100). - - Returns: - A random string that fulfills the provided condition. - - Raises: - RuntimeError: If the maximum number of attempts is reached without success. - """ - for _ in range(max_calls): - if condition(name := _get_name()): - return name - # condition never met - raise RuntimeError("Reached max tries without successfully finding a unique name") - - -# Format conversions - - -def format_value_sql(value: Any) -> Tuple[str, List[Any]]: - """ - Convert a Python value into its SQL-compatible string representation and parameters. - - Args: - value: The value to format. - - Returns: - Tuple containing: - - A string for substitution into a SQL query. - - A list of parameters to be bound into the query. - """ - if isinstance(value, (dict, list)): - if len(value) == 0: - return "%s", [None] - return "CAST(%s as JSON)", [json.dumps(value)] - return "%s", [value] - - -def sql_response_to_df(cursor: MySQLCursorAbstract) -> pd.DataFrame: - """ - Convert the results of a cursor's last executed query to a pandas DataFrame. - - Args: - cursor: MySQLCursorAbstract with a completed query. - - Returns: - DataFrame with data from the cursor. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - If a compatible SELECT query wasn't the last statement ran - """ - - def _json_processor(elem: Optional[str]) -> Optional[dict]: - return json.loads(elem) if elem is not None else None - - def _default_processor(elem: Any) -> Any: - return elem - - idx_to_processor = {} - for idx, col in enumerate(cursor.description): - if col[1] == 245: - # 245 is the MySQL type code for JSON - idx_to_processor[idx] = _json_processor - else: - idx_to_processor[idx] = _default_processor - - rows = cursor.fetchall() - - # Process results - processed_rows = [] - for row in rows: - processed_row = list(row) - - for idx, elem in enumerate(row): - processed_row[idx] = idx_to_processor[idx](elem) - - processed_rows.append(processed_row) - - return pd.DataFrame(processed_rows, columns=cursor.column_names) - - -def sql_table_to_df( - cursor: MySQLCursorAbstract, schema_name: str, table_name: str -) -> pd.DataFrame: - """ - Load the entire contents of a SQL table into a pandas DataFrame. - - Args: - cursor: MySQLCursorAbstract to execute the query. - schema_name: Name of the schema containing the table. - table_name: Name of the table to fetch. - - Returns: - DataFrame containing all rows from the specified table. - - Raises: - DatabaseError: - If the table does not exist - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the schema or table name is not valid - """ - validate_name(schema_name) - validate_name(table_name) - - execute_sql(cursor, f"SELECT * FROM {schema_name}.{table_name}") - return sql_response_to_df(cursor) - - -# Table operations - - -def table_exists( - cursor: MySQLCursorAbstract, schema_name: str, table_name: str -) -> bool: - """ - Check whether a table exists in a specific schema. - - Args: - cursor: MySQLCursorAbstract object to execute the query. - schema_name: Name of the database schema. - table_name: Name of the table. - - Returns: - True if the table exists, False otherwise. - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the schema or table name is not valid - """ - validate_name(schema_name) - validate_name(table_name) - - cursor.execute( - """ - SELECT 1 - FROM information_schema.tables - WHERE table_schema = %s AND table_name = %s - LIMIT 1 - """, - (schema_name, table_name), - ) - return cursor.fetchone() is not None - - -def delete_sql_table( - cursor: MySQLCursorAbstract, schema_name: str, table_name: str -) -> None: - """ - Drop a table from the SQL database if it exists. - - Args: - cursor: MySQLCursorAbstract to execute the drop command. - schema_name: Name of the schema. - table_name: Name of the table to delete. - - Returns: - None - - Raises: - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the schema or table name is not valid - """ - validate_name(schema_name) - validate_name(table_name) - - execute_sql(cursor, f"DROP TABLE IF EXISTS {schema_name}.{table_name}") - - -def extend_sql_table( - cursor: MySQLCursorAbstract, - schema_name: str, - table_name: str, - df: pd.DataFrame, - col_name_to_placeholder_string: Dict[str, str] = None, -) -> None: - """ - Insert all rows from a pandas DataFrame into an existing SQL table. - - Args: - cursor: MySQLCursorAbstract for execution. - schema_name: Name of the database schema. - table_name: Table to insert new rows into. - df: DataFrame containing the rows to insert. - col_name_to_placeholder_string: - Optional mapping of column names to custom SQL value/placeholder - strings. - - Returns: - None - - Raises: - DatabaseError: - If the rows could not be inserted into the table, e.g., a type or shape issue - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the schema or table name is not valid - """ - if col_name_to_placeholder_string is None: - col_name_to_placeholder_string = {} - - validate_name(schema_name) - validate_name(table_name) - for col in df.columns: - validate_name(str(col)) - - qualified_table_name = f"{schema_name}.{table_name}" - - # Iterate over all rows in the DataFrame to build insert statements row by row - for row in df.values: - placeholders, params = [], [] - for elem, col in zip(row, df.columns): - elem = elem.item() if hasattr(elem, "item") else elem - - if col in col_name_to_placeholder_string: - elem_placeholder, elem_params = col_name_to_placeholder_string[col], [ - str(elem) - ] - else: - elem_placeholder, elem_params = format_value_sql(elem) - - placeholders.append(elem_placeholder) - params.extend(elem_params) - - cols_sql = ", ".join([str(col) for col in df.columns]) - placeholders_sql = ", ".join(placeholders) - insert_sql = ( - f"INSERT INTO {qualified_table_name} " - f"({cols_sql}) VALUES ({placeholders_sql})" - ) - execute_sql(cursor, insert_sql, params=params) - - -def sql_table_from_df( - cursor: MySQLCursorAbstract, schema_name: str, df: pd.DataFrame -) -> Tuple[str, str]: - """ - Create a new SQL table with a random name, and populate it with data from a DataFrame. - - If an 'id' column is defined in the dataframe, it will be used as the primary key. - - Args: - cursor: MySQLCursorAbstract for executing SQL. - schema_name: Schema in which to create the table. - df: DataFrame containing the data to be inserted. - - Returns: - Tuple (qualified_table_name, table_name): The schema-qualified and - unqualified table names. - - Raises: - RuntimeError: If a random available table name could not be found. - ValueError: If any schema, table, or a column name is invalid. - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - table_name = get_random_name( - lambda table_name: not table_exists(cursor, schema_name, table_name) - ) - qualified_table_name = f"{schema_name}.{table_name}" - - validate_name(schema_name) - validate_name(table_name) - for col in df.columns: - validate_name(str(col)) - - columns_sql = [] - for col, dtype in df.dtypes.items(): - # Map pandas dtype to SQL type, fallback is VARCHAR - sql_type = PD_TO_SQL_DTYPE_MAPPING.get(str(dtype), "LONGTEXT") - validate_name(str(col)) - columns_sql.append(f"{col} {sql_type}") - - columns_str = ", ".join(columns_sql) - - has_id_col = any(col.lower() == "id" for col in df.columns) - if has_id_col: - columns_str += ", PRIMARY KEY (id)" - - # Create table with generated columns - create_table_sql = f"CREATE TABLE {qualified_table_name} ({columns_str})" - execute_sql(cursor, create_table_sql) - - try: - # Insert provided data into new table - extend_sql_table(cursor, schema_name, table_name, df) - except Exception: # pylint: disable=broad-exception-caught - # Delete table before we lose access to it - delete_sql_table(cursor, schema_name, table_name) - raise - return qualified_table_name, table_name - - -def validate_name(name: str) -> str: - """ - Validate that the string is a legal SQL identifier (letters, digits, underscores). - - Args: - name: Name (schema, table, or column) to validate. - - Returns: - The validated name. - - Raises: - ValueError: If the name does not meet format requirements. - """ - # Accepts only letters, digits, and underscores; change as needed - if not (isinstance(name, str) and re.match(r"^[A-Za-z0-9_]+$", name)): - raise ValueError(f"Unsupported name format {name}") - - return name - - -def source_schema(db_connection: MySQLConnectionAbstract) -> str: - """ - Retrieve the name of the currently selected schema, or set and ensure the default schema. - - Args: - db_connection: MySQL connector database connection object. - - Returns: - Name of the schema (database in use). - - Raises: - ValueError: If the schema name is not valid - DatabaseError: - If a database connection issue occurs. - If an operational error occurs during execution. - """ - schema = db_connection.database - if schema is None: - schema = DEFAULT_SCHEMA - - with atomic_transaction(db_connection) as cursor: - create_database_stmt = f"CREATE DATABASE IF NOT EXISTS {schema}" - execute_sql(cursor, create_database_stmt) - - validate_name(schema) - - return schema - - -def is_table_empty( - cursor: MySQLCursorAbstract, schema_name: str, table_name: str -) -> bool: - """ - Determine if a given SQL table is empty. - - Args: - cursor: MySQLCursorAbstract with access to the database. - schema_name: Name of the schema containing the table. - table_name: Name of the table to check. - - Returns: - True if the table has no rows, False otherwise. - - Raises: - DatabaseError: - If the table does not exist - If a database connection issue occurs. - If an operational error occurs during execution. - ValueError: If the schema or table name is not valid - """ - validate_name(schema_name) - validate_name(table_name) - - cursor.execute(f"SELECT 1 FROM {schema_name}.{table_name} LIMIT 1") - return cursor.fetchone() is None - - -def convert_to_df( - arr: Optional[Union[pd.DataFrame, pd.Series, np.ndarray]], - col_prefix: str = "feature", -) -> Optional[pd.DataFrame]: - """ - Convert input data to a pandas DataFrame if necessary. - - Args: - arr: Input data as a pandas DataFrame, NumPy ndarray, pandas Series, or None. - - Returns: - If the input is None, returns None. - Otherwise, returns a DataFrame backed by the same underlying data whenever - possible (except in cases where pandas or NumPy must copy, such as for - certain views or non-contiguous arrays). - - Notes: - - If an ndarray is passed, column names will be integer indices (0, 1, ...). - - If a DataFrame is passed, column names and indices are preserved. - - The returned DataFrame is a shallow copy and shares data with the original - input when possible; however, copies may still occur for certain input - types or memory layouts. - """ - if arr is None: - return None - - if isinstance(arr, pd.DataFrame): - return pd.DataFrame(arr) - if isinstance(arr, pd.Series): - return arr.to_frame() - - if arr.ndim == 1: - arr = arr.reshape(-1, 1) - col_names = [f"{col_prefix}_{idx}" for idx in range(arr.shape[1])] - - return pd.DataFrame(arr, columns=col_names, copy=False) diff --git a/server/venv/Lib/site-packages/mysql/connector/__init__.py b/server/venv/Lib/site-packages/mysql/connector/__init__.py deleted file mode 100644 index fbb145d..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/__init__.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""MySQL Connector/Python - MySQL driver written in Python.""" - -try: - from .connection_cext import CMySQLConnection -except ImportError: - HAVE_CEXT = False -else: - HAVE_CEXT = True - - -from . import version -from .connection import MySQLConnection -from .constants import CharacterSet, ClientFlag, FieldFlag, FieldType, RefreshOption -from .dbapi import ( - BINARY, - DATETIME, - NUMBER, - ROWID, - STRING, - Binary, - Date, - DateFromTicks, - Time, - TimeFromTicks, - Timestamp, - TimestampFromTicks, - apilevel, - paramstyle, - threadsafety, -) -from .errors import ( # pylint: disable=redefined-builtin - DatabaseError, - DataError, - Error, - IntegrityError, - InterfaceError, - InternalError, - NotSupportedError, - OperationalError, - PoolError, - ProgrammingError, - Warning, - custom_error_exception, -) -from .pooling import connect - -Connect = connect - -__version_info__ = version.VERSION -"""This attribute indicates the Connector/Python version as an array -of version components.""" - -__version__ = version.VERSION_TEXT -"""This attribute indicates the Connector/Python version as a string.""" - -__all__ = [ - "MySQLConnection", - "Connect", - "custom_error_exception", - # Some useful constants - "FieldType", - "FieldFlag", - "ClientFlag", - "CharacterSet", - "RefreshOption", - "HAVE_CEXT", - # Error handling - "Error", - "Warning", - "InterfaceError", - "DatabaseError", - "NotSupportedError", - "DataError", - "IntegrityError", - "PoolError", - "ProgrammingError", - "OperationalError", - "InternalError", - # DBAPI PEP 249 required exports - "connect", - "apilevel", - "threadsafety", - "paramstyle", - "Date", - "Time", - "Timestamp", - "Binary", - "DateFromTicks", - "DateFromTicks", - "TimestampFromTicks", - "TimeFromTicks", - "STRING", - "BINARY", - "NUMBER", - "DATETIME", - "ROWID", - # C Extension - "CMySQLConnection", -] diff --git a/server/venv/Lib/site-packages/mysql/connector/_decorating.py b/server/venv/Lib/site-packages/mysql/connector/_decorating.py deleted file mode 100644 index ae31711..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/_decorating.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Decorators Hub.""" - -import functools -import warnings - -from typing import TYPE_CHECKING, Any, Callable - -from .constants import RefreshOption - -if TYPE_CHECKING: - from .abstracts import MySQLConnectionAbstract - - -def cmd_refresh_verify_options() -> Callable: - """Decorator verifying which options are relevant and which aren't based on - the server version the client is connecting to.""" - - def decorator(cmd_refresh: Callable) -> Callable: - @functools.wraps(cmd_refresh) - def wrapper( - cnx: "MySQLConnectionAbstract", *args: Any, **kwargs: Any - ) -> Callable: - options: int = args[0] - if (options & RefreshOption.GRANT) and cnx.server_version >= ( - 9, - 2, - 0, - ): - warnings.warn( - "As of MySQL Server 9.2.0, refreshing grant tables is not needed " - "if you use statements GRANT, REVOKE, CREATE, DROP, or ALTER. " - "You should expect this option to be unsupported in a future " - "version of MySQL Connector/Python when MySQL Server removes it.", - category=DeprecationWarning, - stacklevel=1, - ) - - return cmd_refresh(cnx, options, **kwargs) - - return wrapper - - return decorator - - -def handle_read_write_timeout() -> Callable: - """ - Decorator to close the current connection if a read or a write timeout - is raised by the method passed via the func parameter. - """ - - def decorator(cnx_method: Callable) -> Callable: - @functools.wraps(cnx_method) - def handle_cnx_method( - cnx: "MySQLConnectionAbstract", *args: Any, **kwargs: Any - ) -> Any: - try: - return cnx_method(cnx, *args, **kwargs) - except Exception as err: - if isinstance(err, TimeoutError): - cnx.close() - raise err - - return handle_cnx_method - - return decorator - - -def deprecated(reason: str) -> Callable: - """Use it to decorate deprecated methods.""" - - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - def wrapper(*args: Any, **kwargs: Any) -> Callable: - warnings.warn( - f"Call to deprecated function {func.__name__}. Reason: {reason}", - category=DeprecationWarning, - stacklevel=2, - ) - return func(*args, **kwargs) - - return wrapper - - return decorator diff --git a/server/venv/Lib/site-packages/mysql/connector/_scripting.py b/server/venv/Lib/site-packages/mysql/connector/_scripting.py deleted file mode 100644 index 7cec860..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/_scripting.py +++ /dev/null @@ -1,406 +0,0 @@ -# Copyright (c) 2024, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Classes and methods utilized to work with MySQL Scripts.""" - -import re -import unicodedata - -from collections import deque -from typing import Deque, Generator, Optional - -from .errors import InterfaceError -from .types import MySQLScriptPartition - -DEFAULT_DELIMITER = b";" -"""The default delimiter of MySQL Client and the only one -recognized by the MySQL server protocol.""" - -DELIMITER_RESERVED_SYMBOLS = { - "$": rb"\$", - "^": rb"\^", - "?": rb"\?", - "(": rb"\(", - ")": rb"\)", - "[": rb"\[", - "]": rb"\]", - "{": rb"\{", - "}": rb"\}", - ".": rb"\.", - "|": rb"\|", - "+": rb"\+", - "-": rb"\-", - "*": rb"\*", -} -"""Symbols with a special meaning in regular expression contexts.""" - -DELIMITER_PATTERN: re.Pattern = re.compile( - rb"""(delimiter\s+)(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)""", - flags=re.IGNORECASE | re.MULTILINE, -) -"""Regular expression pattern recognizing the delimiter command.""" - - -class MySQLScriptSplitter: - """Breaks a MySQL script into single statements. - - It strips custom delimiters and comments along the way, except for comments - representing a MySQL extension or optimizer hint. - """ - - _regex_sql_split_stmts = b"""(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)""" - - def __init__(self, sql_script: bytes) -> None: - """Constructor.""" - self._code = sql_script - self._single_stmts: Optional[list[bytes]] = None - self._mappable_stmts: Optional[list[bytes]] = None - self._re_sql_split_stmts: dict[bytes, re.Pattern] = {} - - def _split_statement(self, code: bytes, delimiter: bytes) -> list[bytes]: - """Split code context by delimiter.""" - snippets = [] - - if delimiter not in self._re_sql_split_stmts: - if b"\\" in delimiter: - raise InterfaceError( - "The backslash (\\) character is not a valid delimiter." - ) - delimiter_pattern = [ - DELIMITER_RESERVED_SYMBOLS.get(char, char.encode()) - for char in delimiter.decode() - ] - self._re_sql_split_stmts[delimiter] = re.compile( - b"".join(delimiter_pattern) + self._regex_sql_split_stmts - ) - - for snippet in self._re_sql_split_stmts[delimiter].split(code): - snippet_strip = snippet.strip() - if snippet_strip: - snippets.append(snippet_strip) - - return snippets - - @staticmethod - def is_white_space_char(char: int) -> bool: - """Validates whether `char` is a white-space character or not.""" - return unicodedata.category(chr(char))[0] in {"Z"} - - @staticmethod - def is_control_char(char: int) -> bool: - """Validates whether `char` is a control character or not.""" - return unicodedata.category(chr(char))[0] in {"C"} - - @staticmethod - def split_by_control_char_or_white_space(string: bytes) -> list[bytes]: - """Split `string` by any control character or whitespace.""" - return re.split(rb"[\s\x00-\x1f\x7f-\x9f]", string) - - @staticmethod - def has_delimiter(code: bytes) -> bool: - """Validates whether `code` has the delimiter command pattern or not.""" - return re.search(DELIMITER_PATTERN, code) is not None - - @staticmethod - def remove_comments(code: bytes) -> bytes: - """Remove MySQL comments which include `--`-style, `#`-style - and `C`-style comments. - - A `--`-style comment spans from `--` to the end of the line. - It requires the second dash to be - followed by at least one whitespace or control character - (such as a space, tab, newline, and so on). - - A `#`-style comment spans from `#` to the end of the line. - - A C-style comment spans from a `/*` sequence to the following `*/` - sequence, as in the C programming language. This syntax enables a - comment to extend over multiple lines because the beginning and - closing sequences need not be on the same line. - - **NOTE: Only C-style comments representing MySQL extensions or - optimizer hints are preserved**. E.g., - - ``` - /*! MySQL-specific code */ - - /*+ MySQL-specific code */ - ``` - - *For Reference Manual- MySQL Comments*, see - https://dev.mysql.com/doc/refman/en/comments.html. - """ - - def is_dash_style(b_str: bytes, b_char: int) -> bool: - return b_str == b"--" and ( - MySQLScriptSplitter.is_control_char(b_char) - or MySQLScriptSplitter.is_white_space_char(b_char) - ) - - def is_hash_style(b_str: bytes) -> bool: - return b_str == b"#" - - def is_c_style(b_str: bytes, b_char: int) -> bool: - return b_str == b"/*" and b_char not in {ord("!"), ord("+")} - - buf = bytearray(b"") - i, literal_ctx = 0, None - line_break, single_quote, double_quote = ord("\n"), ord("'"), ord('"') - while i < len(code): - if literal_ctx is None: - style = None - if is_dash_style(buf[-2:], code[i]): - style = "--" - elif is_hash_style(buf[-1:]): - style = "#" - elif is_c_style(buf[-2:], code[i]): - style = "/*" - if style is not None: - if style in ("--", "#"): - while i < len(code) and code[i] != line_break: - i += 1 - else: - while i + 1 < len(code) and code[i : i + 2] != b"*/": - i += 1 - i += 2 - - for _ in range(len(style)): - buf.pop() - - while buf and ( - MySQLScriptSplitter.is_control_char(buf[-1]) - or MySQLScriptSplitter.is_white_space_char(buf[-1]) - ): - buf.pop() - - continue - - if literal_ctx is None and code[i] in [single_quote, double_quote]: - literal_ctx = code[i] - elif literal_ctx is not None and code[i] in {literal_ctx, line_break}: - literal_ctx = None - - buf.append(code[i]) - i += 1 - - return bytes(buf) - - def split_script(self, remove_comments: bool = True) -> list[bytes]: - """Splits the given script text into a sequence of individual statements. - - The word DELIMITER and any of its lower and upper case combinations - such as delimiter, DeLiMiter, etc., are considered reserved words by - the connector. Users must quote these when included in multi statements - for other purposes different from declaring an actual statement delimiter; - e.g., as names for tables, columns, variables, in comments, etc. - - ``` - CREATE TABLE `delimiter` (begin INT, end INT); -- I am a `DELimiTer` comment - ``` - - If they are not quoted, the statement-mapping will not produce the expected - experience. - - See https://dev.mysql.com/doc/refman/8.0/en/keywords.html to know more - about quoting a reserved word. - - *Note that comments are always ignored as they are not considered to be - part of statements, with one exeception; **C-style comments representing - MySQL extensions or optimizer hints are preserved***. - """ - # If it was already computed, then skip computation and use the cache - if self._single_stmts is not None: - return self._single_stmts - - # initialize variables - self._single_stmts = [] - delimiter = DEFAULT_DELIMITER - buf: list[bytes] = [] - prev = b"" - - # remove comments - if remove_comments: - code = MySQLScriptSplitter.remove_comments(code=self._code) - else: - code = self._code - - # let's split the script by `delimiter pattern` - the pattern is also - # included in the returned list. - for curr in re.split(pattern=DELIMITER_PATTERN, string=code): - # Checking if the previous substring is a "switch of context - # (delimiter)" point. - if re.search(DELIMITER_PATTERN, prev): - # The next delimiter must be the sequence of chars until - # reaching a control char or whitespace - next_delimiter = self.split_by_control_char_or_white_space(curr)[0] - - # We shall remove the delimiter command from the code - buf.pop() - - # At this point buf includes all the code where `delimiter` applies. - self._single_stmts.extend( - self._split_statement(code=b" ".join(buf), delimiter=delimiter) - ) - - # From the current substring, let's take everything but the - # "next delimiter" portion. Also, let's update the delimiter - delimiter, buf = next_delimiter, [curr[len(next_delimiter) :]] - else: - # Let's accumulate - buf.append(curr) - - # track the previous substring - prev = curr - - # Ensure there are no loose ends - if buf: - self._single_stmts.extend( - self._split_statement(code=b" ".join(buf), delimiter=delimiter) - ) - - return self._single_stmts - - def __repr__(self) -> str: - return self._code.decode("utf-8") - - -def split_multi_statement( - sql_code: bytes, - map_results: bool = False, -) -> Generator[MySQLScriptPartition, None, None]: - """Breaks a MySQL script into sub-scripts. - - If the given script uses `DELIMITER` statements (which are not recognized - by MySQL Server), the connector will parse such statements to remove them - from the script and substitute delimiters as needed. This pre-processing - may cause a performance hit when using long scripts. Note that when enabling - `map_results`, the script is expected to use `DELIMITER` statements in order - to split the script into multiple query strings. - - Args: - sql_code: MySQL script. - map_results: If True, each sub-script is `statement-result` mappable. - - Returns: - A generator of typed dictionaries with keys `single_stmts` and `mappable_stmts`. - - If mapping disabled and no delimiters detected, it returns a 1-item generator, - the field `single_stmts` is an empty list and the `mappable_stmt` field - corresponds to the unmodified script, that may be mappable. - - If mapping disabled and delimiters detected, it returns a 1-item generator, - the field `single_stmts` is a list including all the single statements - found in the script and the `mappable_stmt` field corresponds to the processed - script (delimiters are stripped) that may be mappable. - - If maping enabled, the script is broken into mappable partitions. It returns - an N-item generator (as many items as computed partitions), the field - `single_stmts` is a list including all the single statements of the partition - and the `mappable_stmt` field corresponds to the sub-script (partition) that - is guaranteed to be mappable. - - Raises: - `InterfaceError` if an invalid delimiter string is found. - """ - if not MySQLScriptSplitter.has_delimiter(sql_code) and not map_results: - # For those users executing single statements or scripts with no delimiters, - # they can get a performance boost by bypassing the multi statement splitter. - - # Simply wrap the multi statement up (so it can be processed correctly - # downstream) and return it as it is. - yield MySQLScriptPartition(single_stmts=deque([]), mappable_stmt=sql_code) - return - - tok = MySQLScriptSplitter(sql_script=sql_code) - - # The splitter splits the sql code into many single statements - # while also getting rid of the delimiters (if any). - stmts = tok.split_script() - - # if there are not statements to execute - if not stmts: - # Simply wrap the multi statement up (so it can be processed correctly - # downstream). - yield MySQLScriptPartition(single_stmts=deque([b""]), mappable_stmt=b"") - return - - if not map_results: - # group single statements into a unique and possibly no mappable - # multi statement. - yield MySQLScriptPartition( - single_stmts=deque(stmts), mappable_stmt=b";\n".join(stmts) - ) - return - - # group single statements into one or more mappable multi statements. - i = 0 - partition_ids = (j for j, stmt in enumerate(stmts) if stmt[:5].upper() == b"CALL ") - for j in partition_ids: - if j > i: - yield ( - MySQLScriptPartition( - mappable_stmt=b";\n".join(stmts[i:j]), - single_stmts=deque(stmts[i:j]), - ) - ) - yield MySQLScriptPartition( - mappable_stmt=stmts[j], single_stmts=deque([stmts[j]]) - ) - i = j + 1 - - if i < len(stmts): - yield ( - MySQLScriptPartition( - mappable_stmt=b";\n".join(stmts[i : len(stmts)]), - single_stmts=deque(stmts[i : len(stmts)]), - ) - ) - - -def get_local_infile_filenames(script: bytes) -> Deque[str]: - """Scans the MySQL script looking for `filenames` (one for each - `LOCAL INFILE` statement found). - - Arguments: - script: a MySQL script that may include one or more `LOCAL INFILE` statements. - - Returns: - filenames: a list of filenames (one for each `LOCAL INFILE` statement found). - An empty list is returned if no matches are found. - """ - matches = re.findall( - pattern=rb"""LOCAL\s+INFILE\s+(["'])((?:\\\1|(?:(?!\1)).)*)(\1)""", - string=MySQLScriptSplitter.remove_comments(script), - flags=re.IGNORECASE, - ) - if not matches or len(matches[0]) != 3: - return deque([]) - - # If there is a match, we get ("'", "filename", "'") , that's to say, - # the 1st and 3rd entries are the quote symbols, and the 2nd the actual filename. - return deque([match[1].decode("utf-8") for match in matches]) diff --git a/server/venv/Lib/site-packages/mysql/connector/abstracts.py b/server/venv/Lib/site-packages/mysql/connector/abstracts.py deleted file mode 100644 index b6f0df9..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/abstracts.py +++ /dev/null @@ -1,3185 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="assignment,attr-defined" - -"""Module gathering all abstract base classes.""" - -from __future__ import annotations - -import os -import re -import weakref - -from abc import ABC, abstractmethod -from datetime import date, datetime, time, timedelta -from decimal import Decimal -from inspect import signature -from time import sleep -from types import TracebackType -from typing import ( - Any, - BinaryIO, - Callable, - ClassVar, - Deque, - Dict, - Generator, - Iterator, - List, - Mapping, - NoReturn, - Optional, - Sequence, - Tuple, - Type, - Union, - cast, -) - -TLS_V1_3_SUPPORTED = False -try: - import ssl - - if hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3: - TLS_V1_3_SUPPORTED = True -except ImportError: - # If import fails, we don't have SSL support. - pass - -from .constants import ( - CONN_ATTRS_DN, - DEFAULT_CONFIGURATION, - DEPRECATED_METHOD_WARNING, - MYSQL_DEFAULT_CHARSET_ID_57, - MYSQL_DEFAULT_CHARSET_ID_80, - OPENSSL_CS_NAMES, - TLS_CIPHER_SUITES, - TLS_VERSIONS, - CharacterSet, - ClientFlag, -) -from .conversion import MySQLConverter, MySQLConverterBase -from .errors import ( - DatabaseError, - Error, - InterfaceError, - NotSupportedError, - ProgrammingError, -) -from .opentelemetry.constants import ( - CONNECTION_SPAN_NAME, - OPTION_CNX_SPAN, - OPTION_CNX_TRACER, - OTEL_ENABLED, -) - -if OTEL_ENABLED: - from .opentelemetry.instrumentation import ( - end_span, - record_exception_event, - set_connection_span_attrs, - trace, - ) - -from ._decorating import deprecated -from .optionfiles import read_option_files -from .tls_ciphers import UNACCEPTABLE_TLS_CIPHERSUITES, UNACCEPTABLE_TLS_VERSIONS -from .types import ( - BinaryProtocolType, - CextEofPacketType, - DescriptionType, - EofPacketType, - HandShakeType, - MySQLConvertibleType, - MySQLScriptPartition, - RowItemType, - RowType, - StrOrBytes, - WarningType, -) -from .utils import GenericWrapper, import_object - -DUPLICATED_IN_LIST_ERROR = ( - "The '{list}' list must not contain repeated values, the value " - "'{value}' is duplicated." -) - -TLS_VERSION_ERROR = ( - "The given tls_version: '{}' is not recognized as a valid " - "TLS protocol version (should be one of {})." -) - -TLS_VERSION_UNACCEPTABLE_ERROR = ( - "The given tls_version: '{}' are no longer allowed (should be one of {})." -) - -TLS_VER_NO_SUPPORTED = ( - "No supported TLS protocol version found in the 'tls-versions' list '{}'. " -) - -KRB_SERVICE_PRINCIPAL_ERROR = ( - 'Option "krb_service_principal" {error}, must be a string in the form ' - '"primary/instance@realm" e.g "ldap/ldapauth@MYSQL.COM" where "@realm" ' - "is optional and if it is not given will be assumed to belong to the " - "default realm, as configured in the krb5.conf file." -) - -OPENID_TOKEN_FILE_ERROR = ( - 'Option "openid_token_file" {error}, it must be a string in the form ' - '"path/to/openid/token/file".' -) - -MYSQL_PY_TYPES = ( - Decimal, - bytes, - date, - datetime, - float, - int, - str, - time, - timedelta, -) - - -class CMySQLPrepStmt(GenericWrapper): - """Structure to represent a result from `CMySQLConnection.cmd_stmt_prepare`. - It can be used consistently as a type hint. - - `_mysql_connector.MySQLPrepStmt` isn't available when the C-ext isn't built. - - In this regard, `CmdStmtPrepareResult` acts as a proxy/wrapper entity for a - `_mysql_connector.MySQLPrepStmt` instance. - """ - - -class MySQLConnectionAbstract(ABC): - """Abstract class for classes connecting to a MySQL server.""" - - def __init__(self) -> None: - """Initialize""" - # private (shouldn't be manipulated directly internally) - self.__charset_id: Optional[int] = None - """It shouldn't be manipulated directly, even internally. If you need - to manipulate the charset ID, use the property `_charset_id` (read & write) - instead. Similarly, `_charset_id` shouldn't be manipulated externally, - in this case, use property `charset_id` (read-only). - """ - - # protected (can be manipulated directly internally) - self._tracer: Any = None # opentelemetry related - self._span: Any = None # opentelemetry related - self.otel_context_propagation: bool = True # opentelemetry related - - self._client_flags: int = ClientFlag.get_default() - self._sql_mode: Optional[str] = None - self._time_zone: Optional[str] = None - self._autocommit: bool = False - self._server_version: Optional[Tuple[int, ...]] = None - self._handshake: Optional[HandShakeType] = None - self._conn_attrs: Dict[str, str] = {} - - self._user: str = "" - self._password: str = "" - self._password1: str = "" - self._password2: str = "" - self._password3: str = "" - self._database: str = "" - self._host: str = "127.0.0.1" - self._port: int = 3306 - self._unix_socket: Optional[str] = None - self._client_host: str = "" - self._client_port: int = 0 - self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {} - self._ssl_disabled: bool = DEFAULT_CONFIGURATION["ssl_disabled"] - self._force_ipv6: bool = False - self._oci_config_file: Optional[str] = None - self._oci_config_profile: Optional[str] = None - self._webauthn_callback: Optional[Union[str, Callable[[str], None]]] = None - self._krb_service_principal: Optional[str] = None - self._openid_token_file: Optional[str] = None - - self._use_unicode: bool = True - self._get_warnings: bool = False - self._raise_on_warnings: bool = False - self._connection_timeout: Optional[int] = DEFAULT_CONFIGURATION[ - "connect_timeout" - ] - self._read_timeout: Optional[int] = DEFAULT_CONFIGURATION["read_timeout"] - self._write_timeout: Optional[int] = DEFAULT_CONFIGURATION["write_timeout"] - self._buffered: bool = False - self._unread_result: bool = False - self._have_next_result: bool = False - self._raw: bool = False - self._in_transaction: bool = False - self._allow_local_infile: bool = DEFAULT_CONFIGURATION["allow_local_infile"] - self._allow_local_infile_in_path: Optional[str] = DEFAULT_CONFIGURATION[ - "allow_local_infile_in_path" - ] - - self._prepared_statements: Any = None - self._query_attrs: Dict[str, BinaryProtocolType] = {} - - self._ssl_active: bool = False - self._auth_plugin: Optional[str] = None - self._auth_plugin_class: Optional[str] = None - self._pool_config_version: Any = None - self.converter: Optional[MySQLConverter] = None - self._converter_class: Optional[Type[MySQLConverter]] = None - self._converter_str_fallback: bool = False - self._compress: bool = False - - self._consume_results: bool = False - self._init_command: Optional[str] = None - self._character_set: CharacterSet = CharacterSet() - - self._local_infile_filenames: Optional[Deque[str]] = None - """Stores the filenames from `LOCAL INFILE` requests - found in the executed query.""" - - self._query: Optional[bytes] = None - """The query being processed.""" - - def __enter__(self) -> MySQLConnectionAbstract: - return self - - def __exit__( - self, - exc_type: Type[BaseException], - exc_value: BaseException, - traceback: TracebackType, - ) -> None: - self.close() - - def get_self(self) -> MySQLConnectionAbstract: - """Returns self for `weakref.proxy`. - - This method is used when the original object is needed when using - `weakref.proxy`. - """ - return self - - @property - def is_secure(self) -> bool: - """Returns `True` if is a secure connection.""" - return self._ssl_active or ( - self._unix_socket is not None and os.name == "posix" - ) - - @property - def have_next_result(self) -> bool: - """Returns If have next result.""" - return self._have_next_result - - @property - def query_attrs(self) -> List[Tuple[str, BinaryProtocolType]]: - """Returns query attributes list.""" - return list(self._query_attrs.items()) - - def query_attrs_append(self, value: Tuple[str, BinaryProtocolType]) -> None: - """Adds element to the query attributes list on the connector's side. - - If an element in the query attributes list already matches - the attribute name provided, the new element will NOT be added. - - Args: - value: key-value as a 2-tuple. - """ - attr_name, attr_value = value - if attr_name not in self._query_attrs: - self._query_attrs[attr_name] = attr_value - - def query_attrs_remove(self, name: str) -> BinaryProtocolType: - """Removes element by name from the query attributes list on the connector's side. - - If no match, `None` is returned, else the corresponding value is returned. - - Args: - name: key name. - """ - return self._query_attrs.pop(name, None) - - def query_attrs_clear(self) -> None: - """Clears query attributes list on the connector's side.""" - self._query_attrs = {} - - def _validate_tls_ciphersuites(self) -> None: - """Validates the tls_ciphersuites option.""" - tls_ciphersuites = [] - tls_cs = self._ssl["tls_ciphersuites"] - - if isinstance(tls_cs, str): - if not (tls_cs.startswith("[") and tls_cs.endswith("]")): - raise AttributeError( - f"tls_ciphersuites must be a list, found: '{tls_cs}'" - ) - tls_css = tls_cs[1:-1].split(",") - if not tls_css: - raise AttributeError( - "No valid cipher suite found in 'tls_ciphersuites' list" - ) - for _tls_cs in tls_css: - _tls_cs = tls_cs.strip().upper() - if _tls_cs: - tls_ciphersuites.append(_tls_cs) - - elif isinstance(tls_cs, (list, set)): - tls_ciphersuites = [tls_cs for tls_cs in tls_cs if tls_cs] - else: - raise AttributeError( - "tls_ciphersuites should be a list with one or more " - f"ciphersuites. Found: '{tls_cs}'" - ) - - tls_versions = ( - TLS_VERSIONS[:] - if self._ssl.get("tls_versions", None) is None - else self._ssl["tls_versions"][:] # type: ignore[index] - ) - - # A newer TLS version can use a cipher introduced on - # an older version. - tls_versions.sort(reverse=True) # type: ignore[union-attr] - newer_tls_ver = tls_versions[0] - # translated_names[0] are TLSv1.2 only - # translated_names[1] are TLSv1.3 only - translated_names: List[List[str]] = [[], []] - iani_cipher_suites_names = {} - ossl_cipher_suites_names: List[str] = [] - - # Old ciphers can work with new TLS versions. - # Find all the ciphers introduced on previous TLS versions. - for tls_ver in TLS_VERSIONS[: TLS_VERSIONS.index(newer_tls_ver) + 1]: - iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver]) - ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver]) - - for name in tls_ciphersuites: - if "-" in name and name in ossl_cipher_suites_names: - if name in OPENSSL_CS_NAMES["TLSv1.3"]: - translated_names[1].append(name) - else: - translated_names[0].append(name) - elif name in iani_cipher_suites_names: - translated_name = iani_cipher_suites_names[name] - if translated_name in translated_names: - raise AttributeError( - DUPLICATED_IN_LIST_ERROR.format( - list="tls_ciphersuites", value=translated_name - ) - ) - if name in TLS_CIPHER_SUITES["TLSv1.3"]: - translated_names[1].append(iani_cipher_suites_names[name]) - else: - translated_names[0].append(iani_cipher_suites_names[name]) - else: - raise AttributeError( - f"The value '{name}' in tls_ciphersuites is not a valid " - "cipher suite" - ) - if not translated_names[0] and not translated_names[1]: - raise AttributeError( - "No valid cipher suite found in the 'tls_ciphersuites' list" - ) - - # raise an error when using an unacceptable cipher - for cipher_as_ossl in translated_names[0]: - if cipher_as_ossl in UNACCEPTABLE_TLS_CIPHERSUITES["TLSv1.2"].values(): - raise NotSupportedError( - f"Cipher {cipher_as_ossl} when used with TLSv1.2 is unacceptable." - ) - for cipher_as_ossl in translated_names[1]: - if cipher_as_ossl in UNACCEPTABLE_TLS_CIPHERSUITES["TLSv1.3"].values(): - raise NotSupportedError( - f"Cipher {cipher_as_ossl} when used with TLSv1.3 is unacceptable." - ) - - self._ssl["tls_ciphersuites"] = [ - ":".join(translated_names[0]), - ":".join(translated_names[1]), - ] - - def _validate_tls_versions(self) -> None: - """Validates the tls_versions option.""" - tls_versions = [] - tls_version = self._ssl["tls_versions"] - - if isinstance(tls_version, str): - if not (tls_version.startswith("[") and tls_version.endswith("]")): - raise AttributeError( - f"tls_versions must be a list, found: '{tls_version}'" - ) - tls_vers = tls_version[1:-1].split(",") - for tls_ver in tls_vers: - tls_version = tls_ver.strip() - if tls_version == "": - continue - if tls_version in tls_versions: - raise AttributeError( - DUPLICATED_IN_LIST_ERROR.format( - list="tls_versions", value=tls_version - ) - ) - tls_versions.append(tls_version) - if tls_vers == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED: - raise AttributeError( - TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS) - ) - elif isinstance(tls_version, list): - if not tls_version: - raise AttributeError( - "At least one TLS protocol version must be specified in " - "'tls_versions' list" - ) - for tls_ver in tls_version: - if tls_ver in tls_versions: - raise AttributeError( - DUPLICATED_IN_LIST_ERROR.format( - list="tls_versions", value=tls_ver - ) - ) - tls_versions.append(tls_ver) - elif isinstance(tls_version, set): - for tls_ver in tls_version: - tls_versions.append(tls_ver) - else: - raise AttributeError( - "tls_versions should be a list with one or more of versions " - f"in {', '.join(TLS_VERSIONS)}. found: '{tls_versions}'" - ) - - if not tls_versions: - raise AttributeError( - "At least one TLS protocol version must be specified " - "in 'tls_versions' list when this option is given" - ) - - use_tls_versions = [] - unacceptable_tls_versions = [] - invalid_tls_versions = [] - for tls_ver in tls_versions: - if tls_ver in TLS_VERSIONS: - use_tls_versions.append(tls_ver) - if tls_ver in UNACCEPTABLE_TLS_VERSIONS: - unacceptable_tls_versions.append(tls_ver) - else: - invalid_tls_versions.append(tls_ver) - - if use_tls_versions: - if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED: - raise NotSupportedError( - TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS) - ) - self._ssl["tls_versions"] = use_tls_versions - elif unacceptable_tls_versions: - raise NotSupportedError( - TLS_VERSION_UNACCEPTABLE_ERROR.format( - unacceptable_tls_versions, TLS_VERSIONS - ) - ) - elif invalid_tls_versions: - raise AttributeError(TLS_VERSION_ERROR.format(tls_ver, TLS_VERSIONS)) - - @property - def user(self) -> str: - """The user name used for connecting to the MySQL server.""" - return self._user - - @property - def server_host(self) -> str: - """MySQL server IP address or name.""" - return self._host - - @property - def server_port(self) -> int: - "MySQL server TCP/IP port." - return self._port - - @property - def unix_socket(self) -> Optional[str]: - "The Unix socket file for connecting to the MySQL server." - return self._unix_socket - - @property - @abstractmethod - def database(self) -> str: - """The current database.""" - - @database.setter - def database(self, value: str) -> None: - """Sets the current database.""" - self.cmd_query(f"USE {value}") - - @property - def can_consume_results(self) -> bool: - """Returns whether to consume results.""" - return self._consume_results - - @can_consume_results.setter - def can_consume_results(self, value: bool) -> None: - """Sets if can consume results.""" - assert isinstance(value, bool) - self._consume_results = value - - @property - def pool_config_version(self) -> Any: - """Returns the pool configuration version.""" - return self._pool_config_version - - @pool_config_version.setter - def pool_config_version(self, value: Any) -> None: - """Sets the pool configuration version""" - self._pool_config_version = value - - def config(self, **kwargs: Any) -> None: - """Configures the MySQL Connection. - - This method allows you to configure the `MySQLConnection` - instance after it has been instantiated. - - Args: - **kwargs: For a complete list of possible arguments, see [1]. - - Raises: - AttributeError: When provided unsupported connection arguments. - InterfaceError: When the provided connection argument is invalid. - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - # opentelemetry related - self._span = kwargs.pop(OPTION_CNX_SPAN, None) - self._tracer = kwargs.pop(OPTION_CNX_TRACER, None) - - config = kwargs.copy() - if "dsn" in config: - raise NotSupportedError("Data source name is not supported") - - # Read option files - config = read_option_files(**config) - - # Configure how we handle MySQL warnings - try: - self.get_warnings = config["get_warnings"] - del config["get_warnings"] - except KeyError: - pass # Leave what was set or default - try: - self.raise_on_warnings = config["raise_on_warnings"] - del config["raise_on_warnings"] - except KeyError: - pass # Leave what was set or default - - # Configure client flags - try: - default = ClientFlag.get_default() - self.client_flags = config["client_flags"] or default - del config["client_flags"] - except KeyError: - pass # Missing client_flags-argument is OK - - try: - if config["compress"]: - self._compress = True - self.client_flags = [ClientFlag.COMPRESS] - except KeyError: - pass # Missing compress argument is OK - - self._allow_local_infile = config.get( - "allow_local_infile", DEFAULT_CONFIGURATION["allow_local_infile"] - ) - self._allow_local_infile_in_path = config.get( - "allow_local_infile_in_path", - DEFAULT_CONFIGURATION["allow_local_infile_in_path"], - ) - infile_in_path = None - if self._allow_local_infile_in_path: - infile_in_path = os.path.abspath(self._allow_local_infile_in_path) - if ( - infile_in_path - and os.path.exists(infile_in_path) - and not os.path.isdir(infile_in_path) - or os.path.islink(infile_in_path) - ): - raise AttributeError("allow_local_infile_in_path must be a directory") - if self._allow_local_infile or self._allow_local_infile_in_path: - self.client_flags = [ClientFlag.LOCAL_FILES] - else: - self.client_flags = [-ClientFlag.LOCAL_FILES] - - try: - if not config["consume_results"]: - self._consume_results = False - else: - self._consume_results = True - except KeyError: - self._consume_results = False - - # Configure auth_plugin - try: - self._auth_plugin = config["auth_plugin"] - del config["auth_plugin"] - except KeyError: - self._auth_plugin = "" - - # Disallow the usage of some default authentication plugins - if self._auth_plugin == "authentication_webauthn_client": - raise InterfaceError( - f"'{self._auth_plugin}' cannot be used as the default authentication " - "plugin" - ) - - # Set converter class - try: - self.converter_class = config["converter_class"] - except KeyError: - pass # Using default converter class - except TypeError as err: - raise AttributeError( - "Converter class should be a subclass of " - "conversion.MySQLConverterBase" - ) from err - - # Compatible configuration with other drivers - compat_map = [ - # (,) - ("db", "database"), - ("username", "user"), - ("passwd", "password"), - ("connect_timeout", "connection_timeout"), - ("read_default_file", "option_files"), - ] - for compat, translate in compat_map: - try: - if translate not in config: - config[translate] = config[compat] - del config[compat] - except KeyError: - pass # Missing compat argument is OK - - # Configure login information - if "user" in config or "password" in config: - try: - user = config["user"] - del config["user"] - except KeyError: - user = self._user - try: - password = config["password"] - del config["password"] - except KeyError: - password = self._password - self.set_login(user, password) - - # Configure host information - if "host" in config and config["host"]: - self._host = config["host"] - - # Check network locations - try: - self._port = int(config["port"]) - del config["port"] - except KeyError: - pass # Missing port argument is OK - except ValueError as err: - raise InterfaceError("TCP/IP port number should be an integer") from err - - if "ssl_disabled" in config: - self._ssl_disabled = config.pop("ssl_disabled") - - # If an init_command is set, keep it, so we can execute it in _post_connection - if "init_command" in config: - self._init_command = config["init_command"] - del config["init_command"] - - # Other configuration - set_ssl_flag = False - for key, value in config.items(): - try: - DEFAULT_CONFIGURATION[key] - except KeyError: - raise AttributeError(f"Unsupported argument '{key}'") from None - # SSL Configuration - if key.startswith("ssl_"): - set_ssl_flag = True - self._ssl.update({key.replace("ssl_", ""): value}) - elif key.startswith("tls_"): - set_ssl_flag = True - self._ssl.update({key: value}) - else: - attribute = "_" + key - try: - setattr(self, attribute, value.strip()) - except AttributeError: - setattr(self, attribute, value) - - # Disable SSL for unix socket connections - if self._unix_socket and os.name == "posix": - self._ssl_disabled = True - - if self._ssl_disabled: - if self._auth_plugin == "mysql_clear_password": - raise InterfaceError( - "Clear password authentication is not supported over insecure channels" - ) - if self._auth_plugin == "authentication_openid_connect_client": - raise InterfaceError( - "OpenID Connect authentication is not supported over insecure channels" - ) - - if set_ssl_flag: - if "verify_cert" not in self._ssl: - self._ssl["verify_cert"] = DEFAULT_CONFIGURATION["ssl_verify_cert"] - if "verify_identity" not in self._ssl: - self._ssl["verify_identity"] = DEFAULT_CONFIGURATION[ - "ssl_verify_identity" - ] - # Make sure both ssl_key/ssl_cert are set, or neither (XOR) - if "ca" not in self._ssl or self._ssl["ca"] is None: - self._ssl["ca"] = "" - if bool("key" in self._ssl) != bool("cert" in self._ssl): - raise AttributeError( - "ssl_key and ssl_cert need to be both specified, or neither" - ) - # Make sure key/cert are set to None - if not set(("key", "cert")) <= set(self._ssl): - self._ssl["key"] = None - self._ssl["cert"] = None - elif (self._ssl["key"] is None) != (self._ssl["cert"] is None): - raise AttributeError( - "ssl_key and ssl_cert need to be both set, or neither" - ) - if self._ssl.get("tls_versions") is not None: - self._validate_tls_versions() - - if self._ssl.get("tls_ciphersuites") is not None: - self._validate_tls_ciphersuites() - - if self._conn_attrs is None: - self._conn_attrs = {} - elif not isinstance(self._conn_attrs, dict): - raise InterfaceError("conn_attrs must be of type dict") - else: - for attr_name, attr_value in self._conn_attrs.items(): - if attr_name in CONN_ATTRS_DN: - continue - # Validate name type - if not isinstance(attr_name, str): - raise InterfaceError( - "Attribute name should be a string, found: " - f"'{attr_name}' in '{self._conn_attrs}'" - ) - # Validate attribute name limit 32 characters - if len(attr_name) > 32: - raise InterfaceError( - f"Attribute name '{attr_name}' exceeds 32 characters limit size" - ) - # Validate names in connection attributes cannot start with "_" - if attr_name.startswith("_"): - raise InterfaceError( - "Key names in connection attributes cannot start with " - "'_', found: '{attr_name}'" - ) - # Validate value type - if not isinstance(attr_value, str): - raise InterfaceError( - f"Attribute '{attr_name}' value: '{attr_value}' must " - "be a string type" - ) - # Validate attribute value limit 1024 characters - if len(attr_value) > 1024: - raise InterfaceError( - f"Attribute '{attr_name}' value: '{attr_value}' " - "exceeds 1024 characters limit size" - ) - - if self._client_flags & ClientFlag.CONNECT_ARGS: - self._add_default_conn_attrs() - - if "kerberos_auth_mode" in config and config["kerberos_auth_mode"] is not None: - if not isinstance(config["kerberos_auth_mode"], str): - raise InterfaceError("'kerberos_auth_mode' must be of type str") - kerberos_auth_mode = config["kerberos_auth_mode"].lower() - if kerberos_auth_mode == "sspi": - if os.name != "nt": - raise InterfaceError( - "'kerberos_auth_mode=SSPI' is only available on Windows" - ) - self._auth_plugin_class = "MySQLSSPIKerberosAuthPlugin" - elif kerberos_auth_mode == "gssapi": - self._auth_plugin_class = "MySQLKerberosAuthPlugin" - else: - raise InterfaceError( - "Invalid 'kerberos_auth_mode' mode. Please use 'SSPI' or 'GSSAPI'" - ) - - if ( - "krb_service_principal" in config - and config["krb_service_principal"] is not None - ): - self._krb_service_principal = config["krb_service_principal"] - if not isinstance(self._krb_service_principal, str): - raise InterfaceError( - KRB_SERVICE_PRINCIPAL_ERROR.format(error="is not a string") - ) - if self._krb_service_principal == "": - raise InterfaceError( - KRB_SERVICE_PRINCIPAL_ERROR.format( - error="can not be an empty string" - ) - ) - if "/" not in self._krb_service_principal: - raise InterfaceError( - KRB_SERVICE_PRINCIPAL_ERROR.format(error="is incorrectly formatted") - ) - - if self._webauthn_callback: - self._validate_callable("webauth_callback", self._webauthn_callback, 1) - - if config.get("openid_token_file") is not None: - self._openid_token_file = config["openid_token_file"] - if not isinstance(self._openid_token_file, str): - raise InterfaceError( - OPENID_TOKEN_FILE_ERROR.format(error="is not a string") - ) - if self._openid_token_file == "": - raise InterfaceError( - OPENID_TOKEN_FILE_ERROR.format(error="cannot be an empty string") - ) - if not os.path.exists(self._openid_token_file): - raise InterfaceError( - f"The path '{self._openid_token_file}' provided via 'openid_token_file' " - "does not exist" - ) - - if config.get("read_timeout") is not None: - self._read_timeout = config["read_timeout"] - if not isinstance(self._read_timeout, int) or self._read_timeout < 0: - raise InterfaceError("Option read_timeout must be a positive integer") - if config.get("write_timeout") is not None: - self._write_timeout = config["write_timeout"] - if not isinstance(self._write_timeout, int) or self._write_timeout < 0: - raise InterfaceError("Option write_timeout must be a positive integer") - - def _add_default_conn_attrs(self) -> None: - """Adds the default connection attributes.""" - - @staticmethod - def _validate_callable( - option_name: str, callback: Union[str, Callable], num_args: int = 0 - ) -> None: - """Validates if it's a Python callable. - - Args: - option_name (str): Connection option name. - callback (str or callable): The fully qualified path to the callable or - a callable. - num_args (int): Number of positional arguments allowed. - - Raises: - ProgrammingError: If `callback` is not valid or wrong number of positional - arguments. - - .. versionadded:: 8.2.0 - """ - if isinstance(callback, str): - try: - callback = import_object(callback) - except ValueError as err: - raise ProgrammingError(f"{err}") from err - - if not callable(callback): - raise ProgrammingError(f"Expected a callable for '{option_name}'") - - # Check if the callable signature has positional arguments - num_params = len(signature(callback).parameters) - if num_params != num_args: - raise ProgrammingError( - f"'{option_name}' requires {num_args} positional argument, but the " - f"callback provided has {num_params}" - ) - - @staticmethod - def _check_server_version(server_version: StrOrBytes) -> Tuple[int, ...]: - """Checks the MySQL version. - - This method will check the MySQL version and raise an InterfaceError - when it is not supported or invalid. It will return the version - as a tuple with major, minor and patch. - - Raises InterfaceError if invalid server version. - - Returns tuple - """ - if isinstance(server_version, (bytearray, bytes)): - server_version = server_version.decode() - - regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") - match = regex_ver.match(server_version) - if not match: - raise InterfaceError("Failed parsing MySQL version") - - version = tuple(int(v) for v in match.groups()[0:3]) - if version < (4, 1): - raise InterfaceError(f"MySQL Version '{server_version}' is not supported") - - return version - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="server_version")) - def get_server_version(self) -> Optional[Tuple[int, ...]]: - """Gets the MySQL version. - - Returns: - The MySQL server version as a tuple. If not previously connected, it will - return `None`. - """ - return self._server_version - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="server_info")) - def get_server_info(self) -> Optional[str]: - """Gets the original MySQL version information. - - Returns: - The original MySQL server as text. If not previously connected, it will - return `None`. - """ - return self.server_info - - @property - def server_version(self) -> Optional[Tuple[int, ...]]: - """Gets the MySQL Server version the connector is connected to. - - Returns: - The MySQL server version as a tuple. If not previously connected, it will - return `None`. - """ - return self._server_version - - @property - def server_info(self) -> Optional[str]: - """Gets the original MySQL server version information. - - Returns: - The original MySQL server as text. If not previously connected, it will - return `None`. - """ - try: - return self._handshake["server_version_original"] # type: ignore[return-value] - except (TypeError, KeyError): - return None - - @property - @abstractmethod - def in_transaction(self) -> bool: - """Returns bool to indicate whether a transaction is active for the connection. - - The value is `True` regardless of whether you start a transaction using the - `start_transaction()` API call or by directly executing an SQL statement such - as START TRANSACTION or BEGIN. - - `in_transaction` was added in MySQL Connector/Python 1.1.0. - - Examples: - ``` - >>> cnx.start_transaction() - >>> cnx.in_transaction - True - >>> cnx.commit() - >>> cnx.in_transaction - False - ``` - """ - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="client_flags")) - def set_client_flags(self, flags: Union[int, Sequence[int]]) -> int: - """Sets the client flags. - - The flags-argument can be either an int or a list (or tuple) of - ClientFlag-values. If it is an integer, it will set client_flags - to flags as is. - - If flags is a sequence, each item in the sequence sets the flag when the - value is positive or unsets it when negative (see example below). - - Args: - flags: A list (or tuple), each flag will be set or unset when it's negative. - - Returns: - integer: Client flags. - - Raises: - ProgrammingError: When the flags argument is not a set or an integer - bigger than 0. - - Examples: - ``` - For example, to unset `LONG_FLAG` and set the `FOUND_ROWS` flags: - >>> from mysql.connector.constants import ClientFlag - >>> cnx.set_client_flags([ClientFlag.FOUND_ROWS, -ClientFlag.LONG_FLAG]) - >>> cnx.reconnect() - ``` - """ - self.client_flags = flags - return self.client_flags - - @property - def client_flags(self) -> int: - """Gets the client flags of the current session.""" - return self._client_flags - - @client_flags.setter - def client_flags(self, flags: Union[int, Sequence[int]]) -> None: - """Sets the client flags. - - The flags-argument can be either an int or a list (or tuple) of - ClientFlag-values. If it is an integer, it will set client_flags - to flags as is. - - If flags is a sequence, each item in the sequence sets the flag when the - value is positive or unsets it when negative (see example below). - - Args: - flags: A list (or tuple), each flag will be set or unset when it's negative. - - Raises: - ProgrammingError: When the flags argument is not a set or an integer - bigger than 0. - - Examples: - ``` - For example, to unset `LONG_FLAG` and set the `FOUND_ROWS` flags: - >>> from mysql.connector.constants import ClientFlag - >>> cnx.client_flags = [ClientFlag.FOUND_ROWS, -ClientFlag.LONG_FLAG] - >>> cnx.reconnect() - ``` - """ - if isinstance(flags, int) and flags > 0: - self._client_flags = flags - elif isinstance(flags, (tuple, list)): - for flag in flags: - if flag < 0: - self._client_flags &= ~abs(flag) - else: - self._client_flags |= flag - else: - raise ProgrammingError("client_flags setter expect integer (>0) or set") - - def shutdown(self) -> NoReturn: - """Shuts down connection to MySQL Server. - - This method closes the socket. It raises no exceptions. - - Unlike `disconnect()`, `shutdown()` closes the client connection without - attempting to send a `QUIT` command to the server first. Thus, it will not - block if the connection is disrupted for some reason such as network failure. - """ - raise NotImplementedError - - def isset_client_flag(self, flag: int) -> bool: - """Checks if a client flag is set. - - Returns: - `True` if the client flag was set, `False` otherwise. - """ - return (self._client_flags & flag) > 0 - - @property - def time_zone(self) -> str: - """Gets the current time zone.""" - return self.info_query("SELECT @@session.time_zone")[ - 0 - ] # type: ignore[return-value] - - @time_zone.setter - def time_zone(self, value: str) -> None: - """Sets the time zone.""" - self.cmd_query(f"SET @@session.time_zone = '{value}'") - self._time_zone = value - - @property - def sql_mode(self) -> str: - """Gets the SQL mode.""" - if self._sql_mode is None: - self._sql_mode = self.info_query("SELECT @@session.sql_mode")[0] - return self._sql_mode - - @sql_mode.setter - def sql_mode(self, value: Union[str, Sequence[int]]) -> None: - """Sets the SQL mode. - - This method sets the SQL Mode for the current connection. The value - argument can be either a string with comma separate mode names, or - a sequence of mode names. - - It is good practice to use the constants class `SQLMode`: - ``` - >>> from mysql.connector.constants import SQLMode - >>> cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT] - ``` - """ - if isinstance(value, (list, tuple)): - value = ",".join(value) - self.cmd_query(f"SET @@session.sql_mode = '{value}'") - self._sql_mode = value - - @abstractmethod - def info_query(self, query: str) -> Optional[RowType]: - """Sends a query which only returns 1 row. - - Shortcut for: - - ``` - cursor = self.cursor(buffered=True) - cursor.execute(query) - return cursor.fetchone() - ``` - - Args: - query: Statement to execute. - - Returns: - row: A tuple (RowType). - """ - - def set_login( - self, username: Optional[str] = None, password: Optional[str] = None - ) -> None: - """Sets login information for MySQL. - - Sets the username and/or password for the user connecting to the MySQL Server. - - Args: - username: Account's user name. - password: Account's password. - """ - if username is not None: - self._user = username.strip() - else: - self._user = "" - if password is not None: - self._password = password - else: - self._password = "" - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="use_unicode")) - def set_unicode(self, value: bool = True) -> None: - """Sets whether we return string fields as unicode or not. - - Args: - value: A boolean - default is `True`. - """ - self.use_unicode = value - - @property - def use_unicode(self) -> bool: - """Gets whether we return string fields as unicode or not.""" - return self._use_unicode - - @use_unicode.setter - @abstractmethod - def use_unicode(self, value: bool) -> None: - """Sets whether we return string fields as unicode or not. - - Args: - value: A boolean - default is `True`. - """ - - @property - def autocommit(self) -> bool: - """Gets whether autocommit is on or off.""" - value = self.info_query("SELECT @@session.autocommit")[0] - return value == 1 - - @autocommit.setter - def autocommit(self, value: bool) -> None: - """Toggles autocommit.""" - switch = "ON" if value else "OFF" - self.cmd_query(f"SET @@session.autocommit = {switch}") - self._autocommit = value - - @property - def get_warnings(self) -> bool: - """Gets whether this connection retrieves warnings automatically. - - This method returns whether this connection retrieves warnings - automatically. - - Returns `True`, or `False` when warnings are not retrieved. - """ - return self._get_warnings - - @get_warnings.setter - def get_warnings(self, value: bool) -> None: - """Sets whether warnings should be automatically retrieved. - - The toggle-argument must be a boolean. When True, cursors for this - connection will retrieve information about warnings (if any). - - Raises `ValueError` on error. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._get_warnings = value - - @property - def raise_on_warnings(self) -> bool: - """Gets whether this connection raises an error on warnings. - - This method returns whether this connection will raise errors when - MySQL reports warnings. - - Returns `True` or `False`. - """ - return self._raise_on_warnings - - @raise_on_warnings.setter - def raise_on_warnings(self, value: bool) -> None: - """Sets whether warnings raise an error. - - The toggle-argument must be a boolean. When True, cursors for this - connection will raise an error when MySQL reports warnings. - - Raising on warnings implies retrieving warnings automatically. In - other words: warnings will be set to True. If set to False, warnings - will be also set to False. - - Raises `ValueError` on error. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._raise_on_warnings = value - # Don't disable warning retrieval if raising explicitly disabled - if value: - self._get_warnings = value - - @property - def unread_result(self) -> bool: - """Gets whether there is an unread result. - - This method is used by cursors to check whether another cursor still - needs to retrieve its result set. - - Returns `True`, or `False` when there is no unread result. - """ - return self._unread_result - - @unread_result.setter - def unread_result(self, value: bool) -> None: - """Sets whether there is an unread result. - - This method is used by cursors to let other cursors know there is - still a result set that needs to be retrieved. - - Raises `ValueError` on errors. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._unread_result = value - - @property - def collation(self) -> str: - """Returns the collation for current connection. - - This property returns the collation name of the current connection. - The server is queried when the connection is active. If not connected, - the configured collation name is returned. - - Returns a string. - """ - return self._character_set.get_charset_info(self._charset_id)[2] - - @property - def charset(self) -> str: - """Returns the character set for current connection. - - This property returns the character set name of the current connection. - The server is queried when the connection is active. If not connected, - the configured character set name is returned. - - Returns a string. - """ - return self._character_set.get_info(self._charset_id)[0] - - @property - def charset_id(self) -> int: - """The charset ID utilized during the connection phase. - - If the charset ID hasn't been set, the default charset ID is returned. - """ - return self._charset_id - - @property - def _charset_id(self) -> int: - """The charset ID utilized during the connection phase. - - If the charset ID hasn't been set, the default charset ID is returned. - """ - if self.__charset_id is None: - if self._server_version is None: - # We mustn't set the private since we still don't know - # the server version. We temporarily return the default - # charset for undefined scenarios - eventually, the server - # info will be available and the private variable will be set. - return MYSQL_DEFAULT_CHARSET_ID_57 - - self.__charset_id = ( - MYSQL_DEFAULT_CHARSET_ID_57 - if self._server_version < (8, 0) - else MYSQL_DEFAULT_CHARSET_ID_80 - ) - - return self.__charset_id - - @_charset_id.setter - def _charset_id(self, value: int) -> None: - """Sets the charset ID utilized during the connection phase.""" - self.__charset_id = value - - @property - def python_charset(self) -> str: - """Returns the Python character set for current connection. - - This property returns the character set name of the current connection. - Note that, unlike property charset, this checks if the previously set - character set is supported by Python and if not, it returns the - equivalent character set that Python supports. - - Returns a string. - """ - encoding = self._character_set.get_info(self._charset_id)[0] - if encoding in ("utf8mb4", "utf8mb3", "binary"): - return "utf8" - return encoding - - def set_charset_collation( - self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None - ) -> None: - """Sets the character set and collation for the current connection. - - This method sets the character set and collation to be used for - the current connection. The charset argument can be either the - name of a character set as a string, or the numerical equivalent - as defined in constants.CharacterSet. - - When the collation is not given, the default will be looked up and - used. - - Args: - charset: Can be either the name of a character set, or the numerical - equivalent as defined in `constants.CharacterSet`. - collation: When collation is `None`, the default collation for the - character set is used. - - Examples: - The following will set the collation for the latin1 character set to - `latin1_general_ci`: - ``` - >>> cnx = mysql.connector.connect(user='scott') - >>> cnx.set_charset_collation('latin1', 'latin1_general_ci') - ``` - """ - err_msg = "{} should be either integer, string or None" - if not isinstance(charset, (int, str)) and charset is not None: - raise ValueError(err_msg.format("charset")) - if not isinstance(collation, str) and collation is not None: - raise ValueError("collation should be either string or None") - - if charset: - if isinstance(charset, int): - ( - self._charset_id, - charset_name, - collation_name, - ) = self._character_set.get_charset_info(charset) - elif isinstance(charset, str): - ( - self._charset_id, - charset_name, - collation_name, - ) = self._character_set.get_charset_info(charset, collation) - else: - raise ValueError(err_msg.format("charset")) - elif collation: - ( - self._charset_id, - charset_name, - collation_name, - ) = self._character_set.get_charset_info(collation=collation) - else: - charset = DEFAULT_CONFIGURATION["charset"] - ( - self._charset_id, - charset_name, - collation_name, - ) = self._character_set.get_charset_info(charset, collation=None) - - self._execute_query(f"SET NAMES '{charset_name}' COLLATE '{collation_name}'") - - if self.converter: - self.converter.set_charset(charset_name, character_set=self._character_set) - - @property - def read_timeout(self) -> Optional[int]: - """ - Gets the connection context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - """ - return self._read_timeout - - @read_timeout.setter - def read_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the connection context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set - in seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the read_timeout of the current session to - 5 seconds: - ``` - >>> cnx = mysql.connector.connect(user='scott') - >>> cnx.read_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option read_timeout must be a positive integer or None" - ) - self._read_timeout = timeout - - @property - def write_timeout(self) -> Optional[int]: - """ - Gets the connection context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - """ - return self._write_timeout - - @write_timeout.setter - def write_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the connection context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set in - seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the write_timeout of the current - session to 5 seconds: - ``` - >>> cnx = mysql.connector.connect(user='scott') - >>> cnx.write_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option write_timeout must be a positive integer or None" - ) - self._write_timeout = timeout - - @property - @abstractmethod - def connection_id(self) -> Optional[int]: - """MySQL connection ID.""" - - @abstractmethod - def _do_handshake(self) -> None: - """Gathers information of the MySQL server before authentication.""" - - @abstractmethod - def _open_connection(self) -> None: - """Opens the connection to the MySQL server.""" - - def _post_connection(self) -> None: - """Executes commands after connection has been established. - - This method executes commands after the connection has been - established. Some setting like autocommit, character set, and SQL mode - are set using this method. - """ - self.set_charset_collation(charset=self._charset_id) - self.autocommit = self._autocommit - if self._time_zone: - self.time_zone = self._time_zone - if self._sql_mode: - self.sql_mode = self._sql_mode - if self._init_command: - self._execute_query(self._init_command) - - @abstractmethod - def close(self) -> None: - """Disconnects from the MySQL server. - - This method tries to send a `QUIT` command and close the socket. It raises - no exceptions. - - `MySQLConnection.close()` is a synonymous for `MySQLConnection.disconnect()` - method name and more commonly used. - - To shut down the connection without sending a `QUIT` command first, - use `shutdown()`. - """ - - disconnect: ClassVar[Callable[["MySQLConnectionAbstract"], None]] = close - - def connect(self, **kwargs: Any) -> None: - """Connects to the MySQL server. - - This method sets up the connection to the MySQL server. If no - arguments are given, it will use the already configured or default - values. - - Args: - **kwargs: For a complete list of possible arguments, see [1]. - - Examples: - ``` - >>> cnx = MySQLConnection(user='joe', database='test') - ``` - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - # open connection using the default charset id - if kwargs: - self.config(**kwargs) - - self.disconnect() - self._open_connection() - - charset, collation = ( - kwargs.pop("charset", None), - kwargs.pop("collation", None), - ) - if charset or collation: - self._charset_id = self._character_set.get_charset_info(charset, collation)[ - 0 - ] - - if not self._client_flags & ClientFlag.CAN_HANDLE_EXPIRED_PASSWORDS: - self._post_connection() - else: - # the server does not allow to run any other statement different from - # ALTER when user's password has been expired - the server either - # disconnects the client or restricts the client to "sandbox mode" [1]. - # [1]: https://dev.mysql.com/doc/refman/5.7/en/expired-password-handling.html - try: - self.set_charset_collation(charset=self._charset_id) - except DatabaseError: - # get out of sandbox mode - with no FOR user clause, the statement sets - # the password for the current user. - self.cmd_query(f"SET PASSWORD = '{self._password1 or self._password}'") - - # Set charset and collation. - self.set_charset_collation(charset=self._charset_id) - - # go back to sandbox mode. - self.cmd_query("ALTER USER CURRENT_USER() PASSWORD EXPIRE") - - def reconnect(self, attempts: int = 1, delay: int = 0) -> None: - """Attempts to reconnect to the MySQL server. - - The argument `attempts` should be the number of times a reconnect - is tried. The `delay` argument is the number of seconds to wait between - each retry. - - You may want to set the number of attempts higher and use delay when - you expect the MySQL server to be down for maintenance or when you - expect the network to be temporary unavailable. - - Args: - attempts: Number of attempts to make when reconnecting. - delay: Use it (defined in seconds) if you want to wait between each retry. - - Raises: - InterfaceError: When reconnection fails. - """ - counter = 0 - span = None - - if self._tracer: - # pylint: disable=possibly-used-before-assignment - span = self._tracer.start_span( - name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT - ) - - try: - while counter != attempts: - counter = counter + 1 - try: - self.disconnect() - self.connect() - if self.is_connected(): - break - except (Error, IOError) as err: - if counter == attempts: - msg = ( - f"Can not reconnect to MySQL after {attempts} " - f"attempt(s): {err}" - ) - raise InterfaceError(msg) from err - if delay > 0: - sleep(delay) - except InterfaceError as interface_err: - if OTEL_ENABLED: - set_connection_span_attrs(self, span) - record_exception_event(span, interface_err) - end_span(span) - raise - - self._span = span - if OTEL_ENABLED: - set_connection_span_attrs(self, self._span) - - @abstractmethod - def is_connected(self) -> bool: - """Reports whether the connection to MySQL Server is available or not. - - Checks whether the connection to MySQL is available using the `ping()` method, - but unlike `ping()`, `is_connected()` returns `True` when the connection is - available, `False` otherwise - """ - - @abstractmethod - def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None: - """Checks availability of the MySQL server. - - When reconnect is set to `True`, one or more attempts are made to try - to reconnect to the MySQL server using the reconnect()-method. - - `delay` is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. - - Args: - reconnect: If True, one or more `attempts` are made to try to reconnect - to the MySQL server, and these options are forwarded to the - `reconnect()` method. - attempts: Number of attempts to make when reconnecting. - delay: Use it (defined in seconds) if you want to wait between each retry. - - Raises: - InterfaceError: When the connection is not available. Use the - `is_connected()` method if you just want to check the - connection without raising an error. - """ - - @abstractmethod - def commit(self) -> None: - """Commits current transaction. - - This method is part of PEP 249 - Python Database API Specification v2.0. - - This method sends a COMMIT statement to the MySQL server, committing the - current transaction. Since by default Connector/Python does not autocommit. - - It is important to call this method after every transaction that modifies - data for tables that use transactional storage engines. - - Examples: - ``` - >>> stmt = "INSERT INTO employees (first_name) VALUES (%s), (%s)" - >>> cursor.execute(stmt, ('Jane', 'Mary')) - >>> cnx.commit() - ``` - """ - - @abstractmethod - def cursor( - self, - buffered: Optional[bool] = None, - raw: Optional[bool] = None, - prepared: Optional[bool] = None, - cursor_class: Optional[Type["MySQLCursorAbstract"]] = None, - dictionary: Optional[bool] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> "MySQLCursorAbstract": - """Instantiates and returns a cursor. - - By default, `MySQLCursor` or `CMySQLCursor` is returned. Depending on the - options while connecting, a buffered and/or raw cursor is instantiated - instead. Also depending upon the cursor options, rows can be returned as - a dictionary or a tuple. - - Dictionary based cursors are available with buffered output - but not raw. - - It is possible to also give a custom cursor through the `cursor_class` - parameter, but it needs to be a subclass of `mysql.connector.cursor.MySQLCursor` - or `mysql.connector.cursor_cext.CMySQLCursor` according to the type of - connection that's being used. - - **NOTE: The parameters read and write timeouts in cursors are unsupported for - C-Extension.** - - Args: - buffered: If `True`, the cursor fetches all rows from the server after an - operation is executed. This is useful when queries return small - result sets. - raw: If `True`, the cursor skips the conversion from MySQL data types to - Python types when fetching rows. A raw cursor is usually used to get - better performance or when you want to do the conversion yourself. - prepared: If `True`, the cursor is used for executing prepared statements. - cursor_class: It can be used to pass a class to use for instantiating a - new cursor. It must be a subclass of `cursor.MySQLCursor` - or `cursor_cext.CMySQLCursor` according to the type of - connection that's being used. - dictionary: If `True`, the cursor returns rows as dictionaries. - read_timeout: A positive integer representing timeout in seconds for each - attempt to read any data from the server. - write_timeout: A positive integer representing timeout in seconds for each - attempt to send any data to the server. - - Returns: - cursor: A cursor object. - - Raises: - ProgrammingError: When `cursor_class` is not a subclass of - `MySQLCursorAbstract`. - ValueError: When cursor is not available. - InterfaceError: When read_timeout or write_timeout is not a positive integer. - """ - - @abstractmethod - def _execute_query(self, query: str) -> None: - """Executes a query.""" - - @abstractmethod - def rollback(self) -> None: - """Rollbacks current transaction. - - Sends a ROLLBACK statement to the MySQL server, undoing all data changes - from the current transaction. By default, Connector/Python does not - autocommit, so it is possible to cancel transactions when using - transactional storage engines such as `InnoDB`. - - Examples: - ``` - >>> stmt = "INSERT INTO employees (first_name) VALUES (%s), (%s)" - >>> cursor.execute(stmt, ('Jane', 'Mary')) - >>> cnx.rollback() - ``` - """ - - def start_transaction( - self, - consistent_snapshot: bool = False, - isolation_level: Optional[str] = None, - readonly: Optional[bool] = None, - ) -> None: - """Starts a transaction. - - This method explicitly starts a transaction sending the - START TRANSACTION statement to the MySQL server. You can optionally - set whether there should be a consistent snapshot, which - isolation level you need or which access mode i.e. READ ONLY or - READ WRITE. - - Args: - consistent_snapshot: If `True`, Connector/Python sends WITH CONSISTENT - SNAPSHOT with the statement. MySQL ignores this for - isolation levels for which that option does not apply. - isolation_level: Permitted values are 'READ UNCOMMITTED', 'READ COMMITTED', - 'REPEATABLE READ', and 'SERIALIZABLE'. If the value is - `None`, no isolation level is sent, so the default level - applies. - readonly: Can be `True` to start the transaction in READ ONLY mode or - `False` to start it in READ WRITE mode. If readonly is omitted, - the server's default access mode is used. - - Raises: - ProgrammingError: When a transaction is already in progress - and when `ValueError` when `isolation_level` - specifies an Unknown level. - - Examples: - For example, to start a transaction with isolation level `SERIALIZABLE`, - you would do the following: - ``` - >>> cnx = mysql.connector.connect(...) - >>> cnx.start_transaction(isolation_level='SERIALIZABLE') - ``` - """ - if self.in_transaction: - raise ProgrammingError("Transaction already in progress") - - if isolation_level: - level = isolation_level.strip().replace("-", " ").upper() - levels = [ - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - "SERIALIZABLE", - ] - - if level not in levels: - raise ValueError(f'Unknown isolation level "{isolation_level}"') - - self._execute_query(f"SET TRANSACTION ISOLATION LEVEL {level}") - - if readonly is not None: - if self._server_version < (5, 6, 5): - raise ValueError( - f"MySQL server version {self._server_version} does not " - "support this feature" - ) - - if readonly: - access_mode = "READ ONLY" - else: - access_mode = "READ WRITE" - self._execute_query(f"SET TRANSACTION {access_mode}") - - query = "START TRANSACTION" - if consistent_snapshot: - query += " WITH CONSISTENT SNAPSHOT" - self.cmd_query(query) - - @abstractmethod - def reset_session( - self, - user_variables: Optional[Dict[str, Any]] = None, - session_variables: Optional[Dict[str, Any]] = None, - ) -> None: - """Clears the current active session. - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - - Args: - user_variables: User variables map. - session_variables: System variables map. - - Raises: - OperationalError: If not connected. - InternalError: If there are unread results and InterfaceError on errors. - - Examples: - ``` - >>> user_variables = {'var1': '1', 'var2': '10'} - >>> session_variables = {'wait_timeout': 100000, 'sql_mode': 'TRADITIONAL'} - >>> cnx.reset_session(user_variables, session_variables) - ``` - """ - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="converter_class")) - def set_converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None: - """ - Sets the converter class to be used. - - Args: - convclass: Should be a class overloading methods and members of - `conversion.MySQLConverter`. - """ - self.converter_class = convclass - - @property - def converter_class(self) -> Optional[Type[MySQLConverter]]: - """Gets the converter class set for the current session.""" - return self._converter_class - - @converter_class.setter - def converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None: - """ - Sets the converter class to be used. - - Args: - convclass: Should be a class overloading methods and members of - `conversion.MySQLConverter`. - """ - if convclass and issubclass(convclass, MySQLConverterBase): - charset_name = self._character_set.get_info(self._charset_id)[0] - self._converter_class = convclass - self.converter = convclass(charset_name, self.use_unicode) - self.converter.str_fallback = self._converter_str_fallback - else: - raise TypeError( - "Converter class should be a subclass of conversion.MySQLConverterBase." - ) - - @abstractmethod - def get_row( - self, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Optional[CMySQLPrepStmt] = None, - **kwargs: Any, - ) -> Tuple[Optional[RowType], Optional[Dict[str, Any]]]: - """Retrieves the next row of a query result set. - - Args: - binary: If `True`, read as binary result (only meaningful for pure Python - connections). - columns: Field types (only meaningful for pure Python connections and when - `binary=True`). - raw: If `True`, the converter class does not convert the parsed values. - prep_stmt: Prepared statement object (only meaningful for - C-ext connections). - - Returns: - tuple: The row as a tuple (RowType) containing byte objects, or `None` when - no more rows are available. (at position 0). EOF packet - information as a dictionary containing `status_flag` and - `warning_count` (at position 1). - - Raises: - InterfaceError: When all rows have been retrieved. - """ - - @abstractmethod - def get_rows( - self, - count: Optional[int] = None, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Optional[CMySQLPrepStmt] = None, - **kwargs: Any, - ) -> Tuple[List[RowType], Optional[Dict[str, Any]]]: - """Gets all rows returned by the MySQL server. - - Args: - count: Used to obtain a given number of rows. If set to `None`, all - rows are fetched. - binary: If `True`, read as binary result (only meaningful for pure Python - connections). - columns: Field types (only meaningful for pure Python connections and when - `binary=True`). - raw: If `True`, the converter class does not convert the parsed values. - prep_stmt: Prepared statement object (only meaningful for - C-ext connections). - - Returns: - tuple: A list of tuples (RowType) containing the row data as byte objects, - or an empty list when no rows are available (at position 0). - EOF packet information as a dictionary containing `status_flag` - and `warning_count` (at position 1). - - Raises: - InterfaceError: When all rows have been retrieved. - """ - - @abstractmethod - def cmd_init_db(self, database: str) -> Optional[Dict[str, Any]]: - """Changes the current database. - - This method makes specified database the default (current) database. - In subsequent queries, this database is the default for table references - that include no explicit database qualifier. - - Args: - database: Database to become the default (current) database. - - Returns: - ok_packet: Dictionary containing the OK packet information. - """ - - @abstractmethod - def cmd_query( - self, - query: str, - raw: Optional[bool] = False, - buffered: bool = False, - raw_as_string: bool = False, - **kwargs: Any, - ) -> Optional[Dict[str, Any]]: - """Sends a query to the MySQL server. - - This method sends the query to the MySQL server and returns the result. - To **send multiple statements, use the `cmd_query_iter()` method instead**. - - The returned dictionary contains information depending on what kind of query - was executed. If the query is a `SELECT` statement, the result contains - information about columns. Other statements return a dictionary containing - OK or EOF packet information. - - Errors received from the MySQL server are raised as exceptions. - - **Arguments `raw`, `buffered` and `raw_as_string` are only meaningful - for `C-ext` connections**. - - Args: - query: Statement to be executed. - raw: If `True`, the cursor skips the conversion from MySQL data types to - Python types when fetching rows. A raw cursor is usually used to get - better performance or when you want to do the conversion yourself. If - not provided, take its value from the MySQL instance. - buffered: If `True`, the cursor fetches all rows from the server after an - operation is executed. This is useful when queries return small - result sets. - raw_as_string: Is a special argument for Python v2 and returns `str` - instead of `bytearray`. - - Returns: - dictionary: `Result` or `OK packet` information - """ - - @abstractmethod - def cmd_query_iter( - self, - statements: str, - **kwargs: Any, - ) -> Generator[Mapping[str, Any], None, None]: - """Sends one or more statements to the MySQL server. - - Similar to the `cmd_query()` method, but instead returns a generator - object to iterate through results. It sends the statements to the - MySQL server and through the iterator you can get the results. - - Use `cmd_query_iter()` when sending multiple statements, and separate - the statements with semicolons. - - Args: - statements: Statements to be executed separated with semicolons. - - Returns: - generator: Generator object with `Result` or `OK packet` information. - - Examples: - The following example shows how to iterate through the results after - sending multiple statements: - ``` - >>> statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' - >>> for result in cnx.cmd_query_iter(statement): - >>> if 'columns' in result: - >>> columns = result['columns'] - >>> rows = cnx.get_rows() - >>> else: - >>> # do something useful with INSERT result - ``` - """ - - @abstractmethod - def cmd_refresh(self, options: int) -> Dict[str, Any]: - """Send the Refresh command to the MySQL server. - - This method sends the Refresh command to the MySQL server. The options - argument should be a bitwise value using constants.RefreshOption. - - Typical usage example: - ``` - RefreshOption = mysql.connector.RefreshOption - refresh = RefreshOption.LOG | RefreshOption.INFO - cnx.cmd_refresh(refresh) - ``` - - Args: - options: Bitmask value constructed using constants from - the `constants.RefreshOption` class. - - Returns: - A dictionary representing the OK packet got as response when executing - the command. - - Raises: - ValueError: If an invalid command `refresh options` is provided. - DeprecationWarning: If one of the options is deprecated for the server you - are connecting to. - """ - - @abstractmethod - def cmd_quit(self) -> Optional[bytes]: - """Closes the current connection with the server. - - This method sends the `QUIT` command to the MySQL server, closing the - current connection. Since there is no response from the MySQL server, - the packet that was sent is returned. - - Returns: - packet_sent: `None` when using a C-ext connection, - else the actual packet that was sent. - """ - - @abstractmethod - def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None: - """Shuts down the MySQL Server. - - This method sends the SHUTDOWN command to the MySQL server. - The `shutdown_type` is not used, and it's kept for backward compatibility. - """ - - @abstractmethod - def cmd_statistics(self) -> Optional[Dict[str, Any]]: - """Sends the statistics command to the MySQL Server. - - Returns: - dict: Stats packet information about the MySQL server including uptime - in seconds and the number of running threads, questions, reloads, and - open tables. - """ - - @staticmethod - def cmd_process_info() -> NoReturn: - """Get the process list of the MySQL Server. - - This method is a placeholder to notify that the PROCESS_INFO command - is not supported by raising the `NotSupportedError`. - - The command - "SHOW PROCESSLIST" should be send using the cmd_query()-method or - using the `INFORMATION_SCHEMA` database. - - Raises `NotSupportedError` exception. - """ - raise NotSupportedError( - "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA" - ) - - @abstractmethod - def cmd_process_kill(self, mysql_pid: int) -> Optional[Dict[str, Any]]: - """Kills a MySQL process. - - Asks the server to kill the thread specified by `mysql_pid`. Although - still available, it is better to use the KILL SQL statement. - - Args: - mysql_pid: Process ID to be killed. - - Returns: - ok_packet: Dictionary containing the OK packet information. - - Examples: - ``` - >>> cnx.cmd_process_kill(123) # using cmd_process_kill() - >>> cnx.cmd_query('KILL 123') # alternatively (recommended) - ``` - """ - - @abstractmethod - def cmd_debug(self) -> Optional[Dict[str, Any]]: - """Instructs the server to write debugging information to the error log. - - The connected user must have the `SUPER` privilege. - - Returns: - ok_packet: Dictionary containing the EOF (end-of-file) packet information. - """ - - @abstractmethod - def cmd_ping(self) -> Optional[Dict[str, Any]]: - """Checks whether the connection to the server is working. - - This method is not to be used directly. Use `ping()` or - `is_connected()` instead. - - Returns: - ok_packet: Dictionary containing the OK packet information. - """ - - @abstractmethod - def cmd_change_user( - self, - username: str = "", - password: str = "", - database: str = "", - charset: Optional[int] = None, - password1: str = "", - password2: str = "", - password3: str = "", - oci_config_file: str = "", - oci_config_profile: str = "", - openid_token_file: str = "", - ) -> Optional[Dict[str, Any]]: - """Changes the current logged in user. - - It also causes the specified database to become the default (current) - database. It is also possible to change the character set using the - charset argument. The character set passed during initial connection - is reused if no value of charset is passed via this method. - - Args: - username: New account's username. - password: New account's password. - database: Database to become the default (current) database. - charset: Client charset (see [1]), only the lower 8-bits. - password1: New account's password factor 1 - it's used instead - of `password` if set (higher precedence). - password2: New account's password factor 2. - password3: New account's password factor 3. - oci_config_file: OCI configuration file location (path-like string). - oci_config_profile: OCI configuration profile location (path-like string). - openid_token_file: OpenID Connect token file location (path-like string). - - Returns: - ok_packet: Dictionary containing the OK packet information. - - Examples: - ``` - >>> cnx.cmd_change_user(username='', password='', database='', charset=33) - ``` - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_basic_character_set.html#a_protocol_character_set - """ - - @abstractmethod - def cmd_stmt_prepare( - self, - statement: bytes, - **kwargs: Any, - ) -> Union[Mapping[str, Any], CMySQLPrepStmt]: - """Prepares a MySQL statement. - - Args: - statement: statement to prepare. - - Returns: - prepared_stmt: A `Prepared Statement` structure - a dictionary - is returned when using a pure Python connection, and a - `_mysql_connector.MySQLPrepStmt` object is returned when - using a C-ext connection. - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/ - page_protocol_com_stmt_prepare.html - """ - - @abstractmethod - def cmd_stmt_execute( - self, - statement_id: Union[int, CMySQLPrepStmt], - data: Sequence[BinaryProtocolType] = (), - parameters: Sequence = (), - flags: int = 0, - **kwargs: Any, - ) -> Optional[Union[Dict[str, Any], Tuple]]: - """Executes a prepared MySQL statement. - - Args: - statement_id: Statement ID found in the dictionary returned by - `MySQLConnection.cmd_stmt_prepare` when using a pure Python - connection, or a `_mysql_connector.MySQLPrepStmt` instance - as returned by `CMySQLConnection.cmd_stmt_prepare` when - using a C-ext connection. - data: Data sequence against which the prepared statement will be executed. - parameters: Currently unused! - flags: see [1]. - - Returns: - dictionary or tuple: `OK packet` or `Result` information. - - Notes: - The previous method's signature applies to pure Python, the C-ext has - the following signature: - - ``` - def cmd_stmt_execute( - self, statement_id: CMySQLPrepStmt, *args: Any - ) -> Optional[Union[Dict[str, Any], Tuple]]: - ``` - - You should expect a similar returned value type, however, the input - is different. In this case `data` must be provided as positional - arguments instead of a sequence. - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_com_stmt_execute.html - """ - - @abstractmethod - def cmd_stmt_close( - self, - statement_id: Union[int, CMySQLPrepStmt], - **kwargs: Any, - ) -> None: - """Deallocates a prepared MySQL statement. - - Args: - statement_id: Statement ID found in the dictionary returned by - `MySQLConnection.cmd_stmt_prepare` when using a pure Python - connection, or a `_mysql_connector.MySQLPrepStmt` instance - as returned by `CMySQLConnection.cmd_stmt_prepare` when - using a C-ext connection. - """ - - @abstractmethod - def cmd_stmt_send_long_data( - self, - statement_id: Union[int, CMySQLPrepStmt], - param_id: int, - data: BinaryIO, - **kwargs: Any, - ) -> int: - """Sends data for a column. - - Currently, not implemented for the C-ext. - - Args: - statement_id: Statement ID found in the dictionary returned by - `MySQLConnection.cmd_stmt_prepare` when using a pure Python - connection, or a `_mysql_connector.MySQLPrepStmt` instance - as returned by `CMySQLConnection.cmd_stmt_prepare` when - using a C-ext connection. - param_id: The parameter to supply data to [1]. - data: The actual payload to send [1]. - - Returns: - total_sent: The total number of bytes that were sent is returned. - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_com_stmt_send_long_data.html - """ - - @abstractmethod - def cmd_stmt_reset( - self, - statement_id: Union[int, CMySQLPrepStmt], - **kwargs: Any, - ) -> None: - """Resets data for prepared statement sent as long data. - - Args: - statement_id: Statement ID found in the dictionary returned by - `MySQLConnection.cmd_stmt_prepare` when using a pure Python - connection, or a `_mysql_connector.MySQLPrepStmt` instance - as returned by `CMySQLConnection.cmd_stmt_prepare` when - using a C-ext connection. - """ - - @abstractmethod - def cmd_reset_connection(self) -> bool: - """Resets the session state without re-authenticating. - - Reset command only works on MySQL server 5.7.3 or later. - - This method permits the session state to be cleared without reauthenticating. - For MySQL servers older than 5.7.3 (when `COM_RESET_CONNECTION` was introduced) - , the `reset_session()` method can be used instead - that method resets the - session state by reauthenticating, which is more expensive. - - This method was added in Connector/Python 1.2.1. - - Returns: - `True` for a successful reset otherwise `False`. - """ - - -class MySQLCursorAbstract(ABC): - """Abstract cursor class - - Abstract class defining cursor class with method and members - required by the Python Database API Specification v2.0. - """ - - def __init__( - self, - connection: Optional[MySQLConnectionAbstract] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - """Defines the MySQL cursor interface.""" - - self._connection: Optional[MySQLConnectionAbstract] = connection - if connection is not None: - if not isinstance(connection, MySQLConnectionAbstract): - raise InterfaceError(errno=2048) - self._connection = weakref.proxy(connection) - - self._description: Optional[List[DescriptionType]] = None - self._rowcount: int = -1 - self._last_insert_id: Optional[int] = None - self._warnings: Optional[List[WarningType]] = None - self._warning_count: int = 0 - self._executed: Optional[bytes] = None - self._executed_list: List[bytes] = [] - self._stored_results: List[MySQLCursorAbstract] = [] - self.arraysize: int = 1 - self._binary: bool = False - self._raw: bool = False - self._nextrow: Tuple[ - Optional[RowType], Optional[Union[EofPacketType, CextEofPacketType]] - ] = ( - None, - None, - ) - self._read_timeout: Optional[int] = read_timeout - self._write_timeout: Optional[int] = write_timeout - - # multi statement execution - self._stmt_partitions: Optional[Generator[MySQLScriptPartition, None, None]] = ( - None - ) - self._stmt_partition: Optional[MySQLScriptPartition] = None - self._stmt_map_results: bool = False - - def __enter__(self) -> MySQLCursorAbstract: - return self - - def __exit__( - self, - exc_type: Type[BaseException], - exc_value: BaseException, - traceback: TracebackType, - ) -> None: - self.close() - - @property - def read_timeout(self) -> Optional[int]: - """ - Gets the cursor context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - """ - return self._read_timeout - - @read_timeout.setter - def read_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the cursor context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set - in seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the read_timeout of the current session to - 5 seconds: - ``` - >>> cnx = mysql.connector.connect(user='scott') - >>> cur = cnx.cursor() - >>> cur.read_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option read_timeout must be a positive integer or None" - ) - self._read_timeout = timeout - - @property - def write_timeout(self) -> Optional[int]: - """ - Gets the connection context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - """ - return self._write_timeout - - @write_timeout.setter - def write_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the connection context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set in - seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the write_timeout of the current - session to 5 seconds: - ``` - >>> cnx = mysql.connector.connect(user='scott') - >>> cur = cnx.cursor() - >>> cur.write_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option write_timeout must be a positive integer or None" - ) - self._write_timeout = timeout - - @abstractmethod - def callproc( - self, procname: str, args: Sequence = () - ) -> Optional[Union[Dict[str, RowItemType], RowType]]: - """Calls a stored procedure with the given arguments. - - The arguments will be set during this session, meaning they will be called like - ___arg where is an enumeration (+1) of the arguments. - - Args: - procname: The stored procedure name. - args: Sequence of parameters - it must contain one entry for each argument - that the procedure expects. - - Returns: - Does not return a value, but a result set will be available when the - CALL-statement executes successfully. - - `callproc()` returns a modified copy of the input sequence. `Input` - parameters are left untouched. `Output` and `input/output` parameters may - be replaced with new values. - - Result sets produced by the stored procedure are automatically fetched and - stored as `MySQLCursorBuffered` instances. - - The value returned (if any) is a `Dict` when cursor's subclass is - `MySQLCursorDict`, else a `Tuple` (RowType). - - Raises: - InterfaceError: When something is wrong - - Examples: - 1) Defining the Stored Routine in MySQL: - ``` - CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN - SET pProd := pFac1 * pFac2; - END; - ``` - - 2) Executing in Python: - ``` - >>> args = (5, 6, 0) # 0 is to hold value of the OUT parameter pProd - >>> cursor.callproc('multiply', args) - ('5', '6', 30L) - ``` - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/\ - connector-python-api-mysqlcursor-callproc.html - """ - - @abstractmethod - def close(self) -> None: - """Close the cursor. - - Use close() when you are done using a cursor. This method closes the cursor, - resets all results, and ensures that the cursor object has no reference to its - original connection object. - - This method is part of PEP 249 - Python Database API Specification v2.0. - """ - - @abstractmethod - def execute( - self, - operation: str, - params: Union[ - Sequence[MySQLConvertibleType], Dict[str, MySQLConvertibleType] - ] = (), - map_results: bool = False, - ) -> None: - """Executes the given operation (a MySQL script) substituting any markers - with the given parameters. - - For example, getting all rows where id is 5: - ``` - cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) - ``` - - If you want each single statement in the script to be related - to its corresponding result set, you should enable the `map_results` - switch - see workflow example below. - - If the given script uses `DELIMITER` statements (which are not recognized - by MySQL Server), the connector will parse such statements to remove them - from the script and substitute delimiters as needed. This pre-processing - may cause a performance hit when using long scripts. Note that when enabling - `map_results`, the script is expected to use `DELIMITER` statements in order - to split the script into multiple query strings. - - The following characters are currently not supported by the connector in - `DELIMITER` statements: `"`, `'`, #`, `/*` and `*/`. - - If warnings were generated, and `connection.get_warnings` is - `True`, then `self.warnings` will be a list containing these - warnings. - - Args: - operation: Operation to be executed - it can be a single or a - multi statement. - params: The parameters found in the tuple or dictionary params are bound - to the variables in the operation. Specify variables using `%s` or - `%(name)s` parameter style (that is, using format or pyformat style). - map_results: It is `False` by default. If `True`, it allows you to know what - statement caused what result set - see workflow example below. - Only relevant when working with multi statements. - - Returns: - `None`. - - Example (basic usage): - The following example runs many single statements in a - single go and loads the corresponding result sets - sequentially: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - with cnx.cursor() as cur: - cur.execute(sql_operation) - - result_set = cur.fetchall() - # do something with result set - ... - - while cur.nextset(): - result_set = cur.fetchall() - # do something with result set - ... - ``` - - In case the operation is a single statement, you may skip the - looping section as no more result sets are to be expected. - - Example (statement-result mapping): - The following example runs many single statements in a - single go and loads the corresponding result sets - sequentially. Additionally, each result set gets related - to the statement that caused it: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - with cnx.cursor() as cur: - cur.execute(sql_operation, map_results=True) - - # statement 1 is `SET @a=1, @b='2024-02-01'`, - # result set from statement 1 is `[]` - aka, an empty set. - result_set, statement = cur.fetchall(), cur.statement - # do something with result set - ... - - # 1st call to `nextset()` will laod the result set from statement 2, - # statement 2 is `SELECT @a, LENGTH('hello'), @b`, - # result set from statement 2 is `[(1, 5, '2024-02-01')]`. - # - # 2nd call to `nextset()` will laod the result set from statement 3, - # statement 3 is `SELECT @@version`, - # result set from statement 3 is `[('9.0.0-labs-mrs-8',)]`. - # - # 3rd call to `nextset()` will return `None` as there are no more sets, - # leading to the end of the consumption process of result sets. - while cur.nextset(): - result_set, statement = cur.fetchall(), cur.statement - # do something with result set - ... - ``` - - In case the mapping is disabled (`map_results=False`), all result - sets get related to the same statement, which is the one provided - when calling `execute()`. In other words, the property `statement` - will not change as result sets are consumed, which contrasts with - the case in which the mapping is enabled. Note that we offer a - new fetch-related API command which can be leveraged as a shortcut - for consuming result sets - it is equivalent to the previous - workflow. - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - with cnx.cursor() as cur: - cur.execute(sql_operation, map_results=True) - for statement, result_set in cur.fetchsets(): - # do something with result set - ``` - """ - - @abstractmethod - def executemany( - self, - operation: str, - seq_params: Sequence[ - Union[Sequence[MySQLConvertibleType], Dict[str, MySQLConvertibleType]] - ], - ) -> None: - """Executes the given operation multiple times. - - The `executemany()` method will execute the operation iterating - over the list of parameters in `seq_params`. - - `INSERT` statements are optimized by batching the data, that is - using the MySQL multiple rows syntax. - - Args: - operation: Operation to be executed. - seq_params: Parameters to be used when executing the operation. - - Returns: - Results are discarded. If they are needed, consider looping over data - using the `execute()` method. - - Examples: - An optimization is applied for inserts: The data values given by the - parameter sequences are batched using multiple-row syntax. The following - example inserts three records: - - ``` - >>> data = [ - >>> ('Jane', date(2005, 2, 12)), - >>> ('Joe', date(2006, 5, 23)), - >>> ('John', date(2010, 10, 3)), - >>> ] - >>> stmt = "INSERT INTO employees (first_name, hire_date) VALUES (%s, %s)" - >>> cursor.executemany(stmt, data) - ``` - - For the preceding example, the INSERT statement sent to MySQL is: - ``` - >>> INSERT INTO employees (first_name, hire_date) - >>> VALUES ('Jane', '2005-02-12'), ('Joe', '2006-05-23'), ('John', '2010-10-03') - ``` - """ - - @abstractmethod - def fetchone(self) -> Optional[Union[RowType, Dict[str, RowItemType]]]: - """Retrieves next row of a query result set - - Returns: - If the cursor's subclass is `MySQLCursorDict`, a dictionaries is - returned, otherwise a tuple (RowType). `None` is returned when there aren't - results to be read. - - Examples: - ``` - >>> cursor.execute("SELECT * FROM employees") - >>> row = cursor.fetchone() - >>> while row is not None: - >>> print(row) - >>> row = cursor.fetchone() - ``` - """ - - @abstractmethod - def fetchmany(self, size: int = 1) -> List[Union[RowType, Dict[str, RowItemType]]]: - """Fetches the next set of rows of a query result. - - Args: - size: The number of rows returned can be specified using the size - argument, which is one by default. - - Returns: - If the cursor's subclass is `MySQLCursorDict`, a list of dictionaries is - returned, otherwise a list of tuples (RowType). When no more rows are - available, it returns an empty list. - """ - - @abstractmethod - def fetchall(self) -> List[Union[RowType, Dict[str, RowItemType]]]: - """Fetches all (or all remaining) rows of a query result set. - - Returns: - If the cursor's subclass is `MySQLCursorDict`, a list of dictionaries is - returned, otherwise a list of tuples (RowType). - - Examples: - ``` - >>> cursor.execute("SELECT * FROM employees ORDER BY emp_no") - >>> head_rows = cursor.fetchmany(size=2) - >>> remaining_rows = cursor.fetchall() - ``` - """ - - def fetchsets( - self, - ) -> Generator[ - tuple[Optional[str], list[Union[RowType, Dict[str, RowItemType]]]], None, None - ]: - """Generates the result sets stream caused by the last `execute*()`. - - Returns: - A 2-tuple; the first element is the statement that caused the - result set, and the second is the result set itself. - - This method is used as part of the multi statement - execution workflow - see example below. - - Example: - Consider the following example where multiple statements are executed in one - go: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - with cnx.cursor() as cur: - cur.execute(sql_operation, map_results=True) - - result_set, statement = cur.fetchall(), cur.statement - # do something with result set - ... - - while cur.nextset(): - result_set, statement = cur.fetchall(), cur.statement - # do something with result set - ... - ``` - - In this case, as an alternative to loading the result sets with `nextset()` - in combination with a `while` loop, you can use `fetchsets()` which is - equivalent to the previous approach: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - with cnx.cursor() as cur: - cur.execute(sql_operation) - for statement, result_set in cur.fetchsets(): - # do something with result set - ``` - """ - # Some cursor flavor such as `buffered` raise an exception when they don't have - # result sets to fetch from. - # Some others, such as `dictionary` or `raw`, return an empty result set - # under the same circumstances. - statement_cached = None - if not self._stmt_map_results: - statement_cached = self.statement - - try: - result_set = self.fetchall() - except InterfaceError: - result_set = [] - yield ( - self.statement if self._stmt_map_results else statement_cached - ), result_set - while self.nextset(): - try: - result_set = self.fetchall() - except InterfaceError: - result_set = [] - yield ( - self.statement if self._stmt_map_results else statement_cached - ), result_set - - @abstractmethod - def stored_results(self) -> Iterator[MySQLCursorAbstract]: - """Returns an iterator (of MySQLCursorAbstract subclass instances) for stored results. - - This method returns an iterator over results which are stored when - callproc() is called. The iterator will provide `MySQLCursorBuffered` - instances. - - Examples: - ``` - >>> cursor.callproc('myproc') - () - >>> for result in cursor.stored_results(): - ... print result.fetchall() - ... - [(1,)] - [(2,)] - ``` - """ - - @abstractmethod - def nextset(self) -> Optional[bool]: - """Makes the cursor skip to the next available set, discarding - any remaining rows from the current set. - - This method is used as part of the multi statement - execution workflow - see example below. - - Returns: - It returns `None` if there are no more sets. Otherwise, it returns - `True` and subsequent calls to the `fetch*()` methods will return - rows from the next result set. - - Example: - The following example runs many single statements in a - single go and loads the corresponding result sets - sequentially: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - with cnx.cursor() as cur: - cur.execute(sql_operation) - - result_set = cur.fetchall() - # do something with result set - ... - - while cur.nextset(): - result_set = cur.fetchall() - # do something with result set - ... - ``` - - In case the operation is a single statement, you may skip the - looping section as no more result sets are to be expected. - """ - - def setinputsizes(self, sizes: Any) -> NoReturn: - """Not Implemented.""" - - def setoutputsize(self, size: Any, column: Any = None) -> NoReturn: - """Not Implemented.""" - - def reset(self, free: bool = True) -> None: - """Resets the cursor to default""" - - @property - def description(self) -> Optional[List[DescriptionType]]: - """This read-only property returns a list of tuples describing the columns in a - result set. - - A tuple is described as follows:: - ``` - (column_name, - type, - None, - None, - None, - None, - null_ok, - column_flags) # Addition to PEP-249 specs - ``` - - See [1] for more details and examples. - - Returns: - A list of tuples. - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/\ - connector-python-api-mysqlcursor-description.html - """ - return self._description - - @property - def rowcount(self) -> int: - """Returns the number of rows produced or affected. - - This property returns the number of rows produced by queries - such as `SELECT`, or affected rows when executing DML statements - like `INSERT` or `UPDATE`. - - Note that for non-buffered cursors it is impossible to know the - number of rows produced before having fetched them all. For those, - the number of rows will be -1 right after execution, and - incremented when fetching rows. - - Returns an integer. - """ - return self._rowcount - - @property - def lastrowid(self) -> Optional[int]: - """Returns the value generated for an AUTO_INCREMENT column. - - Returns the value generated for an AUTO_INCREMENT column by - the previous INSERT or UPDATE statement or `None` when there is - no such a value available. - - Returns a long value or `None`. - """ - return self._last_insert_id - - @property - def warnings(self) -> Optional[List[WarningType]]: - """Gets a list of tuples (WarningType) containing warnings generated - by the previously executed operation. - - Examples: - ``` - >>> cnx.get_warnings = True - >>> cursor.execute("SELECT 'a'+1") - >>> cursor.fetchall() - [(1.0,)] - >>> cursor.warnings - [(u'Warning', 1292, u"Truncated incorrect DOUBLE value: 'a'")] - ``` - """ - return self._warnings - - @property - def warning_count(self) -> int: - """Returns the number of warnings. - - This property returns the number of warnings generated by the - previously executed operation. - - Returns an integer value. - """ - return self._warning_count - - @property - def statement(self) -> Optional[str]: - """Returns the latest executed statement. - - When a multiple statement is executed, the value of `statement` - corresponds to the one that caused the current result set, provided - the statement-result mapping was enabled. Otherwise, the value of - `statement` matches the statement just as provided when calling - `execute()` and it does not change as result sets are traversed. - """ - if self._executed is None: - return None - try: - return self._executed.strip().decode("utf-8") - except (AttributeError, UnicodeDecodeError): - return cast(str, self._executed.strip()) - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="warnings")) - def fetchwarnings(self) -> Optional[List[WarningType]]: - """Returns a list of tuples (WarningType) containing warnings generated by - the previously executed operation. - - Examples: - ``` - >>> cnx.get_warnings = True - >>> cursor.execute("SELECT 'a'+1") - >>> cursor.fetchall() - [(1.0,)] - >>> cursor.fetchwarnings() - [(u'Warning', 1292, u"Truncated incorrect DOUBLE value: 'a'")] - ``` - """ - return self._warnings - - def get_attributes(self) -> Optional[List[Tuple[str, BinaryProtocolType]]]: - """Gets a list of query attributes from the connector's side. - - Returns: - List of existing query attributes. - """ - if hasattr(self, "_connection"): - return self._connection.query_attrs - return None - - def add_attribute(self, name: str, value: BinaryProtocolType) -> None: - """Adds a query attribute and its value into the connector's query attributes list. - - Query attributes must be enabled on the server - they are disabled by default. A - warning is logged when setting query attributes for a server connection - that does not support them. - - Args: - name: Key name used to identify the attribute. - value: A value converted to the MySQL Binary Protocol. - - Raises: - ProgrammingError: If the value's conversion fails. - """ - if not isinstance(name, str): - raise ProgrammingError("Parameter `name` must be a string type") - if value is not None and not isinstance(value, MYSQL_PY_TYPES): - raise ProgrammingError( - f"Object {value} cannot be converted to a MySQL type" - ) - if hasattr(self, "_connection"): - self._connection.query_attrs_append((name, value)) - - def remove_attribute(self, name: str) -> BinaryProtocolType: - """Removes a query attribute by name from the connector's query attributes list. - - If no match, `None` is returned, else the corresponding value is returned. - - Args: - name: Key name used to identify the attribute. - - Returns: - value: Attribute's value. - """ - if not isinstance(name, str): - raise ProgrammingError("Parameter `name` must be a string type") - if hasattr(self, "_connection"): - return self._connection.query_attrs_remove(name) - return None - - def clear_attributes(self) -> None: - """Clears the list of query attributes on the connector's side.""" - if hasattr(self, "_connection"): - self._connection.query_attrs_clear() diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/__init__.py b/server/venv/Lib/site-packages/mysql/connector/aio/__init__.py deleted file mode 100644 index 2572e6c..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""MySQL Connector/Python - MySQL driver written in Python.""" - -from .connection import MySQLConnection, MySQLConnectionAbstract -from .pooling import MySQLConnectionPool, PooledMySQLConnection, connect - -__all__ = [ - "MySQLConnection", - "connect", - "MySQLConnectionAbstract", - "MySQLConnectionPool", - "PooledMySQLConnection", -] diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/_decorating.py b/server/venv/Lib/site-packages/mysql/connector/aio/_decorating.py deleted file mode 100644 index e1c9b7e..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/_decorating.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Decorators Hub.""" - -import functools -import warnings - -from typing import TYPE_CHECKING, Any, Callable - -from ..constants import RefreshOption -from ..errors import ReadTimeoutError, WriteTimeoutError - -if TYPE_CHECKING: - from .abstracts import MySQLConnectionAbstract - - -def cmd_refresh_verify_options() -> Callable: - """Decorator verifying which options are relevant and which aren't based on - the server version the client is connecting to.""" - - def decorator(cmd_refresh: Callable) -> Callable: - @functools.wraps(cmd_refresh) - async def wrapper( - cnx: "MySQLConnectionAbstract", *args: Any, **kwargs: Any - ) -> Callable: - options: int = args[0] - if (options & RefreshOption.GRANT) and cnx.server_version >= ( - 9, - 2, - 0, - ): - warnings.warn( - "As of MySQL Server 9.2.0, refreshing grant tables is not needed " - "if you use statements GRANT, REVOKE, CREATE, DROP, or ALTER. " - "You should expect this option to be unsupported in a future " - "version of MySQL Connector/Python when MySQL Server removes it.", - category=DeprecationWarning, - stacklevel=1, - ) - - return await cmd_refresh(cnx, options, **kwargs) - - return wrapper - - return decorator - - -def deprecated(reason: str) -> Callable: - """Use it to decorate deprecated methods.""" - - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - async def wrapper(*args: Any, **kwargs: Any) -> Callable: - warnings.warn( - f"Call to deprecated function {func.__name__}. Reason: {reason}", - category=DeprecationWarning, - stacklevel=2, - ) - return await func(*args, **kwargs) - - return wrapper - - return decorator - - -def handle_read_write_timeout() -> Callable: - """ - Decorator to close the current connection if a read or a write timeout - is raised by the method passed via the func parameter. - """ - - def decorator(cnx_method: Callable) -> Callable: - @functools.wraps(cnx_method) - async def handle_cnx_method( - cnx: "MySQLConnectionAbstract", *args: Any, **kwargs: Any - ) -> Any: - try: - return await cnx_method(cnx, *args, **kwargs) - except Exception as err: - if isinstance(err, (ReadTimeoutError, WriteTimeoutError)): - await cnx.close() - raise err - - return handle_cnx_method - - return decorator diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/abstracts.py b/server/venv/Lib/site-packages/mysql/connector/aio/abstracts.py deleted file mode 100644 index 184b3cd..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/abstracts.py +++ /dev/null @@ -1,2703 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="assignment,attr-defined" -# pylint: disable=dangerous-default-value - -"""Module gathering all abstract base classes.""" - -from __future__ import annotations - -from mysql.connector.optionfiles import read_option_files - -__all__ = ["MySQLConnectionAbstract", "MySQLCursorAbstract", "ServerInfo"] - -import asyncio -import os -import re -import weakref - -from abc import ABC, abstractmethod -from dataclasses import dataclass, field -from inspect import signature -from types import TracebackType -from typing import ( - TYPE_CHECKING, - Any, - AsyncGenerator, - Awaitable, - BinaryIO, - Callable, - ClassVar, - Deque, - Dict, - Generator, - Iterator, - List, - Mapping, - NoReturn, - Optional, - Sequence, - Tuple, - Type, - Union, - cast, -) - -from .._decorating import deprecated -from ..abstracts import ( - DUPLICATED_IN_LIST_ERROR, - KRB_SERVICE_PRINCIPAL_ERROR, - MYSQL_PY_TYPES, - OPENID_TOKEN_FILE_ERROR, - TLS_V1_3_SUPPORTED, - TLS_VER_NO_SUPPORTED, - TLS_VERSION_ERROR, - TLS_VERSION_UNACCEPTABLE_ERROR, -) -from ..constants import ( - CONN_ATTRS_DN, - DEFAULT_CONFIGURATION, - DEPRECATED_METHOD_WARNING, - MYSQL_DEFAULT_CHARSET_ID_57, - MYSQL_DEFAULT_CHARSET_ID_80, - OPENSSL_CS_NAMES, - TLS_CIPHER_SUITES, - TLS_VERSIONS, - ClientFlag, -) -from ..conversion import MySQLConverter, MySQLConverterBase -from ..errors import ( - Error, - InterfaceError, - InternalError, - NotSupportedError, - ProgrammingError, -) -from ..tls_ciphers import UNACCEPTABLE_TLS_CIPHERSUITES, UNACCEPTABLE_TLS_VERSIONS -from ..types import ( - BinaryProtocolType, - DescriptionType, - EofPacketType, - HandShakeType, - MySQLScriptPartition, - OkPacketType, - ParamsSequenceType, - ResultType, - RowType, - StatsPacketType, - StrOrBytes, - WarningType, -) -from ..utils import GenericWrapper, import_object -from .authentication import MySQLAuthenticator -from .charsets import Charset, charsets -from .protocol import MySQLProtocol - -if TYPE_CHECKING: - from .network import MySQLTcpSocket, MySQLUnixSocket - - -IS_POSIX = os.name == "posix" - - -@dataclass -class ServerInfo: - """Stores the server information retrieved on the handshake. - - Also parses and validates the server version, storing it as a tuple. - """ - - protocol: int - version: str - version_tuple: Tuple[int, ...] = field(init=False) - thread_id: int - charset: int - status_flags: int - auth_plugin: str - auth_data: bytes - capabilities: int - query_attrs_is_supported: bool = False - - def __post_init__(self) -> None: - """Parse and validate server version. - - Raises: - InterfaceError: If parsing fails or MySQL version is not supported. - """ - version_re = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") - match = version_re.match(self.version) - if not match: - raise InterfaceError("Failed parsing MySQL version") - - version = tuple(int(v) for v in match.groups()[0:3]) - if version < (4, 1): - raise InterfaceError(f"MySQL Version '{self.version}' is not supported") - self.version_tuple = version - - -class MySQLConnectionAbstract(ABC): - """Defines the MySQL connection interface.""" - - def __init__( - self, - *, - user: Optional[str] = None, - password: str = "", - host: str = "127.0.0.1", - port: int = 3306, - database: Optional[str] = None, - password1: str = "", - password2: str = "", - password3: str = "", - charset: str = "", - collation: str = "", - auth_plugin: Optional[str] = None, - client_flags: Optional[int] = None, - compress: bool = False, - consume_results: bool = False, - autocommit: bool = False, - time_zone: Optional[str] = None, - conn_attrs: Dict[str, str] = {}, - sql_mode: Optional[str] = None, - init_command: Optional[str] = None, - get_warnings: bool = False, - raise_on_warnings: bool = False, - buffered: bool = False, - raw: bool = False, - kerberos_auth_mode: Optional[str] = None, - krb_service_principal: Optional[str] = None, - openid_token_file: Optional[str] = None, - webauthn_callback: Optional[Union[str, Callable[[str], None]]] = None, - allow_local_infile: bool = DEFAULT_CONFIGURATION["allow_local_infile"], - allow_local_infile_in_path: Optional[str] = DEFAULT_CONFIGURATION[ - "allow_local_infile_in_path" - ], - converter_class: Optional[MySQLConverter] = None, - converter_str_fallback: bool = False, - connection_timeout: int = DEFAULT_CONFIGURATION["connect_timeout"], - read_timeout: Optional[int] = DEFAULT_CONFIGURATION["read_timeout"], - write_timeout: Optional[int] = DEFAULT_CONFIGURATION["write_timeout"], - unix_socket: Optional[str] = None, - use_unicode: Optional[bool] = True, - ssl_ca: Optional[str] = None, - ssl_cert: Optional[str] = None, - ssl_key: Optional[str] = None, - ssl_verify_cert: Optional[bool] = False, - ssl_verify_identity: Optional[bool] = False, - ssl_disabled: Optional[bool] = DEFAULT_CONFIGURATION["ssl_disabled"], - tls_versions: Optional[List[str]] = None, - tls_ciphersuites: Optional[List[str]] = None, - loop: Optional[asyncio.AbstractEventLoop] = None, - oci_config_file: Optional[str] = None, - oci_config_profile: Optional[str] = None, - ): - # private (shouldn't be manipulated directly internally) - self.__charset: Optional[Charset] = None - """It shouldn't be manipulated directly, even internally. If you need - to manipulate the charset object, use the property `_charset` (read & write) - instead. Similarly, `_charset` shouldn't be manipulated externally. - """ - - # protected (can be manipulated directly internally) - self._user: str = user - self._password: str = password - self._host: str = host - self._port: int = port - self._database: str = database - self._password1: str = password1 - self._password2: str = password2 - self._password3: str = password3 - self._unix_socket: str = unix_socket - self._connection_timeout: int = connection_timeout - self._read_timeout: Optional[int] = read_timeout - self._write_timeout: Optional[int] = write_timeout - self._connection_attrs: Dict[str, str] = conn_attrs - self._compress: bool = compress - self._consume_results: bool = consume_results - self._autocommit: bool = autocommit - self._time_zone: Optional[str] = time_zone - self._sql_mode: Optional[str] = sql_mode - self._init_command: Optional[str] = init_command - self._protocol: MySQLProtocol = MySQLProtocol() - self._socket: Optional[Union[MySQLTcpSocket, MySQLUnixSocket]] = None - self._charset_name: Optional[str] = charset - """Charset name provided by the user at connection time.""" - self._charset_collation: Optional[str] = collation - """Collation provided by the user at connection time.""" - self._ssl_active: bool = False - self._ssl_disabled: bool = ssl_disabled - self._ssl_ca: Optional[str] = ssl_ca - self._ssl_cert: Optional[str] = ssl_cert - self._ssl_key: Optional[str] = ssl_key - self._ssl_verify_cert: Optional[bool] = ssl_verify_cert - self._ssl_verify_identity: Optional[bool] = ssl_verify_identity - self._tls_versions: Optional[List[str]] = tls_versions - self._tls_ciphersuites: Optional[List[str]] = tls_ciphersuites - self._auth_plugin: Optional[str] = auth_plugin - self._auth_plugin_class: Optional[str] = None - self._pool_config_version: Any = None - self._handshake: Optional[HandShakeType] = None - self._loop: Optional[asyncio.AbstractEventLoop] = ( - loop or asyncio.get_event_loop() - ) - self._client_flags: int = client_flags or ClientFlag.get_default() - self._server_info: Optional[ServerInfo] = None - self._cursors: weakref.WeakSet = weakref.WeakSet() - self._query_attrs: Dict[str, BinaryProtocolType] = {} - self._query_attrs_supported: int = False - self._columns_desc: List[DescriptionType] = [] - self._authenticator: MySQLAuthenticator = MySQLAuthenticator() - self._converter_class: Type[MySQLConverter] = converter_class or MySQLConverter - self._converter_str_fallback: bool = converter_str_fallback - self._kerberos_auth_mode: Optional[str] = kerberos_auth_mode - self._krb_service_principal: Optional[str] = krb_service_principal - self._openid_token_file: Optional[str] = openid_token_file - self._allow_local_infile: bool = allow_local_infile - self._allow_local_infile_in_path: Optional[str] = allow_local_infile_in_path - self._get_warnings: bool = get_warnings - self.raise_on_warnings: bool = raise_on_warnings - self._buffered: bool = buffered - self._raw: bool = raw - self._use_unicode: bool = use_unicode - self._have_next_result: bool = False - self._unread_result: bool = False - self._in_transaction: bool = False - self._oci_config_file: Optional[str] = oci_config_file - """Path to the configuration file.""" - self._oci_config_profile: Optional[str] = oci_config_profile - """Profile name.""" - self._webauthn_callback: Optional[Union[str, Callable[[str], None]]] = ( - webauthn_callback - ) - - self.converter: Optional[MySQLConverter] = None - - self._local_infile_filenames: Optional[Deque[str]] = None - """Stores the filenames from `LOCAL INFILE` requests - found in the executed query.""" - - self._query: Optional[bytes] = None - """The query being processed.""" - - self._validate_connection_options() - - async def __aenter__(self) -> MySQLConnectionAbstract: - if not self.is_socket_connected(): - await self.connect() - return self - - async def __aexit__( - self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, - ) -> None: - await self.close() - - def _validate_connection_options(self) -> None: - """Validate connection options.""" - if self._user: - try: - self._user = self._user.strip() - except AttributeError as err: - raise AttributeError("'user' must be a string") from err - - if self._compress: - self.client_flags = [ClientFlag.COMPRESS] - - if self._allow_local_infile_in_path: - infile_in_path = os.path.abspath(self._allow_local_infile_in_path) - if ( - infile_in_path - and os.path.exists(infile_in_path) - and not os.path.isdir(infile_in_path) - or os.path.islink(infile_in_path) - ): - raise AttributeError("allow_local_infile_in_path must be a directory") - if self._allow_local_infile or self._allow_local_infile_in_path: - self.client_flags = [ClientFlag.LOCAL_FILES] - else: - self.client_flags = [-ClientFlag.LOCAL_FILES] - - # Disallow the usage of some default authentication plugins - if self._auth_plugin == "authentication_webauthn_client": - raise InterfaceError( - f"'{self._auth_plugin}' cannot be used as the default authentication " - "plugin" - ) - - # Disable SSL for unix socket connections - if self._unix_socket and os.name == "posix": - self._ssl_disabled = True - - if self._ssl_disabled: - if self._auth_plugin == "mysql_clear_password": - raise InterfaceError( - "Clear password authentication is not supported over insecure " - " channels" - ) - if self._auth_plugin == "authentication_openid_connect_client": - raise InterfaceError( - "OpenID Connect authentication is not supported over insecure channels" - ) - - if not isinstance(self._port, int): - raise InterfaceError("TCP/IP port number should be an integer") - - if any([self._ssl_ca, self._ssl_cert, self._ssl_key]): - # Make sure both ssl_key/ssl_cert are set, or neither (XOR) - if not all([self._ssl_key, self._ssl_cert]): - raise AttributeError( - "ssl_key and ssl_cert need to be both specified, or neither" - ) - - if (self._ssl_key is None) != (self._ssl_cert is None): - raise AttributeError( - "ssl_key and ssl_cert need to be both set, or neither" - ) - if self._tls_versions is not None: - self._validate_tls_versions() - - if self._tls_ciphersuites is not None: - self._validate_tls_ciphersuites() - - if not isinstance(self._connection_attrs, dict): - raise InterfaceError("conn_attrs must be of type dict") - - for attr_name, attr_value in self._connection_attrs.items(): - if attr_name in CONN_ATTRS_DN: - continue - # Validate name type - if not isinstance(attr_name, str): - raise InterfaceError( - "Attribute name should be a string, found: " - f"'{attr_name}' in '{self._connection_attrs}'" - ) - # Validate attribute name limit 32 characters - if len(attr_name) > 32: - raise InterfaceError( - f"Attribute name '{attr_name}' exceeds 32 characters limit size" - ) - # Validate names in connection attributes cannot start with "_" - if attr_name.startswith("_"): - raise InterfaceError( - "Key names in connection attributes cannot start with " - "'_', found: '{attr_name}'" - ) - # Validate value type - if not isinstance(attr_value, str): - raise InterfaceError( - f"Attribute '{attr_name}' value: '{attr_value}' must " - "be a string type" - ) - # Validate attribute value limit 1024 characters - if len(attr_value) > 1024: - raise InterfaceError( - f"Attribute '{attr_name}' value: '{attr_value}' " - "exceeds 1024 characters limit size" - ) - - if self._client_flags & ClientFlag.CONNECT_ARGS: - self._add_default_conn_attrs() - - if self._kerberos_auth_mode: - if not isinstance(self._kerberos_auth_mode, str): - raise InterfaceError("'kerberos_auth_mode' must be of type str") - kerberos_auth_mode = self._kerberos_auth_mode.lower() - if kerberos_auth_mode == "sspi": - if os.name != "nt": - raise InterfaceError( - "'kerberos_auth_mode=SSPI' is only available on Windows" - ) - self._auth_plugin_class = "MySQLSSPIKerberosAuthPlugin" - elif kerberos_auth_mode == "gssapi": - self._auth_plugin_class = "MySQLKerberosAuthPlugin" - else: - raise InterfaceError( - "Invalid 'kerberos_auth_mode' mode. Please use 'SSPI' or 'GSSAPI'" - ) - - if self._krb_service_principal: - if not isinstance(self._krb_service_principal, str): - raise InterfaceError( - KRB_SERVICE_PRINCIPAL_ERROR.format(error="is not a string") - ) - if self._krb_service_principal == "": - raise InterfaceError( - KRB_SERVICE_PRINCIPAL_ERROR.format( - error="can not be an empty string" - ) - ) - if "/" not in self._krb_service_principal: - raise InterfaceError( - KRB_SERVICE_PRINCIPAL_ERROR.format(error="is incorrectly formatted") - ) - - if self._webauthn_callback: - self._validate_callable("webauth_callback", self._webauthn_callback, 1) - - if self._openid_token_file: - if not isinstance(self._openid_token_file, str): - raise InterfaceError( - OPENID_TOKEN_FILE_ERROR.format(error="is not a string") - ) - if self._openid_token_file == "": - raise InterfaceError( - OPENID_TOKEN_FILE_ERROR.format(error="cannot be an empty string") - ) - if not os.path.exists(self._openid_token_file): - raise InterfaceError( - f"The path '{self._openid_token_file}' provided via 'openid_token_file' " - "does not exist" - ) - if self._read_timeout is not None: - if not isinstance(self._read_timeout, int) or self._read_timeout < 0: - raise InterfaceError("Option read_timeout must be a positive integer") - if self._write_timeout is not None: - if not isinstance(self._write_timeout, int) or self._write_timeout < 0: - raise InterfaceError("Option write_timeout must be a positive integer") - - def _set_connection_options(self, **kwargs: Any) -> None: - """Internal utility to set or update the connection options. - - Args: - **kwargs: For a complete list of possible arguments, see [1]. - - Raises: - AttributeError: When provided unsupported connection arguments. - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - config = kwargs.copy() - # Compatible configuration with other drivers - compat_map = [ - # (,) - ("db", "database"), - ("username", "user"), - ("passwd", "password"), - ("connect_timeout", "connection_timeout"), - ("read_default_file", "option_files"), - ] - for compat, translate in compat_map: - try: - if translate not in config: - config[translate] = config[compat] - del config[compat] - except KeyError: - pass # Missing compat argument is OK - # Read option files - config = read_option_files(**config) - - # Set the configurations which needs to be passed via their respective - # property setter methods - self.get_warnings = config.pop("get_warnings", False) - self.raise_on_warnings = config.pop("raise_on_warnings", False) - self.client_flags = config.pop("client_flags", ClientFlag.get_default()) - self.converter_class = config.pop("converter_class", MySQLConverter) - - # Other configurations - for key, value in config.items(): - try: - DEFAULT_CONFIGURATION[key] - except KeyError: - raise AttributeError(f"Unsupported argument '{key}'") from None - attribute = "_" + key - try: - setattr(self, attribute, value.strip()) - except AttributeError: - setattr(self, attribute, value) - - # validate the set configurations - self._validate_connection_options() - - def _validate_tls_ciphersuites(self) -> None: - """Validates the tls_ciphersuites option.""" - tls_ciphersuites = [] - tls_cs = self._tls_ciphersuites - - if isinstance(tls_cs, str): - if not (tls_cs.startswith("[") and tls_cs.endswith("]")): - raise AttributeError( - f"tls_ciphersuites must be a list, found: '{tls_cs}'" - ) - tls_css = tls_cs[1:-1].split(",") - if not tls_css: - raise AttributeError( - "No valid cipher suite found in 'tls_ciphersuites' list" - ) - for _tls_cs in tls_css: - _tls_cs = tls_cs.strip().upper() - if _tls_cs: - tls_ciphersuites.append(_tls_cs) - - elif isinstance(tls_cs, (list, set)): - tls_ciphersuites = [tls_cs for tls_cs in tls_cs if tls_cs] - else: - raise AttributeError( - "tls_ciphersuites should be a list with one or more " - f"ciphersuites. Found: '{tls_cs}'" - ) - - tls_versions = ( - TLS_VERSIONS[:] if self._tls_versions is None else self._tls_versions[:] - ) - - # A newer TLS version can use a cipher introduced on - # an older version. - tls_versions.sort(reverse=True) - newer_tls_ver = tls_versions[0] - # translated_names[0] are TLSv1.2 only - # translated_names[1] are TLSv1.3 only - translated_names: List[List[str]] = [[], []] - iani_cipher_suites_names = {} - ossl_cipher_suites_names: List[str] = [] - - # Old ciphers can work with new TLS versions. - # Find all the ciphers introduced on previous TLS versions. - for tls_ver in TLS_VERSIONS[: TLS_VERSIONS.index(newer_tls_ver) + 1]: - iani_cipher_suites_names.update(TLS_CIPHER_SUITES[tls_ver]) - ossl_cipher_suites_names.extend(OPENSSL_CS_NAMES[tls_ver]) - - for name in tls_ciphersuites: - if "-" in name and name in ossl_cipher_suites_names: - if name in OPENSSL_CS_NAMES["TLSv1.3"]: - translated_names[1].append(name) - else: - translated_names[0].append(name) - elif name in iani_cipher_suites_names: - translated_name = iani_cipher_suites_names[name] - if translated_name in translated_names: - raise AttributeError( - DUPLICATED_IN_LIST_ERROR.format( - list="tls_ciphersuites", value=translated_name - ) - ) - if name in TLS_CIPHER_SUITES["TLSv1.3"]: - translated_names[1].append(iani_cipher_suites_names[name]) - else: - translated_names[0].append(iani_cipher_suites_names[name]) - else: - raise AttributeError( - f"The value '{name}' in tls_ciphersuites is not a valid " - "cipher suite" - ) - if not translated_names[0] and not translated_names[1]: - raise AttributeError( - "No valid cipher suite found in the 'tls_ciphersuites' list" - ) - - # raise an error when using an unacceptable cipher - for cipher_as_ossl in translated_names[0]: - if cipher_as_ossl in UNACCEPTABLE_TLS_CIPHERSUITES["TLSv1.2"].values(): - raise NotSupportedError( - f"Cipher {cipher_as_ossl} when used with TLSv1.2 is unacceptable." - ) - for cipher_as_ossl in translated_names[1]: - if cipher_as_ossl in UNACCEPTABLE_TLS_CIPHERSUITES["TLSv1.3"].values(): - raise NotSupportedError( - f"Cipher {cipher_as_ossl} when used with TLSv1.3 is unacceptable." - ) - - self._tls_ciphersuites = [ - ":".join(translated_names[0]), - ":".join(translated_names[1]), - ] - - def _validate_tls_versions(self) -> None: - """Validates the tls_versions option.""" - tls_versions = [] - tls_version = self._tls_versions - - if isinstance(tls_version, str): - if not (tls_version.startswith("[") and tls_version.endswith("]")): - raise AttributeError( - f"tls_versions must be a list, found: '{tls_version}'" - ) - tls_vers = tls_version[1:-1].split(",") - for tls_ver in tls_vers: - tls_version = tls_ver.strip() - if tls_version == "": - continue - if tls_version in tls_versions: - raise AttributeError( - DUPLICATED_IN_LIST_ERROR.format( - list="tls_versions", value=tls_version - ) - ) - tls_versions.append(tls_version) - if tls_vers == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED: - raise AttributeError( - TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS) - ) - elif isinstance(tls_version, list): - if not tls_version: - raise AttributeError( - "At least one TLS protocol version must be specified in " - "'tls_versions' list" - ) - for tls_ver in tls_version: - if tls_ver in tls_versions: - raise AttributeError( - DUPLICATED_IN_LIST_ERROR.format( - list="tls_versions", value=tls_ver - ) - ) - tls_versions.append(tls_ver) - elif isinstance(tls_version, set): - for tls_ver in tls_version: - tls_versions.append(tls_ver) - else: - raise AttributeError( - "tls_versions should be a list with one or more of versions " - f"in {', '.join(TLS_VERSIONS)}. found: '{tls_versions}'" - ) - - if not tls_versions: - raise AttributeError( - "At least one TLS protocol version must be specified " - "in 'tls_versions' list when this option is given" - ) - - use_tls_versions = [] - unacceptable_tls_versions = [] - invalid_tls_versions = [] - for tls_ver in tls_versions: - if tls_ver in TLS_VERSIONS: - use_tls_versions.append(tls_ver) - if tls_ver in UNACCEPTABLE_TLS_VERSIONS: - unacceptable_tls_versions.append(tls_ver) - else: - invalid_tls_versions.append(tls_ver) - - if use_tls_versions: - if use_tls_versions == ["TLSv1.3"] and not TLS_V1_3_SUPPORTED: - raise NotSupportedError( - TLS_VER_NO_SUPPORTED.format(tls_version, TLS_VERSIONS) - ) - self._tls_versions = use_tls_versions - elif unacceptable_tls_versions: - raise NotSupportedError( - TLS_VERSION_UNACCEPTABLE_ERROR.format( - unacceptable_tls_versions, TLS_VERSIONS - ) - ) - elif invalid_tls_versions: - raise AttributeError(TLS_VERSION_ERROR.format(tls_ver, TLS_VERSIONS)) - - @staticmethod - def _validate_callable( - option_name: str, callback: Union[str, Callable], num_args: int = 0 - ) -> None: - """Validates if it's a Python callable. - - Args: - option_name (str): Connection option name. - callback (str or callable): The fully qualified path to the callable or - a callable. - num_args (int): Number of positional arguments allowed. - - Raises: - ProgrammingError: If `callback` is not valid or wrong number of positional - arguments. - - .. versionadded:: 8.2.0 - """ - if isinstance(callback, str): - try: - callback = import_object(callback) - except ValueError as err: - raise ProgrammingError(f"{err}") from err - - if not callable(callback): - raise ProgrammingError(f"Expected a callable for '{option_name}'") - - # Check if the callable signature has positional arguments - num_params = len(signature(callback).parameters) - if num_params != num_args: - raise ProgrammingError( - f"'{option_name}' requires {num_args} positional argument, but the " - f"callback provided has {num_params}" - ) - - @property - @abstractmethod - def connection_id(self) -> Optional[int]: - """MySQL connection ID.""" - - @property - def user(self) -> str: - """User used while connecting to MySQL.""" - return self._user - - @property - def server_host(self) -> str: - """MySQL server IP address or name.""" - return self._host - - @property - def server_port(self) -> int: - "MySQL server TCP/IP port." - return self._port - - @property - def unix_socket(self) -> Optional[str]: - "MySQL Unix socket file location." - return self._unix_socket - - @property - def database(self) -> str: - """Get the current database.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await get_database()` to get the database instead" - ) - - @database.setter - def database(self, value: str) -> None: - """Set the current database.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await set_database(name)` to set the database instead" - ) - - @property - def read_timeout(self) -> Optional[int]: - """ - Gets the connection context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - """ - return self._read_timeout - - @read_timeout.setter - def read_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the connection context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set - in seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the read_timeout of the current session to - 5 seconds: - ``` - >>> cnx = await mysql.connector.aio.connect(user='scott') - >>> cnx.read_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option read_timeout must be a positive integer or None" - ) - self._read_timeout = timeout - - @property - def write_timeout(self) -> Optional[int]: - """ - Gets the connection context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - """ - return self._write_timeout - - @write_timeout.setter - def write_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the connection context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set in - seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the write_timeout of the current - session to 5 seconds: - ``` - >>> cnx = await mysql.connector.connect(user='scott') - >>> cnx.write_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option write_timeout must be a positive integer or None" - ) - self._write_timeout = timeout - - async def get_database(self) -> str: - """Get the current database.""" - result = await self.info_query("SELECT DATABASE()") - return result[0] # type: ignore[return-value] - - async def set_database(self, value: str) -> None: - """Set the current database.""" - await self.cmd_query(f"USE {value}") - - @property - def can_consume_results(self) -> bool: - """Returns whether to consume results""" - return self._consume_results - - @can_consume_results.setter - def can_consume_results(self, value: bool) -> None: - """Set if can consume results.""" - assert isinstance(value, bool) - self._consume_results = value - - @property - def pool_config_version(self) -> Any: - """Returns the pool configuration version.""" - return self._pool_config_version - - @pool_config_version.setter - def pool_config_version(self, value: Any) -> None: - """Sets the pool configuration version""" - self._pool_config_version = value - - @property - def in_transaction(self) -> bool: - """MySQL session has started a transaction.""" - return self._in_transaction - - @property - def loop(self) -> asyncio.AbstractEventLoop: - """Return the event loop.""" - return self._loop - - @property - def is_secure(self) -> bool: - """Return True if is a secure connection.""" - return self._ssl_active or (self._unix_socket is not None and IS_POSIX) - - @property - def query_attrs(self) -> List[Tuple[str, BinaryProtocolType]]: - """Returns query attributes list.""" - return list(self._query_attrs.items()) - - @property - def have_next_result(self) -> bool: - """Return if have next result.""" - return self._have_next_result - - @property - def autocommit(self) -> bool: - """Get whether autocommit is on or off.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await get_autocommit()` to get the autocommit instead" - ) - - @autocommit.setter - def autocommit(self, value: bool) -> None: - """Toggle autocommit.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await set_autocommit(value)` to set the autocommit instead" - ) - - async def get_autocommit(self) -> bool: - """Get whether autocommit is on or off.""" - value = await self.info_query("SELECT @@session.autocommit") - return value[0] == 1 - - async def set_autocommit(self, value: bool) -> None: - """Toggle autocommit.""" - switch = "ON" if value else "OFF" - await self.cmd_query(f"SET @@session.autocommit = {switch}") - self._autocommit = value - - @property - def time_zone(self) -> str: - """Gets the current time zone.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await get_time_zone()` to get the time zone instead" - ) - - @time_zone.setter - def time_zone(self, value: str) -> None: - """Sets the time zone.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await get_autocommit(value)` to get the autocommit instead" - ) - - async def get_time_zone(self) -> str: - """Gets the current time zone.""" - value = await self.info_query("SELECT @@session.time_zone") - return value[0] # type: ignore[return-value] - - async def set_time_zone(self, value: str) -> None: - """Sets the time zone.""" - await self.cmd_query(f"SET @@session.time_zone = '{value}'") - self._time_zone = value - - @property - async def sql_mode(self) -> str: - """Gets the SQL mode.""" - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await get_sql_mode()` to get the SQL mode instead" - ) - - @sql_mode.setter - async def sql_mode(self, value: Union[str, Sequence[int]]) -> None: - """Sets the SQL mode. - - This method sets the SQL Mode for the current connection. The value - argument can be either a string with comma separate mode names, or - a sequence of mode names. - - It is good practice to use the constants class `SQLMode`: - ``` - >>> from mysql.connector.constants import SQLMode - >>> cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT] - ``` - """ - raise ProgrammingError( - "The use of async properties are not supported by Python. " - "Use `await set_sql_mode(value)` to set the SQL mode instead" - ) - - async def get_sql_mode(self) -> str: - """Gets the SQL mode.""" - if self._sql_mode is None: - self._sql_mode = (await self.info_query("SELECT @@session.sql_mode"))[0] - return self._sql_mode - - async def set_sql_mode(self, value: Union[str, Sequence[int]]) -> None: - """Sets the SQL mode. - - This method sets the SQL Mode for the current connection. The value - argument can be either a string with comma separate mode names, or - a sequence of mode names. - - It is good practice to use the constants class `SQLMode`: - ``` - >>> from mysql.connector.constants import SQLMode - >>> cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT] - ``` - """ - if isinstance(value, (list, tuple)): - value = ",".join(value) - await self.cmd_query(f"SET @@session.sql_mode = '{value}'") - self._sql_mode = value - - @property - def get_warnings(self) -> bool: - """Get whether this connection retrieves warnings automatically. - - This method returns whether this connection retrieves warnings automatically. - """ - return self._get_warnings - - @get_warnings.setter - def get_warnings(self, value: bool) -> None: - """Set whether warnings should be automatically retrieved. - - The toggle-argument must be a boolean. When True, cursors for this connection - will retrieve information about warnings (if any). - - Raises: - ValueError: When the value is not a bool type. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._get_warnings = value - - @property - def raise_on_warnings(self) -> bool: - """Get whether this connection raises an error on warnings. - - This method returns whether this connection will raise errors when MySQL - reports warnings. - """ - return self._raise_on_warnings - - @raise_on_warnings.setter - def raise_on_warnings(self, value: bool) -> None: - """Set whether warnings raise an error. - - The toggle-argument must be a boolean. When True, cursors for this connection - will raise an error when MySQL reports warnings. - - Raising on warnings implies retrieving warnings automatically. - In other words: warnings will be set to True. If set to False, warnings will - be also set to False. - - Raises: - ValueError: When the value is not a bool type. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._raise_on_warnings = value - # Don't disable warning retrieval if raising explicitly disabled - if value: - self._get_warnings = value - - @property - def unread_result(self) -> bool: - """Get whether there is an unread result. - - This method is used by cursors to check whether another cursor still needs to - retrieve its result set. - """ - return self._unread_result - - @unread_result.setter - def unread_result(self, value: bool) -> None: - """Set whether there is an unread result. - - This method is used by cursors to let other cursors know there is still a - result set that needs to be retrieved. - - Raises: - ValueError: When the value is not a bool type. - """ - if not isinstance(value, bool): - raise ValueError("Expected a boolean type") - self._unread_result = value - - @property - def collation(self) -> str: - """Returns the collation for current connection. - - This property returns the collation name of the current connection. - The server is queried when the connection is active. If not connected, - the configured collation name is returned. - - Returns a string. - """ - return self._charset.collation - - @property - def charset(self) -> str: - """Return the character set for current connection. - - This property returns the character set name of the current connection. - The server is queried when the connection is active. - If not connected, the configured character set name is returned. - """ - return self._charset.name - - @property - def charset_id(self) -> int: - """The charset ID utilized during the connection phase. - - If the charset ID hasn't been set, the default charset ID is returned. - """ - return self._charset.charset_id - - @property - def _charset(self) -> Charset: - """The charset object encapsulates charset and collation information.""" - if self.__charset is None: - if self._server_info is None: - # We mustn't set `_charset` since we still don't know - # the server version. We temporarily return the default - # charset for undefined scenarios - eventually, the server - # info will be available and `_charset` (data class) will be set. - return charsets.get_by_id(MYSQL_DEFAULT_CHARSET_ID_57) - - self.__charset = charsets.get_by_id( - ( - MYSQL_DEFAULT_CHARSET_ID_57 - if self._server_info.version_tuple < (8, 0) - else MYSQL_DEFAULT_CHARSET_ID_80 - ) - ) - return self.__charset - - @_charset.setter - def _charset(self, value: Charset) -> None: - """The charset object encapsulates charset and collation information.""" - self.__charset = value - - @property - def python_charset(self) -> str: - """Return the Python character set for current connection. - - This property returns the character set name of the current connection. - Note that, unlike property charset, this checks if the previously set - character set is supported by Python and if not, it returns the equivalent - character set that Python supports. - """ - if self._charset is None or self._charset.name in ( - "utf8mb4", - "utf8mb3", - "binary", - ): - return "utf8" - return self._charset.name - - @abstractmethod - def _add_default_conn_attrs(self) -> None: - """Add the default connection attributes.""" - - @abstractmethod - async def _execute_query(self, query: str) -> ResultType: - """Execute a query. - - This method simply calls cmd_query() after checking for unread result. If there - are still unread result, an InterfaceError is raised. Otherwise whatever - cmd_query() returns is returned. - """ - - async def _post_connection(self) -> None: - """Executes commands after connection has been established. - - This method executes commands after the connection has been established. - Some setting like autocommit, character set, and SQL mode are set using this - method. - """ - await self.set_charset_collation(charset=self._charset.charset_id) - await self.set_autocommit(self._autocommit) - if self._time_zone: - await self.set_time_zone(self._time_zone) - if self._sql_mode: - await self.set_sql_mode(self._sql_mode) - if self._init_command: - await self._execute_query(self._init_command) - - async def set_charset_collation( - self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None - ) -> None: - """Set the character set and collation for the current connection. - - This method sets the character set and collation to be used for the current - connection. The charset argument can be either the name of a character set as - a string, or the numerical equivalent as defined in constants.CharacterSet. - - When the collation is not given, the default will be looked up and used. - - Args: - charset: Can be either the name of a character set, or the numerical - equivalent as defined in `constants.CharacterSet`. - collation: When collation is `None`, the default collation for the - character set is used. - - Examples: - The following will set the collation for the latin1 character set to - `latin1_general_ci`: - ``` - >>> cnx = mysql.connector.connect(user='scott') - >>> cnx.set_charset_collation('latin1', 'latin1_general_ci') - ``` - """ - err_msg = "{} should be either integer, string or None" - if not isinstance(charset, (int, str)) and charset is not None: - raise ValueError(err_msg.format("charset")) - if not isinstance(collation, str) and collation is not None: - raise ValueError("collation should be either string or None") - - if charset and collation: - charset_str: str = ( - charsets.get_by_id(charset).name - if isinstance(charset, int) - else charset - ) - self._charset = charsets.get_by_name_and_collation(charset_str, collation) - elif charset: - if isinstance(charset, int): - self._charset = charsets.get_by_id(charset) - elif isinstance(charset, str): - self._charset = charsets.get_by_name(charset) - else: - raise ValueError(err_msg.format("charset")) - elif collation: - self._charset = charsets.get_by_collation(collation) - else: - charset = DEFAULT_CONFIGURATION["charset"] - self._charset = charsets.get_by_name(charset) # type: ignore[arg-type] - - await self.cmd_query( - f"SET NAMES '{self._charset.name}' COLLATE '{self._charset.collation}'" - ) - - if self.converter: - self.converter.set_charset(self._charset.name) - - def isset_client_flag(self, flag: int) -> bool: - """Checks if a client flag is set. - - Returns: - `True` if the client flag was set, `False` otherwise. - """ - return (self._client_flags & flag) > 0 - - def set_allow_local_infile_in_path(self, path: str) -> None: - """Set the path that user can upload files. - - Args: - path (str): Path that user can upload files. - """ - - self._allow_local_infile_in_path = path - - def get_self(self) -> MySQLConnectionAbstract: - """Return self for weakref.proxy. - - This method is used when the original object is needed when using - weakref.proxy. - """ - return self - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="server_version")) - def get_server_version(self) -> Optional[Tuple[int, ...]]: - """Gets the MySQL version. - - Returns: - The MySQL server version as a tuple. If not previously connected, it will - return `None`. - """ - return self.server_version - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="server_info")) - def get_server_info(self) -> Optional[str]: - """Gets the original MySQL version information. - - Returns: - The original MySQL server as text. If not previously connected, it will - return `None`. - """ - return self.server_info - - @property - def server_version(self) -> Optional[Tuple[int, ...]]: - """Gets the MySQL version. - - Returns: - The MySQL server version as a tuple. If not previously connected, it will - return `None`. - """ - if self._server_info is not None: - return self._server_info.version_tuple - return None - - @property - def server_info(self) -> Optional[str]: - """Gets the original MySQL version information. - - Returns: - The original MySQL server as text. If not previously connected, it will - return `None`. - """ - try: - return self._handshake["server_version_original"] # type: ignore[return-value] - except (TypeError, KeyError): - return None - - @abstractmethod - def is_socket_connected(self) -> bool: - """Reports whether the socket is connected. - - Instead of ping the server like ``is_connected()``, it only checks if the - socket connection flag is set. - """ - - @abstractmethod - async def is_connected(self) -> bool: - """Reports whether the connection to MySQL Server is available. - - This method checks whether the connection to MySQL is available. - It is similar to ``ping()``, but unlike the ``ping()`` method, either `True` - or `False` is returned and no exception is raised. - """ - - @abstractmethod - async def ping( - self, reconnect: bool = False, attempts: int = 1, delay: int = 0 - ) -> bool: - """Check availability of the MySQL server. - - When reconnect is set to `True`, one or more attempts are made to try to - reconnect to the MySQL server using the ``reconnect()`` method. - - ``delay`` is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. Use the - ``is_connected()`` method if you just want to check the connection without - raising an error. - - Raises: - InterfaceError: On errors. - """ - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="client_flags")) - def set_client_flags(self, flags: Union[int, Sequence[int]]) -> int: - """Set the client flags. - - The flags-argument can be either an int or a list (or tuple) of ClientFlag - values. If it is an integer, it will set client_flags to flags as is. - If flags is a list or tuple, each flag will be set or unset when it's negative. - - client_flags = [ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG] - - Raises: - ProgrammingError: When the flags argument is not a set or an integer bigger - than 0. - """ - self.client_flags = flags - return self.client_flags - - @property - def client_flags(self) -> int: - """Gets the client flags of the current session.""" - return self._client_flags - - @client_flags.setter - def client_flags(self, flags: Union[int, Sequence[int]]) -> None: - """Sets the client flags. - - The flags-argument can be either an int or a list (or tuple) of ClientFlag - values. If it is an integer, it will set client_flags to flags as is. - If flags is a list or tuple, each flag will be set or unset when it's negative. - - client_flags = [ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG] - - Raises: - ProgrammingError: When the flags argument is not a set or an integer bigger - than 0. - """ - if isinstance(flags, int) and flags > 0: - self._client_flags = flags - elif isinstance(flags, (tuple, list)): - for flag in flags: - if flag < 0: - self._client_flags &= ~abs(flag) - else: - self._client_flags |= flag - else: - raise ProgrammingError("client_flags setter expect integer (>0) or set") - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="converter_class")) - def set_converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None: - """Set the converter class to be used. - - This should be a class overloading methods and members of - conversion.MySQLConverter. - - Raises: - TypeError: When the class is not a subclass of `conversion.MySQLConverter`. - """ - self.converter_class = convclass - - @property - def converter_class(self) -> Type[MySQLConverter]: - """Gets the converter class set for the current session.""" - return self._converter_class - - @converter_class.setter - def converter_class(self, convclass: Optional[Type[MySQLConverter]]) -> None: - """Sets the converter class to be used. - - This should be a class overloading methods and members of - conversion.MySQLConverter. - - Raises: - TypeError: When the class is not a subclass of `conversion.MySQLConverter`. - """ - if convclass and issubclass(convclass, MySQLConverterBase): - self._converter_class = convclass - self.converter = convclass(self._charset.name, self.use_unicode) - self.converter.str_fallback = self._converter_str_fallback - else: - raise TypeError( - "Converter class should be a subclass of conversion.MySQLConverter" - ) - - @property - def use_unicode(self) -> bool: - """Gets whether we return string fields as unicode or not.""" - return self._use_unicode - - @use_unicode.setter - def use_unicode(self, value: bool) -> None: - """Sets whether we return string fields as unicode or not. - - Args: - value: A boolean - default is `True`. - """ - self._use_unicode = value - if self.converter: - self.converter.set_unicode(value) - - def query_attrs_append(self, value: Tuple[str, BinaryProtocolType]) -> None: - """Add element to the query attributes list on the connector's side. - - If an element in the query attributes list already matches - the attribute name provided, the new element will NOT be added. - - Args: - value: key-value as a 2-tuple. - """ - attr_name, attr_value = value - if attr_name not in self._query_attrs: - self._query_attrs[attr_name] = attr_value - - def query_attrs_remove(self, name: str) -> BinaryProtocolType: - """Remove element by name from the query attributes list. - - If no match, `None` is returned, else the corresponding value is returned. - - Args: - name: key name. - """ - return self._query_attrs.pop(name, None) - - def query_attrs_clear(self) -> None: - """Clears query attributes list on the connector's side.""" - self._query_attrs = {} - - async def handle_unread_result(self) -> None: - """Handle unread result. - - Consume pending results if is configured for it. - - Raises: - InternalError: When there are pending results and they were not consumed. - """ - if self._consume_results: - await self.consume_results() - elif self.unread_result: - raise InternalError("Unread result found") - - async def consume_results(self) -> None: - """Consume pending results.""" - if self.unread_result: - await self.get_rows() - - async def info_query(self, query: StrOrBytes) -> Optional[RowType]: - """Send a query which only returns 1 row.""" - async with await self.cursor(buffered=True) as cursor: - await cursor.execute(cast(str, query)) - return await cursor.fetchone() - - def add_cursor(self, cursor: MySQLCursorAbstract) -> None: - """Add cursor to the weakref set.""" - self._cursors.add(cursor) - - def remove_cursor(self, cursor: MySQLCursorAbstract) -> None: - """Remove cursor from the weakref set.""" - self._cursors.remove(cursor) - - @abstractmethod - async def connect(self) -> None: - """Connect to the MySQL server.""" - - async def reconnect(self, attempts: int = 1, delay: int = 0) -> None: - """Attempts to reconnect to the MySQL server. - - The argument `attempts` should be the number of times a reconnect is tried. - The `delay` argument is the number of seconds to wait between each retry. - - You may want to set the number of attempts higher and use delay when you expect - the MySQL server to be down for maintenance or when you expect the network to - be temporary unavailable. - - Args: - attempts: Number of attempts to make when reconnecting. - delay: Use it (defined in seconds) if you want to wait between each retry. - - Raises: - InterfaceError: When reconnection fails. - """ - counter = 0 - while counter != attempts: - counter = counter + 1 - try: - await self.disconnect() - await self.connect() - if await self.is_connected(): - break - except (Error, IOError) as err: - if counter == attempts: - msg = ( - f"Can not reconnect to MySQL after {attempts} " - f"attempt(s): {err}" - ) - raise InterfaceError(msg) from err - if delay > 0: - await asyncio.sleep(delay) - - async def shutdown(self) -> NoReturn: - """Shuts down connection to MySQL Server. - - This method closes the socket. It raises no exceptions. - - Unlike `disconnect()`, `shutdown()` closes the client connection without - attempting to send a `QUIT` command to the server first. Thus, it will not - block if the connection is disrupted for some reason such as network failure. - """ - raise NotImplementedError - - @abstractmethod - async def close(self) -> None: - """Close the connection. - - It closes any opened cursor associated to this connection, and closes the - underling socket connection. - - `MySQLConnection.close()` is a synonymous for `MySQLConnection.disconnect()` - method name and more commonly used. - - This method tries to send a `QUIT` command and close the socket. It raises - no exceptions. - """ - - disconnect: ClassVar[Callable[["MySQLConnectionAbstract"], Awaitable[None]]] = close - - @abstractmethod - async def cursor( - self, - buffered: Optional[bool] = None, - raw: Optional[bool] = None, - prepared: Optional[bool] = None, - cursor_class: Optional[Type[MySQLCursorAbstract]] = None, - dictionary: Optional[bool] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> MySQLCursorAbstract: - """Instantiate and return a cursor. - - By default, MySQLCursor is returned. Depending on the options while - connecting, a buffered and/or raw cursor is instantiated instead. - Also depending upon the cursor options, rows can be returned as a dictionary - or a tuple. - - It is possible to also give a custom cursor through the cursor_class - parameter, but it needs to be a subclass of - mysql.connector.aio.abstracts.MySQLCursorAbstract. - - Raises: - ProgrammingError: When cursor_class is not a subclass of - MySQLCursor. - ValueError: When cursor is not available. - """ - - @abstractmethod - async def get_row( - self, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - **kwargs: Any, - ) -> Tuple[Optional[RowType], Optional[EofPacketType]]: - """Get the next rows returned by the MySQL server. - - This method gets one row from the result set after sending, for example, the - query command. The result is a tuple consisting of the row and the EOF packet. - If no row was available in the result set, the row data will be None. - """ - - @abstractmethod - async def get_rows( - self, - count: Optional[int] = None, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Any = None, - **kwargs: Any, - ) -> Tuple[List[RowType], Optional[EofPacketType]]: - """Get all rows returned by the MySQL server. - - This method gets all rows returned by the MySQL server after sending, for - example, the query command. The result is a tuple consisting of a list of rows - and the EOF packet. - """ - - @abstractmethod - async def commit(self) -> None: - """Commit current transaction.""" - - @abstractmethod - async def rollback(self) -> None: - """Rollback current transaction.""" - - async def start_transaction( - self, - consistent_snapshot: bool = False, - isolation_level: Optional[str] = None, - readonly: Optional[bool] = None, - ) -> None: - """Starts a transaction. - This method explicitly starts a transaction sending the - START TRANSACTION statement to the MySQL server. You can optionally - set whether there should be a consistent snapshot, which - isolation level you need or which access mode i.e. READ ONLY or - READ WRITE. - Args: - consistent_snapshot: If `True`, Connector/Python sends WITH CONSISTENT - SNAPSHOT with the statement. MySQL ignores this for - isolation levels for which that option does not apply. - isolation_level: Permitted values are 'READ UNCOMMITTED', 'READ COMMITTED', - 'REPEATABLE READ', and 'SERIALIZABLE'. If the value is - `None`, no isolation level is sent, so the default level - applies. - readonly: Can be `True` to start the transaction in READ ONLY mode or - `False` to start it in READ WRITE mode. If readonly is omitted, - the server's default access mode is used. - Raises: - ProgrammingError: When a transaction is already in progress - and when `ValueError` when `isolation_level` - specifies an Unknown level. - Examples: - For example, to start a transaction with isolation level `SERIALIZABLE`, - you would do the following: - ``` - >>> cnx = mysql.connector.aio.connect(...) - >>> await cnx.start_transaction(isolation_level='SERIALIZABLE') - ``` - """ - if self.in_transaction: - raise ProgrammingError("Transaction already in progress") - - if isolation_level: - level = isolation_level.strip().replace("-", " ").upper() - levels = [ - "READ UNCOMMITTED", - "READ COMMITTED", - "REPEATABLE READ", - "SERIALIZABLE", - ] - - if level not in levels: - raise ValueError(f'Unknown isolation level "{isolation_level}"') - - await self._execute_query(f"SET TRANSACTION ISOLATION LEVEL {level}") - - if readonly is not None: - if self.server_version < (5, 6, 5): - raise ValueError( - f"MySQL server version {self.server_version} does not " - "support this feature" - ) - - if readonly: - access_mode = "READ ONLY" - else: - access_mode = "READ WRITE" - await self._execute_query(f"SET TRANSACTION {access_mode}") - - query = "START TRANSACTION" - if consistent_snapshot: - query += " WITH CONSISTENT SNAPSHOT" - await self.cmd_query(query) - - @abstractmethod - async def reset_session( - self, - user_variables: Optional[Dict[str, Any]] = None, - session_variables: Optional[Dict[str, Any]] = None, - ) -> None: - """Clears the current active session. - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - - Args: - user_variables: User variables map. - session_variables: System variables map. - - Raises: - OperationalError: If not connected. - InternalError: If there are unread results and InterfaceError on errors. - - Examples: - ``` - >>> user_variables = {'var1': '1', 'var2': '10'} - >>> session_variables = {'wait_timeout': 100000, 'sql_mode': 'TRADITIONAL'} - >>> await cnx.reset_session(user_variables, session_variables) - ``` - """ - - @abstractmethod - async def cmd_reset_connection(self) -> bool: - """Resets the session state without re-authenticating. - - Reset command only works on MySQL server 5.7.3 or later. - The result is True for a successful reset otherwise False. - """ - - @abstractmethod - async def cmd_init_db(self, database: str) -> OkPacketType: - """Change the current database. - - This method changes the current (default) database by sending the INIT_DB - command. The result is a dictionary containing the OK packet infawaitormation. - """ - - @abstractmethod - async def cmd_query( - self, - query: StrOrBytes, - raw: bool = False, - buffered: bool = False, - raw_as_string: bool = False, - **kwargs: Any, - ) -> ResultType: - """Send a query to the MySQL server. - - This method send the query to the MySQL server and returns the result. - - If there was a text result, a tuple will be returned consisting of the number - of columns and a list containing information about these columns. - - When the query doesn't return a text result, the OK or EOF packet information - as dictionary will be returned. In case the result was an error, exception - Error will be raised. - """ - - async def cmd_query_iter( - self, - statements: StrOrBytes, - **kwargs: Any, - ) -> Generator[ResultType, None, None]: - """Send one or more statements to the MySQL server. - - Similar to the cmd_query method, but instead returns a generator - object to iterate through results. It sends the statements to the - MySQL server and through the iterator you can get the results. - - statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' - for result in await cnx.cmd_query(statement, iterate=True): - if 'columns' in result: - columns = result['columns'] - rows = await cnx.get_rows() - else: - # do something useful with INSERT result - """ - - @abstractmethod - async def cmd_stmt_fetch( - self, statement_id: int, rows: int = 1, **kwargs: Any - ) -> None: - """Fetch a MySQL statement Result Set. - - This method will send the FETCH command to MySQL together with the given - statement id and the number of rows to fetch. - """ - - @abstractmethod - async def cmd_stmt_prepare( - self, - statement: bytes, - **kwargs: Any, - ) -> Mapping[str, Union[int, List[DescriptionType]]]: - """Prepare a MySQL statement. - - This method will send the PREPARE command to MySQL together with the given - statement. - """ - - @abstractmethod - async def cmd_stmt_execute( - self, - statement_id: Union[int, CMySQLPrepStmt], - data: Sequence[BinaryProtocolType] = (), - parameters: Sequence = (), - flags: int = 0, - **kwargs: Any, - ) -> Optional[Union[Dict[str, Any], Tuple]]: - """Execute a prepared MySQL statement.""" - - @abstractmethod - async def cmd_stmt_reset( - self, - statement_id: int, - **kwargs: Any, - ) -> None: - """Reset data for prepared statement sent as long data. - - The result is a dictionary with OK packet information. - """ - - @abstractmethod - async def cmd_stmt_close(self, statement_id: int, **kwargs: Any) -> None: - """Deallocate a prepared MySQL statement. - - This method deallocates the prepared statement using the statement_id. - Note that the MySQL server does not return anything. - """ - - @abstractmethod - async def cmd_refresh(self, options: int) -> OkPacketType: - """Send the Refresh command to the MySQL server. - - This method sends the Refresh command to the MySQL server. The options - argument should be a bitwise value using constants.RefreshOption. - - Typical usage example: - ``` - RefreshOption = mysql.connector.RefreshOption - refresh = RefreshOption.LOG | RefreshOption.INFO - await cnx.cmd_refresh(refresh) - ``` - - Args: - options: Bitmask value constructed using constants from - the `constants.RefreshOption` class. - - Returns: - A dictionary representing the OK packet got as response when executing - the command. - - Raises: - ValueError: If an invalid command `refresh options` is provided. - DeprecationWarning: If one of the options is deprecated for the server you - are connecting to. - """ - - async def cmd_stmt_send_long_data( - self, - statement_id: int, - param_id: int, - data: BinaryIO, - **kwargs: Any, - ) -> int: - """Send data for a column. - - This methods send data for a column (for example BLOB) for statement identified - by statement_id. The param_id indicate which parameter the data belongs too. - The data argument should be a file-like object. - - Since MySQL does not send anything back, no error is raised. When the MySQL - server is not reachable, an OperationalError is raised. - - cmd_stmt_send_long_data should be called before cmd_stmt_execute. - - The total bytes send is returned. - """ - - @abstractmethod - async def cmd_quit(self) -> bytes: - """Close the current connection with the server. - - Send the QUIT command to the MySQL server, closing the current connection. - """ - - @abstractmethod - async def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None: - """Shut down the MySQL Server. - - This method sends the SHUTDOWN command to the MySQL server. - The `shutdown_type` is not used, and it's kept for backward compatibility. - """ - - @abstractmethod - async def cmd_statistics(self) -> StatsPacketType: - """Send the statistics command to the MySQL Server. - - This method sends the STATISTICS command to the MySQL server. The result is a - dictionary with various statistical information. - """ - - @abstractmethod - async def cmd_process_kill(self, mysql_pid: int) -> OkPacketType: - """Kill a MySQL process. - - This method send the PROCESS_KILL command to the server along with the - process ID. The result is a dictionary with the OK packet information. - """ - - @abstractmethod - async def cmd_debug(self) -> EofPacketType: - """Send the DEBUG command. - - This method sends the DEBUG command to the MySQL server, which requires the - MySQL user to have SUPER privilege. The output will go to the MySQL server - error log and the result of this method is a dictionary with EOF packet - information. - """ - - @abstractmethod - async def cmd_ping(self) -> OkPacketType: - """Send the PING command. - - This method sends the PING command to the MySQL server. It is used to check - if the the connection is still valid. The result of this method is dictionary - with OK packet information. - """ - - @abstractmethod - async def cmd_change_user( - self, - username: str = "", - password: str = "", - database: str = "", - charset: Optional[int] = None, - password1: str = "", - password2: str = "", - password3: str = "", - oci_config_file: str = "", - oci_config_profile: str = "", - openid_token_file: str = "", - ) -> Optional[OkPacketType]: - """Changes the current logged in user. - - It also causes the specified database to become the default (current) - database. It is also possible to change the character set using the - charset argument. The character set passed during initial connection - is reused if no value of charset is passed via this method. - - Args: - username: New account's username. - password: New account's password. - database: Database to become the default (current) database. - charset: Client charset (see [1]), only the lower 8-bits. - password1: New account's password factor 1 - it's used instead - of `password` if set (higher precedence). - password2: New account's password factor 2. - password3: New account's password factor 3. - oci_config_file: OCI configuration file location (path-like string). - oci_config_profile: OCI configuration profile location (path-like string). - openid_token_file: OpenID Connect token file location (path-like string). - - Returns: - ok_packet: Dictionary containing the OK packet information. - - Examples: - ``` - >>> cnx.cmd_change_user(username='', password='', database='', charset=33) - ``` - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_basic_character_set.html#a_protocol_character_set - """ - - -class MySQLCursorAbstract(ABC): - """Defines the MySQL cursor interface.""" - - def __init__( - self, - connection: MySQLConnectionAbstract, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ): - self._connection: MySQLConnectionAbstract = connection - self._loop: asyncio.AbstractEventLoop = connection.loop - self._description: Optional[List[DescriptionType]] = None - self._last_insert_id: Optional[int] = None - self._warnings: Optional[List[WarningType]] = None - self._warning_count: int = 0 - self._executed: Optional[bytes] = None - self._executed_list: List[bytes] = [] - self._stored_results: List[Any] = [] - self._binary: bool = False - self._raw: bool = False - self._rowcount: int = -1 - self._nextrow: Tuple[Optional[RowType], Optional[EofPacketType]] = ( - None, - None, - ) - self.arraysize: int = 1 - - # multi statement execution - self._stmt_partitions: Optional[Generator[MySQLScriptPartition, None, None]] = ( - None - ) - self._stmt_partition: Optional[MySQLScriptPartition] = None - self._stmt_map_results: bool = False - - self._read_timeout: Optional[int] = read_timeout - self._write_timeout: Optional[int] = write_timeout - self._connection.add_cursor(self) - - async def __aenter__(self) -> MySQLCursorAbstract: - return self - - async def __aexit__( - self, - exc_type: Optional[Type[BaseException]] = None, - exc_value: Optional[BaseException] = None, - traceback: Optional[TracebackType] = None, - ) -> None: - await self.close() - - async def __aiter__(self) -> Iterator[RowType]: - """Iterate over result set. - - Iteration over the result set which calls self.fetchone() - and returns the next row. - """ - return self # type: ignore[return-value] - - async def __anext__(self) -> RowType: - """ - Used for iterating over the result set. Calls self.fetchone() - to get the next row. - """ - try: - row = await self.fetchone() - except InterfaceError: - raise StopAsyncIteration from None - if not row: - raise StopAsyncIteration - return row - - @property - def read_timeout(self) -> Optional[int]: - """ - Gets the cursor context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - """ - return self._read_timeout - - @read_timeout.setter - def read_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the cursor context's timeout in seconds for each attempt - to read any data from the server. - - `read_timeout` is number of seconds upto which the connector should wait - for the server to reply back before raising an ReadTimeoutError. We can set - this option to None, which would signal the connector to wait indefinitely - till the read operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set - in seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the read_timeout of the current session's cursor - context to 5 seconds: - ``` - >>> cnx = await mysql.connector.connect(user='scott') - >>> cur = await cnx.cursor() - >>> cur.read_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option read_timeout must be a positive integer or None" - ) - self._read_timeout = timeout - - @property - def write_timeout(self) -> Optional[int]: - """ - Gets the cursor context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - """ - return self._write_timeout - - @write_timeout.setter - def write_timeout(self, timeout: Optional[int]) -> None: - """ - Sets or updates the cursor context's timeout in seconds for each attempt - to send data to the server. - - `write_timeout` is number of seconds upto which the connector should spend to - write to the server before raising an WriteTimeoutError. We can set this option - to None, which would signal the connector to wait indefinitely till the write - operation is completed or stopped abruptly. - - Args: - timeout: Accepts a non-negative integer which is the timeout to be set in - seconds or None. - Raises: - InterfaceError: If a positive integer or None is not passed via the - timeout parameter. - Examples: - The following will set the write_timeout of the current session's cursor - context to 5 seconds: - ``` - >>> cnx = await mysql.connector.connect(user='scott') - >>> cur = await cnx.cursor() - >>> cur.write_timeout = 5 - ``` - """ - if timeout is not None: - if not isinstance(timeout, int) or timeout < 0: - raise InterfaceError( - "Option write_timeout must be a positive integer or None" - ) - self._write_timeout = timeout - - @property - def description(self) -> Optional[List[DescriptionType]]: - """Return description of columns in a result. - - This property returns a list of tuples describing the columns in in a result - set. A tuple is described as follows: - - (column_name, - type, - None, - None, - None, - None, - null_ok, - column_flags) # Addition to PEP-249 specs - - Returns a list of tuples. - """ - return self._description - - @property - def rowcount(self) -> int: - """Return the number of rows produced or affected. - - This property returns the number of rows produced by queries such as a - SELECT, or affected rows when executing DML statements like INSERT or UPDATE. - - Note that for non-buffered cursors it is impossible to know the number of rows - produced before having fetched them all. For those, the number of rows will - be -1 right after execution, and incremented when fetching rows. - """ - return self._rowcount - - @property - def lastrowid(self) -> Optional[int]: - """Gets the value generated for an AUTO_INCREMENT column by the previous - INSERT or UPDATE statement or None when there is no such value available. - """ - return self._last_insert_id - - @property - def warnings(self) -> Optional[List[WarningType]]: - """Gets warnings.""" - return self._warnings - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="warnings")) - def fetchwarnings(self) -> Optional[List[WarningType]]: - """Returns Warnings.""" - return self._warnings - - @property - def warning_count(self) -> int: - """Return the number of warnings. - - This property returns the number of warnings generated by the previously - executed operation. - """ - return self._warning_count - - @property - def column_names(self) -> Tuple[str, ...]: - """Returns column names. - - This property returns the columns names as a tuple. - """ - if not self.description: - return tuple() - return tuple(d[0] for d in self.description) - - @property - def statement(self) -> Optional[str]: - """Returns the latest executed statement. - - When a multiple statement is executed, the value of `statement` - corresponds to the one that caused the current result set, provided - the statement-result mapping was enabled. Otherwise, the value of - `statement` matches the statement just as provided when calling - `execute()` and it does not change as result sets are traversed. - """ - if self._executed is None: - return None - try: - return self._executed.strip().decode("utf-8") - except (AttributeError, UnicodeDecodeError): - return cast(str, self._executed.strip()) - - @property - def with_rows(self) -> bool: - """Returns whether the cursor could have rows returned. - - This property returns True when column descriptions are available and possibly - also rows, which will need to be fetched. - """ - return bool(self._description) - - @abstractmethod - def stored_results(self) -> Iterator[MySQLCursorAbstract]: - """Returns an iterator for stored results. - - This method returns an iterator over results which are stored when callproc() - is called. The iterator will provide MySQLCursorBuffered instances. - """ - - @abstractmethod - async def execute( - self, - operation: str, - params: Union[Sequence[Any], Dict[str, Any]] = (), - map_results: bool = False, - ) -> None: - """Executes the given operation (a MySQL script) substituting any markers - with the given parameters. - - For example, getting all rows where id is 5: - ``` - cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) - ``` - - If you want each single statement in the script to be related - to its corresponding result set, you should enable the `map_results` - switch - see workflow example below. - - If the given script uses `DELIMITER` statements (which are not recognized - by MySQL Server), the connector will parse such statements to remove them - from the script and substitute delimiters as needed. This pre-processing - may cause a performance hit when using long scripts. Note that when enabling - `map_results`, the script is expected to use `DELIMITER` statements in order - to split the script into multiple query strings. - - The following characters are currently not supported by the connector in - `DELIMITER` statements: `"`, `'`, #`, `/*` and `*/`. - - If warnings were generated, and `connection.get_warnings` is - `True`, then `self.warnings` will be a list containing these - warnings. - - Args: - operation: Operation to be executed - it can be a single or a - multi statement. - params: The parameters found in the tuple or dictionary params are bound - to the variables in the operation. Specify variables using `%s` or - `%(name)s` parameter style (that is, using format or pyformat style). - map_results: It is `False` by default. If `True`, it allows you to know what - statement caused what result set - see workflow example below. - Only relevant when working with multi statements. - - Returns: - `None`. - - Example (basic usage): - The following example runs many single statements in a - single go and loads the corresponding result sets - sequentially: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - async with await cnx.cursor() as cur: - await cur.execute(sql_operation) - - result_set = await cur.fetchall() - # do something with result set - ... - - while (await cur.nextset()): - result_set = await cur.fetchall() - # do something with result set - ... - ``` - - In case the operation is a single statement, you may skip the - looping section as no more result sets are to be expected. - - Example (statement-result mapping): - The following example runs many single statements in a - single go and loads the corresponding result sets - sequentially. Additionally, each result set gets related - to the statement that caused it: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - async with await cnx.cursor() as cur: - await cur.execute(sql_operation, map_results=True) - - # statement 1 is `SET @a=1, @b='2024-02-01'`, - # result set from statement 1 is `[]` - aka, an empty set. - result_set, statement = await cur.fetchall(), cur.statement - # do something with result set - ... - - # 1st call to `nextset()` will laod the result set from statement 2, - # statement 2 is `SELECT @a, LENGTH('hello'), @b`, - # result set from statement 2 is `[(1, 5, '2024-02-01')]`. - # - # 2nd call to `nextset()` will laod the result set from statement 3, - # statement 3 is `SELECT @@version`, - # result set from statement 3 is `[('9.0.0-labs-mrs-8',)]`. - # - # 3rd call to `nextset()` will return `None` as there are no more sets, - # leading to the end of the consumption process of result sets. - while (await cur.nextset()): - result_set, statement = await cur.fetchall(), cur.statement - # do something with result set - ... - ``` - - In case the mapping is disabled (`map_results=False`), all result - sets get related to the same statement, which is the one provided - when calling `execute()`. In other words, the property `statement` - will not change as result sets are consumed, which contrasts with - the case in which the mapping is enabled. Note that we offer a - new fetch-related API command which can be leveraged as a shortcut - for consuming result sets - it is equivalent to the previous - workflow. - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - async with await cnx.cursor() as cur: - await cur.execute(sql_operation, map_results=True) - async for statement, result_set in cur.fetchsets(): - # do something with result set - ``` - """ - - @abstractmethod - async def executemulti( - self, - operation: str, - params: Union[Sequence[Any], Dict[str, Any]] = (), - map_results: bool = False, - ) -> None: - """Executes the given operation (it can be a multi statement - or a MySQL script) substituting any markers with the given parameters. - - **NOTE: `executemulti()` is deprecated and will be removed in a - future release. Use `execute()` instead.** - - If you want each single statement in the script to be related - to its corresponding result set, you should enable the `map_results` - switch - see workflow example below. This capability reduces performance. - - **Unexpected behavior might happen if your script includes the following - symbols as delimiters `"`, `'`, `#`, `/*` and `*/`. The use of these should - be avoided for now**. - - Refer to the documentation of `execute()` to see the multi statement execution - workflow. - - Args: - operation: Operation to be executed - it can be a single or a - multi statement. - params: The parameters found in the tuple or dictionary params are bound - to the variables in the operation. Specify variables using `%s` or - `%(name)s` parameter style (that is, using format or pyformat style). - map_results: It is `False` by default. If `True`, it allows you to know what - statement caused what result set - see workflow example below. - Only relevant when working with multi statements. - - Returns: - `None`. - """ - - @abstractmethod - async def executemany( - self, - operation: str, - seq_params: Sequence[ParamsSequenceType], - ) -> None: - """Prepare and execute a MySQL Prepared Statement many times. - - This method will prepare the given operation and execute with each tuple found - the list seq_params. - - If the cursor instance already had a prepared statement, it is first closed. - - executemany() simply calls execute(). - """ - - @abstractmethod - async def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Raises: - InterfaceError: If there is no result to fetch. - - Returns: - tuple or None: A row from query result set. - """ - - @abstractmethod - async def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Raises: - InterfaceError: If there is no result to fetch. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - - @abstractmethod - async def fetchmany(self, size: int = 1) -> List[Sequence[Any]]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, which - defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - - async def fetchsets( - self, - ) -> AsyncGenerator[tuple[Optional[str], list[RowType]], None]: - """Generates the result sets stream caused by the last `execute*()`. - - Returns: - A 2-tuple; the first element is the statement that caused the - result set, and the second is the result set itself. - - Example: - Consider the following example where multiple statements are executed in one - go: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - async with await cnx.cursor() as cur: - await cur.execute(sql_operation, map_results=True) - - result_set, statement = await cur.fetchall(), cur.statement - # do something with result set - ... - - while (await cur.nextset()): - result_set, statement = await cur.fetchall(), cur.statement - # do something with result set - ... - ``` - - In this case, as an alternative to loading the result sets with `nextset()` - in combination with a `while` loop, you can use `fetchsets()` which is - equivalent to the previous approach: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - async with await cnx.cursor() as cur: - await cur.execute(sql_operation, map_results=True) - async for statement, result_set in cur.fetchsets(): - # do something with result set - ``` - """ - # Some cursor flavor such as `buffered` raise an exception when they don't have - # result sets to fetch from. - # Some others, such as `dictionary` or `raw`, return an empty result set - # under the same circumstances. - statement_cached = None - if not self._stmt_map_results: - statement_cached = self.statement - - try: - result_set = await self.fetchall() - except InterfaceError: - result_set = [] - yield ( - self.statement if self._stmt_map_results else statement_cached - ), result_set - while await self.nextset(): - try: - result_set = await self.fetchall() - except InterfaceError: - result_set = [] - yield ( - self.statement if self._stmt_map_results else statement_cached - ), result_set - - @abstractmethod - async def nextset(self) -> Optional[bool]: - """Makes the cursor skip to the next available set, discarding - any remaining rows from the current set. - - This method should be used as part of the multi statement - execution workflow - see example below. - - Returns: - It returns `None` if there are no more sets. Otherwise, it returns - `True` and subsequent calls to the `fetch*()` methods will return - rows from the next result set. - - Example: - The following example runs many single statements in a - single go and loads the corresponding result sets - sequentially: - - ``` - sql_operation = ''' - SET @a=1, @b='2024-02-01'; - SELECT @a, LENGTH('hello'), @b; - SELECT @@version; - ''' - async with await cnx.cursor() as cur: - await cur.execute(sql_operation) - - result_set = await cur.fetchall() - # do something with result set - ... - - while (await cur.nextset()): - result_set = await cur.fetchall() - # do something with result set - ... - ``` - - In case the operation is a single statement, you may skip the - looping section as no more result sets are to be expected. - """ - - @abstractmethod - async def close(self) -> bool: - """Close the cursor.""" - - @deprecated(DEPRECATED_METHOD_WARNING.format(property_name="lastrowid")) - def getlastrowid(self) -> Optional[int]: - """Return the value generated for an AUTO_INCREMENT column. - - Returns the value generated for an AUTO_INCREMENT column by the previous - INSERT or UPDATE statement. - """ - return self._last_insert_id - - async def reset(self, free: bool = True) -> Any: - """Reset the cursor to default.""" - - def get_attributes(self) -> Optional[List[Tuple[str, BinaryProtocolType]]]: - """Gets a list of query attributes from the connector's side. - - Returns: - List of existing query attributes. - """ - if hasattr(self, "_connection"): - return self._connection.query_attrs - return None - - def add_attribute(self, name: str, value: BinaryProtocolType) -> None: - """Add a query attribute and its value into the connector's query attributes. - - Query attributes must be enabled on the server - they are disabled by default. A - warning is logged when setting query attributes for a server connection - that does not support them. - - Args: - name: Key name used to identify the attribute. - value: A value converted to the MySQL Binary Protocol. - - Raises: - ProgrammingError: If the value's conversion fails. - """ - if not isinstance(name, str): - raise ProgrammingError("Parameter `name` must be a string type") - if value is not None and not isinstance(value, MYSQL_PY_TYPES): - raise ProgrammingError( - f"Object {value} cannot be converted to a MySQL type" - ) - self._connection.query_attrs_append((name, value)) - - def remove_attribute(self, name: str) -> BinaryProtocolType: - """Removes a query attribute by name from the connector's query attributes. - - If no match, `None` is returned, else the corresponding value is returned. - - Args: - name: Key name used to identify the attribute. - - Returns: - value: Attribute's value. - """ - if not isinstance(name, str): - raise ProgrammingError("Parameter `name` must be a string type") - return self._connection.query_attrs_remove(name) - - def clear_attributes(self) -> None: - """Clears the list of query attributes on the connector's side.""" - self._connection.query_attrs_clear() - - -class CMySQLPrepStmt(GenericWrapper): - """Structure to represent a result from `CMySQLConnection.cmd_stmt_prepare`. - It can be used consistently as a type hint. - - `_mysql_connector.MySQLPrepStmt` isn't available when the C-ext isn't built. - - In this regard, `CmdStmtPrepareResult` acts as a proxy/wrapper entity for a - `_mysql_connector.MySQLPrepStmt` instance. - """ diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/authentication.py b/server/venv/Lib/site-packages/mysql/connector/aio/authentication.py deleted file mode 100644 index 321b26d..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/authentication.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implementing support for MySQL Authentication Plugins.""" - -from __future__ import annotations - -__all__ = ["MySQLAuthenticator"] - -from typing import TYPE_CHECKING, Any, Dict, Optional - -from ..errors import InterfaceError, NotSupportedError, get_exception -from ..protocol import ( - AUTH_SWITCH_STATUS, - DEFAULT_CHARSET_ID, - DEFAULT_MAX_ALLOWED_PACKET, - ERR_STATUS, - EXCHANGE_FURTHER_STATUS, - MFA_STATUS, - OK_STATUS, -) -from ..types import HandShakeType -from .logger import logger -from .plugins import MySQLAuthPlugin, get_auth_plugin -from .protocol import MySQLProtocol - -if TYPE_CHECKING: - from .network import MySQLSocket - - -class MySQLAuthenticator: - """Implements the authentication phase.""" - - def __init__(self) -> None: - """Constructor.""" - self._username: str = "" - self._passwords: Dict[int, str] = {} - self._plugin_config: Dict[str, Any] = {} - self._ssl_enabled: bool = False - self._auth_strategy: Optional[MySQLAuthPlugin] = None - self._auth_plugin_class: Optional[str] = None - - @property - def ssl_enabled(self) -> bool: - """Signals whether or not SSL is enabled.""" - return self._ssl_enabled - - @property - def plugin_config(self) -> Dict[str, Any]: - """Custom arguments that are being provided to the authentication plugin. - - The parameters defined here will override the ones defined in the - auth plugin itself. - - The plugin config is a read-only property - the plugin configuration - provided when invoking `authenticate()` is recorded and can be queried - by accessing this property. - - Returns: - dict: The latest plugin configuration provided when invoking - `authenticate()`. - """ - return self._plugin_config - - def update_plugin_config(self, config: Dict[str, Any]) -> None: - """Update the 'plugin_config' instance variable""" - self._plugin_config.update(config) - - def _switch_auth_strategy( - self, - new_strategy_name: str, - strategy_class: Optional[str] = None, - username: Optional[str] = None, - password_factor: int = 1, - ) -> None: - """Switch the authorization plugin. - - Args: - new_strategy_name: New authorization plugin name to switch to. - strategy_class: New authorization plugin class to switch to - (has higher precedence than the authorization plugin name). - username: Username to be used - if not defined, the username - provided when `authentication()` was invoked is used. - password_factor: Up to three levels of authentication (MFA) are allowed, - hence you can choose the password corresponding to the 1st, - 2nd, or 3rd factor - 1st is the default. - """ - if username is None: - username = self._username - - if strategy_class is None: - strategy_class = self._auth_plugin_class - - logger.debug("Switching to strategy %s", new_strategy_name) - self._auth_strategy = get_auth_plugin( - plugin_name=new_strategy_name, auth_plugin_class=strategy_class - )( - username, - self._passwords.get(password_factor, ""), - ssl_enabled=self.ssl_enabled, - ) - - async def _mfa_n_factor( - self, - sock: MySQLSocket, - pkt: bytes, - ) -> Optional[bytes]: - """Handle MFA (Multi-Factor Authentication) response. - - Up to three levels of authentication (MFA) are allowed. - - Args: - sock: Pointer to the socket connection. - pkt: MFA response. - - Returns: - ok_packet: If last server's response is an OK packet. - None: If last server's response isn't an OK packet and no ERROR was raised. - - Raises: - InterfaceError: If got an invalid N factor. - errors.ErrorTypes: If got an ERROR response. - """ - n_factor = 2 - while pkt[4] == MFA_STATUS: - if n_factor not in self._passwords: - raise InterfaceError( - "Failed Multi Factor Authentication (invalid N factor)" - ) - - new_strategy_name, auth_data = MySQLProtocol.parse_auth_next_factor(pkt) - self._switch_auth_strategy(new_strategy_name, password_factor=n_factor) - logger.debug("MFA %i factor %s", n_factor, self._auth_strategy.name) - - pkt = await self._auth_strategy.auth_switch_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == EXCHANGE_FURTHER_STATUS: - auth_data = MySQLProtocol.parse_auth_more_data(pkt) - pkt = await self._auth_strategy.auth_more_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == OK_STATUS: - logger.debug("MFA completed succesfully") - return pkt - - if pkt[4] == ERR_STATUS: - raise get_exception(pkt) - - n_factor += 1 - - logger.warning("MFA terminated with a no ok packet") - return None - - async def _handle_server_response( - self, - sock: MySQLSocket, - pkt: bytes, - ) -> Optional[bytes]: - """Handle server's response. - - Args: - sock: Pointer to the socket connection. - pkt: Server's response after completing the `HandShakeResponse`. - - Returns: - ok_packet: If last server's response is an OK packet. - None: If last server's response isn't an OK packet and no ERROR was raised. - - Raises: - errors.ErrorTypes: If got an ERROR response. - NotSupportedError: If got Authentication with old (insecure) passwords. - """ - if pkt[4] == AUTH_SWITCH_STATUS and len(pkt) == 5: - raise NotSupportedError( - "Authentication with old (insecure) passwords " - "is not supported. For more information, lookup " - "Password Hashing in the latest MySQL manual" - ) - - if pkt[4] == AUTH_SWITCH_STATUS: - logger.debug("Server's response is an auth switch request") - new_strategy_name, auth_data = MySQLProtocol.parse_auth_switch_request(pkt) - self._switch_auth_strategy(new_strategy_name) - pkt = await self._auth_strategy.auth_switch_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == EXCHANGE_FURTHER_STATUS: - logger.debug("Exchanging further packets") - auth_data = MySQLProtocol.parse_auth_more_data(pkt) - pkt = await self._auth_strategy.auth_more_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == OK_STATUS: - logger.debug("%s completed succesfully", self._auth_strategy.name) - return pkt - - if pkt[4] == MFA_STATUS: - logger.debug("Starting multi-factor authentication") - logger.debug("MFA 1 factor %s", self._auth_strategy.name) - return await self._mfa_n_factor(sock, pkt) - - if pkt[4] == ERR_STATUS: - raise get_exception(pkt) - - return None - - async def authenticate( - self, - sock: MySQLSocket, - handshake: HandShakeType, - username: str = "", - password1: str = "", - password2: str = "", - password3: str = "", - database: Optional[str] = None, - charset: int = DEFAULT_CHARSET_ID, - client_flags: int = 0, - ssl_enabled: bool = False, - max_allowed_packet: int = DEFAULT_MAX_ALLOWED_PACKET, - auth_plugin: Optional[str] = None, - auth_plugin_class: Optional[str] = None, - conn_attrs: Optional[Dict[str, str]] = None, - is_change_user_request: bool = False, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> bytes: - """Perform the authentication phase. - - During re-authentication you must set `is_change_user_request` to True. - - Args: - sock: Pointer to the socket connection. - handshake: Initial handshake. - username: Account's username. - password1: Account's password factor 1. - password2: Account's password factor 2. - password3: Account's password factor 3. - database: Initial database name for the connection. - charset: Client charset (see [1]), only the lower 8-bits. - client_flags: Integer representing client capabilities flags. - ssl_enabled: Boolean indicating whether SSL is enabled, - max_allowed_packet: Maximum packet size. - auth_plugin: Authorization plugin name. - auth_plugin_class: Authorization plugin class (has higher precedence - than the authorization plugin name). - conn_attrs: Connection attributes. - is_change_user_request: Whether is a `change user request` operation or not. - read_timeout: Timeout in seconds upto which the connector should wait for - the server to reply back before raising an ReadTimeoutError. - write_timeout: Timeout in seconds upto which the connector should spend to - send data to the server before raising an WriteTimeoutError. - - Returns: - ok_packet: OK packet. - - Raises: - InterfaceError: If OK packet is NULL. - ReadTimeoutError: If the time taken for the server to reply back exceeds - 'read_timeout' (if set). - WriteTimeoutError: If the time taken to send data packets to the server - exceeds 'write_timeout' (if set). - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_basic_character_set.html#a_protocol_character_set - """ - # update credentials, plugin config and plugin class - self._username = username - self._passwords = {1: password1, 2: password2, 3: password3} - self._ssl_enabled = ssl_enabled - self._auth_plugin_class = auth_plugin_class - - # client's handshake response - response_payload, self._auth_strategy = MySQLProtocol.make_auth( - handshake=handshake, - username=username, - password=password1, - database=database, - charset=charset, - client_flags=client_flags, - max_allowed_packet=max_allowed_packet, - auth_plugin=auth_plugin, - auth_plugin_class=auth_plugin_class, - conn_attrs=conn_attrs, - is_change_user_request=is_change_user_request, - ssl_enabled=self.ssl_enabled, - plugin_config=self.plugin_config, - ) - - # client sends transaction response - send_args = ( - (0, 0, write_timeout) - if is_change_user_request - else (None, None, write_timeout) - ) - await sock.write(response_payload, *send_args) - - # server replies back - pkt = bytes(await sock.read(read_timeout)) - - ok_pkt = await self._handle_server_response(sock, pkt) - if ok_pkt is None: - raise InterfaceError("Got a NULL ok_pkt") from None - - return ok_pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/charsets.py b/server/venv/Lib/site-packages/mysql/connector/aio/charsets.py deleted file mode 100644 index 12e552c..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/charsets.py +++ /dev/null @@ -1,686 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""This module contains the MySQL Server Character Sets.""" - -__all__ = ["Charset", "charsets"] - -from collections import defaultdict -from dataclasses import dataclass -from typing import DefaultDict, Dict, Optional, Sequence, Tuple - -from ..errors import ProgrammingError - - -@dataclass -class Charset: - """Dataclass representing a character set.""" - - charset_id: int - name: str - collation: str - is_default: bool - - -class Charsets: - """MySQL supported character sets and collations class. - - This class holds the list of character sets with their collations supported by - MySQL, making available methods to get character sets by name, collation, or ID. - It uses a sparse matrix or tree-like representation using a dict in a dict to hold - the character set name and collations combinations. - The list is hardcoded, so we avoid a database query when getting the name of the - used character set or collation. - - The call of ``charsets.set_mysql_major_version()`` should be done before using any - of the retrieval methods. - - Usage: - >>> from mysql.connector.aio.charsets import charsets - >>> charsets.set_mysql_major_version(8) - >>> charsets.get_by_name("utf-8") - Charset(charset_id=255, - name='utf8mb4', - collation='utf8mb4_0900_ai_ci', - is_default=True) - """ - - def __init__(self) -> None: - self._charset_id_store: Dict[int, Charset] = {} - self._collation_store: Dict[str, Charset] = {} - self._name_store: DefaultDict[str, Dict[str, Charset]] = defaultdict(dict) - self._mysql_major_version: Optional[int] = None - - def set_mysql_major_version(self, version: int) -> None: - """Set the MySQL major version. - - Sets what tuple should be used based on the MySQL major version to store the - list of character sets and collations. - - Args: - version: The MySQL major version (i.e. 8 or 5) - """ - self._mysql_major_version = version - self._charset_id_store.clear() - self._collation_store.clear() - self._name_store.clear() - - charsets_tuple: Sequence[Tuple[int, str, str, bool]] = None - if version >= 8: - charsets_tuple = MYSQL_8_CHARSETS - elif version == 5: - charsets_tuple = MYSQL_5_CHARSETS - else: - raise ProgrammingError("Invalid MySQL major version") - - for charset_id, name, collation, is_default in charsets_tuple: - charset = Charset(charset_id, name, collation, is_default) - self._charset_id_store[charset_id] = charset - self._collation_store[collation] = charset - self._name_store[name][collation] = charset - - def get_by_id(self, charset_id: int) -> Charset: - """Get character set by ID. - - Args: - charset_id: The charset ID. - - Returns: - Charset: The Charset dataclass instance. - """ - try: - return self._charset_id_store[charset_id] - except KeyError as err: - raise ProgrammingError(f"Character set ID {charset_id} unknown") from err - - def get_by_collation(self, collation: str) -> Charset: - """Get character set by collation. - - Args: - collation: The collation name. - - Returns: - Charset: The Charset dataclass instance. - """ - try: - return self._collation_store[collation] - except KeyError as err: - raise ProgrammingError(f"Collation {collation} unknown") from err - - def get_by_name(self, name: str) -> Charset: - """Get character set by name. - - Args: - name: The charset name. - - Returns: - Charset: The Charset dataclass instance. - """ - try: - if name in ("utf8", "utf-8") and self._mysql_major_version == 8: - name = "utf8mb4" - for charset in self._name_store[name].values(): - if charset.is_default: - return charset - except KeyError as err: - raise ProgrammingError(f"Character set name {name} unknown") from err - raise ProgrammingError(f"No default was found for character set '{name}'") - - def get_by_name_and_collation(self, name: str, collation: str) -> Charset: - """Get character set by name and collation. - - Args: - name: The charset name. - collation: The collation name. - - Returns: - Charset: The Charset dataclass instance. - """ - try: - return self._name_store[name][collation] - except KeyError as err: - raise ProgrammingError( - f"Character set name '{name}' with collation '{collation}' not found" - ) from err - - -MYSQL_8_CHARSETS = ( - (1, "big5", "big5_chinese_ci", True), - (2, "latin2", "latin2_czech_cs", False), - (3, "dec8", "dec8_swedish_ci", True), - (4, "cp850", "cp850_general_ci", True), - (5, "latin1", "latin1_german1_ci", False), - (6, "hp8", "hp8_english_ci", True), - (7, "koi8r", "koi8r_general_ci", True), - (8, "latin1", "latin1_swedish_ci", True), - (9, "latin2", "latin2_general_ci", True), - (10, "swe7", "swe7_swedish_ci", True), - (11, "ascii", "ascii_general_ci", True), - (12, "ujis", "ujis_japanese_ci", True), - (13, "sjis", "sjis_japanese_ci", True), - (14, "cp1251", "cp1251_bulgarian_ci", False), - (15, "latin1", "latin1_danish_ci", False), - (16, "hebrew", "hebrew_general_ci", True), - (18, "tis620", "tis620_thai_ci", True), - (19, "euckr", "euckr_korean_ci", True), - (20, "latin7", "latin7_estonian_cs", False), - (21, "latin2", "latin2_hungarian_ci", False), - (22, "koi8u", "koi8u_general_ci", True), - (23, "cp1251", "cp1251_ukrainian_ci", False), - (24, "gb2312", "gb2312_chinese_ci", True), - (25, "greek", "greek_general_ci", True), - (26, "cp1250", "cp1250_general_ci", True), - (27, "latin2", "latin2_croatian_ci", False), - (28, "gbk", "gbk_chinese_ci", True), - (29, "cp1257", "cp1257_lithuanian_ci", False), - (30, "latin5", "latin5_turkish_ci", True), - (31, "latin1", "latin1_german2_ci", False), - (32, "armscii8", "armscii8_general_ci", True), - (33, "utf8mb3", "utf8mb3_general_ci", True), - (34, "cp1250", "cp1250_czech_cs", False), - (35, "ucs2", "ucs2_general_ci", True), - (36, "cp866", "cp866_general_ci", True), - (37, "keybcs2", "keybcs2_general_ci", True), - (38, "macce", "macce_general_ci", True), - (39, "macroman", "macroman_general_ci", True), - (40, "cp852", "cp852_general_ci", True), - (41, "latin7", "latin7_general_ci", True), - (42, "latin7", "latin7_general_cs", False), - (43, "macce", "macce_bin", False), - (44, "cp1250", "cp1250_croatian_ci", False), - (45, "utf8mb4", "utf8mb4_general_ci", False), - (46, "utf8mb4", "utf8mb4_bin", False), - (47, "latin1", "latin1_bin", False), - (48, "latin1", "latin1_general_ci", False), - (49, "latin1", "latin1_general_cs", False), - (50, "cp1251", "cp1251_bin", False), - (51, "cp1251", "cp1251_general_ci", True), - (52, "cp1251", "cp1251_general_cs", False), - (53, "macroman", "macroman_bin", False), - (54, "utf16", "utf16_general_ci", True), - (55, "utf16", "utf16_bin", False), - (56, "utf16le", "utf16le_general_ci", True), - (57, "cp1256", "cp1256_general_ci", True), - (58, "cp1257", "cp1257_bin", False), - (59, "cp1257", "cp1257_general_ci", True), - (60, "utf32", "utf32_general_ci", True), - (61, "utf32", "utf32_bin", False), - (62, "utf16le", "utf16le_bin", False), - (63, "binary", "binary", True), - (64, "armscii8", "armscii8_bin", False), - (65, "ascii", "ascii_bin", False), - (66, "cp1250", "cp1250_bin", False), - (67, "cp1256", "cp1256_bin", False), - (68, "cp866", "cp866_bin", False), - (69, "dec8", "dec8_bin", False), - (70, "greek", "greek_bin", False), - (71, "hebrew", "hebrew_bin", False), - (72, "hp8", "hp8_bin", False), - (73, "keybcs2", "keybcs2_bin", False), - (74, "koi8r", "koi8r_bin", False), - (75, "koi8u", "koi8u_bin", False), - (76, "utf8mb3", "utf8mb3_tolower_ci", False), - (77, "latin2", "latin2_bin", False), - (78, "latin5", "latin5_bin", False), - (79, "latin7", "latin7_bin", False), - (80, "cp850", "cp850_bin", False), - (81, "cp852", "cp852_bin", False), - (82, "swe7", "swe7_bin", False), - (83, "utf8mb3", "utf8mb3_bin", False), - (84, "big5", "big5_bin", False), - (85, "euckr", "euckr_bin", False), - (86, "gb2312", "gb2312_bin", False), - (87, "gbk", "gbk_bin", False), - (88, "sjis", "sjis_bin", False), - (89, "tis620", "tis620_bin", False), - (90, "ucs2", "ucs2_bin", False), - (91, "ujis", "ujis_bin", False), - (92, "geostd8", "geostd8_general_ci", True), - (93, "geostd8", "geostd8_bin", False), - (94, "latin1", "latin1_spanish_ci", False), - (95, "cp932", "cp932_japanese_ci", True), - (96, "cp932", "cp932_bin", False), - (97, "eucjpms", "eucjpms_japanese_ci", True), - (98, "eucjpms", "eucjpms_bin", False), - (99, "cp1250", "cp1250_polish_ci", False), - (101, "utf16", "utf16_unicode_ci", False), - (102, "utf16", "utf16_icelandic_ci", False), - (103, "utf16", "utf16_latvian_ci", False), - (104, "utf16", "utf16_romanian_ci", False), - (105, "utf16", "utf16_slovenian_ci", False), - (106, "utf16", "utf16_polish_ci", False), - (107, "utf16", "utf16_estonian_ci", False), - (108, "utf16", "utf16_spanish_ci", False), - (109, "utf16", "utf16_swedish_ci", False), - (110, "utf16", "utf16_turkish_ci", False), - (111, "utf16", "utf16_czech_ci", False), - (112, "utf16", "utf16_danish_ci", False), - (113, "utf16", "utf16_lithuanian_ci", False), - (114, "utf16", "utf16_slovak_ci", False), - (115, "utf16", "utf16_spanish2_ci", False), - (116, "utf16", "utf16_roman_ci", False), - (117, "utf16", "utf16_persian_ci", False), - (118, "utf16", "utf16_esperanto_ci", False), - (119, "utf16", "utf16_hungarian_ci", False), - (120, "utf16", "utf16_sinhala_ci", False), - (121, "utf16", "utf16_german2_ci", False), - (122, "utf16", "utf16_croatian_ci", False), - (123, "utf16", "utf16_unicode_520_ci", False), - (124, "utf16", "utf16_vietnamese_ci", False), - (128, "ucs2", "ucs2_unicode_ci", False), - (129, "ucs2", "ucs2_icelandic_ci", False), - (130, "ucs2", "ucs2_latvian_ci", False), - (131, "ucs2", "ucs2_romanian_ci", False), - (132, "ucs2", "ucs2_slovenian_ci", False), - (133, "ucs2", "ucs2_polish_ci", False), - (134, "ucs2", "ucs2_estonian_ci", False), - (135, "ucs2", "ucs2_spanish_ci", False), - (136, "ucs2", "ucs2_swedish_ci", False), - (137, "ucs2", "ucs2_turkish_ci", False), - (138, "ucs2", "ucs2_czech_ci", False), - (139, "ucs2", "ucs2_danish_ci", False), - (140, "ucs2", "ucs2_lithuanian_ci", False), - (141, "ucs2", "ucs2_slovak_ci", False), - (142, "ucs2", "ucs2_spanish2_ci", False), - (143, "ucs2", "ucs2_roman_ci", False), - (144, "ucs2", "ucs2_persian_ci", False), - (145, "ucs2", "ucs2_esperanto_ci", False), - (146, "ucs2", "ucs2_hungarian_ci", False), - (147, "ucs2", "ucs2_sinhala_ci", False), - (148, "ucs2", "ucs2_german2_ci", False), - (149, "ucs2", "ucs2_croatian_ci", False), - (150, "ucs2", "ucs2_unicode_520_ci", False), - (151, "ucs2", "ucs2_vietnamese_ci", False), - (159, "ucs2", "ucs2_general_mysql500_ci", False), - (160, "utf32", "utf32_unicode_ci", False), - (161, "utf32", "utf32_icelandic_ci", False), - (162, "utf32", "utf32_latvian_ci", False), - (163, "utf32", "utf32_romanian_ci", False), - (164, "utf32", "utf32_slovenian_ci", False), - (165, "utf32", "utf32_polish_ci", False), - (166, "utf32", "utf32_estonian_ci", False), - (167, "utf32", "utf32_spanish_ci", False), - (168, "utf32", "utf32_swedish_ci", False), - (169, "utf32", "utf32_turkish_ci", False), - (170, "utf32", "utf32_czech_ci", False), - (171, "utf32", "utf32_danish_ci", False), - (172, "utf32", "utf32_lithuanian_ci", False), - (173, "utf32", "utf32_slovak_ci", False), - (174, "utf32", "utf32_spanish2_ci", False), - (175, "utf32", "utf32_roman_ci", False), - (176, "utf32", "utf32_persian_ci", False), - (177, "utf32", "utf32_esperanto_ci", False), - (178, "utf32", "utf32_hungarian_ci", False), - (179, "utf32", "utf32_sinhala_ci", False), - (180, "utf32", "utf32_german2_ci", False), - (181, "utf32", "utf32_croatian_ci", False), - (182, "utf32", "utf32_unicode_520_ci", False), - (183, "utf32", "utf32_vietnamese_ci", False), - (192, "utf8mb3", "utf8mb3_unicode_ci", False), - (193, "utf8mb3", "utf8mb3_icelandic_ci", False), - (194, "utf8mb3", "utf8mb3_latvian_ci", False), - (195, "utf8mb3", "utf8mb3_romanian_ci", False), - (196, "utf8mb3", "utf8mb3_slovenian_ci", False), - (197, "utf8mb3", "utf8mb3_polish_ci", False), - (198, "utf8mb3", "utf8mb3_estonian_ci", False), - (199, "utf8mb3", "utf8mb3_spanish_ci", False), - (200, "utf8mb3", "utf8mb3_swedish_ci", False), - (201, "utf8mb3", "utf8mb3_turkish_ci", False), - (202, "utf8mb3", "utf8mb3_czech_ci", False), - (203, "utf8mb3", "utf8mb3_danish_ci", False), - (204, "utf8mb3", "utf8mb3_lithuanian_ci", False), - (205, "utf8mb3", "utf8mb3_slovak_ci", False), - (206, "utf8mb3", "utf8mb3_spanish2_ci", False), - (207, "utf8mb3", "utf8mb3_roman_ci", False), - (208, "utf8mb3", "utf8mb3_persian_ci", False), - (209, "utf8mb3", "utf8mb3_esperanto_ci", False), - (210, "utf8mb3", "utf8mb3_hungarian_ci", False), - (211, "utf8mb3", "utf8mb3_sinhala_ci", False), - (212, "utf8mb3", "utf8mb3_german2_ci", False), - (213, "utf8mb3", "utf8mb3_croatian_ci", False), - (214, "utf8mb3", "utf8mb3_unicode_520_ci", False), - (215, "utf8mb3", "utf8mb3_vietnamese_ci", False), - (223, "utf8mb3", "utf8mb3_general_mysql500_ci", False), - (224, "utf8mb4", "utf8mb4_unicode_ci", False), - (225, "utf8mb4", "utf8mb4_icelandic_ci", False), - (226, "utf8mb4", "utf8mb4_latvian_ci", False), - (227, "utf8mb4", "utf8mb4_romanian_ci", False), - (228, "utf8mb4", "utf8mb4_slovenian_ci", False), - (229, "utf8mb4", "utf8mb4_polish_ci", False), - (230, "utf8mb4", "utf8mb4_estonian_ci", False), - (231, "utf8mb4", "utf8mb4_spanish_ci", False), - (232, "utf8mb4", "utf8mb4_swedish_ci", False), - (233, "utf8mb4", "utf8mb4_turkish_ci", False), - (234, "utf8mb4", "utf8mb4_czech_ci", False), - (235, "utf8mb4", "utf8mb4_danish_ci", False), - (236, "utf8mb4", "utf8mb4_lithuanian_ci", False), - (237, "utf8mb4", "utf8mb4_slovak_ci", False), - (238, "utf8mb4", "utf8mb4_spanish2_ci", False), - (239, "utf8mb4", "utf8mb4_roman_ci", False), - (240, "utf8mb4", "utf8mb4_persian_ci", False), - (241, "utf8mb4", "utf8mb4_esperanto_ci", False), - (242, "utf8mb4", "utf8mb4_hungarian_ci", False), - (243, "utf8mb4", "utf8mb4_sinhala_ci", False), - (244, "utf8mb4", "utf8mb4_german2_ci", False), - (245, "utf8mb4", "utf8mb4_croatian_ci", False), - (246, "utf8mb4", "utf8mb4_unicode_520_ci", False), - (247, "utf8mb4", "utf8mb4_vietnamese_ci", False), - (248, "gb18030", "gb18030_chinese_ci", True), - (249, "gb18030", "gb18030_bin", False), - (250, "gb18030", "gb18030_unicode_520_ci", False), - (255, "utf8mb4", "utf8mb4_0900_ai_ci", True), - (256, "utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False), - (257, "utf8mb4", "utf8mb4_is_0900_ai_ci", False), - (258, "utf8mb4", "utf8mb4_lv_0900_ai_ci", False), - (259, "utf8mb4", "utf8mb4_ro_0900_ai_ci", False), - (260, "utf8mb4", "utf8mb4_sl_0900_ai_ci", False), - (261, "utf8mb4", "utf8mb4_pl_0900_ai_ci", False), - (262, "utf8mb4", "utf8mb4_et_0900_ai_ci", False), - (263, "utf8mb4", "utf8mb4_es_0900_ai_ci", False), - (264, "utf8mb4", "utf8mb4_sv_0900_ai_ci", False), - (265, "utf8mb4", "utf8mb4_tr_0900_ai_ci", False), - (266, "utf8mb4", "utf8mb4_cs_0900_ai_ci", False), - (267, "utf8mb4", "utf8mb4_da_0900_ai_ci", False), - (268, "utf8mb4", "utf8mb4_lt_0900_ai_ci", False), - (269, "utf8mb4", "utf8mb4_sk_0900_ai_ci", False), - (270, "utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False), - (271, "utf8mb4", "utf8mb4_la_0900_ai_ci", False), - (273, "utf8mb4", "utf8mb4_eo_0900_ai_ci", False), - (274, "utf8mb4", "utf8mb4_hu_0900_ai_ci", False), - (275, "utf8mb4", "utf8mb4_hr_0900_ai_ci", False), - (277, "utf8mb4", "utf8mb4_vi_0900_ai_ci", False), - (278, "utf8mb4", "utf8mb4_0900_as_cs", False), - (279, "utf8mb4", "utf8mb4_de_pb_0900_as_cs", False), - (280, "utf8mb4", "utf8mb4_is_0900_as_cs", False), - (281, "utf8mb4", "utf8mb4_lv_0900_as_cs", False), - (282, "utf8mb4", "utf8mb4_ro_0900_as_cs", False), - (283, "utf8mb4", "utf8mb4_sl_0900_as_cs", False), - (284, "utf8mb4", "utf8mb4_pl_0900_as_cs", False), - (285, "utf8mb4", "utf8mb4_et_0900_as_cs", False), - (286, "utf8mb4", "utf8mb4_es_0900_as_cs", False), - (287, "utf8mb4", "utf8mb4_sv_0900_as_cs", False), - (288, "utf8mb4", "utf8mb4_tr_0900_as_cs", False), - (289, "utf8mb4", "utf8mb4_cs_0900_as_cs", False), - (290, "utf8mb4", "utf8mb4_da_0900_as_cs", False), - (291, "utf8mb4", "utf8mb4_lt_0900_as_cs", False), - (292, "utf8mb4", "utf8mb4_sk_0900_as_cs", False), - (293, "utf8mb4", "utf8mb4_es_trad_0900_as_cs", False), - (294, "utf8mb4", "utf8mb4_la_0900_as_cs", False), - (296, "utf8mb4", "utf8mb4_eo_0900_as_cs", False), - (297, "utf8mb4", "utf8mb4_hu_0900_as_cs", False), - (298, "utf8mb4", "utf8mb4_hr_0900_as_cs", False), - (300, "utf8mb4", "utf8mb4_vi_0900_as_cs", False), - (303, "utf8mb4", "utf8mb4_ja_0900_as_cs", False), - (304, "utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False), - (305, "utf8mb4", "utf8mb4_0900_as_ci", False), - (306, "utf8mb4", "utf8mb4_ru_0900_ai_ci", False), - (307, "utf8mb4", "utf8mb4_ru_0900_as_cs", False), - (308, "utf8mb4", "utf8mb4_zh_0900_as_cs", False), - (309, "utf8mb4", "utf8mb4_0900_bin", False), - (310, "utf8mb4", "utf8mb4_nb_0900_ai_ci", False), - (311, "utf8mb4", "utf8mb4_nb_0900_as_cs", False), - (312, "utf8mb4", "utf8mb4_nn_0900_ai_ci", False), - (313, "utf8mb4", "utf8mb4_nn_0900_as_cs", False), - (314, "utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False), - (315, "utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False), - (316, "utf8mb4", "utf8mb4_bs_0900_ai_ci", False), - (317, "utf8mb4", "utf8mb4_bs_0900_as_cs", False), - (318, "utf8mb4", "utf8mb4_bg_0900_ai_ci", False), - (319, "utf8mb4", "utf8mb4_bg_0900_as_cs", False), - (320, "utf8mb4", "utf8mb4_gl_0900_ai_ci", False), - (321, "utf8mb4", "utf8mb4_gl_0900_as_cs", False), - (322, "utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False), - (323, "utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False), -) - -MYSQL_5_CHARSETS = ( - (1, "big5", "big5_chinese_ci", True), - (2, "latin2", "latin2_czech_cs", False), - (3, "dec8", "dec8_swedish_ci", True), - (4, "cp850", "cp850_general_ci", True), - (5, "latin1", "latin1_german1_ci", False), - (6, "hp8", "hp8_english_ci", True), - (7, "koi8r", "koi8r_general_ci", True), - (8, "latin1", "latin1_swedish_ci", True), - (9, "latin2", "latin2_general_ci", True), - (10, "swe7", "swe7_swedish_ci", True), - (11, "ascii", "ascii_general_ci", True), - (12, "ujis", "ujis_japanese_ci", True), - (13, "sjis", "sjis_japanese_ci", True), - (14, "cp1251", "cp1251_bulgarian_ci", False), - (15, "latin1", "latin1_danish_ci", False), - (16, "hebrew", "hebrew_general_ci", True), - (18, "tis620", "tis620_thai_ci", True), - (19, "euckr", "euckr_korean_ci", True), - (20, "latin7", "latin7_estonian_cs", False), - (21, "latin2", "latin2_hungarian_ci", False), - (22, "koi8u", "koi8u_general_ci", True), - (23, "cp1251", "cp1251_ukrainian_ci", False), - (24, "gb2312", "gb2312_chinese_ci", True), - (25, "greek", "greek_general_ci", True), - (26, "cp1250", "cp1250_general_ci", True), - (27, "latin2", "latin2_croatian_ci", False), - (28, "gbk", "gbk_chinese_ci", True), - (29, "cp1257", "cp1257_lithuanian_ci", False), - (30, "latin5", "latin5_turkish_ci", True), - (31, "latin1", "latin1_german2_ci", False), - (32, "armscii8", "armscii8_general_ci", True), - (33, "utf8", "utf8_general_ci", True), - (34, "cp1250", "cp1250_czech_cs", False), - (35, "ucs2", "ucs2_general_ci", True), - (36, "cp866", "cp866_general_ci", True), - (37, "keybcs2", "keybcs2_general_ci", True), - (38, "macce", "macce_general_ci", True), - (39, "macroman", "macroman_general_ci", True), - (40, "cp852", "cp852_general_ci", True), - (41, "latin7", "latin7_general_ci", True), - (42, "latin7", "latin7_general_cs", False), - (43, "macce", "macce_bin", False), - (44, "cp1250", "cp1250_croatian_ci", False), - (45, "utf8mb4", "utf8mb4_general_ci", True), - (46, "utf8mb4", "utf8mb4_bin", False), - (47, "latin1", "latin1_bin", False), - (48, "latin1", "latin1_general_ci", False), - (49, "latin1", "latin1_general_cs", False), - (50, "cp1251", "cp1251_bin", False), - (51, "cp1251", "cp1251_general_ci", True), - (52, "cp1251", "cp1251_general_cs", False), - (53, "macroman", "macroman_bin", False), - (54, "utf16", "utf16_general_ci", True), - (55, "utf16", "utf16_bin", False), - (56, "utf16le", "utf16le_general_ci", True), - (57, "cp1256", "cp1256_general_ci", True), - (58, "cp1257", "cp1257_bin", False), - (59, "cp1257", "cp1257_general_ci", True), - (60, "utf32", "utf32_general_ci", True), - (61, "utf32", "utf32_bin", False), - (62, "utf16le", "utf16le_bin", False), - (63, "binary", "binary", True), - (64, "armscii8", "armscii8_bin", False), - (65, "ascii", "ascii_bin", False), - (66, "cp1250", "cp1250_bin", False), - (67, "cp1256", "cp1256_bin", False), - (68, "cp866", "cp866_bin", False), - (69, "dec8", "dec8_bin", False), - (70, "greek", "greek_bin", False), - (71, "hebrew", "hebrew_bin", False), - (72, "hp8", "hp8_bin", False), - (73, "keybcs2", "keybcs2_bin", False), - (74, "koi8r", "koi8r_bin", False), - (75, "koi8u", "koi8u_bin", False), - (77, "latin2", "latin2_bin", False), - (78, "latin5", "latin5_bin", False), - (79, "latin7", "latin7_bin", False), - (80, "cp850", "cp850_bin", False), - (81, "cp852", "cp852_bin", False), - (82, "swe7", "swe7_bin", False), - (83, "utf8", "utf8_bin", False), - (84, "big5", "big5_bin", False), - (85, "euckr", "euckr_bin", False), - (86, "gb2312", "gb2312_bin", False), - (87, "gbk", "gbk_bin", False), - (88, "sjis", "sjis_bin", False), - (89, "tis620", "tis620_bin", False), - (90, "ucs2", "ucs2_bin", False), - (91, "ujis", "ujis_bin", False), - (92, "geostd8", "geostd8_general_ci", True), - (93, "geostd8", "geostd8_bin", False), - (94, "latin1", "latin1_spanish_ci", False), - (95, "cp932", "cp932_japanese_ci", True), - (96, "cp932", "cp932_bin", False), - (97, "eucjpms", "eucjpms_japanese_ci", True), - (98, "eucjpms", "eucjpms_bin", False), - (99, "cp1250", "cp1250_polish_ci", False), - (101, "utf16", "utf16_unicode_ci", False), - (102, "utf16", "utf16_icelandic_ci", False), - (103, "utf16", "utf16_latvian_ci", False), - (104, "utf16", "utf16_romanian_ci", False), - (105, "utf16", "utf16_slovenian_ci", False), - (106, "utf16", "utf16_polish_ci", False), - (107, "utf16", "utf16_estonian_ci", False), - (108, "utf16", "utf16_spanish_ci", False), - (109, "utf16", "utf16_swedish_ci", False), - (110, "utf16", "utf16_turkish_ci", False), - (111, "utf16", "utf16_czech_ci", False), - (112, "utf16", "utf16_danish_ci", False), - (113, "utf16", "utf16_lithuanian_ci", False), - (114, "utf16", "utf16_slovak_ci", False), - (115, "utf16", "utf16_spanish2_ci", False), - (116, "utf16", "utf16_roman_ci", False), - (117, "utf16", "utf16_persian_ci", False), - (118, "utf16", "utf16_esperanto_ci", False), - (119, "utf16", "utf16_hungarian_ci", False), - (120, "utf16", "utf16_sinhala_ci", False), - (121, "utf16", "utf16_german2_ci", False), - (122, "utf16", "utf16_croatian_ci", False), - (123, "utf16", "utf16_unicode_520_ci", False), - (124, "utf16", "utf16_vietnamese_ci", False), - (128, "ucs2", "ucs2_unicode_ci", False), - (129, "ucs2", "ucs2_icelandic_ci", False), - (130, "ucs2", "ucs2_latvian_ci", False), - (131, "ucs2", "ucs2_romanian_ci", False), - (132, "ucs2", "ucs2_slovenian_ci", False), - (133, "ucs2", "ucs2_polish_ci", False), - (134, "ucs2", "ucs2_estonian_ci", False), - (135, "ucs2", "ucs2_spanish_ci", False), - (136, "ucs2", "ucs2_swedish_ci", False), - (137, "ucs2", "ucs2_turkish_ci", False), - (138, "ucs2", "ucs2_czech_ci", False), - (139, "ucs2", "ucs2_danish_ci", False), - (140, "ucs2", "ucs2_lithuanian_ci", False), - (141, "ucs2", "ucs2_slovak_ci", False), - (142, "ucs2", "ucs2_spanish2_ci", False), - (143, "ucs2", "ucs2_roman_ci", False), - (144, "ucs2", "ucs2_persian_ci", False), - (145, "ucs2", "ucs2_esperanto_ci", False), - (146, "ucs2", "ucs2_hungarian_ci", False), - (147, "ucs2", "ucs2_sinhala_ci", False), - (148, "ucs2", "ucs2_german2_ci", False), - (149, "ucs2", "ucs2_croatian_ci", False), - (150, "ucs2", "ucs2_unicode_520_ci", False), - (151, "ucs2", "ucs2_vietnamese_ci", False), - (159, "ucs2", "ucs2_general_mysql500_ci", False), - (160, "utf32", "utf32_unicode_ci", False), - (161, "utf32", "utf32_icelandic_ci", False), - (162, "utf32", "utf32_latvian_ci", False), - (163, "utf32", "utf32_romanian_ci", False), - (164, "utf32", "utf32_slovenian_ci", False), - (165, "utf32", "utf32_polish_ci", False), - (166, "utf32", "utf32_estonian_ci", False), - (167, "utf32", "utf32_spanish_ci", False), - (168, "utf32", "utf32_swedish_ci", False), - (169, "utf32", "utf32_turkish_ci", False), - (170, "utf32", "utf32_czech_ci", False), - (171, "utf32", "utf32_danish_ci", False), - (172, "utf32", "utf32_lithuanian_ci", False), - (173, "utf32", "utf32_slovak_ci", False), - (174, "utf32", "utf32_spanish2_ci", False), - (175, "utf32", "utf32_roman_ci", False), - (176, "utf32", "utf32_persian_ci", False), - (177, "utf32", "utf32_esperanto_ci", False), - (178, "utf32", "utf32_hungarian_ci", False), - (179, "utf32", "utf32_sinhala_ci", False), - (180, "utf32", "utf32_german2_ci", False), - (181, "utf32", "utf32_croatian_ci", False), - (182, "utf32", "utf32_unicode_520_ci", False), - (183, "utf32", "utf32_vietnamese_ci", False), - (192, "utf8", "utf8_unicode_ci", False), - (193, "utf8", "utf8_icelandic_ci", False), - (194, "utf8", "utf8_latvian_ci", False), - (195, "utf8", "utf8_romanian_ci", False), - (196, "utf8", "utf8_slovenian_ci", False), - (197, "utf8", "utf8_polish_ci", False), - (198, "utf8", "utf8_estonian_ci", False), - (199, "utf8", "utf8_spanish_ci", False), - (200, "utf8", "utf8_swedish_ci", False), - (201, "utf8", "utf8_turkish_ci", False), - (202, "utf8", "utf8_czech_ci", False), - (203, "utf8", "utf8_danish_ci", False), - (204, "utf8", "utf8_lithuanian_ci", False), - (205, "utf8", "utf8_slovak_ci", False), - (206, "utf8", "utf8_spanish2_ci", False), - (207, "utf8", "utf8_roman_ci", False), - (208, "utf8", "utf8_persian_ci", False), - (209, "utf8", "utf8_esperanto_ci", False), - (210, "utf8", "utf8_hungarian_ci", False), - (211, "utf8", "utf8_sinhala_ci", False), - (212, "utf8", "utf8_german2_ci", False), - (213, "utf8", "utf8_croatian_ci", False), - (214, "utf8", "utf8_unicode_520_ci", False), - (215, "utf8", "utf8_vietnamese_ci", False), - (223, "utf8", "utf8_general_mysql500_ci", False), - (224, "utf8mb4", "utf8mb4_unicode_ci", False), - (225, "utf8mb4", "utf8mb4_icelandic_ci", False), - (226, "utf8mb4", "utf8mb4_latvian_ci", False), - (227, "utf8mb4", "utf8mb4_romanian_ci", False), - (228, "utf8mb4", "utf8mb4_slovenian_ci", False), - (229, "utf8mb4", "utf8mb4_polish_ci", False), - (230, "utf8mb4", "utf8mb4_estonian_ci", False), - (231, "utf8mb4", "utf8mb4_spanish_ci", False), - (232, "utf8mb4", "utf8mb4_swedish_ci", False), - (233, "utf8mb4", "utf8mb4_turkish_ci", False), - (234, "utf8mb4", "utf8mb4_czech_ci", False), - (235, "utf8mb4", "utf8mb4_danish_ci", False), - (236, "utf8mb4", "utf8mb4_lithuanian_ci", False), - (237, "utf8mb4", "utf8mb4_slovak_ci", False), - (238, "utf8mb4", "utf8mb4_spanish2_ci", False), - (239, "utf8mb4", "utf8mb4_roman_ci", False), - (240, "utf8mb4", "utf8mb4_persian_ci", False), - (241, "utf8mb4", "utf8mb4_esperanto_ci", False), - (242, "utf8mb4", "utf8mb4_hungarian_ci", False), - (243, "utf8mb4", "utf8mb4_sinhala_ci", False), - (244, "utf8mb4", "utf8mb4_german2_ci", False), - (245, "utf8mb4", "utf8mb4_croatian_ci", False), - (246, "utf8mb4", "utf8mb4_unicode_520_ci", False), - (247, "utf8mb4", "utf8mb4_vietnamese_ci", False), - (248, "gb18030", "gb18030_chinese_ci", True), - (249, "gb18030", "gb18030_bin", False), - (250, "gb18030", "gb18030_unicode_520_ci", False), -) - -charsets = Charsets() diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/connection.py b/server/venv/Lib/site-packages/mysql/connector/aio/connection.py deleted file mode 100644 index eaac247..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/connection.py +++ /dev/null @@ -1,1580 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="arg-type,operator,attr-defined,assignment" - -"""Implemention of the communication with MySQL servers in pure Python.""" - -__all__ = ["MySQLConnection"] - -import asyncio -import contextlib -import datetime -import getpass -import os -import socket -import struct -import warnings - -from decimal import Decimal -from io import IOBase -from typing import ( - Any, - AsyncGenerator, - BinaryIO, - Dict, - List, - Mapping, - Optional, - Sequence, - Tuple, - Type, - Union, -) - -from .. import version -from .._scripting import get_local_infile_filenames -from ..constants import ( - ClientFlag, - FieldType, - RefreshOption, - ServerCmd, - ServerFlag, - flag_is_set, -) -from ..errors import ( - ConnectionTimeoutError, - DatabaseError, - Error, - InterfaceError, - InternalError, - NotSupportedError, - OperationalError, - ProgrammingError, - ReadTimeoutError, - WriteTimeoutError, - get_exception, -) -from ..protocol import EOF_STATUS, ERR_STATUS, LOCAL_INFILE_STATUS, OK_STATUS -from ..types import ( - BinaryProtocolType, - DescriptionType, - EofPacketType, - OkPacketType, - ResultType, - RowType, - StatsPacketType, - StrOrBytes, -) -from ..utils import ( - get_platform, - int1store, - int4store, - lc_int, - warn_ciphersuites_deprecated, - warn_tls_version_deprecated, -) -from ._decorating import cmd_refresh_verify_options, handle_read_write_timeout -from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract, ServerInfo -from .charsets import charsets -from .cursor import ( - MySQLCursor, - MySQLCursorBuffered, - MySQLCursorBufferedDict, - MySQLCursorBufferedRaw, - MySQLCursorDict, - MySQLCursorPrepared, - MySQLCursorPreparedDict, - MySQLCursorRaw, -) -from .logger import logger -from .network import MySQLTcpSocket, MySQLUnixSocket - - -class MySQLConnection(MySQLConnectionAbstract): - """Implementation of the pure Python MySQL connection.""" - - _mfa_nfactor: int = 1 - - @property - def connection_id(self) -> Optional[int]: - """MySQL connection ID.""" - if self._handshake: - return self._handshake.get("server_threadid") # type: ignore[return-value] - return None - - async def connect(self) -> None: - try: - if self._unix_socket and os.name == "posix": - self._socket = MySQLUnixSocket(unix_socket=self._unix_socket) - else: - self._socket = MySQLTcpSocket(host=self._host, port=self._port) - await asyncio.wait_for( - self._socket.open_connection(), self._connection_timeout - ) - await self._do_handshake() - await self._do_auth() - except Exception as err: - await self._socket.close_connection() - if isinstance(err, (asyncio.CancelledError, asyncio.TimeoutError)): - raise ConnectionTimeoutError( - errno=2003, - msg=f"Can't connect to MySQL server on {self._host}:{self._port} (timed out)", - ) from err - raise - - if self._client_flags & ClientFlag.COMPRESS: - # Update the network layer accordingly - self._socket.switch_to_compressed_mode() - - # Set converter class - try: - self.converter_class = self._converter_class - except TypeError as err: - raise AttributeError( - "Converter classA should be a subclass of " - "conversion.MySQLConverterBase" - ) from err - - # Post connection settings - await self._post_connection() - - # pylint: disable=protected-access - if ( - not self._ssl_disabled - and hasattr(self._socket._writer, "get_extra_info") - and callable(self._socket._writer.get_extra_info) - ): - # Raise a deprecation warning if deprecated TLS version - # or cipher is being used. - - # `get_extra_info("cipher")` returns a three-value tuple containing the name - # of the cipher being used, the version of the SSL protocol - # that defines its use, and the number of secret bits being used. - cipher, tls_version, _ = self._socket._writer.get_extra_info("cipher") - warn_tls_version_deprecated(tls_version) - warn_ciphersuites_deprecated(cipher, tls_version) - - def _add_default_conn_attrs(self) -> None: - """Add the default connection attributes.""" - platform = get_platform() - license_chunks = version.LICENSE.split(" ") - if license_chunks[0] == "GPLv2": - client_license = "GPL-2.0" - else: - client_license = "Commercial" - default_conn_attrs = { - "_pid": str(os.getpid()), - "_platform": platform["arch"], - "_source_host": socket.gethostname(), - "_client_name": "mysql-connector-python", - "_client_license": client_license, - "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]), - "_os": platform["version"], - } - - self._connection_attrs.update(default_conn_attrs) - - async def _execute_query(self, query: str) -> ResultType: - """Execute a query. - - This method simply calls cmd_query() after checking for unread result. If there - are still unread result, an InterfaceError is raised. Otherwise whatever - cmd_query() returns is returned. - """ - await self.handle_unread_result() - return await self.cmd_query(query) - - async def _do_handshake(self) -> None: - """Get the handshake from the MySQL server.""" - packet = await self._socket.read() - logger.debug("Protocol::Handshake packet: %s", packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - - self._handshake = self._protocol.parse_handshake(packet) - - self._server_info = ServerInfo( - protocol=self._handshake["protocol"], - version=self._handshake["server_version_original"], - thread_id=self._handshake["server_threadid"], - charset=self._handshake["charset"], - status_flags=self._handshake["server_status"], - auth_plugin=self._handshake["auth_plugin"], - auth_data=self._handshake["auth_data"], - capabilities=self._handshake["capabilities"], - ) - - # Set the charsets for the correspondent server major version - charsets.set_mysql_major_version(self._server_info.version_tuple[0]) - logger.debug("Protocol::Handshake charset: %s", self._server_info.charset) - - # Set charset if provided else use the server default - if self._charset_name and self._charset_collation: - self._charset = charsets.get_by_name_and_collation( - self._charset_name, self._charset_collation - ) - elif self._charset_name: - self._charset = charsets.get_by_name(self._charset_name) - elif self._charset_collation: - self._charset = charsets.get_by_collation(self._charset_collation) - - if not self._handshake["capabilities"] & ClientFlag.SSL: - if not self.is_secure: - if self._auth_plugin == "mysql_clear_password": - raise InterfaceError( - "Clear password authentication is not supported over " - "insecure channels" - ) - if self._auth_plugin == "authentication_openid_connect_client": - raise InterfaceError( - "OpenID Connect authentication is not supported over " - "insecure channels" - ) - if self._ssl_verify_cert: - raise InterfaceError( - "SSL is required but the server doesn't support it", - errno=2026, - ) - self._client_flags &= ~ClientFlag.SSL - elif not self._ssl_disabled: - self._client_flags |= ClientFlag.SSL - - if self._handshake["capabilities"] & ClientFlag.PLUGIN_AUTH: - self.client_flags = [ClientFlag.PLUGIN_AUTH] - - if self._handshake["capabilities"] & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - self._server_info.query_attrs_is_supported = True - self.client_flags = [ClientFlag.CLIENT_QUERY_ATTRIBUTES] - - if self._handshake["capabilities"] & ClientFlag.MULTI_FACTOR_AUTHENTICATION: - self.client_flags = [ClientFlag.MULTI_FACTOR_AUTHENTICATION] - - async def _do_auth(self) -> None: - """Authenticate with the MySQL server. - - Authentication happens in two parts. We first send a response to the - handshake. The MySQL server will then send either an AuthSwitchRequest - or an error packet. - - Raises NotSupportedError when we get the old, insecure password - reply back. Raises any error coming from MySQL. - """ - if ( # pylint: disable=too-many-boolean-expressions - self._auth_plugin - and self._auth_plugin.startswith("authentication_oci") - or ( - self._auth_plugin - and self._auth_plugin.startswith("authentication_kerberos") - and os.name == "nt" - ) - ) and not self._user: - self._user = getpass.getuser() - logger.debug( - "MySQL user is empty, OS user: %s will be used for %s", - self._user, - self._auth_plugin, - ) - - password = ( - self._password1 - if self._password1 and self._password != self._password1 - else self._password - ) - - self._ssl_active = False - if not self._ssl_disabled and (self._client_flags & ClientFlag.SSL): - ssl_context = self._socket.build_ssl_context( - ssl_ca=self._ssl_ca, - ssl_cert=self._ssl_cert, - ssl_key=self._ssl_key, - ssl_verify_cert=self._ssl_verify_cert, - ssl_verify_identity=self._ssl_verify_identity, - tls_versions=self._tls_versions, - tls_cipher_suites=self._tls_ciphersuites, - ) - packet: bytes = self._protocol.make_auth_ssl( - charset=self._charset.charset_id, client_flags=self._client_flags - ) - await self._socket.write(packet) - await self._socket.switch_to_ssl(ssl_context) - self._ssl_active = True - - # Add the custom configurations required by specific auth plugins - self._authenticator.update_plugin_config( - config={ - "krb_service_principal": self._krb_service_principal, - "oci_config_file": self._oci_config_file, - "oci_config_profile": self._oci_config_profile, - "webauthn_callback": self._webauthn_callback, - "openid_token_file": self._openid_token_file, - } - ) - - ok_pkt = await self._authenticator.authenticate( - sock=self._socket, - handshake=self._handshake, - username=self._user, - password1=password, - password2=self._password2, - password3=self._password3, - database=self._database, - charset=self._charset.charset_id, - client_flags=self._client_flags, - ssl_enabled=self._ssl_active, - auth_plugin=self._auth_plugin, - auth_plugin_class=self._auth_plugin_class, - conn_attrs=self._connection_attrs, - ) - self._handle_ok(ok_pkt) - - if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and self._database: - await self.cmd_init_db(self._database) - - def _handle_ok(self, packet: bytes) -> OkPacketType: - """Handle a MySQL OK packet. - - This method handles a MySQL OK packet. When the packet is found to be an Error - packet, an error will be raised. If the packet is neither an OK or an Error - packet, InterfaceError will be raised. - """ - if packet[4] == OK_STATUS: - ok_pkt = self._protocol.parse_ok(packet) - self._handle_server_status(ok_pkt["status_flag"]) - return ok_pkt - if packet[4] == ERR_STATUS: - raise get_exception(packet) - raise InterfaceError("Expected OK packet") - - def _handle_server_status(self, flags: int) -> None: - """Handle the server flags found in MySQL packets. - - This method handles the server flags send by MySQL OK and EOF packets. - It, for example, checks whether there exists more result sets or whether there - is an ongoing transaction. - """ - self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags) - self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags) - - def _handle_eof(self, packet: bytes) -> EofPacketType: - """Handle a MySQL EOF packet. - - This method handles a MySQL EOF packet. When the packet is found to be an Error - packet, an error will be raised. If the packet is neither and OK or an Error - packet, InterfaceError will be raised. - """ - if packet[4] == EOF_STATUS: - eof = self._protocol.parse_eof(packet) - self._handle_server_status(eof["status_flag"]) - return eof - if packet[4] == ERR_STATUS: - raise get_exception(packet) - raise InterfaceError("Expected EOF packet") - - @handle_read_write_timeout() - async def _handle_load_data_infile( - self, - filename: str, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> OkPacketType: - """Handle a LOAD DATA INFILE LOCAL request.""" - if self._local_infile_filenames is None: - self._local_infile_filenames = get_local_infile_filenames(self._query) - if not self._local_infile_filenames: - raise InterfaceError( - "No `LOCAL INFILE` statements found in the client's request. " - "Check your request includes valid `LOCAL INFILE` statements." - ) - elif not self._local_infile_filenames: - raise InterfaceError( - "Got more `LOCAL INFILE` responses than number of `LOCAL INFILE` " - "statements specified in the client's request. Please, report this " - "issue to the development team." - ) - - file_name = os.path.abspath(filename) - file_name_from_request = os.path.abspath(self._local_infile_filenames.popleft()) - - # Verify the file location specified by `filename` from client's request exists - if not os.path.exists(file_name_from_request): - raise InterfaceError( - f"Location specified by filename {file_name_from_request} " - "from client's request does not exist." - ) - - # Verify the file location specified by `filename` from server's response exists - if not os.path.exists(file_name): - raise InterfaceError( - f"Location specified by filename {file_name} from server's " - "response does not exist." - ) - - # Verify the `filename` specified by server's response matches the one from - # the client's request. - try: - if not os.path.samefile(file_name, file_name_from_request): - raise InterfaceError( - f"Filename {file_name} from the server's response is not the same " - f"as filename {file_name_from_request} from the " - "client's request." - ) - except OSError as err: - raise InterfaceError from err - - if os.path.islink(file_name): - raise OperationalError("Use of symbolic link is not allowed") - if not self._allow_local_infile and not self._allow_local_infile_in_path: - raise DatabaseError( - "LOAD DATA LOCAL INFILE file request rejected due to " - "restrictions on access." - ) - if not self._allow_local_infile and self._allow_local_infile_in_path: - # Validate filename is inside of allow_local_infile_in_path path. - infile_path = os.path.abspath(self._allow_local_infile_in_path) - c_path = None - try: - c_path = os.path.commonpath([infile_path, file_name]) - except ValueError as err: - err_msg = ( - "{} while loading file `{}` and path `{}` given" - " in allow_local_infile_in_path" - ) - raise InterfaceError( - err_msg.format(str(err), file_name, infile_path) - ) from err - - if c_path != infile_path: - err_msg = ( - "The file `{}` is not found in the given " - "allow_local_infile_in_path {}" - ) - raise DatabaseError(err_msg.format(file_name, infile_path)) - - try: - data_file = open(file_name, "rb") # pylint: disable=consider-using-with - return self._handle_ok( - await self._send_data(data_file, True, read_timeout, write_timeout) - ) - except IOError: - # Send a empty packet to cancel the operation - try: - await self._socket.write( - b"", write_timeout=write_timeout or self._write_timeout - ) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - raise InterfaceError(f"File '{file_name}' could not be read") from None - finally: - try: - data_file.close() - except (IOError, NameError): - pass - - @handle_read_write_timeout() - async def _handle_result( - self, - packet: bytes, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> ResultType: - """Handle a MySQL Result. - - This method handles a MySQL result, for example, after sending the query - command. OK and EOF packets will be handled and returned. - If the packet is an Error packet, an Error-exception will be raised. - - The dictionary returned of: - - columns: column information - - eof: the EOF-packet information - """ - if not packet or len(packet) < 4: - raise InterfaceError("Empty response") - if packet[4] == OK_STATUS: - return self._handle_ok(packet) - if packet[4] == LOCAL_INFILE_STATUS: - filename = packet[5:].decode() - return await self._handle_load_data_infile( - filename, read_timeout, write_timeout - ) - if packet[4] == EOF_STATUS: - return self._handle_eof(packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - - # We have a text result set - column_count = self._protocol.parse_column_count(packet) - if not column_count or not isinstance(column_count, int): - raise InterfaceError("Illegal result set") - - self._columns_desc = [ - None, - ] * column_count - for i in range(0, column_count): - self._columns_desc[i] = self._protocol.parse_column( - await self._socket.read(read_timeout or self._read_timeout), - self.python_charset, - ) - - eof = self._handle_eof( - await self._socket.read(read_timeout or self._read_timeout) - ) - self.unread_result = True - return {"columns": self._columns_desc, "eof": eof} - - def _handle_binary_ok(self, packet: bytes) -> Dict[str, int]: - """Handle a MySQL Binary Protocol OK packet - - This method handles a MySQL Binary Protocol OK packet. When the - packet is found to be an Error packet, an error will be raised. If - the packet is neither an OK or an Error packet, InterfaceError - will be raised. - - Returns a dict() - """ - if packet[4] == OK_STATUS: - return self._protocol.parse_binary_prepare_ok(packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - raise InterfaceError("Expected Binary OK packet") - - @handle_read_write_timeout() - async def _handle_binary_result( - self, packet: bytes - ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]: - """Handle a MySQL Result - - This method handles a MySQL result, for example, after sending the - query command. OK and EOF packets will be handled and returned. If - the packet is an Error packet, an Error exception will be raised. - - The tuple returned by this method consist of: - - the number of columns in the result, - - a list of tuples with information about the columns, - - the EOF packet information as a dictionary. - - Returns tuple() or dict() - """ - if not packet or len(packet) < 4: - raise InterfaceError("Empty response") - if packet[4] == OK_STATUS: - return self._handle_ok(packet) - if packet[4] == EOF_STATUS: - return self._handle_eof(packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - - # We have a binary result set - column_count = self._protocol.parse_column_count(packet) - if not column_count or not isinstance(column_count, int): - raise InterfaceError("Illegal result set.") - - columns: List[DescriptionType] = [None] * column_count - for i in range(0, column_count): - columns[i] = self._protocol.parse_column( - await self._socket.read(self._read_timeout), self.python_charset - ) - - eof = self._handle_eof(await self._socket.read(self._read_timeout)) - return (column_count, columns, eof) - - @handle_read_write_timeout() - async def _send_cmd( - self, - command: int, - argument: Optional[bytes] = None, - packet_number: int = 0, - packet: Optional[bytes] = None, - expect_response: bool = True, - compressed_packet_number: int = 0, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> Optional[bytearray]: - """Send a command to the MySQL server. - - This method sends a command with an optional argument. - If packet is not None, it will be sent and the argument will be ignored. - - The packet_number is optional and should usually not be used. - - Some commands might not result in the MySQL server returning a response. If a - command does not return anything, you should set expect_response to False. - The _send_cmd method will then return None instead of a MySQL packet. - """ - await self.handle_unread_result() - - try: - await self._socket.write( - self._protocol.make_command(command, packet or argument), - packet_number, - compressed_packet_number, - write_timeout or self._write_timeout, - ) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - if not expect_response: - return None - return await self._socket.read(read_timeout or self._read_timeout) - - @handle_read_write_timeout() - async def _send_data( - self, - data_file: BinaryIO, - send_empty_packet: bool = False, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> bytearray: - """Send data to the MySQL server - - This method accepts a file-like object and sends its data - as is to the MySQL server. If the send_empty_packet is - True, it will send an extra empty package (for example - when using LOAD LOCAL DATA INFILE). - - Returns a MySQL packet. - """ - await self.handle_unread_result() - - if not hasattr(data_file, "read"): - raise ValueError("expecting a file-like object") - - chunk_size = 131072 # 128 KB - try: - buf = data_file.read(chunk_size - 16) - while buf: - await self._socket.write( - buf, write_timeout=write_timeout or self._write_timeout - ) - buf = data_file.read(chunk_size - 16) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - if send_empty_packet: - try: - await self._socket.write(b"", write_timeout or self._write_timeout) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - res = await self._socket.read(read_timeout or self._read_timeout) - return res - - def is_socket_connected(self) -> bool: - """Reports whether the socket is connected. - - Instead of ping the server like ``is_connected()``, it only checks if the - socket connection flag is set. - """ - return bool(self._socket and self._socket.is_connected()) - - async def is_connected(self) -> bool: - """Reports whether the connection to MySQL Server is available. - - This method checks whether the connection to MySQL is available. - It is similar to ``ping()``, but unlike the ``ping()`` method, either `True` - or `False` is returned and no exception is raised. - """ - try: - await self.cmd_ping() - except Error: - return False - return True - - async def reset_session( - self, - user_variables: Optional[Dict[str, Any]] = None, - session_variables: Optional[Dict[str, Any]] = None, - ) -> None: - """Clears the current active session - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - This method takes two arguments user_variables and session_variables - which are dictionaries. - - Raises OperationalError if not connected, InternalError if there are - unread results and InterfaceError on errors. - """ - if not await self.is_connected(): - raise OperationalError("MySQL Connection not available.") - - if not await self.cmd_reset_connection(): - try: - await self.cmd_change_user( - self._user, - self._password, - self._database, - self._charset.charset_id, - self._password1, - self._password2, - self._password3, - self._oci_config_file, - self._oci_config_profile, - self._openid_token_file, - ) - except ProgrammingError: - await self.reconnect() - - cur = await self.cursor() - if user_variables: - for key, value in user_variables.items(): - await cur.execute(f"SET @`{key}` = %s", (value,)) - if session_variables: - for key, value in session_variables.items(): - await cur.execute(f"SET SESSION `{key}` = %s", (value,)) - await cur.close() - - async def ping( - self, reconnect: bool = False, attempts: int = 1, delay: int = 0 - ) -> None: - """Check availability of the MySQL server. - - When reconnect is set to `True`, one or more attempts are made to try to - reconnect to the MySQL server using the ``reconnect()`` method. - - ``delay`` is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. Use the - ``is_connected()`` method if you just want to check the connection without - raising an error. - - Raises: - InterfaceError: On errors. - """ - try: - await self.cmd_ping() - except Error as err: - if reconnect: - await self.reconnect(attempts=attempts, delay=delay) - else: - raise InterfaceError("Connection to MySQL is not available") from err - - async def shutdown(self) -> None: - """Shut down connection to MySQL Server. - - This method closes the socket. It raises no exceptions. - - Unlike `disconnect()`, `shutdown()` closes the client connection without - attempting to send a QUIT command to the server first. Thus, it will not - block if the connection is disrupted for some reason such as network failure. - """ - if not self._socket: - return - - try: - await self._socket.close_connection() - except Exception: # pylint: disable=broad-exception-caught - pass # Getting an exception would mean we are disconnected. - - async def close(self) -> None: - with contextlib.suppress(Error): - for cursor in tuple(self._cursors): - await cursor.close() - self._cursors.clear() - - if self._socket and self._socket.is_connected(): - await self.cmd_quit() - - if self._socket: - await self._socket.close_connection() - self._socket = None - - disconnect = close - - async def cursor( - self, - buffered: Optional[bool] = None, - raw: Optional[bool] = None, - prepared: Optional[bool] = None, - cursor_class: Optional[Type[MySQLCursorAbstract]] = None, - dictionary: Optional[bool] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> MySQLCursor: - """Instantiate and return a cursor. - - By default, MySQLCursor is returned. Depending on the options while - connecting, a buffered and/or raw cursor is instantiated instead. - Also depending upon the cursor options, rows can be returned as a dictionary - or a tuple. - - It is possible to also give a custom cursor through the cursor_class - parameter, but it needs to be a subclass of - mysql.connector.aio.abstracts.MySQLCursorAbstract. - - Raises: - ProgrammingError: When cursor_class is not a subclass of - MySQLCursor. - ValueError: When cursor is not available. - InterfaceError: When read or write timeout is not a positive integer. - """ - if not self._socket or not self._socket.is_connected(): - raise OperationalError("MySQL Connection not available") - - if read_timeout is not None and ( - not isinstance(read_timeout, int) or read_timeout < 0 - ): - raise InterfaceError("Option read_timeout must be a positive integer") - if write_timeout is not None and ( - not isinstance(write_timeout, int) or write_timeout < 0 - ): - raise InterfaceError("Option write_timeout must be a positive integer") - - await self.handle_unread_result() - - if cursor_class is not None: - if not issubclass(cursor_class, MySQLCursor): - raise ProgrammingError( - "Cursor class needs be to subclass of MySQLCursorAbstract" - ) - return (cursor_class)(self, read_timeout, write_timeout) - - buffered = buffered if buffered is not None else self._buffered - raw = raw if raw is not None else self._raw - - cursor_type = 0 - if buffered is True: - cursor_type |= 1 - if raw is True: - cursor_type |= 2 - if dictionary is True: - cursor_type |= 4 - if prepared is True: - cursor_type |= 16 - - types = { - 0: MySQLCursor, - 1: MySQLCursorBuffered, - 2: MySQLCursorRaw, - 3: MySQLCursorBufferedRaw, - 4: MySQLCursorDict, - 5: MySQLCursorBufferedDict, - 16: MySQLCursorPrepared, - 20: MySQLCursorPreparedDict, - } - try: - return (types[cursor_type])(self, read_timeout, write_timeout) - except KeyError: - args = ("buffered", "raw", "dictionary", "prepared") - criteria = ", ".join( - [args[i] for i in range(4) if cursor_type & (1 << i) != 0] - ) - raise ValueError( - f"Cursor not available with given criteria: {criteria}" - ) from None - - @handle_read_write_timeout() - async def get_row( - self, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - **kwargs: Any, - ) -> Tuple[Optional[RowType], Optional[EofPacketType]]: - """Get the next rows returned by the MySQL server. - - This method gets one row from the result set after sending, for example, the - query command. The result is a tuple consisting of the row and the EOF packet. - If no row was available in the result set, the row data will be None. - """ - rows, eof = await self.get_rows( - count=1, - binary=binary, - columns=columns, - raw=raw, - **kwargs, - ) - if rows: - return (rows[0], eof) - return (None, eof) - - @handle_read_write_timeout() - async def get_rows( - self, - count: Optional[int] = None, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Any = None, - **kwargs: Any, - ) -> Tuple[List[RowType], Optional[EofPacketType]]: - """Get all rows returned by the MySQL server. - - This method gets all rows returned by the MySQL server after sending, for - example, the query command. The result is a tuple consisting of a list of rows - and the EOF packet. - """ - if raw is None: - raw = self._raw - - if not self.unread_result: - raise InternalError("No result set available") - - rows = ([], None) # type: ignore[var-annotated] - try: - read_timeout = kwargs.get("read_timeout", None) - if binary: - charset = self.charset - if charset == "utf8mb4": - charset = "utf8" - rows = await self._protocol.read_binary_result( - self._socket, - columns, - count, - charset, - read_timeout or self._read_timeout, - ) - else: - rows = await self._protocol.read_text_result( - self._socket, - self._server_info.version, - count, - read_timeout or self._read_timeout, - ) - except Error as err: - self.unread_result = False - raise err - - rows, eof_p = rows - if ( - not (binary or raw) - and self._columns_desc is not None - and rows - and hasattr(self, "converter") - ): - row_to_python = self.converter.row_to_python - rows = [row_to_python(row, self._columns_desc) for row in rows] - - if eof_p is not None: - self._handle_server_status( - eof_p["status_flag"] - if "status_flag" in eof_p - else eof_p["server_status"] - ) - self.unread_result = False - - return rows, eof_p - - async def commit(self) -> None: - """Commit current transaction.""" - await self._execute_query("COMMIT") - - async def rollback(self) -> None: - """Rollback current transaction.""" - if self.unread_result: - await self.get_rows() - await self._execute_query("ROLLBACK") - - async def cmd_reset_connection(self) -> bool: - """Resets the session state without re-authenticating. - - Reset command only works on MySQL server 5.7.3 or later. - The result is True for a successful reset otherwise False. - """ - try: - self._handle_ok(await self._send_cmd(ServerCmd.RESET_CONNECTION)) - await self._post_connection() - return True - except (NotSupportedError, OperationalError): - return False - - async def cmd_init_db(self, database: str) -> OkPacketType: - """Change the current database. - - This method changes the current (default) database by sending the INIT_DB - command. The result is a dictionary containing the OK packet infawaitormation. - """ - return self._handle_ok( - await self._send_cmd(ServerCmd.INIT_DB, database.encode("utf-8")) - ) - - async def cmd_query( - self, - query: StrOrBytes, - raw: bool = False, - buffered: bool = False, - raw_as_string: bool = False, - **kwargs: Any, - ) -> ResultType: - if not isinstance(query, bytearray): - if isinstance(query, str): - query = query.encode() - query = bytearray(query) - - # Set/Reset internal state related to query execution - self._query = query - self._local_infile_filenames = None - - # Prepare query attrs - charset = self._charset.name if self._charset.name != "utf8mb4" else "utf8" - packet = bytearray() - if not self._server_info.query_attrs_is_supported and self._query_attrs: - warnings.warn( - "This version of the server does not support Query Attributes", - category=Warning, - ) - if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - names = [] - types = [] - values: List[bytes] = [] - null_bitmap = [0] * ((len(self._query_attrs) + 7) // 8) - for pos, attr_tuple in enumerate(self._query_attrs): - value = attr_tuple[1] - flags = 0 - if value is None: - null_bitmap[(pos // 8)] |= 1 << (pos % 8) - types.append(int1store(FieldType.NULL) + int1store(flags)) - continue - if isinstance(value, int): - ( - packed, - field_type, - flags, - ) = self._protocol.prepare_binary_integer(value) - values.append(packed) - elif isinstance(value, str): - value = value.encode(charset) - values.append(lc_int(len(value)) + value) - field_type = FieldType.VARCHAR - elif isinstance(value, bytes): - values.append(lc_int(len(value)) + value) - field_type = FieldType.BLOB - elif isinstance(value, Decimal): - values.append( - lc_int(len(str(value).encode(charset))) - + str(value).encode(charset) - ) - field_type = FieldType.DECIMAL - elif isinstance(value, float): - values.append(struct.pack(" parameter_count Number of parameters - packet.extend(lc_int(len(self._query_attrs))) - # int parameter_set_count Number of parameter sets. - # Currently always 1 - packet.extend(lc_int(1)) - if values: - packet.extend( - b"".join([struct.pack("B", bit) for bit in null_bitmap]) - + int1store(1) - ) - for _type, name in zip(types, names): - packet.extend(_type) - packet.extend(name) - - for value in values: - packet.extend(value) - - packet.extend(query) - query = bytes(packet) - try: - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - result = await self._handle_result( - await self._send_cmd( - ServerCmd.QUERY, - query, - read_timeout=read_timeout, - write_timeout=write_timeout, - ), - read_timeout, - write_timeout, - ) - except ProgrammingError as err: - if err.errno == 3948 and "Loading local data is disabled" in err.msg: - err_msg = ( - "LOAD DATA LOCAL INFILE file request rejected due " - "to restrictions on access." - ) - raise DatabaseError(err_msg) from err - raise - return result - - async def cmd_query_iter( # type: ignore[override] - self, - statements: StrOrBytes, - **kwargs: Any, - ) -> AsyncGenerator[ResultType, None]: - """Send one or more statements to the MySQL server. - - Similar to the cmd_query method, but instead returns a generator - object to iterate through results. It sends the statements to the - MySQL server and through the iterator you can get the results. - - statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' - for result in await cnx.cmd_query(statement, iterate=True): - if 'columns' in result: - columns = result['columns'] - rows = await cnx.get_rows() - else: - # do something useful with INSERT result - """ - try: - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - packet = bytearray() - if not isinstance(statements, bytearray): - if isinstance(statements, str): - statements = statements.encode("utf8") - statements = bytearray(statements) - - if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - # int parameter_count Number of parameters - packet.extend(lc_int(0)) - # int parameter_set_count Number of parameter sets. - # Currently always 1 - packet.extend(lc_int(1)) - - packet.extend(statements) - query = bytes(packet) - # Handle the first query result - yield await self._handle_result( - await self._send_cmd( - ServerCmd.QUERY, - query, - read_timeout=read_timeout, - write_timeout=write_timeout, - ), - read_timeout, - write_timeout, - ) - - # Handle next results, if any - while self._have_next_result: - await self.handle_unread_result() - yield await self._handle_result( - await self._socket.read(read_timeout or self._read_timeout), - read_timeout, - write_timeout, - ) - except (ReadTimeoutError, WriteTimeoutError) as err: - raise err - - async def cmd_stmt_fetch( - self, statement_id: int, rows: int = 1, **kwargs: Any - ) -> None: - """Fetch a MySQL statement Result Set. - - This method will send the FETCH command to MySQL together with the given - statement id and the number of rows to fetch. - """ - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - packet = self._protocol.make_stmt_fetch(statement_id, rows) - self.unread_result = False - await self._send_cmd( - ServerCmd.STMT_FETCH, - packet, - expect_response=False, - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - self.unread_result = True - - @handle_read_write_timeout() - async def cmd_stmt_prepare( - self, - statement: bytes, - **kwargs: Any, - ) -> Mapping[str, Union[int, List[DescriptionType]]]: - """Prepare a MySQL statement. - - This method will send the PREPARE command to MySQL together with the given - statement. - """ - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - packet = await self._send_cmd( - ServerCmd.STMT_PREPARE, - statement, - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - result = self._handle_binary_ok(packet) - - result["columns"] = [] - result["parameters"] = [] - if result["num_params"] > 0: - for _ in range(0, result["num_params"]): - result["parameters"].append( - self._protocol.parse_column( - await self._socket.read(read_timeout or self._read_timeout), - self.python_charset, - ) - ) - self._handle_eof( - await self._socket.read(read_timeout or self._read_timeout) - ) - if result["num_columns"] > 0: - for _ in range(0, result["num_columns"]): - result["columns"].append( - self._protocol.parse_column( - await self._socket.read(read_timeout or self._read_timeout), - self.python_charset, - ) - ) - self._handle_eof( - await self._socket.read(read_timeout or self._read_timeout) - ) - - return result - - async def cmd_stmt_execute( - self, - statement_id: int, # type: ignore[override] - data: Sequence[BinaryProtocolType] = (), - parameters: Sequence[Any] = (), - flags: int = 0, - **kwargs: Any, - ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]: - """Execute a prepared MySQL statement.""" - parameters = list(parameters) - long_data_used = {} - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - - if data: - for param_id, _ in enumerate(parameters): - if isinstance(data[param_id], IOBase): - binary = True - try: - binary = "b" not in data[param_id].mode # type: ignore[union-attr] - except AttributeError: - pass - await self.cmd_stmt_send_long_data( - statement_id, - param_id, - data[param_id], - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - long_data_used[param_id] = (binary,) - if not self._query_attrs_supported and self._query_attrs: - warnings.warn( - "This version of the server does not support Query Attributes", - category=Warning, - ) - if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - execute_packet = self._protocol.make_stmt_execute( - statement_id, - data, - tuple(parameters), - flags, - long_data_used, - self.charset, - self._query_attrs, - self._converter_str_fallback, - ) - else: - execute_packet = self._protocol.make_stmt_execute( - statement_id, - data, - tuple(parameters), - flags, - long_data_used, - self.charset, - converter_str_fallback=self._converter_str_fallback, - ) - packet = await self._send_cmd( - ServerCmd.STMT_EXECUTE, - packet=execute_packet, - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - result = await self._handle_binary_result(packet) - return result - - async def cmd_stmt_reset(self, statement_id: int, **kwargs: Any) -> None: - """Reset data for prepared statement sent as long data. - - The result is a dictionary with OK packet information. - """ - self._handle_ok( - await self._send_cmd( - ServerCmd.STMT_RESET, - int4store(statement_id), - read_timeout=kwargs.get("read_timeout", None), - write_timeout=kwargs.get("write_timeout", None), - ), - ) - - async def cmd_stmt_close(self, statement_id: int, **kwargs: Any) -> None: - """Deallocate a prepared MySQL statement. - - This method deallocates the prepared statement using the statement_id. - Note that the MySQL server does not return anything. - """ - await self._send_cmd( - ServerCmd.STMT_CLOSE, - int4store(statement_id), - expect_response=False, - read_timeout=kwargs.get("read_timeout", None), - write_timeout=kwargs.get("write_timeout", None), - ) - - @cmd_refresh_verify_options() - async def cmd_refresh(self, options: int) -> OkPacketType: - if not options & ( - RefreshOption.GRANT - | RefreshOption.LOG - | RefreshOption.TABLES - | RefreshOption.HOST - | RefreshOption.STATUS - | RefreshOption.REPLICA - ): - raise ValueError("Invalid command REFRESH option") - - res = None - if options & RefreshOption.GRANT: - res = await self.cmd_query("FLUSH PRIVILEGES") - if options & RefreshOption.LOG: - res = await self.cmd_query("FLUSH LOGS") - if options & RefreshOption.TABLES: - res = await self.cmd_query("FLUSH TABLES") - if options & RefreshOption.HOST: - res = await self.cmd_query("TRUNCATE TABLE performance_schema.host_cache") - if options & RefreshOption.STATUS: - res = await self.cmd_query("FLUSH STATUS") - if options & RefreshOption.REPLICA: - res = await self.cmd_query( - "RESET SLAVE" - if self._server_info.version_tuple < (8, 0, 22) - else "RESET REPLICA" - ) - - return res # type: ignore[return-value] - - async def cmd_stmt_send_long_data( - self, - statement_id: int, - param_id: int, - data: BinaryIO, - **kwargs: Any, - ) -> int: - """Send data for a column. - - This methods send data for a column (for example BLOB) for statement identified - by statement_id. The param_id indicate which parameter the data belongs too. - The data argument should be a file-like object. - - Since MySQL does not send anything back, no error is raised. When the MySQL - server is not reachable, an OperationalError is raised. - - cmd_stmt_send_long_data should be called before cmd_stmt_execute. - - The total bytes send is returned. - """ - chunk_size = 131072 # 128 KB - total_sent = 0 - try: - buf = data.read(chunk_size) - while buf: - packet = self._protocol.prepare_stmt_send_long_data( - statement_id, param_id, buf - ) - await self._send_cmd( - ServerCmd.STMT_SEND_LONG_DATA, - packet=packet, - expect_response=False, - read_timeout=kwargs.get("read_timeout", None), - write_timeout=kwargs.get("write_timeout", None), - ) - total_sent += len(buf) - buf = data.read(chunk_size) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - return total_sent - - async def cmd_quit(self) -> bytes: - """Close the current connection with the server. - - Send the QUIT command to the MySQL server, closing the current connection. - """ - await self.handle_unread_result() - packet = self._protocol.make_command(ServerCmd.QUIT) - try: - await self._socket.write(packet, write_timeout=self._write_timeout) - except WriteTimeoutError as _: - pass - return packet - - async def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None: - """Shut down the MySQL Server. - - This method sends the SHUTDOWN command to the MySQL server. - The `shutdown_type` is not used, and it's kept for backward compatibility. - """ - await self.cmd_query("SHUTDOWN") - - async def cmd_statistics(self) -> StatsPacketType: - """Send the statistics command to the MySQL Server. - - This method sends the STATISTICS command to the MySQL server. The result is a - dictionary with various statistical information. - """ - await self.handle_unread_result() - - packet = self._protocol.make_command(ServerCmd.STATISTICS) - await self._socket.write(packet, 0, 0, self._write_timeout) - return self._protocol.parse_statistics( - await self._socket.read(self._read_timeout) - ) - - async def cmd_process_kill(self, mysql_pid: int) -> OkPacketType: - """Kill a MySQL process. - - This method send the PROCESS_KILL command to the server along with the - process ID. The result is a dictionary with the OK packet information. - """ - if not isinstance(mysql_pid, int): - raise ValueError("MySQL PID must be int") - return await self.cmd_query(f"KILL {mysql_pid}") # type: ignore[return-value] - - async def cmd_debug(self) -> EofPacketType: - """Send the DEBUG command. - - This method sends the DEBUG command to the MySQL server, which requires the - MySQL user to have SUPER privilege. The output will go to the MySQL server - error log and the result of this method is a dictionary with EOF packet - information. - """ - return self._handle_eof(await self._send_cmd(ServerCmd.DEBUG)) - - async def cmd_ping(self) -> OkPacketType: - """Send the PING command. - - This method sends the PING command to the MySQL server. It is used to check - if the the connection is still valid. The result of this method is dictionary - with OK packet information. - """ - return self._handle_ok(await self._send_cmd(ServerCmd.PING)) - - async def cmd_change_user( - self, - username: str = "", - password: str = "", - database: str = "", - charset: Optional[int] = None, - password1: str = "", - password2: str = "", - password3: str = "", - oci_config_file: str = "", - oci_config_profile: str = "", - openid_token_file: str = "", - ) -> Optional[OkPacketType]: - """Change the current logged in user. - - This method allows to change the current logged in user information. - The result is a dictionary with OK packet information. - """ - # If charset isn't defined, we use the same charset ID defined previously, - # otherwise, we run a verification and update the charset ID. - if charset is not None: - if not isinstance(charset, int): - raise ValueError("charset must be an integer") - if charset < 0: - raise ValueError("charset should be either zero or a postive integer") - self._charset = charsets.get_by_id(charset) - - self._mfa_nfactor = 1 - self._user = username - self._password = password - self._password1 = password1 - self._password2 = password2 - self._password3 = password3 - - if self._password1 and password != self._password1: - self._password = self._password1 - - await self.handle_unread_result() - - if self._compress: - raise NotSupportedError("Change user is not supported with compression") - - if oci_config_file: - self._oci_config_file = oci_config_file - if openid_token_file: - self._openid_token_file = openid_token_file - self._oci_config_profile = oci_config_profile - - # Update the custom configurations needed by specific auth plugins - self._authenticator.update_plugin_config( - config={ - "oci_config_file": self._oci_config_file, - "oci_config_profile": self._oci_config_profile, - "openid_token_file": self._openid_token_file, - } - ) - - ok_packet: bytes = await self._authenticator.authenticate( - sock=self._socket, - handshake=self._handshake, - username=self._user, - password1=self._password, - password2=self._password2, - password3=self._password3, - database=self._database, - charset=self._charset.charset_id, - client_flags=self._client_flags, - ssl_enabled=self._ssl_active, - auth_plugin=self._auth_plugin, - conn_attrs=self._connection_attrs, - is_change_user_request=True, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - - if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and database: - await self.cmd_init_db(database) - - # return ok_pkt - return self._handle_ok(ok_packet) diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/cursor.py b/server/venv/Lib/site-packages/mysql/connector/aio/cursor.py deleted file mode 100644 index 8eec4bf..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/cursor.py +++ /dev/null @@ -1,1392 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="assignment,arg-type,attr-defined,index,override,call-overload" - -"""Implementation of cursor classes in pure Python.""" - -__all__ = ["MySQLCursor"] - -import re -import warnings - -from decimal import Decimal -from typing import ( - Any, - AsyncGenerator, - Dict, - Iterator, - List, - NoReturn, - Optional, - Sequence, - Tuple, - Union, -) - -from .._decorating import deprecated as deprecated_sync -from .._scripting import split_multi_statement -from ..constants import ServerFlag -from ..cursor import ( - MAX_RESULTS, - RE_PY_MAPPING_PARAM, - RE_PY_PARAM, - RE_SQL_COMMENT, - RE_SQL_FIND_PARAM, - RE_SQL_INSERT_STMT, - RE_SQL_INSERT_VALUES, - RE_SQL_ON_DUPLICATE, - RE_SQL_PYTHON_CAPTURE_PARAM_NAME, - RE_SQL_PYTHON_REPLACE_PARAM, -) -from ..errors import ( - Error, - InterfaceError, - NotSupportedError, - ProgrammingError, - get_mysql_exception, -) -from ..types import ( - DescriptionType, - EofPacketType, - ParamsDictType, - ParamsSequenceOrDictType, - ParamsSequenceType, - ResultType, - RowItemType, - RowType, - StrOrBytes, - WarningType, -) -from ._decorating import deprecated -from .abstracts import MySQLConnectionAbstract, MySQLCursorAbstract - -ERR_NO_RESULT_TO_FETCH = "No result set to fetch from" - - -class _ParamSubstitutor: - """Substitute parameters into a SQL statement.""" - - def __init__(self, params: Sequence[bytes]) -> None: - self.params: Sequence[bytes] = params - self.index: int = 0 - - def __call__(self, matchobj: re.Match) -> bytes: - index = self.index - self.index += 1 - try: - return bytes(self.params[index]) - except IndexError: - raise ProgrammingError( - "Not enough parameters for the SQL statement" - ) from None - - @property - def remaining(self) -> int: - """Return the number of parameters remaining to be substituted.""" - return len(self.params) - self.index - - -def _bytestr_format_dict(bytestr: bytes, value_dict: Dict[bytes, bytes]) -> bytes: - """ - >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'}) - b'foobar - >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'}) - b'%%(a)s' - >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'}) - b'%%foobar' - >>> _bytestr_format_dict(b'%(x)s %(y)s', - ... {b'x': b'x=%(y)s', b'y': b'y=%(x)s'}) - b'x=%(y)s y=%(x)s' - """ - - def replace(matchobj: re.Match) -> bytes: - """Replace pattern.""" - value: Optional[bytes] = None - groups = matchobj.groupdict() - if groups["conversion_type"] == b"%": - value = b"%" - if groups["conversion_type"] == b"s": - key = groups["mapping_key"] - value = value_dict[key] - if value is None: - raise ValueError( - f"Unsupported conversion_type: {groups['conversion_type']}" - ) - return value - - stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr) - return stmt - - -class MySQLCursor(MySQLCursorAbstract): - """Default cursor for interacting with MySQL. - - This cursor will execute statements and handle the result. It will not automatically - fetch all rows. - - MySQLCursor should be inherited whenever other functionallity is required. - An example would to change the fetch* member functions to return dictionaries instead - of lists of values. - - Implements the Python Database API Specification v2.0 (PEP-249). - """ - - async def __anext__(self) -> RowType: - res = await self.fetchone() - if res is not None: - return res - raise StopAsyncIteration - - async def close(self) -> bool: - if not self._connection: - return False - self._connection.remove_cursor(self) - await self._connection.handle_unread_result() - await self._reset_result() - self._connection = None - return True - - async def _process_params_dict( - self, params: ParamsDictType - ) -> Dict[bytes, Union[bytes, Decimal]]: - """Process query parameters given as dictionary.""" - res: Dict[bytes, Any] = {} - try: - sql_mode = await self._connection.get_sql_mode() - to_mysql = self._connection.converter.to_mysql - escape = self._connection.converter.escape - quote = self._connection.converter.quote - for key, value in params.items(): - conv = value - conv = to_mysql(conv) - conv = escape(conv, sql_mode) - if not isinstance(value, Decimal): - conv = quote(conv) - res[key.encode()] = conv - except Exception as err: - raise ProgrammingError( - f"Failed processing pyformat-parameters; {err}" - ) from err - return res - - async def _process_params( - self, params: ParamsSequenceType - ) -> Tuple[Union[bytes, Decimal], ...]: - """Process query parameters.""" - result = params[:] - try: - sql_mode = await self._connection.get_sql_mode() - to_mysql = self._connection.converter.to_mysql - escape = self._connection.converter.escape - quote = self._connection.converter.quote - result = [to_mysql(value) for value in result] - result = [escape(value, sql_mode) for value in result] - result = [ - quote(value) if not isinstance(params[i], Decimal) else value - for i, value in enumerate(result) - ] - except Exception as err: - raise ProgrammingError( - f"Failed processing format-parameters; {err}" - ) from err - return tuple(result) - - async def _fetch_row(self, raw: bool = False) -> Optional[RowType]: - """Return the next row in the result set.""" - if not self._connection.unread_result: - return None - row = None - - if self._nextrow == (None, None): - (row, eof) = await self._connection.get_row( - binary=self._binary, - columns=self.description, - raw=raw, - read_timeout=self._read_timeout, - ) - else: - (row, eof) = self._nextrow - - if row: - self._nextrow = await self._connection.get_row( - binary=self._binary, - columns=self.description, - raw=raw, - read_timeout=self._read_timeout, - ) - eof = self._nextrow[1] - if eof is not None: - await self._handle_eof(eof) - if self._rowcount == -1: - self._rowcount = 1 - else: - self._rowcount += 1 - if eof: - await self._handle_eof(eof) - - return row - - async def _handle_result(self, result: ResultType) -> None: - """Handle the result after a command was send. - - The result can be either an OK-packet or a dictionary containing column/eof - information. - - Raises: - InterfaceError: When result is not a dict() or result is invalid. - """ - if not isinstance(result, dict): - raise InterfaceError("Result was not a dict()") - - if "columns" in result: - # Weak test, must be column/eof information - self._description = result["columns"] - self._connection.unread_result = True - await self._handle_resultset() - elif "affected_rows" in result: - # Weak test, must be an OK-packet - self._connection.unread_result = False - await self._handle_noresultset(result) - else: - raise InterfaceError("Invalid result") - - async def _handle_resultset(self) -> None: - """Handle the result set. - - This method handles the result set and is called after reading and storing - column information in _handle_result(). For non-buffering cursors, this method - is usually doing nothing. - """ - - async def _handle_noresultset(self, res: ResultType) -> None: - """Handle result of execute() when there is no result set. - - Raises: - ProgrammingError: When failing handling a non-resultset. - """ - try: - self._rowcount = res["affected_rows"] - self._last_insert_id = res["insert_id"] - self._warning_count = res["warning_count"] - except (KeyError, TypeError) as err: - raise ProgrammingError(f"Failed handling non-resultset; {err}") from None - - await self._handle_warnings() - - async def _handle_warnings(self) -> None: - """Handle possible warnings after all results are consumed. - - Raises: - Error: Also raises exceptions if raise_on_warnings is set. - """ - if self._connection.get_warnings and self._warning_count: - self._warnings = await self._fetch_warnings() - - if not self._warnings: - return - - err = get_mysql_exception( - self._warnings[0][1], - self._warnings[0][2], - warning=not self._connection.raise_on_warnings, - ) - - if self._connection.raise_on_warnings: - raise err - - warnings.warn(err, stacklevel=4) - - async def _handle_eof(self, eof: EofPacketType) -> None: - """Handle EOF packet.""" - self._connection.unread_result = False - self._nextrow = (None, None) - self._warning_count = eof["warning_count"] - await self._handle_warnings() - - async def _reset_result(self, preserve_last_executed_stmt: bool = False) -> None: - """Reset the cursor to default. - - Args: - preserve_last_executed_stmt: If it is False, the last executed - statement value is reset. Otherwise, - such a value is preserved. - """ - self._description = None - self._warnings = None - self._warning_count = 0 - self._stored_results = [] - self._rowcount = -1 - self._nextrow = (None, None) - - if not preserve_last_executed_stmt: - # reset inner state related to statement execution - self._executed = None - self._executed_list = [] - self._stmt_partitions = None - self._stmt_partition = None - self._stmt_map_results = False - - await self.reset() - - def _have_unread_result(self) -> bool: - """Check whether there is an unread result.""" - try: - return self._connection.unread_result - except AttributeError: - return False - - def _check_executed(self) -> None: - """Check if the statement has been executed. - - Raises: - InterfaceError: If the statement has not been executed. - """ - if self._executed is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - - async def _prepare_statement( - self, - operation: StrOrBytes, - params: Union[Sequence[Any], Dict[str, Any]] = (), - ) -> bytes: - """Prepare SQL statement for execution. - - Converts the SQL statement to bytes and replaces the parameters in the - placeholders. - - Raises: - ProgrammingError: On converting to bytes, missing parameters or invalid - parameters type. - """ - try: - stmt = ( - operation - if isinstance(operation, (bytes, bytearray)) - else operation.encode(self._connection.python_charset) - ) - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise ProgrammingError(str(err)) from err - - if params: - if isinstance(params, dict): - stmt = _bytestr_format_dict( - stmt, await self._process_params_dict(params) - ) - elif isinstance(params, (list, tuple)): - psub = _ParamSubstitutor(await self._process_params(params)) - stmt = RE_PY_PARAM.sub(psub, stmt) - if psub.remaining != 0: - raise ProgrammingError( - "Not all parameters were used in the SQL statement" - ) - else: - raise ProgrammingError( - f"Could not process parameters: {type(params).__name__}({params})," - " it must be of type list, tuple or dict" - ) - - return stmt - - async def _fetch_warnings(self) -> Optional[List[WarningType]]: - """Fetch warnings doing a SHOW WARNINGS.""" - result = [] - async with await self._connection.cursor( - raw=False, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) as cur: - await cur.execute("SHOW WARNINGS") - result = await cur.fetchall() - return result if result else None # type: ignore[return-value] - - async def _batch_insert( - self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType] - ) -> Optional[bytes]: - """Implements multi row insert""" - - def remove_comments(match: re.Match) -> str: - """Remove comments from INSERT statements. - - This function is used while removing comments from INSERT - statements. If the matched string is a comment not enclosed - by quotes, it returns an empty string, else the string itself. - """ - if match.group(1): - return "" - return match.group(2) - - tmp = re.sub( - RE_SQL_ON_DUPLICATE, - "", - re.sub(RE_SQL_COMMENT, remove_comments, operation), - ) - - matches = re.search(RE_SQL_INSERT_VALUES, tmp) - if not matches: - raise InterfaceError( - "Failed rewriting statement for multi-row INSERT. Check SQL syntax" - ) - fmt = matches.group(1).encode(self._connection.python_charset) - values = [] - - try: - stmt = operation.encode(self._connection.python_charset) - for params in seq_params: - tmp = fmt - if isinstance(params, dict): - tmp = _bytestr_format_dict( - tmp, await self._process_params_dict(params) - ) - else: - psub = _ParamSubstitutor(await self._process_params(params)) - tmp = RE_PY_PARAM.sub(psub, tmp) - if psub.remaining != 0: - raise ProgrammingError( - "Not all parameters were used in the SQL statement" - ) - values.append(tmp) - if fmt in stmt: - stmt = stmt.replace(fmt, b",".join(values), 1) - self._executed = stmt - return stmt - return None - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise ProgrammingError(str(err)) from err - except Error: - raise - except Exception as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - - @deprecated_sync( - "The property counterpart 'stored_results' will be added in a future release, " - "and this method will be removed." - ) - def stored_results(self) -> Iterator[MySQLCursorAbstract]: - """Returns an iterator for stored results. - - This method returns an iterator over results which are stored when callproc() - is called. The iterator will provide MySQLCursorBuffered instances. - """ - return iter(self._stored_results) - - async def callproc( - self, - procname: str, - args: Sequence[Any] = (), - ) -> Optional[Union[Dict[str, RowItemType], RowType]]: - """Calls a stored procedure with the given arguments - - The arguments will be set during this session, meaning - they will be called like ___arg where - is an enumeration (+1) of the arguments. - - Coding Example: - 1) Defining the Stored Routine in MySQL: - CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN - SET pProd := pFac1 * pFac2; - END - - 2) Executing in Python: - args = (5, 5, 0) # 0 is to hold pprod - await cursor.callproc('multiply', args) - print(await cursor.fetchone()) - - For OUT and INOUT parameters the user should provide the - type of the parameter as well. The argument should be a - tuple with first item as the value of the parameter to pass - and second argument the type of the argument. - - In the above example, one can call callproc method like: - args = (5, 5, (0, 'INT')) - await cursor.callproc('multiply', args) - - The type of the argument given in the tuple will be used by - the MySQL CAST function to convert the values in the corresponding - MySQL type (See CAST in MySQL Reference for more information) - - Does not return a value, but a result set will be - available when the CALL-statement execute successfully. - Raises exceptions when something is wrong. - """ - if not procname or not isinstance(procname, str): - raise ValueError("procname must be a string") - - if not isinstance(args, (tuple, list)): - raise ValueError("args must be a sequence") - - self._stored_results = [] - - results = [] - try: - argnames = [] - argtypes = [] - - # MySQL itself does support calling procedures with their full - # name .. It's necessary to split - # by '.' and grab the procedure name from procname. - procname_abs = procname.split(".")[-1] - if args: - argvalues = [] - for idx, arg in enumerate(args): - argname = f"@_{procname_abs}_arg{idx + 1}" - argnames.append(argname) - if isinstance(arg, tuple): - argtypes.append(f" CAST({argname} AS {arg[1]})") - argvalues.append(arg[0]) - else: - argtypes.append(argname) - argvalues.append(arg) - - placeholders = ",".join(f"{arg}=%s" for arg in argnames) - await self.execute(f"SET {placeholders}", argvalues) - - call = f"CALL {procname}({','.join(argnames)})" - - # We disable consuming results temporary to make sure we - # getting all results - can_consume_results = self._connection.can_consume_results - async for result in self._connection.cmd_query_iter( - call, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ): - self._connection.can_consume_results = False - if isinstance(self, (MySQLCursorDict, MySQLCursorBufferedDict)): - cursor_class = MySQLCursorBufferedDict - elif self._raw: - cursor_class = MySQLCursorBufferedRaw - else: - cursor_class = MySQLCursorBuffered - # pylint: disable=protected-access - - # cursor_class = MySQLCursorBuffered - cur = cursor_class(self._connection.get_self()) - cur._executed = f"(a result of {call})" - await cur._handle_result(result) - # pylint: enable=protected-access - if cur.warnings is not None: - self._warnings = cur.warnings - if "columns" in result: - results.append(cur) - - self._connection.can_consume_results = can_consume_results - if argnames: - # Create names aliases to be compatible with namedtuples - args = [ - f"{name} AS {alias}" - for name, alias in zip( - argtypes, [arg.lstrip("@_") for arg in argnames] - ) - ] - select = f"SELECT {','.join(args)}" - await self.execute(select) - self._stored_results = results - return await self.fetchone() - - self._stored_results = results - return tuple() - except Error: - raise - except Exception as err: - raise InterfaceError(f"Failed calling stored routine; {err}") from None - - async def execute( - self, - operation: str, - params: Union[Sequence[Any], Dict[str, Any]] = (), - map_results: bool = False, - ) -> None: - if not self._connection: - raise ProgrammingError("Cursor is not connected") - - if not operation: - return None - - await self._connection.handle_unread_result() - await self._reset_result() - - stmt = await self._prepare_statement(operation, params) - - self._stmt_partitions = split_multi_statement( - sql_code=stmt, map_results=map_results - ) - self._stmt_partition = next(self._stmt_partitions) - self._stmt_map_results = map_results - self._executed_list = self._stmt_partition["single_stmts"] - self._executed = ( - self._stmt_partition["single_stmts"].popleft() - if map_results - else self._stmt_partition["mappable_stmt"] - ) - - await self._handle_result( - await self._connection.cmd_query( - self._stmt_partition["mappable_stmt"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - ) - - return None - - @deprecated( - "executemulti() is deprecated and will be removed in a future release. " - + "Use execute() instead." - ) - async def executemulti( - self, - operation: str, - params: Union[Sequence[Any], Dict[str, Any]] = (), - map_results: bool = False, - ) -> None: - return await self.execute( - operation=operation, params=params, map_results=map_results - ) - - async def executemany( - self, - operation: str, - seq_params: Sequence[ParamsSequenceType], - ) -> None: - """Prepare and execute a MySQL Prepared Statement many times. - - This method will prepare the given operation and execute with each tuple found - the list seq_params. - - If the cursor instance already had a prepared statement, it is first closed. - """ - if not operation or not seq_params: - return None - await self._connection.handle_unread_result() - - try: - _ = iter(seq_params) - except TypeError as err: - raise ProgrammingError("Parameters for query must be an Iterable") from err - - # Optimize INSERTs by batching them - if re.match(RE_SQL_INSERT_STMT, operation): - if not seq_params: - self._rowcount = 0 - return None - stmt = await self._batch_insert(operation, seq_params) - if stmt is not None: - self._executed = stmt - return await self.execute(stmt) - - rowcnt = 0 - try: - for params in seq_params: - await self.execute(operation, params) - if self.with_rows and self._have_unread_result(): - await self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise ProgrammingError(f"Failed executing the operation; {err}") from None - self._rowcount = rowcnt - - async def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Raises: - InterfaceError: If there is no result to fetch. - - Returns: - tuple or None: A row from query result set. - """ - if self._executed is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - return await self._fetch_row() - - async def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - if self._executed is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - - if not self._connection.unread_result: - return [] - - rows, eof = await self._connection.get_rows(read_timeout=self._read_timeout) - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - - await self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return rows - - async def fetchmany(self, size: Optional[int] = None) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, which - defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0: - cnt -= 1 - row = await self.fetchone() - if row: - res.append(row) - - return res - - async def nextset(self) -> Optional[bool]: - if self._connection._have_next_result: - # prepare cursor to load the next result set, and ultimately, load it. - await self._connection.handle_unread_result() - await self._reset_result(preserve_last_executed_stmt=True) - await self._handle_result( - await self._connection._handle_result( - await self._connection._socket.read(read_timeout=self._read_timeout) - ) - ) - - # if mapping is enabled, run the if-block, otherwise simply return `True`. - if self._stmt_partitions is not None and self._stmt_map_results: - if not self._stmt_partition["single_stmts"]: - # It means there are still results to be consumed, but no more - # statements to relate these results to. - # In this case, we raise a no fatal error and don't clear - # `_executed` so its current value is reported when users - # access the property `statement`. - # If this case ever happens, a bug report should be filed, - # assuming it is happening on supported use cases. - warnings.warn( - "MappingWarning: Number of result sets greater than number " - "of single statements." - ) - else: - self._executed = self._stmt_partition["single_stmts"].popleft() - return True - if self._stmt_partitions is not None: - # Let's see if there are more mappable statements (partitions) - # to be executed. - # If there are no more partitions, we simply return `None`, otherwise - # we execute the correponding mappable multi statement and repeat the - # process all over again. - try: - self._stmt_partition = next(self._stmt_partitions) - except StopIteration: - pass - else: - # This block only happens when mapping is enabled because when it - # is disabled, only one partition is generated, and at this point, - # such partiton has already been processed. - self._executed = self._stmt_partition["single_stmts"].popleft() - await self._handle_result( - await self._connection.cmd_query( - self._stmt_partition["mappable_stmt"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - ) - return True - - await self._reset_result() - return None - - -class MySQLCursorBuffered(MySQLCursor): - """Cursor which fetches rows within execute().""" - - def __init__( - self, - connection: MySQLConnectionAbstract, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - super().__init__(connection, read_timeout, write_timeout) - self._rows: Optional[List[RowType]] = None - self._next_row: int = 0 - - async def _handle_resultset(self) -> None: - """Handle the result set. - - This method handles the result set and is called after reading and storing - column information in _handle_result(). For non-buffering cursors, this method - is usually doing nothing. - """ - self._rows, eof = await self._connection.get_rows( - raw=self._raw, read_timeout=self._read_timeout - ) - self._rowcount = len(self._rows) - await self._handle_eof(eof) - self._next_row = 0 - self._connection.unread_result = False - - async def _fetch_row(self, raw: bool = False) -> Optional[RowType]: - """Return the next row in the result set.""" - row = None - try: - row = self._rows[self._next_row] - except (IndexError, TypeError): - return None - self._next_row += 1 - return row - - async def reset(self, free: bool = True) -> None: - """Reset the cursor to default.""" - self._rows = None - - async def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return await self._fetch_row() - - async def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - if self._executed is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - if self._rows is None: - return [] - res = [] - res = self._rows[self._next_row :] - self._next_row = len(self._rows) - return res - - @property - def with_rows(self) -> bool: - return self._rows is not None - - -class MySQLCursorRaw(MySQLCursor): - """Skip conversion from MySQL datatypes to Python types when fetching rows.""" - - def __init__( - self, - connection: MySQLConnectionAbstract, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - super().__init__(connection, read_timeout, write_timeout) - self._raw: bool = True - - async def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return await self._fetch_row(raw=True) - - async def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if not self._have_unread_result(): - return [] - rows, eof = await self._connection.get_rows( - raw=True, read_timeout=self._read_timeout - ) - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - await self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return rows - - -class MySQLCursorBufferedRaw(MySQLCursorBuffered): - """ - Cursor which skips conversion from MySQL datatypes to Python types when - fetching rows and fetches rows within execute(). - """ - - def __init__( - self, - connection: MySQLConnectionAbstract, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - super().__init__(connection, read_timeout, write_timeout) - self._raw: bool = True - - @property - def with_rows(self) -> bool: - return self._rows is not None - - -class MySQLCursorDict(MySQLCursor): - """ - Cursor fetching rows as dictionaries. - - The fetch methods of this class will return dictionaries instead of tuples. - Each row is a dictionary that looks like: - row = { - "col1": value1, - "col2": value2 - } - """ - - def _row_to_python( - self, - rowdata: RowType, - desc: Optional[List[DescriptionType]] = None, # pylint: disable=unused-argument - ) -> Optional[Dict[str, RowItemType]]: - """Convert a MySQL text result row to Python types - - Returns a dictionary. - """ - return dict(zip(self.column_names, rowdata)) if rowdata else None - - async def fetchone(self) -> Optional[Dict[str, RowItemType]]: - """Return next row of a query result set. - - Returns: - dict or None: A dict from query result set. - """ - return self._row_to_python(await super().fetchone(), self.description) - - async def fetchall(self) -> List[Optional[Dict[str, RowItemType]]]: - """Return all rows of a query result set. - - Returns: - list: A list of dictionaries with all rows of a query - result set where column names are used as keys. - """ - return [ - self._row_to_python(row, self.description) - for row in await super().fetchall() - if row - ] - - -class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered): - """ - Buffered Cursor fetching rows as dictionaries. - """ - - async def fetchone(self) -> Optional[Dict[str, RowItemType]]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - row = await self._fetch_row() - if row: - return self._row_to_python(row, self.description) - return None - - async def fetchall(self) -> List[Optional[Dict[str, RowItemType]]]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - if self._executed is None or self._rows is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - res = [] - for row in self._rows[self._next_row :]: - res.append(self._row_to_python(row, self.description)) - self._next_row = len(self._rows) - return res - - -class MySQLCursorPrepared(MySQLCursor): - """Cursor using MySQL Prepared Statements""" - - def __init__( - self, - connection: MySQLConnectionAbstract, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ): - super().__init__(connection, read_timeout, write_timeout) - self._rows: Optional[List[RowType]] = None - self._next_row: int = 0 - self._prepared: Optional[Dict[str, Union[int, List[DescriptionType]]]] = None - self._binary: bool = True - self._have_result: Optional[bool] = None - self._last_row_sent: bool = False - self._cursor_exists: bool = False - - async def reset(self, free: bool = True) -> None: - if self._prepared: - try: - await self._connection.cmd_stmt_close( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - except Error: - # We tried to deallocate, but it's OK when we fail. - pass - self._prepared = None - self._last_row_sent = False - self._cursor_exists = False - - async def _handle_noresultset(self, res: ResultType) -> None: - self._handle_server_status( - res.get("status_flag", res.get("server_status", 0)), - ) - await super()._handle_noresultset(res) - - def _handle_server_status(self, flags: int) -> None: - self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0 - self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0 - - async def _handle_eof(self, eof: EofPacketType) -> None: - self._handle_server_status( - eof.get("status_flag", eof.get("server_status", 0)), - ) - await super()._handle_eof(eof) - - async def callproc(self, procname: Any, args: Any = ()) -> NoReturn: - """Calls a stored procedue - - Not supported with MySQLCursorPrepared. - """ - raise NotSupportedError() - - @deprecated( - "executemulti() is deprecated and will be removed in a future release. " - + "Use execute() instead." - ) - async def executemulti( - self, - operation: str, - params: Union[Sequence[Any], Dict[str, Any]] = (), - map_results: bool = False, - ) -> AsyncGenerator[MySQLCursorAbstract, None]: - """Execute multiple statements. - - Executes the given operation substituting any markers with the given - parameters. - """ - raise NotSupportedError() - - async def close(self) -> None: - """Close the cursor - - This method will try to deallocate the prepared statement and close - the cursor. - """ - await self.reset() - await super().close() - - def _row_to_python(self, rowdata: Any, desc: Any = None) -> Any: - """Convert row data from MySQL to Python types - - The conversion is done while reading binary data in the protocol module. - """ - - async def _handle_result(self, result: ResultType) -> None: - """Handle result after execution""" - if isinstance(result, dict): - self._connection.unread_result = False - self._have_result = False - await self._handle_noresultset(result) - else: - self._description = result[1] - self._connection.unread_result = True - self._have_result = True - if "status_flag" in result[2]: # type: ignore[operator] - self._handle_server_status(result[2]["status_flag"]) - elif "server_status" in result[2]: # type: ignore[operator] - self._handle_server_status(result[2]["server_status"]) - - async def execute( - self, - operation: StrOrBytes, - params: Optional[ParamsSequenceOrDictType] = None, - map_results: bool = False, - ) -> None: - """Prepare and execute a MySQL Prepared Statement - - This method will prepare the given operation and execute it using - the optionally given parameters. - - If the cursor instance already had a prepared statement, it is - first closed. - - *Argument "map_results" is unused as multi statement execution - is not supported for prepared statements*. - - Raises: - ProgrammingError: When providing a multi statement operation - or setting *map_results* to True. - """ - if map_results: - raise ProgrammingError( - "Multi statement execution not supported for prepared statements." - ) - - if not self._connection: - raise ProgrammingError("Cursor is not connected") - - if not operation: - return None - - charset = self._connection.charset - if charset == "utf8mb4": - charset = "utf8" - - if not isinstance(operation, str): - try: - operation = operation.decode(charset) - except UnicodeDecodeError as err: - raise ProgrammingError(str(err)) from err - - if isinstance(params, dict): - replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation) - try: - # Replace params dict with params tuple in correct order - params = tuple(params[key] for key in replacement_keys) - except KeyError as err: - raise ProgrammingError( - "Not all placeholders were found in the parameters dict" - ) from err - # Convert %(name)s to ? before sending it to MySQL - operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation) - - if operation is not self._executed: - if self._prepared: - await self._connection.cmd_stmt_close( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - self._executed = operation - - try: - operation = operation.encode(charset) - except UnicodeEncodeError as err: - raise ProgrammingError(str(err)) from err - - if b"%s" in operation: - # Convert %s to ? before sending it to MySQL - operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation) - - try: - self._prepared = await self._connection.cmd_stmt_prepare( - operation, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - except Error: - self._executed = None - raise - - await self._connection.cmd_stmt_reset( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - - if self._prepared["parameters"] and not params: - return - if params: - if not isinstance(params, (tuple, list)): - raise ProgrammingError( - errno=1210, - msg="Incorrect type of argument: " - f"{type(params).__name__}({params})" - ", it must be of type tuple or list the argument given to " - "the prepared statement", - ) - if len(self._prepared["parameters"]) != len(params): - raise ProgrammingError( - errno=1210, - msg="Incorrect number of arguments executing prepared statement", - ) - - if params is None: - params = () - res = await self._connection.cmd_stmt_execute( - self._prepared["statement_id"], - data=params, - parameters=self._prepared["parameters"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - await self._handle_result(res) - - async def executemany( - self, - operation: str, - seq_params: Sequence[ParamsSequenceType], - ) -> None: - """Prepare and execute a MySQL Prepared Statement many times - - This method will prepare the given operation and execute with each - tuple found the list seq_params. - - If the cursor instance already had a prepared statement, it is - first closed. - - executemany() simply calls execute(). - """ - if not operation or not seq_params: - return None - await self._connection.handle_unread_result() - - try: - _ = iter(seq_params) - except TypeError as err: - raise ProgrammingError("Parameters for query must be an Iterable") from err - - rowcnt = 0 - try: - for params in seq_params: - await self.execute(operation, params) - if self.with_rows and self._have_unread_result(): - await self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - self._rowcount = rowcnt - - async def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - if self._cursor_exists: - await self._connection.cmd_stmt_fetch( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - return await self._fetch_row() or None - - async def fetchmany(self, size: Optional[int] = None) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0 and self._have_unread_result(): - cnt -= 1 - row = await self._fetch_row() - if row: - res.append(row) - return res - - async def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - rows = [] - if self._nextrow[0]: - rows.append(self._nextrow[0]) - while self._have_unread_result(): - if self._cursor_exists: - await self._connection.cmd_stmt_fetch( - self._prepared["statement_id"], - MAX_RESULTS, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - tmp, eof = await self._connection.get_rows( - binary=self._binary, - columns=self.description, - read_timeout=self._read_timeout, - ) - rows.extend(tmp) - await self._handle_eof(eof) - self._rowcount = len(rows) - return rows - - -class MySQLCursorPreparedDict(MySQLCursorDict, MySQLCursorPrepared): # type: ignore[misc] - """ - This class is a blend of features from MySQLCursorDict and MySQLCursorPrepared - - Multiple inheritance in python is allowed but care must be taken - when assuming methods resolution. In the case of multiple - inheritance, a given attribute is first searched in the current - class if it's not found then it's searched in the parent classes. - The parent classes are searched in a left-right fashion and each - class is searched once. - Based on python's attribute resolution, in this case, attributes - are searched as follows: - 1. MySQLCursorPreparedDict (current class) - 2. MySQLCursorDict (left parent class) - 3. MySQLCursorPrepared (right parent class) - 4. MySQLCursor (base class) - """ - - async def fetchmany( - self, size: Optional[int] = None - ) -> List[Dict[str, RowItemType]]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set represented - as a list of dictionaries where column names are used as keys. - """ - return [ - self._row_to_python(row, self.description) - for row in await super().fetchmany(size=size) - if row - ] diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/logger.py b/server/venv/Lib/site-packages/mysql/connector/aio/logger.py deleted file mode 100644 index bdb7035..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/logger.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Setup of the `mysql.connector.aio` logger.""" - -import logging - -logger = logging.getLogger("mysql.connector.aio") diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/network.py b/server/venv/Lib/site-packages/mysql/connector/aio/network.py deleted file mode 100644 index 60a4340..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/network.py +++ /dev/null @@ -1,765 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# pylint: disable=dangerous-default-value - -"""Module implementing low-level socket communication with MySQL servers.""" - - -__all__ = ["MySQLTcpSocket", "MySQLUnixSocket"] - -import asyncio -import struct -import zlib - -try: - import ssl - - TLS_VERSIONS = { - "TLSv1": ssl.PROTOCOL_TLSv1, - "TLSv1.1": ssl.PROTOCOL_TLSv1_1, - "TLSv1.2": ssl.PROTOCOL_TLSv1_2, - "TLSv1.3": ssl.PROTOCOL_TLS, - } -except ImportError: - ssl = None - -from abc import ABC, abstractmethod -from collections import deque -from typing import Any, Deque, List, Optional, Tuple - -from ..errors import ( - InterfaceError, - NotSupportedError, - OperationalError, - ProgrammingError, - ReadTimeoutError, - WriteTimeoutError, -) -from ..network import ( - COMPRESSED_PACKET_HEADER_LENGTH, - MAX_PAYLOAD_LENGTH, - MIN_COMPRESS_LENGTH, - PACKET_HEADER_LENGTH, -) -from .utils import StreamWriter, open_connection - - -def _strioerror(err: IOError) -> str: - """Reformat the IOError error message. - - This function reformats the IOError error message. - """ - return str(err) if not err.errno else f"{err.errno} {err.strerror}" - - -class NetworkBroker(ABC): - """Broker class interface. - - The network object is a broker used as a delegate by a socket object. Whenever the - socket wants to deliver or get packets to or from the MySQL server it needs to rely - on its network broker (netbroker). - - The netbroker sends `payloads` and receives `packets`. - - A packet is a bytes sequence, it has a header and body (referred to as payload). - The first `PACKET_HEADER_LENGTH` or `COMPRESSED_PACKET_HEADER_LENGTH` - (as appropriate) bytes correspond to the `header`, the remaining ones represent the - `payload`. - - The maximum payload length allowed to be sent per packet to the server is - `MAX_PAYLOAD_LENGTH`. When `send` is called with a payload whose length is greater - than `MAX_PAYLOAD_LENGTH` the netbroker breaks it down into packets, so the caller - of `send` can provide payloads of arbitrary length. - - Finally, data received by the netbroker comes directly from the server, expect to - get a packet for each call to `recv`. The received packet contains a header and - payload, the latter respecting `MAX_PAYLOAD_LENGTH`. - """ - - @abstractmethod - async def write( - self, - writer: StreamWriter, - address: str, - payload: bytes, - packet_number: Optional[int] = None, - compressed_packet_number: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - """Send `payload` to the MySQL server. - - If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is - broken down into packets. - - Args: - sock: Object holding the socket connection. - address: Socket's location. - payload: Packet's body to send. - packet_number: Sequence id (packet ID) to attach to the header when sending - plain packets. - compressed_packet_number: Same as `packet_number` but used when sending - compressed packets. - write_timeout: Timeout in seconds before which sending a packet to the server - should finish else WriteTimeoutError is raised. - - - Raises: - :class:`OperationalError`: If something goes wrong while sending packets to - the MySQL server. - """ - - @abstractmethod - async def read( - self, - reader: asyncio.StreamReader, - address: str, - read_timeout: Optional[int] = None, - ) -> bytearray: - """Get the next available packet from the MySQL server. - - Args: - sock: Object holding the socket connection. - address: Socket's location. - read_timeout: Timeout in seconds before which reading a packet from the server - should finish. - - Returns: - packet: A packet from the MySQL server. - - Raises: - :class:`OperationalError`: If something goes wrong while receiving packets - from the MySQL server. - :class:`ReadTimeoutError`: If the time to receive a packet from the server takes - longer than `read_timeout`. - :class:`InterfaceError`: If something goes wrong while receiving packets - from the MySQL server. - """ - - -class NetworkBrokerPlain(NetworkBroker): - """Broker class for MySQL socket communication.""" - - def __init__(self) -> None: - self._pktnr: int = -1 # packet number - - @staticmethod - def get_header(pkt: bytes) -> Tuple[int, int]: - """Recover the header information from a packet.""" - if len(pkt) < PACKET_HEADER_LENGTH: - raise ValueError("Can't recover header info from an incomplete packet") - - pll, seqid = ( - struct.unpack(" None: - """Set the given packet id, if any, else increment packet id.""" - if next_id is None: - self._pktnr += 1 - else: - self._pktnr = next_id - self._pktnr %= 256 - - async def _write_pkt( - self, - writer: StreamWriter, - address: str, - pkt: bytes, - ) -> None: - """Write packet to the comm channel.""" - try: - writer.write(pkt) - await writer.drain() - except IOError as err: - raise OperationalError( - errno=2055, values=(address, _strioerror(err)) - ) from err - except AttributeError as err: - raise OperationalError(errno=2006) from err - - async def _read_chunk( - self, - reader: asyncio.StreamReader, - size: int = 0, - read_timeout: Optional[int] = None, - ) -> bytearray: - """Read `size` bytes from the comm channel.""" - try: - pkt = bytearray(b"") - while len(pkt) < size: - chunk = await asyncio.wait_for( - reader.read(size - len(pkt)), read_timeout - ) - if not chunk: - raise InterfaceError(errno=2013) - pkt += chunk - return pkt - except asyncio.TimeoutError as err: - raise ReadTimeoutError(errno=3024) from err - except asyncio.CancelledError as err: - raise err - - async def write( - self, - writer: StreamWriter, - address: str, - payload: bytes, - packet_number: Optional[int] = None, - compressed_packet_number: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - """Send payload to the MySQL server. - - If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is - broken down into packets. - """ - self._set_next_pktnr(packet_number) - # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH the length is - # set to 2^24 - 1 (ff ff ff) and additional packets are sent with the rest of - # the payload until the payload of a packet is less than MAX_PAYLOAD_LENGTH. - offset = 0 - try: - for _ in range(len(payload) // MAX_PAYLOAD_LENGTH): - # payload_len, sequence_id, payload - await asyncio.wait_for( - self._write_pkt( - writer, - address, - b"\xff" * 3 - + struct.pack(" bytearray: - """Receive `one` packet from the MySQL server.""" - try: - # Read the header of the MySQL packet. - header = await self._read_chunk(reader, PACKET_HEADER_LENGTH, read_timeout) - - # Pull the payload length and sequence id. - payload_len, self._pktnr = self.get_header(header) - - # Read the payload, and return packet. - return header + await self._read_chunk(reader, payload_len, read_timeout) - except IOError as err: - raise OperationalError( - errno=2055, values=(address, _strioerror(err)) - ) from err - - -class NetworkBrokerCompressed(NetworkBrokerPlain): - """Broker class for MySQL socket communication.""" - - def __init__(self) -> None: - super().__init__() - self._compressed_pktnr = -1 - self._queue_read: Deque[bytearray] = deque() - - @staticmethod - def _prepare_packets(payload: bytes, pktnr: int) -> List[bytes]: - """Prepare a payload for sending to the MySQL server.""" - offset = 0 - pkts = [] - - # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH the length is - # set to 2^24 - 1 (ff ff ff) and additional packets are sent with the rest of - # the payload until the payload of a packet is less than MAX_PAYLOAD_LENGTH. - for _ in range(len(payload) // MAX_PAYLOAD_LENGTH): - # payload length + sequence id + payload - pkts.append( - b"\xff" * 3 - + struct.pack(" Tuple[int, int, int]: # type: ignore[override] - """Recover the header information from a packet.""" - if len(pkt) < COMPRESSED_PACKET_HEADER_LENGTH: - raise ValueError("Can't recover header info from an incomplete packet") - - compressed_pll, seqid, uncompressed_pll = ( - struct.unpack(" None: - """Set the given packet id, if any, else increment packet id.""" - if next_id is None: - self._compressed_pktnr += 1 - else: - self._compressed_pktnr = next_id - self._compressed_pktnr %= 256 - - async def _write_pkt( - self, - writer: StreamWriter, - address: str, - pkt: bytes, - ) -> None: - """Compress packet and write it to the comm channel.""" - compressed_pkt = zlib.compress(pkt) - pkt = ( - struct.pack(" None: - """Send `payload` as compressed packets to the MySQL server. - - If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is - broken down into packets. - """ - # Get next packet numbers. - self._set_next_pktnr(packet_number) - self._set_next_compressed_pktnr(compressed_packet_number) - try: - payload_prep = bytearray(b"").join( - self._prepare_packets(payload, self._pktnr) - ) - if len(payload) >= MAX_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH: - # Sending a MySQL payload of the size greater or equal to 2^24 - 5 via - # compression leads to at least one extra compressed packet WHY? let's say - # len(payload) is MAX_PAYLOAD_LENGTH - 3; when preparing the payload, a - # header of size PACKET_HEADER_LENGTH is pre-appended to the payload. - # This means that len(payload_prep) is - # MAX_PAYLOAD_LENGTH - 3 + PACKET_HEADER_LENGTH = MAX_PAYLOAD_LENGTH + 1 - # surpassing the maximum allowed payload size per packet. - offset = 0 - - # Send several MySQL packets. - for _ in range(len(payload_prep) // MAX_PAYLOAD_LENGTH): - await asyncio.wait_for( - self._write_pkt( - writer, - address, - payload_prep[offset : offset + MAX_PAYLOAD_LENGTH], - ), - write_timeout, - ) - self._set_next_compressed_pktnr() - offset += MAX_PAYLOAD_LENGTH - await asyncio.wait_for( - self._write_pkt(writer, address, payload_prep[offset:]), - write_timeout, - ) - else: - # Send one MySQL packet. - # For small packets it may be too costly to compress the packet. - # Usually payloads less than 50 bytes (MIN_COMPRESS_LENGTH) aren't - # compressed (see MySQL source code Documentation). - if len(payload) > MIN_COMPRESS_LENGTH: - # Perform compression. - await asyncio.wait_for( - self._write_pkt(writer, address, payload_prep), write_timeout - ) - else: - # Skip compression. - await asyncio.wait_for( - super()._write_pkt( - writer, - address, - struct.pack(" None: - """Handle reading of a compressed packet.""" - # compressed_pll stands for compressed payload length. - pkt = bytearray( - zlib.decompress( - await super()._read_chunk(reader, compressed_pll, read_timeout) - ) - ) - offset = 0 - while offset < len(pkt): - # pll stands for payload length - pll = struct.unpack( - " len(pkt) - offset: - # More bytes need to be consumed. - # Read the header of the next MySQL packet. - header = await super()._read_chunk( - reader, COMPRESSED_PACKET_HEADER_LENGTH, read_timeout - ) - - # compressed payload length, sequence id, uncompressed payload length. - ( - compressed_pll, - self._compressed_pktnr, - uncompressed_pll, - ) = self.get_header(header) - compressed_pkt = await super()._read_chunk( - reader, compressed_pll, read_timeout - ) - - # Recalling that if uncompressed payload length == 0, the packet comes - # in uncompressed, so no decompression is needed. - pkt += ( - compressed_pkt - if uncompressed_pll == 0 - else zlib.decompress(compressed_pkt) - ) - - self._queue_read.append(pkt[offset : offset + PACKET_HEADER_LENGTH + pll]) - offset += PACKET_HEADER_LENGTH + pll - - async def read( - self, - reader: asyncio.StreamReader, - address: str, - read_timeout: Optional[int] = None, - ) -> bytearray: - """Receive `one` or `several` packets from the MySQL server, enqueue them, and - return the packet at the head. - """ - - if not self._queue_read: - try: - # Read the header of the next MySQL packet. - header = await super()._read_chunk( - reader, COMPRESSED_PACKET_HEADER_LENGTH, read_timeout - ) - - # compressed payload length, sequence id, uncompressed payload length - ( - compressed_pll, - self._compressed_pktnr, - uncompressed_pll, - ) = self.get_header(header) - - if uncompressed_pll == 0: - # Packet is not compressed, so just store it. - self._queue_read.append( - await super()._read_chunk(reader, compressed_pll, read_timeout) - ) - else: - # Packet comes in compressed, further action is needed. - await self._read_compressed_pkt( - reader, compressed_pll, read_timeout - ) - except IOError as err: - raise OperationalError( - errno=2055, values=(address, _strioerror(err)) - ) from err - - if not self._queue_read: - return None - - pkt = self._queue_read.popleft() - self._pktnr = pkt[3] - - return pkt - - -class MySQLSocket(ABC): - """MySQL socket communication interface. - - Examples: - Subclasses: network.MySQLTCPSocket and network.MySQLUnixSocket. - """ - - def __init__(self) -> None: - """Network layer where transactions are made with plain (uncompressed) packets - is enabled by default. - """ - self._reader: Optional[asyncio.StreamReader] = None - self._writer: Optional[StreamWriter] = None - self._connection_timeout: Optional[int] = None - self._address: Optional[str] = None - self._netbroker: NetworkBroker = NetworkBrokerPlain() - self._is_connected: bool = False - - @property - def address(self) -> str: - """Socket location.""" - return self._address - - @abstractmethod - async def open_connection(self, **kwargs: Any) -> None: - """Open the socket.""" - - async def close_connection(self) -> None: - """Close the connection.""" - if self._writer: - try: - self._writer.close() - # Without transport.abort(), an error is raised when using SSL - if self._writer.transport is not None: - self._writer.transport.abort() - await self._writer.wait_closed() - except Exception as _: # pylint: disable=broad-exception-caught) - # we can ignore issues like ConnectionRefused or ConnectionAborted - # as these instances might popup if the connection was closed due to timeout issues - pass - self._is_connected = False - - def is_connected(self) -> bool: - """Check if the socket is connected. - - Return: - bool: Returns `True` if the socket is connected to MySQL server. - """ - return self._is_connected - - def set_connection_timeout(self, timeout: int) -> None: - """Set the connection timeout.""" - self._connection_timeout = timeout - - def switch_to_compressed_mode(self) -> None: - """Enable network layer where transactions are made with compressed packets.""" - self._netbroker = NetworkBrokerCompressed() - - async def switch_to_ssl(self, ssl_context: ssl.SSLContext) -> None: - """Upgrade an existing stream-based connection to TLS. - - The `start_tls()` method from `asyncio.streams.StreamWriter` is only available - in Python 3.11. This method is used as a workaround. - - The MySQL TLS negotiation happens in the middle of the TCP connection. - Therefore, passing a socket to open connection will cause it to negotiate - TLS on an existing connection. - - Args: - ssl_context: The SSL Context to be used. - - Raises: - RuntimeError: If the transport does not expose the socket instance. - """ - # Ensure that self._writer is already created - assert self._writer is not None - - socket = self._writer.transport.get_extra_info("socket") - if socket.family == 1: # socket.AF_UNIX - raise ProgrammingError("SSL is not supported when using Unix sockets") - - await self._writer.start_tls(ssl_context) - - async def write( - self, - payload: bytes, - packet_number: Optional[int] = None, - compressed_packet_number: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - """Send packets to the MySQL server.""" - await self._netbroker.write( - self._writer, - self.address, - payload, - packet_number=packet_number, - compressed_packet_number=compressed_packet_number, - write_timeout=write_timeout, - ) - - async def read(self, read_timeout: Optional[int] = None) -> bytearray: - """Read packets from the MySQL server.""" - return await self._netbroker.read(self._reader, self.address, read_timeout) - - def build_ssl_context( - self, - ssl_ca: Optional[str] = None, - ssl_cert: Optional[str] = None, - ssl_key: Optional[str] = None, - ssl_verify_cert: Optional[bool] = False, - ssl_verify_identity: Optional[bool] = False, - tls_versions: Optional[List[str]] = [], - tls_cipher_suites: Optional[List[str]] = [], - ) -> ssl.SSLContext: - """Build a SSLContext.""" - tls_version: Optional[str] = None - - if not self._reader: - raise InterfaceError(errno=2048) - - if ssl is None: - raise RuntimeError("Python installation has no SSL support") - - try: - if tls_versions: - tls_versions.sort(reverse=True) - tls_version = tls_versions[0] - ssl_protocol = TLS_VERSIONS[tls_version] - context = ssl.SSLContext(ssl_protocol) - - if tls_version == "TLSv1.3": - if "TLSv1.2" not in tls_versions: - context.options |= ssl.OP_NO_TLSv1_2 - if "TLSv1.1" not in tls_versions: - context.options |= ssl.OP_NO_TLSv1_1 - if "TLSv1" not in tls_versions: - context.options |= ssl.OP_NO_TLSv1 - else: - context = ssl.create_default_context() - - context.check_hostname = ssl_verify_identity - - if ssl_verify_cert: - context.verify_mode = ssl.CERT_REQUIRED - elif ssl_verify_identity: - context.verify_mode = ssl.CERT_OPTIONAL - else: - context.verify_mode = ssl.CERT_NONE - - context.load_default_certs() - - if ssl_ca: - try: - context.load_verify_locations(ssl_ca) - except (IOError, ssl.SSLError) as err: - raise InterfaceError(f"Invalid CA Certificate: {err}") from err - if ssl_cert: - try: - context.load_cert_chain(ssl_cert, ssl_key) - except (IOError, ssl.SSLError) as err: - raise InterfaceError(f"Invalid Certificate/Key: {err}") from err - - # TLSv1.3 ciphers cannot be disabled with `SSLContext.set_ciphers(...)`, - # see https://docs.python.org/3/library/ssl.html#ssl.SSLContext.set_ciphers. - if tls_cipher_suites and tls_version == "TLSv1.2": - context.set_ciphers(":".join(tls_cipher_suites)) - - return context - except NameError as err: - raise NotSupportedError("Python installation has no SSL support") from err - except ( - IOError, - NotImplementedError, - ssl.CertificateError, - ssl.SSLError, - ) as err: - raise InterfaceError(str(err)) from err - - -class MySQLTcpSocket(MySQLSocket): - """MySQL socket class using TCP/IP. - - Args: - host: MySQL host name. - port: MySQL port. - force_ipv6: Force IPv6 usage. - """ - - def __init__( - self, host: str = "127.0.0.1", port: int = 3306, force_ipv6: bool = False - ): - super().__init__() - self._host: str = host - self._port: int = port - self._force_ipv6: bool = force_ipv6 - self._address: str = f"{host}:{port}" - - async def open_connection(self, **kwargs: Any) -> None: - """Open TCP/IP connection.""" - self._reader, self._writer = await open_connection( - host=self._host, port=self._port, **kwargs - ) - self._is_connected = True - - -class MySQLUnixSocket(MySQLSocket): - """MySQL socket class using UNIX sockets. - - Args: - unix_socket: UNIX socket file path. - """ - - def __init__(self, unix_socket: str = "/tmp/mysql.sock"): - super().__init__() - self._address: str = unix_socket - - async def open_connection(self, **kwargs: Any) -> None: - """Open UNIX socket connection.""" - ( - self._reader, - self._writer, - ) = await asyncio.open_unix_connection( # type: ignore[assignment] - path=self._address, **kwargs - ) - self._is_connected = True diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/__init__.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/__init__.py deleted file mode 100644 index 015700e..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/__init__.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Base Authentication Plugin class.""" - -__all__ = ["MySQLAuthPlugin", "get_auth_plugin"] - -import importlib - -from abc import ABC, abstractmethod -from functools import lru_cache -from typing import TYPE_CHECKING, Any, Optional, Type - -from mysql.connector.errors import NotSupportedError, ProgrammingError -from mysql.connector.logger import logger - -if TYPE_CHECKING: - from ..network import MySQLSocket - -DEFAULT_PLUGINS_PKG = "mysql.connector.aio.plugins" - - -class MySQLAuthPlugin(ABC): - """Authorization plugin interface.""" - - def __init__( - self, - username: str, - password: str, - ssl_enabled: bool = False, - ) -> None: - """Constructor.""" - self._username: str = "" if username is None else username - self._password: str = "" if password is None else password - self._ssl_enabled: bool = ssl_enabled - - @property - def ssl_enabled(self) -> bool: - """Signals whether or not SSL is enabled.""" - return self._ssl_enabled - - @property - @abstractmethod - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - - @property - @abstractmethod - def name(self) -> str: - """Plugin official name.""" - - @abstractmethod - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Make the client's authorization response. - - Args: - auth_data: Authorization data. - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Client's authorization response. - """ - - async def auth_more_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth more data` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Authentication method data (from a packet representing - an `auth more data` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth communication. - """ - raise NotImplementedError - - @abstractmethod - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth communication. - """ - - -@lru_cache(maxsize=10, typed=False) -def get_auth_plugin( - plugin_name: str, - auth_plugin_class: Optional[str] = None, -) -> Type[MySQLAuthPlugin]: - """Return authentication class based on plugin name - - This function returns the class for the authentication plugin plugin_name. - The returned class is a subclass of BaseAuthPlugin. - - Args: - plugin_name (str): Authentication plugin name. - auth_plugin_class (str): Authentication plugin class name. - - Raises: - NotSupportedError: When plugin_name is not supported. - - Returns: - Subclass of `MySQLAuthPlugin`. - """ - package = DEFAULT_PLUGINS_PKG - if plugin_name: - try: - logger.info("package: %s", package) - logger.info("plugin_name: %s", plugin_name) - plugin_module = importlib.import_module(f".{plugin_name}", package) - if not auth_plugin_class or not hasattr(plugin_module, auth_plugin_class): - auth_plugin_class = plugin_module.AUTHENTICATION_PLUGIN_CLASS - logger.info("AUTHENTICATION_PLUGIN_CLASS: %s", auth_plugin_class) - return getattr(plugin_module, auth_plugin_class) - except ModuleNotFoundError as err: - logger.warning("Requested Module was not found: %s", err) - except ValueError as err: - raise ProgrammingError(f"Invalid module name: {err}") from err - raise NotSupportedError(f"Authentication plugin '{plugin_name}' is not supported") diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_kerberos_client.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_kerberos_client.py deleted file mode 100644 index 73dc753..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_kerberos_client.py +++ /dev/null @@ -1,577 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="str-bytes-safe,misc" - -"""Kerberos Authentication Plugin.""" - -import getpass -import os -import struct - -from abc import abstractmethod -from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional, Tuple - -from mysql.connector.errors import InterfaceError, ProgrammingError -from mysql.connector.logger import logger - -from ..authentication import ERR_STATUS - -if TYPE_CHECKING: - from ..network import MySQLSocket - -try: - import gssapi -except ImportError: - gssapi = None - if os.name != "nt": - raise ProgrammingError( - "Module gssapi is required for GSSAPI authentication " - "mechanism but was not found. Unable to authenticate " - "with the server" - ) from None - -try: - import sspi - import sspicon -except ImportError: - sspi = None - sspicon = None - -from . import MySQLAuthPlugin - -AUTHENTICATION_PLUGIN_CLASS = ( - "MySQLSSPIKerberosAuthPlugin" if os.name == "nt" else "MySQLKerberosAuthPlugin" -) - - -class MySQLBaseKerberosAuthPlugin(MySQLAuthPlugin): - """Base class for the MySQL Kerberos authentication plugin.""" - - @property - def name(self) -> str: - """Plugin official name.""" - return "authentication_kerberos_client" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - @abstractmethod - def auth_continue( - self, tgt_auth_challenge: Optional[bytes] - ) -> Tuple[Optional[bytes], bool]: - """Continue with the Kerberos TGT service request. - - With the TGT authentication service given response generate a TGT - service request. This method must be invoked sequentially (in a loop) - until the security context is completed and an empty response needs to - be send to acknowledge the server. - - Args: - tgt_auth_challenge: the challenge for the negotiation. - - Returns: - tuple (bytearray TGS service request, - bool True if context is completed otherwise False). - """ - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - logger.debug("# auth_data: %s", auth_data) - response = self.auth_response(auth_data, ignore_auth_data=False, **kwargs) - if response is None: - raise InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - packet = await sock.read() - logger.debug("# server response packet: %s", packet) - - if packet != ERR_STATUS: - rcode_size = 5 # Reader size for the response status code - logger.debug("# Continue with GSSAPI authentication") - logger.debug("# Response header: %s", packet[: rcode_size + 1]) - logger.debug("# Response size: %s", len(packet)) - logger.debug("# Negotiate a service request") - complete = False - tries = 0 - - while not complete and tries < 5: - logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20) - logger.debug("<< Server response: %s", packet) - logger.debug("# Response code: %s", packet[: rcode_size + 1]) - token, complete = self.auth_continue(packet[rcode_size:]) - if token: - await sock.write(token) - if complete: - break - packet = await sock.read() - - logger.debug(">> Response to server: %s", token) - tries += 1 - - if not complete: - raise InterfaceError( - f"Unable to fulfill server request after {tries} " - f"attempts. Last server response: {packet}" - ) - - logger.debug( - "Last response from server: %s length: %d", - packet, - len(packet), - ) - - # Receive OK packet from server. - packet = await sock.read() - logger.debug("<< Ok packet from server: %s", packet) - - return bytes(packet) - - -# pylint: disable=c-extension-no-member,no-member -class MySQLKerberosAuthPlugin(MySQLBaseKerberosAuthPlugin): - """Implement the MySQL Kerberos authentication plugin.""" - - context: Optional[gssapi.SecurityContext] = None - - @staticmethod - def get_user_from_credentials() -> str: - """Get user from credentials without realm.""" - try: - creds = gssapi.Credentials(usage="initiate") - user = str(creds.name) - if user.find("@") != -1: - user, _ = user.split("@", 1) - return user - except gssapi.raw.misc.GSSError: - return getpass.getuser() - - @staticmethod - def get_store() -> dict: - """Get a credentials store dictionary. - - Returns: - dict: Credentials store dictionary with the krb5 ccache name. - - Raises: - InterfaceError: If 'KRB5CCNAME' environment variable is empty. - """ - krb5ccname = os.environ.get( - "KRB5CCNAME", - ( - f"/tmp/krb5cc_{os.getuid()}" - if os.name == "posix" - else Path("%TEMP%").joinpath("krb5cc") - ), - ) - if not krb5ccname: - raise InterfaceError( - "The 'KRB5CCNAME' environment variable is set to empty" - ) - logger.debug("Using krb5 ccache name: FILE:%s", krb5ccname) - store = {b"ccache": f"FILE:{krb5ccname}".encode("utf-8")} - return store - - def _acquire_cred_with_password(self, upn: str) -> gssapi.raw.creds.Creds: - """Acquire and store credentials through provided password. - - Args: - upn (str): User Principal Name. - - Returns: - gssapi.raw.creds.Creds: GSSAPI credentials. - """ - logger.debug("Attempt to acquire credentials through provided password") - user = gssapi.Name(upn, gssapi.NameType.user) - password = self._password.encode("utf-8") - - try: - acquire_cred_result = gssapi.raw.acquire_cred_with_password( - user, password, usage="initiate" - ) - creds = acquire_cred_result.creds - gssapi.raw.store_cred_into( - self.get_store(), - creds=creds, - mech=gssapi.MechType.kerberos, - overwrite=True, - set_default=True, - ) - except gssapi.raw.misc.GSSError as err: - raise ProgrammingError( - f"Unable to acquire credentials with the given password: {err}" - ) from err - return creds - - @staticmethod - def _parse_auth_data(packet: bytes) -> Tuple[str, str]: - """Parse authentication data. - - Get the SPN and REALM from the authentication data packet. - - Format: - SPN string length two bytes + - SPN string + - UPN realm string length two bytes + - UPN realm string - - Returns: - tuple: With 'spn' and 'realm'. - """ - spn_len = struct.unpack(" Optional[bytes]: - """Prepare the first message to the server.""" - spn = None - realm = None - - if auth_data and not kwargs.get("ignore_auth_data", True): - try: - spn, realm = self._parse_auth_data(auth_data) - except struct.error as err: - raise InterruptedError(f"Invalid authentication data: {err}") from err - - if spn is None: - return self._password.encode() + b"\x00" - - upn = f"{self._username}@{realm}" if self._username else None - - logger.debug("Service Principal: %s", spn) - logger.debug("Realm: %s", realm) - - try: - # Attempt to retrieve credentials from cache file - creds: Any = gssapi.Credentials(usage="initiate") - creds_upn = str(creds.name) - - logger.debug("Cached credentials found") - logger.debug("Cached credentials UPN: %s", creds_upn) - - # Remove the realm from user - if creds_upn.find("@") != -1: - creds_user, creds_realm = creds_upn.split("@", 1) - else: - creds_user = creds_upn - creds_realm = None - - upn = f"{self._username}@{realm}" if self._username else creds_upn - - # The user from cached credentials matches with the given user? - if self._username and self._username != creds_user: - logger.debug( - "The user from cached credentials doesn't match with the " - "given user" - ) - if self._password is not None: - creds = self._acquire_cred_with_password(upn) - if creds_realm and creds_realm != realm and self._password is not None: - creds = self._acquire_cred_with_password(upn) - except gssapi.raw.exceptions.ExpiredCredentialsError as err: - if upn and self._password is not None: - creds = self._acquire_cred_with_password(upn) - else: - raise InterfaceError(f"Credentials has expired: {err}") from err - except gssapi.raw.misc.GSSError as err: - if upn and self._password is not None: - creds = self._acquire_cred_with_password(upn) - else: - raise InterfaceError( - f"Unable to retrieve cached credentials error: {err}" - ) from err - - flags = ( - gssapi.RequirementFlag.mutual_authentication, - gssapi.RequirementFlag.extended_error, - gssapi.RequirementFlag.delegate_to_peer, - ) - name = gssapi.Name(spn, name_type=gssapi.NameType.kerberos_principal) - cname = name.canonicalize(gssapi.MechType.kerberos) - self.context = gssapi.SecurityContext( - name=cname, creds=creds, flags=sum(flags), usage="initiate" - ) - - try: - initial_client_token: Optional[bytes] = self.context.step() - except gssapi.raw.misc.GSSError as err: - raise InterfaceError(f"Unable to initiate security context: {err}") from err - - logger.debug("Initial client token: %s", initial_client_token) - return initial_client_token - - def auth_continue( - self, tgt_auth_challenge: Optional[bytes] - ) -> Tuple[Optional[bytes], bool]: - """Continue with the Kerberos TGT service request. - - With the TGT authentication service given response generate a TGT - service request. This method must be invoked sequentially (in a loop) - until the security context is completed and an empty response needs to - be send to acknowledge the server. - - Args: - tgt_auth_challenge: the challenge for the negotiation. - - Returns: - tuple (bytearray TGS service request, - bool True if context is completed otherwise False). - """ - logger.debug("tgt_auth challenge: %s", tgt_auth_challenge) - - resp: Optional[bytes] = self.context.step(tgt_auth_challenge) - - logger.debug("Context step response: %s", resp) - logger.debug("Context completed?: %s", self.context.complete) - - return resp, self.context.complete - - def auth_accept_close_handshake(self, message: bytes) -> bytes: - """Accept handshake and generate closing handshake message for server. - - This method verifies the server authenticity from the given message - and included signature and generates the closing handshake for the - server. - - When this method is invoked the security context is already established - and the client and server can send GSSAPI formated secure messages. - - To finish the authentication handshake the server sends a message - with the security layer availability and the maximum buffer size. - - Since the connector only uses the GSSAPI authentication mechanism to - authenticate the user with the server, the server will verify clients - message signature and terminate the GSSAPI authentication and send two - messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a - OK packet (that must be received after sent the returned message from - this method). - - Args: - message: a wrapped gssapi message from the server. - - Returns: - bytearray (closing handshake message to be send to the server). - """ - if not self.context.complete: - raise ProgrammingError("Security context is not completed") - logger.debug("Server message: %s", message) - logger.debug("GSSAPI flags in use: %s", self.context.actual_flags) - try: - unwraped = self.context.unwrap(message) - logger.debug("Unwraped: %s", unwraped) - except gssapi.raw.exceptions.BadMICError as err: - logger.debug("Unable to unwrap server message: %s", err) - raise InterfaceError(f"Unable to unwrap server message: {err}") from err - - logger.debug("Unwrapped server message: %s", unwraped) - # The message contents for the clients closing message: - # - security level 1 byte, must be always 1. - # - conciliated buffer size 3 bytes, without importance as no - # further GSSAPI messages will be sends. - response = bytearray(b"\x01\x00\x00\00") - # Closing handshake must not be encrypted. - logger.debug("Message response: %s", response) - wraped = self.context.wrap(response, encrypt=False) - logger.debug( - "Wrapped message response: %s, length: %d", - wraped[0], - len(wraped[0]), - ) - - return wraped.message - - -class MySQLSSPIKerberosAuthPlugin(MySQLBaseKerberosAuthPlugin): - """Implement the MySQL Kerberos authentication plugin with Windows SSPI""" - - context: Any = None - clientauth: Any = None - - @staticmethod - def _parse_auth_data(packet: bytes) -> Tuple[str, str]: - """Parse authentication data. - - Get the SPN and REALM from the authentication data packet. - - Format: - SPN string length two bytes + - SPN string + - UPN realm string length two bytes + - UPN realm string - - Returns: - tuple: With 'spn' and 'realm'. - """ - spn_len = struct.unpack(" Optional[bytes]: - """Prepare the first message to the server. - - Args: - kwargs: - ignore_auth_data (bool): if True, the provided auth data is ignored. - """ - logger.debug("auth_response for sspi") - spn = None - realm = None - - if auth_data and not kwargs.get("ignore_auth_data", True): - try: - spn, realm = self._parse_auth_data(auth_data) - except struct.error as err: - raise InterruptedError(f"Invalid authentication data: {err}") from err - - logger.debug("Service Principal: %s", spn) - logger.debug("Realm: %s", realm) - - if sspicon is None or sspi is None: - raise ProgrammingError( - 'Package "pywin32" (Python for Win32 (pywin32) extensions)' - " is not installed." - ) - - flags = (sspicon.ISC_REQ_MUTUAL_AUTH, sspicon.ISC_REQ_DELEGATE) - - if self._username and self._password: - _auth_info = (self._username, realm, self._password) - else: - _auth_info = None - - targetspn = spn - logger.debug("targetspn: %s", targetspn) - logger.debug("_auth_info is None: %s", _auth_info is None) - - # The Security Support Provider Interface (SSPI) is an interface - # that allows us to choose from a set of SSPs available in the - # system; the idea of SSPI is to keep interface consistent no - # matter what back end (a.k.a., SSP) we choose. - - # When using SSPI we should not use Kerberos directly as SSP, - # as remarked in [2], but we can use it indirectly via another - # SSP named Negotiate that acts as an application layer between - # SSPI and the other SSPs [1]. - - # Negotiate can select between Kerberos and NTLM on the fly; - # it chooses Kerberos unless it cannot be used by one of the - # systems involved in the authentication or the calling - # application did not provide sufficient information to use - # Kerberos. - - # prefix: https://docs.microsoft.com/en-us/windows/win32/secauthn - # [1] prefix/microsoft-negotiate?source=recommendations - # [2] prefix/microsoft-kerberos?source=recommendations - self.clientauth = sspi.ClientAuth( - "Negotiate", - targetspn=targetspn, - auth_info=_auth_info, - scflags=sum(flags), - datarep=sspicon.SECURITY_NETWORK_DREP, - ) - - try: - data = None - err, out_buf = self.clientauth.authorize(data) - logger.debug("Context step err: %s", err) - logger.debug("Context step out_buf: %s", out_buf) - logger.debug("Context completed?: %s", self.clientauth.authenticated) - initial_client_token = out_buf[0].Buffer - logger.debug("pkg_info: %s", self.clientauth.pkg_info) - except Exception as err: - raise InterfaceError(f"Unable to initiate security context: {err}") from err - - logger.debug("Initial client token: %s", initial_client_token) - return initial_client_token - - def auth_continue( - self, tgt_auth_challenge: Optional[bytes] - ) -> Tuple[Optional[bytes], bool]: - """Continue with the Kerberos TGT service request. - - With the TGT authentication service given response generate a TGT - service request. This method must be invoked sequentially (in a loop) - until the security context is completed and an empty response needs to - be send to acknowledge the server. - - Args: - tgt_auth_challenge: the challenge for the negotiation. - - Returns: - tuple (bytearray TGS service request, - bool True if context is completed otherwise False). - """ - logger.debug("tgt_auth challenge: %s", tgt_auth_challenge) - - err, out_buf = self.clientauth.authorize(tgt_auth_challenge) - - logger.debug("Context step err: %s", err) - logger.debug("Context step out_buf: %s", out_buf) - resp = out_buf[0].Buffer - logger.debug("Context step resp: %s", resp) - logger.debug("Context completed?: %s", self.clientauth.authenticated) - - return resp, self.clientauth.authenticated diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_ldap_sasl_client.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_ldap_sasl_client.py deleted file mode 100644 index cf4d15b..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_ldap_sasl_client.py +++ /dev/null @@ -1,595 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""LDAP SASL Authentication Plugin.""" - -import hmac - -from base64 import b64decode, b64encode -from hashlib import sha1, sha256 -from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple -from uuid import uuid4 - -from mysql.connector.authentication import ERR_STATUS -from mysql.connector.errors import InterfaceError, ProgrammingError -from mysql.connector.logger import logger -from mysql.connector.types import StrOrBytes -from mysql.connector.utils import ( - normalize_unicode_string as norm_ustr, - validate_normalized_unicode_string as valid_norm, -) - -if TYPE_CHECKING: - from ..network import MySQLSocket - -try: - import gssapi -except ImportError: - raise ProgrammingError( - "Module gssapi is required for GSSAPI authentication " - "mechanism but was not found. Unable to authenticate " - "with the server" - ) from None - -from . import MySQLAuthPlugin - -AUTHENTICATION_PLUGIN_CLASS = "MySQLLdapSaslPasswordAuthPlugin" - - -# pylint: disable=c-extension-no-member,no-member -class MySQLLdapSaslPasswordAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL ldap sasl authentication plugin. - - The MySQL's ldap sasl authentication plugin support two authentication - methods SCRAM-SHA-1 and GSSAPI (using Kerberos). This implementation only - support SCRAM-SHA-1 and SCRAM-SHA-256. - - SCRAM-SHA-1 amd SCRAM-SHA-256 - This method requires 2 messages from client and 2 responses from - server. - - The first message from client will be generated by prepare_password(), - after receive the response from the server, it is required that this - response is passed back to auth_continue() which will return the - second message from the client. After send this second message to the - server, the second server respond needs to be passed to auth_finalize() - to finish the authentication process. - """ - - sasl_mechanisms: List[str] = ["SCRAM-SHA-1", "SCRAM-SHA-256", "GSSAPI"] - def_digest_mode: Callable = sha1 - client_nonce: Optional[str] = None - client_salt: Any = None - server_salt: Optional[str] = None - krb_service_principal: Optional[str] = None - iterations: int = 0 - server_auth_var: Optional[str] = None - target_name: Optional[gssapi.Name] = None - ctx: gssapi.SecurityContext = None - servers_first: Optional[str] = None - server_nonce: Optional[str] = None - - @staticmethod - def _xor(bytes1: bytes, bytes2: bytes) -> bytes: - return bytes([b1 ^ b2 for b1, b2 in zip(bytes1, bytes2)]) - - def _hmac(self, password: bytes, salt: bytes) -> bytes: - digest_maker = hmac.new(password, salt, self.def_digest_mode) - return digest_maker.digest() - - def _hi(self, password: str, salt: bytes, count: int) -> bytes: - """Prepares Hi - Hi(password, salt, iterations) where Hi(p,s,i) is defined as - PBKDF2 (HMAC, p, s, i, output length of H). - """ - pw = password.encode() - hi = self._hmac(pw, salt + b"\x00\x00\x00\x01") - aux = hi - for _ in range(count - 1): - aux = self._hmac(pw, aux) - hi = self._xor(hi, aux) - return hi - - @staticmethod - def _normalize(string: str) -> str: - norm_str = norm_ustr(string) - broken_rule = valid_norm(norm_str) - if broken_rule is not None: - raise InterfaceError(f"broken_rule: {broken_rule}") - return norm_str - - def _first_message(self) -> bytes: - """This method generates the first message to the server to start the - - The client-first message consists of a gs2-header, - the desired username, and a randomly generated client nonce cnonce. - - The first message from the server has the form: - b'n,a=,n=,r= - - Returns client's first message - """ - cfm_fprnat = "n,a={user_name},n={user_name},r={client_nonce}" - self.client_nonce = str(uuid4()).replace("-", "") - cfm: StrOrBytes = cfm_fprnat.format( - user_name=self._normalize(self._username), - client_nonce=self.client_nonce, - ) - - if isinstance(cfm, str): - cfm = cfm.encode("utf8") - return cfm - - def _first_message_krb(self) -> Optional[bytes]: - """Get a TGT Authentication request and initiates security context. - - This method will contact the Kerberos KDC in order of obtain a TGT. - """ - user_name = gssapi.raw.names.import_name( - self._username.encode("utf8"), name_type=gssapi.NameType.user - ) - - # Use defaults store = {'ccache': 'FILE:/tmp/krb5cc_1000'}#, - # 'keytab':'/etc/some.keytab' } - # Attempt to retrieve credential from default cache file. - try: - cred: Any = gssapi.Credentials() - logger.debug( - "# Stored credentials found, if password was given it will be ignored." - ) - try: - # validate credentials has not expired. - cred.lifetime - except gssapi.raw.exceptions.ExpiredCredentialsError as err: - logger.warning(" Credentials has expired: %s", err) - cred.acquire(user_name) - raise InterfaceError(f"Credentials has expired: {err}") from err - except gssapi.raw.misc.GSSError as err: - if not self._password: - raise InterfaceError( - f"Unable to retrieve stored credentials error: {err}" - ) from err - try: - logger.debug("# Attempt to retrieve credentials with given password") - acquire_cred_result = gssapi.raw.acquire_cred_with_password( - user_name, - self._password.encode("utf8"), - usage="initiate", - ) - cred = acquire_cred_result[0] - except gssapi.raw.misc.GSSError as err2: - raise ProgrammingError( - f"Unable to retrieve credentials with the given password: {err2}" - ) from err - - flags_l = ( - gssapi.RequirementFlag.mutual_authentication, - gssapi.RequirementFlag.extended_error, - gssapi.RequirementFlag.delegate_to_peer, - ) - - if self.krb_service_principal: - service_principal = self.krb_service_principal - else: - service_principal = "ldap/ldapauth" - logger.debug("# service principal: %s", service_principal) - servk = gssapi.Name( - service_principal, name_type=gssapi.NameType.kerberos_principal - ) - self.target_name = servk - self.ctx = gssapi.SecurityContext( - name=servk, creds=cred, flags=sum(flags_l), usage="initiate" - ) - - try: - # step() returns bytes | None, see documentation, - # so this method could return a NULL payload. - # ref: https://pythongssapi.github.io/ - # suffix: python-gssapi/latest/gssapi.html#gssapi.sec_contexts.SecurityContext - initial_client_token = self.ctx.step() - except gssapi.raw.misc.GSSError as err: - raise InterfaceError(f"Unable to initiate security context: {err}") from err - - logger.debug("# initial client token: %s", initial_client_token) - return initial_client_token - - def auth_continue_krb( - self, tgt_auth_challenge: Optional[bytes] - ) -> Tuple[Optional[bytes], bool]: - """Continue with the Kerberos TGT service request. - - With the TGT authentication service given response generate a TGT - service request. This method must be invoked sequentially (in a loop) - until the security context is completed and an empty response needs to - be send to acknowledge the server. - - Args: - tgt_auth_challenge the challenge for the negotiation. - - Returns: tuple (bytearray TGS service request, - bool True if context is completed otherwise False). - """ - logger.debug("tgt_auth challenge: %s", tgt_auth_challenge) - - resp = self.ctx.step(tgt_auth_challenge) - logger.debug("# context step response: %s", resp) - logger.debug("# context completed?: %s", self.ctx.complete) - - return resp, self.ctx.complete - - def auth_accept_close_handshake(self, message: bytes) -> bytes: - """Accept handshake and generate closing handshake message for server. - - This method verifies the server authenticity from the given message - and included signature and generates the closing handshake for the - server. - - When this method is invoked the security context is already established - and the client and server can send GSSAPI formated secure messages. - - To finish the authentication handshake the server sends a message - with the security layer availability and the maximum buffer size. - - Since the connector only uses the GSSAPI authentication mechanism to - authenticate the user with the server, the server will verify clients - message signature and terminate the GSSAPI authentication and send two - messages; an authentication acceptance b'\x01\x00\x00\x08\x01' and a - OK packet (that must be received after sent the returned message from - this method). - - Args: - message a wrapped hssapi message from the server. - - Returns: bytearray closing handshake message to be send to the server. - """ - if not self.ctx.complete: - raise ProgrammingError("Security context is not completed.") - logger.debug("# servers message: %s", message) - logger.debug("# GSSAPI flags in use: %s", self.ctx.actual_flags) - try: - unwraped = self.ctx.unwrap(message) - logger.debug("# unwraped: %s", unwraped) - except gssapi.raw.exceptions.BadMICError as err: - raise InterfaceError(f"Unable to unwrap server message: {err}") from err - - logger.debug("# unwrapped server message: %s", unwraped) - # The message contents for the clients closing message: - # - security level 1 byte, must be always 1. - # - conciliated buffer size 3 bytes, without importance as no - # further GSSAPI messages will be sends. - response = bytearray(b"\x01\x00\x00\00") - # Closing handshake must not be encrypted. - logger.debug("# message response: %s", response) - wraped = self.ctx.wrap(response, encrypt=False) - logger.debug( - "# wrapped message response: %s, length: %d", - wraped[0], - len(wraped[0]), - ) - - return wraped.message - - def auth_response( - self, - auth_data: bytes, - **kwargs: Any, - ) -> Optional[bytes]: - """This method will prepare the fist message to the server. - - Returns bytes to send to the server as the first message. - """ - # pylint: disable=attribute-defined-outside-init - self._auth_data = auth_data - - auth_mechanism = self._auth_data.decode() - logger.debug("read_method_name_from_server: %s", auth_mechanism) - if auth_mechanism not in self.sasl_mechanisms: - auth_mechanisms = '", "'.join(self.sasl_mechanisms[:-1]) - raise InterfaceError( - f'The sasl authentication method "{auth_mechanism}" requested ' - f'from the server is not supported. Only "{auth_mechanisms}" ' - f'and "{self.sasl_mechanisms[-1]}" are supported' - ) - - if b"GSSAPI" in self._auth_data: - return self._first_message_krb() - - if self._auth_data == b"SCRAM-SHA-256": - self.def_digest_mode = sha256 - - return self._first_message() - - def _second_message(self) -> bytes: - """This method generates the second message to the server - - Second message consist on the concatenation of the client and the - server nonce, and cproof. - - c=>,r=,p= - where: - : xor(, ) - - : hmac(salted_password, b"Client Key") - : hmac(, ) - : h() - : ,, - c=,r= - : n=r= - """ - if not self._auth_data: - raise InterfaceError("Missing authentication data (seed)") - - passw = self._normalize(self._password) - salted_password = self._hi(passw, b64decode(self.server_salt), self.iterations) - logger.debug("salted_password: %s", b64encode(salted_password).decode()) - - client_key = self._hmac(salted_password, b"Client Key") - logger.debug("client_key: %s", b64encode(client_key).decode()) - - stored_key = self.def_digest_mode(client_key).digest() - logger.debug("stored_key: %s", b64encode(stored_key).decode()) - - server_key = self._hmac(salted_password, b"Server Key") - logger.debug("server_key: %s", b64encode(server_key).decode()) - - client_first_no_header = ",".join( - [ - f"n={self._normalize(self._username)}", - f"r={self.client_nonce}", - ] - ) - logger.debug("client_first_no_header: %s", client_first_no_header) - - client_header = b64encode( - f"n,a={self._normalize(self._username)},".encode() - ).decode() - - auth_msg = ",".join( - [ - client_first_no_header, - self.servers_first, - f"c={client_header}", - f"r={self.server_nonce}", - ] - ) - logger.debug("auth_msg: %s", auth_msg) - - client_signature = self._hmac(stored_key, auth_msg.encode()) - logger.debug("client_signature: %s", b64encode(client_signature).decode()) - - client_proof = self._xor(client_key, client_signature) - logger.debug("client_proof: %s", b64encode(client_proof).decode()) - - self.server_auth_var = b64encode( - self._hmac(server_key, auth_msg.encode()) - ).decode() - logger.debug("server_auth_var: %s", self.server_auth_var) - - msg = ",".join( - [ - f"c={client_header}", - f"r={self.server_nonce}", - f"p={b64encode(client_proof).decode()}", - ] - ) - logger.debug("second_message: %s", msg) - return msg.encode() - - def _validate_first_reponse(self, servers_first: bytes) -> None: - """Validates first message from the server. - - Extracts the server's salt and iterations from the servers 1st response. - First message from the server is in the form: - ,i= - """ - if not servers_first or not isinstance(servers_first, (bytearray, bytes)): - raise InterfaceError(f"Unexpected server message: {repr(servers_first)}") - try: - servers_first_str = servers_first.decode() - self.servers_first = servers_first_str - r_server_nonce, s_salt, i_counter = servers_first_str.split(",") - except ValueError: - raise InterfaceError( - f"Unexpected server message: {servers_first_str}" - ) from None - if ( - not r_server_nonce.startswith("r=") - or not s_salt.startswith("s=") - or not i_counter.startswith("i=") - ): - raise InterfaceError( - f"Incomplete reponse from the server: {servers_first_str}" - ) - if self.client_nonce in r_server_nonce: - self.server_nonce = r_server_nonce[2:] - logger.debug("server_nonce: %s", self.server_nonce) - else: - raise InterfaceError( - "Unable to authenticate response: response not well formed " - f"{servers_first_str}" - ) - self.server_salt = s_salt[2:] - logger.debug( - "server_salt: %s length: %s", - self.server_salt, - len(self.server_salt), - ) - try: - i_counter = i_counter[2:] - logger.debug("iterations: %s", i_counter) - self.iterations = int(i_counter) - except Exception as err: - raise InterfaceError( - f"Unable to authenticate: iterations not found {servers_first_str}" - ) from err - - def auth_continue(self, servers_first_response: bytes) -> bytes: - """return the second message from the client. - - Returns bytes to send to the server as the second message. - """ - self._validate_first_reponse(servers_first_response) - return self._second_message() - - def _validate_second_reponse(self, servers_second: bytearray) -> bool: - """Validates second message from the server. - - The client and the server prove to each other they have the same Auth - variable. - - The second message from the server consist of the server's proof: - server_proof = HMAC(, ) - where: - : hmac(, b"Server Key") - : ,, - c=,r= - - Our server_proof must be equal to the Auth variable send on this second - response. - """ - if ( - not servers_second - or not isinstance(servers_second, bytearray) - or len(servers_second) <= 2 - or not servers_second.startswith(b"v=") - ): - raise InterfaceError("The server's proof is not well formated") - server_var = servers_second[2:].decode() - logger.debug("server auth variable: %s", server_var) - return self.server_auth_var == server_var - - def auth_finalize(self, servers_second_response: bytearray) -> bool: - """finalize the authentication process. - - Raises InterfaceError if the ervers_second_response is invalid. - - Returns True in successful authentication False otherwise. - """ - if not self._validate_second_reponse(servers_second_response): - raise InterfaceError( - "Authentication failed: Unable to proof server identity" - ) - return True - - @property - def name(self) -> str: - """Plugin official name.""" - return "authentication_ldap_sasl_client" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - logger.debug("# auth_data: %s", auth_data) - self.krb_service_principal = kwargs.get("krb_service_principal") - - response = self.auth_response(auth_data, **kwargs) - if response is None: - raise InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - packet = await sock.read() - logger.debug("# server response packet: %s", packet) - - if len(packet) >= 6 and packet[5] == 114 and packet[6] == 61: # 'r' and '=' - # Continue with sasl authentication - dec_response = packet[5:] - cresponse = self.auth_continue(dec_response) - await sock.write(cresponse) - packet = await sock.read() - if packet[5] == 118 and packet[6] == 61: # 'v' and '=' - if self.auth_finalize(packet[5:]): - # receive packed OK - packet = await sock.read() - elif auth_data == b"GSSAPI" and packet[4] != ERR_STATUS: - rcode_size = 5 # header size for the response status code. - logger.debug("# Continue with sasl GSSAPI authentication") - logger.debug("# response header: %s", packet[: rcode_size + 1]) - logger.debug("# response size: %s", len(packet)) - - logger.debug("# Negotiate a service request") - complete = False - tries = 0 # To avoid a infinite loop attempt no more than feedback messages - while not complete and tries < 5: - logger.debug("%s Attempt %s %s", "-" * 20, tries + 1, "-" * 20) - logger.debug("<< server response: %s", packet) - logger.debug("# response code: %s", packet[: rcode_size + 1]) - step, complete = self.auth_continue_krb(packet[rcode_size:]) - logger.debug(" >> response to server: %s", step) - await sock.write(step or b"") - packet = await sock.read() - tries += 1 - if not complete: - raise InterfaceError( - f"Unable to fulfill server request after {tries} " - f"attempts. Last server response: {packet}" - ) - logger.debug( - " last GSSAPI response from server: %s length: %d", - packet, - len(packet), - ) - last_step = self.auth_accept_close_handshake(packet[rcode_size:]) - logger.debug( - " >> last response to server: %s length: %d", - last_step, - len(last_step), - ) - await sock.write(last_step) - # Receive final handshake from server - packet = await sock.read() - logger.debug("<< final handshake from server: %s", packet) - - # receive OK packet from server. - packet = await sock.read() - logger.debug("<< ok packet from server: %s", packet) - - return bytes(packet) - - -# pylint: enable=c-extension-no-member,no-member diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_oci_client.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_oci_client.py deleted file mode 100644 index 4237eb9..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_oci_client.py +++ /dev/null @@ -1,234 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="arg-type,union-attr,call-arg" - -"""OCI Authentication Plugin.""" - -import json -import os - -from base64 import b64encode -from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, Optional - -from mysql.connector import errors -from mysql.connector.logger import logger - -if TYPE_CHECKING: - from ..network import MySQLSocket - -try: - from cryptography.exceptions import UnsupportedAlgorithm - from cryptography.hazmat.primitives import hashes, serialization - from cryptography.hazmat.primitives.asymmetric import padding - from cryptography.hazmat.primitives.asymmetric.types import PRIVATE_KEY_TYPES -except ImportError: - raise errors.ProgrammingError("Package 'cryptography' is not installed") from None - -try: - from oci import config, exceptions -except ImportError: - raise errors.ProgrammingError( - "Package 'oci' (Oracle Cloud Infrastructure Python SDK) is not installed" - ) from None - -from . import MySQLAuthPlugin - -AUTHENTICATION_PLUGIN_CLASS = "MySQLOCIAuthPlugin" -OCI_SECURITY_TOKEN_MAX_SIZE = 10 * 1024 # In bytes -OCI_SECURITY_TOKEN_TOO_LARGE = "Ephemeral security token is too large (10KB max)" -OCI_SECURITY_TOKEN_FILE_NOT_AVAILABLE = ( - "Ephemeral security token file ('security_token_file') could not be read" -) -OCI_PROFILE_MISSING_PROPERTIES = ( - "OCI configuration file does not contain a 'fingerprint' or 'key_file' entry" -) - - -class MySQLOCIAuthPlugin(MySQLAuthPlugin): - """Implement the MySQL OCI IAM authentication plugin.""" - - context: Any = None - oci_config_profile: str = "DEFAULT" - oci_config_file: str = config.DEFAULT_LOCATION - - @staticmethod - def _prepare_auth_response(signature: bytes, oci_config: Dict[str, Any]) -> str: - """Prepare client's authentication response - - Prepares client's authentication response in JSON format - Args: - signature (bytes): server's nonce to be signed by client. - oci_config (dict): OCI configuration object. - - Returns: - str: JSON string with the following format: - {"fingerprint": str, "signature": str, "token": base64.base64.base64} - - Raises: - ProgrammingError: If the ephemeral security token file can't be open or the - token is too large. - """ - signature_64 = b64encode(signature) - auth_response = { - "fingerprint": oci_config["fingerprint"], - "signature": signature_64.decode(), - } - - # The security token, if it exists, should be a JWT (JSON Web Token), consisted - # of a base64-encoded header, body, and signature, separated by '.', - # e.g. "Base64.Base64.Base64", stored in a file at the path specified by the - # security_token_file configuration property - if oci_config.get("security_token_file"): - try: - security_token_file = Path(oci_config["security_token_file"]) - # Check if token exceeds the maximum size - if security_token_file.stat().st_size > OCI_SECURITY_TOKEN_MAX_SIZE: - raise errors.ProgrammingError(OCI_SECURITY_TOKEN_TOO_LARGE) - auth_response["token"] = security_token_file.read_text(encoding="utf-8") - except (OSError, UnicodeError) as err: - raise errors.ProgrammingError( - OCI_SECURITY_TOKEN_FILE_NOT_AVAILABLE - ) from err - return json.dumps(auth_response, separators=(",", ":")) - - @staticmethod - def _get_private_key(key_path: str) -> PRIVATE_KEY_TYPES: - """Get the private_key form the given location""" - try: - with open(os.path.expanduser(key_path), "rb") as key_file: - private_key = serialization.load_pem_private_key( - key_file.read(), - password=None, - ) - except (TypeError, OSError, ValueError, UnsupportedAlgorithm) as err: - raise errors.ProgrammingError( - "An error occurred while reading the API_KEY from " - f'"{key_path}": {err}' - ) - - return private_key - - def _get_valid_oci_config(self) -> Dict[str, Any]: - """Get a valid OCI config from the given configuration file path""" - error_list = [] - req_keys = { - "fingerprint": (lambda x: len(x) > 32), - "key_file": (lambda x: os.path.exists(os.path.expanduser(x))), - } - - oci_config: Dict[str, Any] = {} - try: - # key_file is validated by oci.config if present - oci_config = config.from_file( - self.oci_config_file or config.DEFAULT_LOCATION, - self.oci_config_profile or "DEFAULT", - ) - for req_key, req_value in req_keys.items(): - try: - # Verify parameter in req_key is present and valid - if oci_config[req_key] and not req_value(oci_config[req_key]): - error_list.append(f'Parameter "{req_key}" is invalid') - except KeyError: - error_list.append(f"Does not contain parameter {req_key}") - except ( - exceptions.ConfigFileNotFound, - exceptions.InvalidConfig, - exceptions.InvalidKeyFilePath, - exceptions.InvalidPrivateKey, - exceptions.ProfileNotFound, - ) as err: - error_list.append(str(err)) - - # Raise errors if any - if error_list: - raise errors.ProgrammingError( - f"Invalid oci-config-file: {self.oci_config_file}. " - f"Errors found: {error_list}" - ) - - return oci_config - - @property - def name(self) -> str: - """Plugin official name.""" - return "authentication_oci_client" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Prepare authentication string for the server.""" - logger.debug("server nonce: %s, len %d", auth_data, len(auth_data)) - - oci_config = self._get_valid_oci_config() - - private_key = self._get_private_key(oci_config["key_file"]) - signature = private_key.sign(auth_data, padding.PKCS1v15(), hashes.SHA256()) - - auth_response = self._prepare_auth_response(signature, oci_config) - logger.debug("authentication response: %s", auth_response) - return auth_response.encode() - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - self.oci_config_file = kwargs.get("oci_config_file", "DEFAULT") - self.oci_config_profile = kwargs.get( - "oci_config_profile", config.DEFAULT_LOCATION - ) - logger.debug("# oci configuration file path: %s", self.oci_config_file) - - response = self.auth_response(auth_data, **kwargs) - if response is None: - raise errors.InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - packet = await sock.read() - logger.debug("# server response packet: %s", packet) - - return bytes(packet) diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_openid_connect_client.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_openid_connect_client.py deleted file mode 100644 index 87ee74b..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_openid_connect_client.py +++ /dev/null @@ -1,172 +0,0 @@ -# Copyright (c) 2024, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""OpenID Authentication Plugin.""" - -import re - -from pathlib import Path -from typing import Any, List - -from mysql.connector import errors, utils -from mysql.connector.aio.network import MySQLSocket -from mysql.connector.logger import logger - -from . import MySQLAuthPlugin - -AUTHENTICATION_PLUGIN_CLASS = "MySQLOpenIDConnectAuthPlugin" -OPENID_TOKEN_MAX_SIZE = 10 * 1024 # In bytes - - -class MySQLOpenIDConnectAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL OpenID Connect Authentication Plugin.""" - - _openid_capability_flag: bytes = utils.int1store(1) - - @property - def name(self) -> str: - """Plugin official name.""" - return "authentication_openid_connect_client" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return True - - @staticmethod - def _validate_openid_token(token: str) -> bool: - """Helper method used to validate OpenID Connect token - - The Token is represented as a JSON Web Token (JWT) consists of a - base64-encoded header, body, and signature, separated by '.' e.g., - "Base64url.Base64url.Base64url". The First part of the token contains - the header, the second part contains payload and the third part contains - signature. These token parts should be Base64 URLSafe i.e., Token cannot - contain characters other than a-z, A-Z, 0-9 and special characters '-', '_'. - - Args: - token (str): Base64url-encoded OpenID connect token fetched from - the file path passed via `openid_token_file` connection - argument. - - Returns: - bool: Signal indicating whether the token is valid or not. - """ - header_payload_sig: List[str] = token.split(".") - if len(header_payload_sig) != 3: - # invalid structure - return False - urlsafe_pattern = re.compile("^[a-zA-Z0-9-_]*$") - return all( - ( - len(token_part) and urlsafe_pattern.search(token_part) is not None - for token_part in header_payload_sig - ) - ) - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> bytes: - """Prepares authentication string for the server. - Args: - auth_data: Authorization data. - kwargs: Custom configuration to be passed to the auth plugin - when invoked. - - Returns: - packet: Client's authorization response. - The OpenID Connect authorization response follows the pattern :- - int<1> capability flag - string id token - - Raises: - InterfaceError: If the connection is insecure or the OpenID Token is too large, - invalid or non-existent. - ProgrammingError: If the OpenID Token file could not be read. - """ - try: - # Check if the connection is secure - if self.requires_ssl and not self._ssl_enabled: - raise errors.InterfaceError(f"{self.name} requires SSL") - - # Validate the file - token_file_path: str = kwargs.get("openid_token_file", None) - openid_token_file: Path = Path(token_file_path) - # Check if token exceeds the maximum size - if openid_token_file.stat().st_size > OPENID_TOKEN_MAX_SIZE: - raise errors.InterfaceError( - "The OpenID Connect token file size is too large (> 10KB)" - ) - openid_token: str = openid_token_file.read_text(encoding="utf-8") - openid_token = openid_token.strip() - # Validate the JWT Token - if not self._validate_openid_token(openid_token): - raise errors.InterfaceError("The OpenID Connect Token is invalid") - - # build the auth_response packet - auth_response: List[bytes] = [ - self._openid_capability_flag, - utils.lc_int(len(openid_token)), - openid_token.encode(), - ] - return b"".join(auth_response) - except (SyntaxError, TypeError, OSError, UnicodeError) as err: - raise errors.ProgrammingError( - "The OpenID Connect Token File (openid_token_file) could not be read" - ) from err - - async def auth_switch_response( - self, sock: MySQLSocket, auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - - Raises: - InterfaceError: If a NULL auth response is received from auth_response method. - """ - response = self.auth_response(auth_data, **kwargs) - - if response is None: - raise errors.InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - packet = await sock.read() - logger.debug("# server response packet: %s", packet) - - return bytes(packet) diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_webauthn_client.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_webauthn_client.py deleted file mode 100644 index ac8b158..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/authentication_webauthn_client.py +++ /dev/null @@ -1,291 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""WebAuthn Authentication Plugin.""" - - -from typing import TYPE_CHECKING, Any, Callable, Optional - -from mysql.connector import errors, utils - -from ..logger import logger -from . import MySQLAuthPlugin - -if TYPE_CHECKING: - from ..network import MySQLSocket - -try: - from fido2.cbor import dump_bytes as cbor_dump_bytes - from fido2.client import Fido2Client, UserInteraction - from fido2.hid import CtapHidDevice - from fido2.webauthn import PublicKeyCredentialRequestOptions -except ImportError as import_err: - raise errors.ProgrammingError( - "Module fido2 is required for WebAuthn authentication mechanism but was " - "not found. Unable to authenticate with the server" - ) from import_err - -try: - from fido2.pcsc import CtapPcscDevice - - CTAP_PCSC_DEVICE_AVAILABLE = True -except ModuleNotFoundError: - CTAP_PCSC_DEVICE_AVAILABLE = False - - -AUTHENTICATION_PLUGIN_CLASS = "MySQLWebAuthnAuthPlugin" - - -class ClientInteraction(UserInteraction): - """Provides user interaction to the Client.""" - - def __init__(self, callback: Optional[Callable] = None): - self.callback = callback - self.msg = ( - "Please insert FIDO device and perform gesture action for authentication " - "to complete." - ) - - def prompt_up(self) -> None: - """Prompt message for the user interaction with the FIDO device.""" - if self.callback is None: - print(self.msg) - else: - self.callback(self.msg) - - -class MySQLWebAuthnAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL WebAuthn authentication plugin.""" - - client: Optional[Fido2Client] = None - callback: Optional[Callable] = None - options: dict = {"rpId": None, "challenge": None, "allowCredentials": []} - - @property - def name(self) -> str: - """Plugin official name.""" - return "authentication_webauthn_client" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - def get_assertion_response( - self, credential_id: Optional[bytearray] = None - ) -> bytes: - """Get assertion from authenticator and return the response. - - Args: - credential_id (Optional[bytearray]): The credential ID. - - Returns: - bytearray: The response packet with the data from the assertion. - """ - if self.client is None: - raise errors.InterfaceError("No WebAuthn client found") - - if credential_id is not None: - # If credential_id is not None, it's because the FIDO device does not - # support resident keys and the credential_id was requested from the server - self.options["allowCredentials"] = [ - { - "id": credential_id, - "type": "public-key", - } - ] - - # Get assertion from authenticator - assertion = self.client.get_assertion( - PublicKeyCredentialRequestOptions.from_dict(self.options) - ) - number_of_assertions = len(assertion.get_assertions()) - client_data_json = b"" - - # Build response packet - # - # Format: - # int<1> 0x02 (2) status tag - # int number of assertions length encoded number of assertions - # string authenticator data variable length raw binary string - # string signed challenge variable length raw binary string - # ... - # ... - # string authenticator data variable length raw binary string - # string signed challenge variable length raw binary string - # string ClientDataJSON variable length raw binary string - packet = utils.lc_int(2) - packet += utils.lc_int(number_of_assertions) - - # Add authenticator data and signed challenge for each assertion - for i in range(number_of_assertions): - assertion_response = assertion.get_response(i) - - # string authenticator_data - authenticator_data = cbor_dump_bytes(assertion_response.authenticator_data) - - # string signed_challenge - signature = assertion_response.signature - - packet += utils.lc_int(len(authenticator_data)) - packet += authenticator_data - packet += utils.lc_int(len(signature)) - packet += signature - - # string client_data_json - client_data_json = assertion_response.client_data - - packet += utils.lc_int(len(client_data_json)) - packet += client_data_json - - logger.debug("WebAuthn - payload response packet: %s", packet) - return packet - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Find authenticator device and check if supports resident keys. - - It also creates a Fido2Client using the relying party ID from the server. - - Raises: - InterfaceError: When the FIDO device is not found. - - Returns: - bytes: 2 if the authenticator supports resident keys else 1. - """ - try: - packets, capability = utils.read_int(auth_data, 1) - challenge, rp_id = utils.read_lc_string_list(packets) - self.options["challenge"] = challenge - self.options["rpId"] = rp_id.decode() - logger.debug("WebAuthn - capability: %d", capability) - logger.debug("WebAuthn - challenge: %s", self.options["challenge"]) - logger.debug("WebAuthn - relying party id: %s", self.options["rpId"]) - except ValueError as err: - raise errors.InterfaceError( - "Unable to parse MySQL WebAuthn authentication data" - ) from err - - # Locate a device - device = next(CtapHidDevice.list_devices(), None) - if device is not None: - logger.debug("WebAuthn - Use USB HID channel") - elif CTAP_PCSC_DEVICE_AVAILABLE: - device = next(CtapPcscDevice.list_devices(), None) - - if device is None: - raise errors.InterfaceError("No FIDO device found") - - # Set up a FIDO 2 client using the origin relying party id - self.client = Fido2Client( - device, - f"https://{self.options['rpId']}", - user_interaction=ClientInteraction(self.callback), - ) - - if not self.client.info.options.get("rk"): - logger.debug("WebAuthn - Authenticator doesn't support resident keys") - return b"1" - - logger.debug("WebAuthn - Authenticator with support for resident key found") - return b"2" - - async def auth_more_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth more data` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Authentication method data (from a packet representing - an `auth more data` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - _, credential_id = utils.read_lc_string(auth_data) - - response = self.get_assertion_response(credential_id) - - logger.debug("WebAuthn - request: %s size: %s", response, len(response)) - await sock.write(response) - - pkt = bytes(await sock.read()) - logger.debug("WebAuthn - server response packet: %s", pkt) - - return pkt - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - webauth_callback = kwargs.get("webauthn_callback") or kwargs.get( - "fido_callback" - ) - self.callback = ( - utils.import_object(webauth_callback) - if isinstance(webauth_callback, str) - else webauth_callback - ) - - response = self.auth_response(auth_data) - credential_id = None - - if response == b"1": - # Authenticator doesn't support resident keys, request credential_id - logger.debug("WebAuthn - request credential_id") - await sock.write(utils.lc_int(int(response))) - - # return a packet representing an `auth more data` response - return bytes(await sock.read()) - - response = self.get_assertion_response(credential_id) - - logger.debug("WebAuthn - request: %s size: %s", response, len(response)) - await sock.write(response) - - pkt = bytes(await sock.read()) - logger.debug("WebAuthn - server response packet: %s", pkt) - - return pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/caching_sha2_password.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/caching_sha2_password.py deleted file mode 100644 index 83385c3..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/caching_sha2_password.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Caching SHA2 Password Authentication Plugin.""" - -import struct - -from hashlib import sha256 -from typing import TYPE_CHECKING, Any, Optional - -from mysql.connector.errors import InterfaceError -from mysql.connector.logger import logger - -from . import MySQLAuthPlugin - -if TYPE_CHECKING: - from ..network import MySQLSocket - -AUTHENTICATION_PLUGIN_CLASS = "MySQLCachingSHA2PasswordAuthPlugin" - - -class MySQLCachingSHA2PasswordAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL caching_sha2_password authentication plugin - - Note that encrypting using RSA is not supported since the Python - Standard Library does not provide this OpenSSL functionality. - """ - - perform_full_authentication: int = 4 - - def _scramble(self, auth_data: bytes) -> bytes: - """Return a scramble of the password using a Nonce sent by the - server. - - The scramble is of the form: - XOR(SHA2(password), SHA2(SHA2(SHA2(password)), Nonce)) - """ - if not auth_data: - raise InterfaceError("Missing authentication data (seed)") - - if not self._password: - return b"" - - hash1 = sha256(self._password.encode()).digest() - hash2 = sha256() - hash2.update(sha256(hash1).digest()) - hash2.update(auth_data) - hash2_digest = hash2.digest() - xored = [h1 ^ h2 for (h1, h2) in zip(hash1, hash2_digest)] - hash3 = struct.pack("32B", *xored) - return hash3 - - @property - def name(self) -> str: - """Plugin official name.""" - return "caching_sha2_password" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Make the client's authorization response. - - Args: - auth_data: Authorization data. - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Client's authorization response. - """ - if not auth_data: - return None - if len(auth_data) > 1: - return self._scramble(auth_data) - if auth_data[0] == self.perform_full_authentication: - # return password as clear text. - return self._password.encode() + b"\x00" - - return None - - async def auth_more_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth more data` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Authentication method data (from a packet representing - an `auth more data` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - response = self.auth_response(auth_data, **kwargs) - if response: - await sock.write(response) - - return bytes(await sock.read()) - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - response = self.auth_response(auth_data, **kwargs) - if response is None: - raise InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - pkt = bytes(await sock.read()) - logger.debug("# server response packet: %s", pkt) - - return pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/mysql_clear_password.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/mysql_clear_password.py deleted file mode 100644 index c4260c2..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/mysql_clear_password.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Clear Password Authentication Plugin.""" - -from typing import TYPE_CHECKING, Any, Optional - -from mysql.connector import errors -from mysql.connector.logger import logger - -from . import MySQLAuthPlugin - -if TYPE_CHECKING: - from ..network import MySQLSocket - -AUTHENTICATION_PLUGIN_CLASS = "MySQLClearPasswordAuthPlugin" - - -class MySQLClearPasswordAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL Clear Password authentication plugin""" - - def _prepare_password(self) -> bytes: - """Prepare and return password as as clear text. - - Returns: - bytes: Prepared password. - """ - return self._password.encode() + b"\x00" - - @property - def name(self) -> str: - """Plugin official name.""" - return "mysql_clear_password" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Return the prepared password to send to MySQL. - - Raises: - InterfaceError: When SSL is required by not enabled. - - Returns: - str: The prepared password. - """ - if self.requires_ssl and not self._ssl_enabled: - raise errors.InterfaceError(f"{self.name} requires SSL") - return self._prepare_password() - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - response = self.auth_response(auth_data, **kwargs) - if response is None: - raise errors.InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - pkt = bytes(await sock.read()) - logger.debug("# server response packet: %s", pkt) - - return pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/mysql_native_password.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/mysql_native_password.py deleted file mode 100644 index a1c6aa8..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/mysql_native_password.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Native Password Authentication Plugin.""" - -import struct - -from hashlib import sha1 -from typing import TYPE_CHECKING, Any, Optional - -from mysql.connector.errors import InterfaceError -from mysql.connector.logger import logger - -from . import MySQLAuthPlugin - -if TYPE_CHECKING: - from ..network import MySQLSocket - -AUTHENTICATION_PLUGIN_CLASS = "MySQLNativePasswordAuthPlugin" - - -class MySQLNativePasswordAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL Native Password authentication plugin""" - - def _prepare_password(self, auth_data: bytes) -> bytes: - """Prepares and returns password as native MySQL 4.1+ password""" - if not auth_data: - raise InterfaceError("Missing authentication data (seed)") - - if not self._password: - return b"" - - hash4 = None - try: - hash1 = sha1(self._password.encode()).digest() - hash2 = sha1(hash1).digest() - hash3 = sha1(auth_data + hash2).digest() - xored = [h1 ^ h3 for (h1, h3) in zip(hash1, hash3)] - hash4 = struct.pack("20B", *xored) - except (struct.error, TypeError) as err: - raise InterfaceError(f"Failed scrambling password; {err}") from err - - return hash4 - - @property - def name(self) -> str: - """Plugin official name.""" - return "mysql_native_password" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return False - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Make the client's authorization response. - - Args: - auth_data: Authorization data. - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Client's authorization response. - """ - return self._prepare_password(auth_data) - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - response = self.auth_response(auth_data, **kwargs) - if response is None: - raise InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - pkt = bytes(await sock.read()) - logger.debug("# server response packet: %s", pkt) - - return pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/sha256_password.py b/server/venv/Lib/site-packages/mysql/connector/aio/plugins/sha256_password.py deleted file mode 100644 index 680b98a..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/plugins/sha256_password.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""SHA256 Password Authentication Plugin.""" - -from typing import TYPE_CHECKING, Any, Optional - -from mysql.connector import errors -from mysql.connector.logger import logger - -from . import MySQLAuthPlugin - -if TYPE_CHECKING: - from ..network import MySQLSocket - -AUTHENTICATION_PLUGIN_CLASS = "MySQLSHA256PasswordAuthPlugin" - - -class MySQLSHA256PasswordAuthPlugin(MySQLAuthPlugin): - """Class implementing the MySQL SHA256 authentication plugin - - Note that encrypting using RSA is not supported since the Python - Standard Library does not provide this OpenSSL functionality. - """ - - def _prepare_password(self) -> bytes: - """Prepare and return password as as clear text. - - Returns: - password (bytes): Prepared password. - """ - return self._password.encode() + b"\x00" - - @property - def name(self) -> str: - """Plugin official name.""" - return "sha256_password" - - @property - def requires_ssl(self) -> bool: - """Signals whether or not SSL is required.""" - return True - - def auth_response(self, auth_data: bytes, **kwargs: Any) -> Optional[bytes]: - """Return the prepared password to send to MySQL. - - Raises: - InterfaceError: When SSL is required by not enabled. - - Returns: - str: The prepared password. - """ - if self.requires_ssl and not self.ssl_enabled: - raise errors.InterfaceError(f"{self.name} requires SSL") - return self._prepare_password() - - async def auth_switch_response( - self, sock: "MySQLSocket", auth_data: bytes, **kwargs: Any - ) -> bytes: - """Handles server's `auth switch request` response. - - Args: - sock: Pointer to the socket connection. - auth_data: Plugin provided data (extracted from a packet - representing an `auth switch request` response). - kwargs: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override the ones - defined in the auth plugin itself. - - Returns: - packet: Last server's response after back-and-forth - communication. - """ - response = self.auth_response(auth_data, **kwargs) - if response is None: - raise errors.InterfaceError("Got a NULL auth response") - - logger.debug("# request: %s size: %s", response, len(response)) - await sock.write(response) - - pkt = bytes(await sock.read()) - logger.debug("# server response packet: %s", pkt) - - return pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/pooling.py b/server/venv/Lib/site-packages/mysql/connector/aio/pooling.py deleted file mode 100644 index 928a515..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/pooling.py +++ /dev/null @@ -1,688 +0,0 @@ -# Copyright (c) 2025, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implementing pooling of connections to MySQL servers.""" - -from __future__ import annotations - -import asyncio -import os -import random -import re -import sys - -from types import TracebackType -from typing import TYPE_CHECKING, Any, Dict, NoReturn, Optional, Tuple, Type, Union -from uuid import UUID, uuid4 - -from mysql.connector.constants import CNX_POOL_ARGS - -try: - import dns.asyncresolver - import dns.exception -except ImportError: - HAVE_DNSPYTHON = False -else: - HAVE_DNSPYTHON = True - -from ..errors import ( - Error, - InterfaceError, - NotSupportedError, - PoolError, - ProgrammingError, -) -from ..pooling import DEFAULT_CONFIGURATION, generate_pool_name, read_option_files -from .connection import MySQLConnection - -if TYPE_CHECKING: - from .abstracts import MySQLConnectionAbstract - - -CONNECTION_POOL_LOCK = asyncio.Lock() -CNX_POOL_MAXSIZE = 32 -CNX_POOL_MAXNAMESIZE = 64 -CNX_POOL_NAMEREGEX = re.compile(r"[^a-zA-Z0-9._:\-*$#]") -MYSQL_CNX_CLASS: Union[type, Tuple[type, ...]] = (MySQLConnection,) - -_CONNECTION_POOLS: Dict[str, MySQLConnectionPool] = {} - - -async def _get_pooled_connection(**kwargs: Any) -> PooledMySQLConnection: - """Return a pooled MySQL connection.""" - # If no pool name specified, generate one - pool_name = ( - kwargs["pool_name"] if "pool_name" in kwargs else generate_pool_name(**kwargs) - ) - - # Setup the pool, ensuring only 1 thread can update at a time - if pool_name not in _CONNECTION_POOLS: - pool = MySQLConnectionPool(**kwargs) - await pool.initialize_pool() - - async with CONNECTION_POOL_LOCK: - _CONNECTION_POOLS[pool_name] = pool - elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool): - # pool_size must be the same - check_size = _CONNECTION_POOLS[pool_name].pool_size - if "pool_size" in kwargs and kwargs["pool_size"] != check_size: - raise PoolError("Size can not be changed for active pools.") - - # Return pooled connection - try: - return await _CONNECTION_POOLS[pool_name].get_connection() - except AttributeError: - raise InterfaceError( - f"Failed getting connection from pool '{pool_name}'" - ) from None - - -async def _get_failover_connection( - **kwargs: Any, -) -> Union[PooledMySQLConnection, MySQLConnectionAbstract]: - """Return a MySQL connection and try to failover if needed. - - An InterfaceError is raise when no MySQL is available. ValueError is - raised when the failover server configuration contains an illegal - connection argument. Supported arguments are user, password, host, port, - unix_socket and database. ValueError is also raised when the failover - argument was not provided. - - Returns MySQLConnection instance. - """ - config = kwargs.copy() - try: - failover = config["failover"] - except KeyError: - raise ValueError("failover argument not provided") from None - del config["failover"] - - support_cnx_args = { - "user", - "password", - "host", - "port", - "unix_socket", - "database", - "pool_name", - "pool_size", - "priority", - } - - # First check if we can add all use the configuration - priority_count = 0 - for server in failover: - diff = set(server.keys()) - support_cnx_args - if diff: - arg = "s" if len(diff) > 1 else "" - lst = ", ".join(diff) - raise ValueError( - f"Unsupported connection argument {arg} in failover: {lst}" - ) - if hasattr(server, "priority"): - priority_count += 1 - - server["priority"] = server.get("priority", 100) - if server["priority"] < 0 or server["priority"] > 100: - raise InterfaceError( - "Priority value should be in the range of 0 to 100, " - f"got : {server['priority']}" - ) - if not isinstance(server["priority"], int): - raise InterfaceError( - "Priority value should be an integer in the range of 0 to " - f"100, got : {server['priority']}" - ) - - if 0 < priority_count < len(failover): - raise ProgrammingError( - "You must either assign no priority to any " - "of the routers or give a priority for " - "every router" - ) - - server_directory = {} - server_priority_list = [] - for server in sorted(failover, key=lambda x: x["priority"], reverse=True): - if server["priority"] not in server_directory: - server_directory[server["priority"]] = [server] - server_priority_list.append(server["priority"]) - else: - server_directory[server["priority"]].append(server) - - for priority in server_priority_list: - failover_list = server_directory[priority] - for _ in range(len(failover_list)): - last = len(failover_list) - 1 - index = random.randint(0, last) - server = failover_list.pop(index) - new_config = config.copy() - new_config.update(server) - new_config.pop("priority", None) - try: - return await connect(**new_config) - except Error: - # If we failed to connect, we try the next server - pass - - raise InterfaceError("Unable to connect to any of the target hosts") - - -async def connect( - *args: Any, **kwargs: Any -) -> Union[PooledMySQLConnection, MySQLConnectionAbstract]: - """Creates a MySQL connection object. - - In its simpliest form, `connect()` will open a connection to a - MySQL server and return a `MySQLConnectionAbstract` subclass - object such as `MySQLConnection` or `CMySQLConnection`. - - Args: - *args: N/A. - **kwargs: For a complete list of possible arguments, see [1]. If no arguments - are given, it uses the already configured or default values. - - Returns: - A `MySQLConnectionAbstract` subclass instance (such as `MySQLConnection`) - or a `PooledMySQLConnection` instance. - - Raises: - `mysql.connector.errors.NotSupportedError`: if pooled connection is not supported - for the Python version and OS that are being used. - - Examples: - A connection with the MySQL server can be established using either the - `mysql.connector.aio.connect()` method or a `MySQLConnectionAbstract` subclass: - ``` - >>> from mysql.connector.aio import MySQLConnection - >>> - >>> cnx1 = await mysql.connector.aio.connect(user='joe', database='test') - >>> cnx2 = MySQLConnection(user='joe', database='test') - >>> - ``` - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - # DNS SRV - dns_srv = kwargs.pop("dns_srv") if "dns_srv" in kwargs else False - - if not isinstance(dns_srv, bool): - raise InterfaceError("The value of 'dns-srv' must be a boolean") - - if dns_srv: - if not HAVE_DNSPYTHON: - raise InterfaceError( - "MySQL host configuration requested DNS " - "SRV. This requires the Python dnspython " - "module. Please refer to documentation" - ) - if "unix_socket" in kwargs: - raise InterfaceError( - "Using Unix domain sockets with DNS SRV lookup is not allowed" - ) - if "port" in kwargs: - raise InterfaceError( - "Specifying a port number with DNS SRV lookup is not allowed" - ) - if "failover" in kwargs: - raise InterfaceError( - "Specifying multiple hostnames with DNS SRV look up is not allowed" - ) - if "host" not in kwargs: - kwargs["host"] = DEFAULT_CONFIGURATION["host"] - - try: - srv_records = await dns.asyncresolver.query(kwargs["host"], "SRV") - except dns.exception.DNSException: - raise InterfaceError( - f"Unable to locate any hosts for '{kwargs['host']}'" - ) from None - - failover = [] - for srv in srv_records: - failover.append( - { - "host": srv.target.to_text(omit_final_dot=True), - "port": srv.port, - "priority": srv.priority, - "weight": srv.weight, - } - ) - - failover.sort(key=lambda x: (x["priority"], -x["weight"])) - kwargs["failover"] = [ - {"host": srv["host"], "port": srv["port"]} for srv in failover - ] - - # Failover - if "failover" in kwargs: - return await _get_failover_connection(**kwargs) - - # Pooled connections - try: - if any(key in kwargs for key in CNX_POOL_ARGS): - return await _get_pooled_connection(**kwargs) - except NameError: - # No pooling - pass - - # Option files - if "read_default_file" in kwargs: - kwargs["option_files"] = kwargs["read_default_file"] - kwargs.pop("read_default_file") - - if "option_files" in kwargs: - new_config = read_option_files(**kwargs) - return await connect(**new_config) - - if "use_pure" in kwargs: - del kwargs["use_pure"] # Remove 'use_pure' from kwargs - - cnx = MySQLConnection(*args, **kwargs) - await cnx.connect() - return cnx - - -def _check_support() -> None: - """Raises error if the Python version and OS combination is not - supported for Pooling.""" - py_version_info = sys.version_info - if os.name == "nt" and py_version_info.major == 3 and py_version_info.minor <= 10: - raise NotSupportedError( - "Pooled connections are not supported for Python versions " - "lower than 3.11 on Windows" - ) - - -class PooledMySQLConnection: - """Class holding a MySQL Connection in a pool - PooledMySQLConnection is used by MySQLConnectionPool to return an - instance holding a MySQL connection. It works like a MySQLConnection - except for methods like close() and config(). - The close()-method will add the connection back to the pool rather - than disconnecting from the MySQL server. - Configuring the connection have to be done through the MySQLConnectionPool - method set_config(). Using config() on pooled connection will raise a - PoolError. - Attributes: - pool_name (str): Returns the name of the connection pool to which the - connection belongs. - """ - - def __init__(self, pool: MySQLConnectionPool, cnx: MySQLConnectionAbstract) -> None: - """Constructor. - Args: - pool: A `MySQLConnectionPool` instance. - cnx: A `MySQLConnectionAbstract` subclass instance. - """ - _check_support() - - if not isinstance(pool, MySQLConnectionPool): - raise AttributeError("pool must be an instance of MySQLConnectionPool") - if not isinstance(cnx, MYSQL_CNX_CLASS): - raise AttributeError("cnx should be an instance of MySQLConnection") - self._cnx_pool: MySQLConnectionPool = pool - self._cnx: MySQLConnectionAbstract = cnx - - async def __aenter__(self) -> PooledMySQLConnection: - return self - - async def __aexit__( - self, - exc_type: Type[BaseException], - exc_value: BaseException, - traceback: TracebackType, - ) -> None: - await self.close() - - def __getattr__(self, attr: Any) -> Any: - """Calls attributes of the MySQLConnection instance""" - return getattr(self._cnx, attr) - - async def close(self) -> None: - """Do not close, but adds connection back to pool. - For a pooled connection, close() does not actually close it but returns it - to the pool and makes it available for subsequent connection requests. If the - pool configuration parameters are changed, a returned connection is closed - and reopened with the new configuration before being returned from the pool - again in response to a connection request. - """ - cnx = self._cnx - try: - if self._cnx_pool.can_reset_session and await cnx.is_connected(): - await cnx.reset_session() - finally: - await self._cnx_pool.add_connection(cnx) - self._cnx = None - - @staticmethod - def config(**kwargs: Any) -> NoReturn: - """Configuration is done through the pool. - For pooled connections, the `config()` method raises a `PoolError` - exception. Configuration for pooled connections should be done - using the pool object. - """ - raise PoolError( - "Configuration for pooled connections should be done through the " - "pool itself" - ) - - @property - def pool_name(self) -> str: - """Returns the name of the connection pool to which the connection belongs.""" - return self._cnx_pool.pool_name - - -class MySQLConnectionPool: - """Class defining a pool of MySQL connections""" - - def __init__( - self, - pool_size: int = 5, - pool_name: Optional[str] = None, - pool_reset_session: bool = True, - **kwargs: Any, - ) -> None: - """Constructor. - - Initialize a MySQL connection pool with a maximum number of - connections set to `pool_size`. - - NOTE: The coroutine `await cnxpool.initialize_pool(**dbconfig)` must be called - after initializing this class to open up the pool of required number of database - connections and making the instance of MySQLConnectionPool ready-to-use. - Contents of `dbconfig` is described in [1]. - - Args: - pool_name: The pool name. If this argument is not given, Connector/Python - automatically generates the name, composed from whichever of - the host, port, user, and database connection arguments are - given in kwargs, in that order. - pool_size: The pool size. If this argument is not given, the default is 5. - pool_reset_session: Whether to reset session variables when the connection - is returned to the pool. - - Examples: - ``` - >>> dbconfig = { - >>> "database": "test", - >>> "user": "joe", - >>> } - >>> # initialize the MySQLConnectionPool instance - >>> cnxpool = mysql.connector.aio.pooling.MySQLConnectionPool( - >>> pool_name = "mypool", - >>> pool_size = 3, - >>> ) - >>> # Open up the pool of connections - >>> await cnxpool.initialize_pool(**dbconfig) - >>> # cnxpool is ready to be used ... - ``` - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - _check_support() - - self._pool_size: Optional[int] = None - self._pool_name: Optional[str] = None - self._reset_session: bool = pool_reset_session - self._set_pool_size(pool_size) - if pool_name: - self._set_pool_name(pool_name) - self._cnx_config: Dict[str, Any] = kwargs - self._cnx_queue: asyncio.Queue[MySQLConnectionAbstract] = asyncio.Queue( - self._pool_size - ) - self._config_version: UUID = uuid4() - - async def initialize_pool(self) -> None: - """Opens the connection pool and fill with MySQL database connections. - - This coroutine must be called after initializing an instance of MySQLConnectionPool - to make the build the pool of connections and make the instance ready to be used. - - Args: - **kwargs: Connection arguments, as described in [1]. - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - if not self._pool_name: - async with CONNECTION_POOL_LOCK: - self._set_pool_name(generate_pool_name(**self._cnx_config)) - if self._cnx_config: - await self.set_config(**self._cnx_config) - cnt = 0 - while cnt < self._pool_size: - await self.add_connection() - cnt += 1 - - @property - def pool_name(self) -> str: - """Returns the name of the connection pool.""" - return self._pool_name - - @property - def pool_size(self) -> int: - """Returns number of connections managed by the pool.""" - return self._pool_size - - @property - def can_reset_session(self) -> bool: - """Returns whether to reset session.""" - return self._reset_session - - async def set_config(self, **kwargs: Any) -> None: - """Set the connection configuration for `MySQLConnectionAbstract` subclass instances. - This method sets the configuration used for creating `MySQLConnectionAbstract` - subclass instances such as `MySQLConnection`. See [1] for valid - connection arguments. - Args: - **kwargs: Connection arguments - for a complete list of possible - arguments, see [1]. - Raises: - PoolError: When a connection argument is not valid, missing - or not supported by `MySQLConnectionAbstract`. - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-connectargs.html - """ - if not kwargs: - return - - try: - # test if we're able to connect using the provided connection options - cnx = await connect(**kwargs) - await cnx.disconnect() - async with CONNECTION_POOL_LOCK: - self._cnx_config = kwargs - self._config_version = uuid4() - except Exception as err: - raise PoolError(f"Connection configuration not valid: {err}") from err - - def _set_pool_size(self, pool_size: int) -> None: - """Set the size of the pool - This method sets the size of the pool but it will not resize the pool. - Raises an AttributeError when the pool_size is not valid. Invalid size - is 0, negative or higher than pooling.CNX_POOL_MAXSIZE. - """ - if pool_size <= 0 or pool_size > CNX_POOL_MAXSIZE: - raise AttributeError( - "Pool size should be higher than 0 and lower or equal to " - f"{CNX_POOL_MAXSIZE}" - ) - self._pool_size = pool_size - - def _set_pool_name(self, pool_name: str) -> None: - r"""Set the name of the pool. - This method checks the validity and sets the name of the pool. - Raises an AttributeError when pool_name contains illegal characters - ([^a-zA-Z0-9._\-*$#]) or is longer than pooling.CNX_POOL_MAXNAMESIZE. - """ - if CNX_POOL_NAMEREGEX.search(pool_name): - raise AttributeError(f"Pool name '{pool_name}' contains illegal characters") - if len(pool_name) > CNX_POOL_MAXNAMESIZE: - raise AttributeError(f"Pool name '{pool_name}' is too long") - self._pool_name = pool_name - - def _queue_connection(self, cnx: MySQLConnectionAbstract) -> None: - """Put connection back in the queue - This method is putting a connection back in the queue. It will not - acquire a lock as the methods using _queue_connection() will have it - set. - Raises `PoolError` on errors. - """ - if not isinstance(cnx, MYSQL_CNX_CLASS): - raise PoolError( - "Connection instance not subclass of MySQLConnectionAbstract" - ) - - try: - self._cnx_queue.put_nowait(cnx) - except asyncio.QueueFull as err: - raise PoolError("Failed adding connection; queue is full") from err - - async def add_connection( - self, cnx: Optional[MySQLConnectionAbstract] = None - ) -> None: - """Adds a connection to the pool. - This method instantiates a `MySQLConnection` using the configuration - passed when initializing the `MySQLConnectionPool` instance or using - the `set_config()` method. - If cnx is a `MySQLConnection` instance, it will be added to the - queue. - Args: - cnx: The `MySQLConnectionAbstract` subclass object to be added to - the pool. If this argument is missing (aka `None`), the pool - creates a new connection and adds it. - Raises: - PoolError: When no configuration is set, when no more - connection can be added (maximum reached) or when the connection - can not be instantiated. - """ - if not self._cnx_config: - raise PoolError("Connection configuration not available") - - if self._cnx_queue.full(): - raise PoolError("Failed adding connection; queue is full") - - if not cnx: - cnx = await connect(**self._cnx_config) - try: - if ( - self._reset_session - and self._cnx_config["compress"] - and cnx.get_server_version() < (5, 7, 3) - ): - raise NotSupportedError( - "Pool reset session is not supported with " - "compression for MySQL server version 5.7.2 " - "or earlier" - ) - except KeyError: - pass - - cnx.pool_config_version = self._config_version - else: - if not isinstance(cnx, MYSQL_CNX_CLASS): - raise PoolError( - "Connection instance not subclass of MySQLConnectionAbstract" - ) - - self._queue_connection(cnx) - - async def get_connection(self) -> PooledMySQLConnection: - """Gets a connection from the pool. - This method returns an PooledMySQLConnection instance which - has a reference to the pool that created it, and the next available - MySQL connection. - When the MySQL connection is not connect, a reconnect is attempted. - Returns: - A `PooledMySQLConnection` instance. - Raises: - PoolError: On errors. - """ - - try: - cnx = self._cnx_queue.get_nowait() - except asyncio.QueueEmpty as err: - raise PoolError("Failed getting connection; pool exhausted") from err - - if ( - not await cnx.is_connected() - or self._config_version != cnx.pool_config_version - ): - try: - cnx._set_connection_options(**self._cnx_config) - await cnx.reconnect() - except InterfaceError: - self._queue_connection(cnx) - raise - cnx.pool_config_version = self._config_version - - return PooledMySQLConnection(self, cnx) - - async def _remove_connections(self) -> int: - """Close all connections - This method closes all connections and returns the number of connections it closed. - This method should be used internally while cleaning-up the connection pool by closing each - and every active connection objects currently residing in the pool. - - Returns: - An integer defining the number of open connections closed during clean-up. - Raises: - PoolError: On errors while fetching connections from the pool or while disconnecting - an open connection. - """ - cnt = 0 - cnxq = self._cnx_queue - while cnxq.qsize(): - try: - cnx = cnxq.get_nowait() - await cnx.disconnect() - cnt += 1 - except asyncio.QueueEmpty: - return cnt - except PoolError: - raise - except Error: - # Any other error when closing means connection is closed - pass - - return cnt - - async def close_pool(self) -> int: - """Cleans up the connection pool - This public method gracefully shuts down the connection pool by disconnecting all of the - open connections currently active in the pool. - - Returns: - An integer defining the number of open connections closed while shutting down the pool. - Raises: - PoolError: On errors while fetching connections from the pool or while disconnecting - an open connection. - """ - return await self._remove_connections() diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/protocol.py b/server/venv/Lib/site-packages/mysql/connector/aio/protocol.py deleted file mode 100644 index 9b30004..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/protocol.py +++ /dev/null @@ -1,325 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implements the MySQL Client/Server protocol.""" - -__all__ = ["MySQLProtocol"] - -import struct - -from typing import Any, Dict, List, Optional, Tuple - -from ..constants import ClientFlag, ServerCmd -from ..errors import InterfaceError, ProgrammingError, get_exception -from ..logger import logger -from ..protocol import ( - DEFAULT_CHARSET_ID, - DEFAULT_MAX_ALLOWED_PACKET, - MySQLProtocol as _MySQLProtocol, -) -from ..types import BinaryProtocolType, DescriptionType, EofPacketType, HandShakeType -from ..utils import lc_int, read_lc_string_list -from .network import MySQLSocket -from .plugins import MySQLAuthPlugin, get_auth_plugin -from .plugins.caching_sha2_password import MySQLCachingSHA2PasswordAuthPlugin - - -class MySQLProtocol(_MySQLProtocol): - """Implements MySQL client/server protocol. - - Create and parses MySQL packets. - """ - - @staticmethod - def auth_plugin_first_response( # type: ignore[override] - auth_data: bytes, - username: str, - password: str, - auth_plugin: str, - auth_plugin_class: Optional[str] = None, - ssl_enabled: bool = False, - plugin_config: Optional[Dict[str, Any]] = None, - ) -> Tuple[bytes, MySQLAuthPlugin]: - """Prepare the first authentication response. - - Args: - auth_data: Authorization data from initial handshake. - username: Account's username. - password: Account's password. - client_flags: Integer representing client capabilities flags. - auth_plugin: Authorization plugin name. - auth_plugin_class: Authorization plugin class (has higher precedence - than the authorization plugin name). - ssl_enabled: Whether SSL is enabled or not. - plugin_config: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override - the ones defined in the auth plugin itself. - - Returns: - auth_response: Authorization plugin response. - auth_strategy: Authorization plugin instance created based - on the provided `auth_plugin` and `auth_plugin_class` - parameters. - - Raises: - InterfaceError: If authentication fails or when got a NULL auth response. - """ - if not password and auth_plugin == "": - # return auth response and an arbitrary auth strategy - return b"\x00", MySQLCachingSHA2PasswordAuthPlugin( - username, password, ssl_enabled=ssl_enabled - ) - - if plugin_config is None: - plugin_config = {} - - try: - auth_strategy = get_auth_plugin(auth_plugin, auth_plugin_class)( - username, password, ssl_enabled=ssl_enabled - ) - auth_response = auth_strategy.auth_response(auth_data, **plugin_config) - except (TypeError, InterfaceError) as err: - raise InterfaceError(f"Failed authentication: {err}") from err - - if auth_response is None: - raise InterfaceError( - "Got NULL auth response while authenticating with " - f"plugin {auth_strategy.name}" - ) - - auth_response = lc_int(len(auth_response)) + auth_response - - return auth_response, auth_strategy - - @staticmethod - def make_auth( # type: ignore[override] - handshake: HandShakeType, - username: str, - password: str, - database: Optional[str] = None, - charset: int = DEFAULT_CHARSET_ID, - client_flags: int = 0, - max_allowed_packet: int = DEFAULT_MAX_ALLOWED_PACKET, - auth_plugin: Optional[str] = None, - auth_plugin_class: Optional[str] = None, - conn_attrs: Optional[Dict[str, str]] = None, - is_change_user_request: bool = False, - ssl_enabled: bool = False, - plugin_config: Optional[Dict[str, Any]] = None, - ) -> Tuple[bytes, MySQLAuthPlugin]: - """Make a MySQL Authentication packet. - - Args: - handshake: Initial handshake. - username: Account's username. - password: Account's password. - database: Initial database name for the connection - charset: Client charset (see [2]), only the lower 8-bits. - client_flags: Integer representing client capabilities flags. - max_allowed_packet: Maximum packet size. - auth_plugin: Authorization plugin name. - auth_plugin_class: Authorization plugin class (has higher precedence - than the authorization plugin name). - conn_attrs: Connection attributes. - is_change_user_request: Whether is a `change user request` operation or not. - ssl_enabled: Whether SSL is enabled or not. - plugin_config: Custom configuration to be passed to the auth plugin - when invoked. The parameters defined here will override - the one defined in the auth plugin itself. - - Returns: - handshake_response: Handshake response as per [1]. - auth_strategy: Authorization plugin instance created based - on the provided `auth_plugin` and `auth_plugin_class`. - - Raises: - ProgrammingError: Handshake misses authentication info. - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_connection_phase_packets_protocol_handshake_response.html - - [2]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_basic_character_set.html#a_protocol_character_set - """ - b_username = username.encode() - response_payload = [] - - if is_change_user_request: - logger.debug("Got a `change user` request") - - logger.debug("Starting authorization phase") - if handshake is None: - raise ProgrammingError("Got a NULL handshake") from None - - if handshake.get("auth_data") is None: - raise ProgrammingError("Handshake misses authentication info") from None - - try: - auth_plugin = auth_plugin or handshake["auth_plugin"] # type: ignore[assignment] - except (TypeError, KeyError) as err: - raise ProgrammingError( - f"Handshake misses authentication plugin info ({err})" - ) from None - - logger.debug("The provided initial strategy is %s", auth_plugin) - - if is_change_user_request: - response_payload.append( - struct.pack( - f" Tuple[ - List[Tuple[BinaryProtocolType, ...]], - Optional[EofPacketType], - ]: - """Read MySQL binary protocol result. - - Reads all or given number of binary resultset rows from the socket. - """ - rows = [] - eof = None - values = None - i = 0 - while True: - if eof or i == count: - break - packet = await sock.read(read_timeout) - if packet[4] == 254: - eof = self.parse_eof(packet) - values = None - elif packet[4] == 0: - eof = None - values = self._parse_binary_values(columns, packet[5:], charset) - if eof is None and values is not None: - rows.append(values) - elif eof is None and values is None: - raise get_exception(packet) - i += 1 - return (rows, eof) - - # pylint: disable=invalid-overridden-method - async def read_text_result( # type: ignore[override] - self, - sock: MySQLSocket, - version: Tuple[int, ...], - count: int = 1, - read_timeout: Optional[int] = None, - ) -> Tuple[ - List[Tuple[Optional[bytes], ...]], - Optional[EofPacketType], - ]: - """Read MySQL text result. - - Reads all or given number of rows from the socket. - - Returns a tuple with 2 elements: a list with all rows and - the EOF packet. - """ - # Keep unused 'version' for API backward compatibility - _ = version - rows = [] - eof = None - rowdata = None - i = 0 - while True: - if eof or i == count: - break - packet = await sock.read(read_timeout) - if packet.startswith(b"\xff\xff\xff"): - datas = [packet[4:]] - packet = await sock.read(read_timeout) - while packet.startswith(b"\xff\xff\xff"): - datas.append(packet[4:]) - packet = await sock.read(read_timeout) - datas.append(packet[4:]) - rowdata = read_lc_string_list(b"".join(datas)) - elif packet[4] == 254 and packet[0] < 7: - eof = self.parse_eof(packet) - rowdata = None - else: - eof = None - rowdata = read_lc_string_list(bytes(packet[4:])) - if eof is None and rowdata is not None: - rows.append(rowdata) - elif eof is None and rowdata is None: - raise get_exception(packet) - i += 1 - return rows, eof diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/py.typed b/server/venv/Lib/site-packages/mysql/connector/aio/py.typed deleted file mode 100644 index 1f2326a..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/py.typed +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/server/venv/Lib/site-packages/mysql/connector/aio/utils.py b/server/venv/Lib/site-packages/mysql/connector/aio/utils.py deleted file mode 100644 index cf3328f..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/aio/utils.py +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="attr-defined" -# pylint: disable=protected-access - -"""Utilities.""" - -__all__ = ["to_thread", "open_connection"] - -import asyncio -import contextvars -import functools - -try: - import ssl -except ImportError: - ssl = None - -from typing import TYPE_CHECKING, Any, Callable, Tuple - -if TYPE_CHECKING: - from mysql.connector.aio.abstracts import MySQLConnectionAbstract - - __all__.append("StreamWriter") - - -class StreamReaderProtocol(asyncio.StreamReaderProtocol): - """Extends asyncio.streams.StreamReaderProtocol for adding start_tls(). - - The ``start_tls()`` is based on ``asyncio.streams.StreamWriter`` introduced - in Python 3.11. It provides the same functionality for older Python versions. - """ - - def _replace_writer(self, writer: asyncio.StreamWriter) -> None: - """Replace stream writer. - - Args: - writer: Stream Writer. - """ - transport = writer.transport - self._stream_writer = writer - self._transport = transport - self._over_ssl = transport.get_extra_info("sslcontext") is not None - - -class StreamWriter(asyncio.streams.StreamWriter): - """Extends asyncio.streams.StreamWriter for adding start_tls(). - - The ``start_tls()`` is based on ``asyncio.streams.StreamWriter`` introduced - in Python 3.11. It provides the same functionality for older Python versions. - """ - - async def start_tls( - self, - ssl_context: ssl.SSLContext, - *, - server_hostname: str = None, - ssl_handshake_timeout: int = None, - ) -> None: - """Upgrade an existing stream-based connection to TLS. - - Args: - ssl_context: Configured SSL context. - server_hostname: Server host name. - ssl_handshake_timeout: SSL handshake timeout. - """ - server_side = self._protocol._client_connected_cb is not None - protocol = self._protocol - await self.drain() - new_transport = await self._loop.start_tls( - # pylint: disable=access-member-before-definition - self._transport, # type: ignore[has-type] - protocol, - ssl_context, - server_side=server_side, - server_hostname=server_hostname, - ssl_handshake_timeout=ssl_handshake_timeout, - ) - self._transport = ( # pylint: disable=attribute-defined-outside-init - new_transport - ) - protocol._replace_writer(self) - - -async def open_connection( - host: str = None, port: int = None, *, limit: int = 2**16, **kwds: Any -) -> Tuple[asyncio.StreamReader, StreamWriter]: - """A wrapper for create_connection() returning a (reader, writer) pair. - - This function is based on ``asyncio.streams.open_connection`` and adds a custom - stream reader. - - MySQL expects TLS negotiation to happen in the middle of a TCP connection, not at - the start. - This function in conjunction with ``_StreamReaderProtocol`` and ``_StreamWriter`` - allows the TLS negotiation on an existing connection. - - Args: - host: Server host name. - port: Server port. - limit: The buffer size limit used by the returned ``StreamReader`` instance. - By default the limit is set to 64 KiB. - - Returns: - tuple: Returns a pair of reader and writer objects that are instances of - ``StreamReader`` and ``StreamWriter`` classes. - """ - loop = asyncio.get_running_loop() - reader = asyncio.streams.StreamReader(limit=limit, loop=loop) - protocol = StreamReaderProtocol(reader, loop=loop) - transport, _ = await loop.create_connection(lambda: protocol, host, port, **kwds) - writer = StreamWriter(transport, protocol, reader, loop) - return reader, writer - - -async def to_thread(func: Callable, *args: Any, **kwargs: Any) -> asyncio.Future: - """Asynchronously run function ``func`` in a separate thread. - - This function is based on ``asyncio.to_thread()`` introduced in Python 3.9, which - provides the same functionality for older Python versions. - - Returns: - coroutine: A coroutine that can be awaited to get the eventual result of - ``func``. - """ - loop = asyncio.get_running_loop() - ctx = contextvars.copy_context() - func_call = functools.partial(ctx.run, func, *args, **kwargs) - return await loop.run_in_executor(None, func_call) diff --git a/server/venv/Lib/site-packages/mysql/connector/authentication.py b/server/venv/Lib/site-packages/mysql/connector/authentication.py deleted file mode 100644 index 6a40b70..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/authentication.py +++ /dev/null @@ -1,385 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Implementing support for MySQL Authentication Plugins""" -from __future__ import annotations - -from typing import TYPE_CHECKING, Any, Dict, Optional - -from .errors import InterfaceError, NotSupportedError, get_exception -from .logger import logger -from .plugins import MySQLAuthPlugin, get_auth_plugin -from .protocol import ( - AUTH_SWITCH_STATUS, - DEFAULT_CHARSET_ID, - DEFAULT_MAX_ALLOWED_PACKET, - ERR_STATUS, - EXCHANGE_FURTHER_STATUS, - MFA_STATUS, - OK_STATUS, - MySQLProtocol, -) -from .types import HandShakeType - -if TYPE_CHECKING: - from .network import MySQLSocket - - -class MySQLAuthenticator: - """Implements the authentication phase.""" - - def __init__(self) -> None: - """Constructor.""" - self._username: str = "" - self._passwords: Dict[int, str] = {} - self._plugin_config: Dict[str, Any] = {} - self._ssl_enabled: bool = False - self._auth_strategy: Optional[MySQLAuthPlugin] = None - self._auth_plugin_class: Optional[str] = None - - @property - def ssl_enabled(self) -> bool: - """Signals whether or not SSL is enabled.""" - return self._ssl_enabled - - @property - def plugin_config(self) -> Dict[str, Any]: - """Custom arguments that are being provided to the authentication plugin when called. - - The parameters defined here will override the ones defined in the - auth plugin itself. - - The plugin config is a read-only property - the plugin configuration - provided when invoking `authenticate()` is recorded and can be queried - by accessing this property. - - Returns: - dict: The latest plugin configuration provided when invoking - `authenticate()`. - """ - return self._plugin_config - - def update_plugin_config(self, config: Dict[str, Any]) -> None: - """Update the 'plugin_config' instance variable""" - self._plugin_config.update(config) - - def setup_ssl( - self, - sock: MySQLSocket, - host: str, - ssl_options: Optional[Dict[str, Any]], - charset: int = DEFAULT_CHARSET_ID, - client_flags: int = 0, - max_allowed_packet: int = DEFAULT_MAX_ALLOWED_PACKET, - ) -> bytes: - """Sets up an SSL communication channel. - - Args: - sock: Pointer to the socket connection. - host: Server host name. - ssl_options: SSL and TLS connection options (see - `network.MySQLSocket.build_ssl_context`). - charset: Client charset (see [1]), only the lower 8-bits. - client_flags: Integer representing client capabilities flags. - max_allowed_packet: Maximum packet size. - - Returns: - ssl_request_payload: Payload used to carry out SSL authentication. - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_basic_character_set.html#a_protocol_character_set - """ - if ssl_options is None: - ssl_options = {} - - # SSL connection request packet - ssl_request_payload = MySQLProtocol.make_auth_ssl( - charset=charset, - client_flags=client_flags, - max_allowed_packet=max_allowed_packet, - ) - sock.send(ssl_request_payload) - - logger.debug("Building SSL context") - ssl_context = sock.build_ssl_context( - ssl_ca=ssl_options.get("ca"), - ssl_cert=ssl_options.get("cert"), - ssl_key=ssl_options.get("key"), - ssl_verify_cert=ssl_options.get("verify_cert", False), - ssl_verify_identity=ssl_options.get("verify_identity", False), - tls_versions=ssl_options.get("tls_versions"), - tls_cipher_suites=ssl_options.get("tls_ciphersuites"), - ) - - logger.debug("Switching to SSL") - sock.switch_to_ssl(ssl_context, host) - - logger.debug("SSL has been enabled") - self._ssl_enabled = True - - return ssl_request_payload - - def _switch_auth_strategy( - self, - new_strategy_name: str, - strategy_class: Optional[str] = None, - username: Optional[str] = None, - password_factor: int = 1, - ) -> None: - """Switches the authorization plugin. - - Args: - new_strategy_name: New authorization plugin name to switch to. - strategy_class: New authorization plugin class to switch to - (has higher precedence than the authorization plugin name). - username: Username to be used - if not defined, the username - provided when `authentication()` was invoked is used. - password_factor: Up to three levels of authentication (MFA) are allowed, - hence you can choose the password corresponding to the 1st, - 2nd, or 3rd factor - 1st is the default. - """ - if username is None: - username = self._username - - if strategy_class is None: - strategy_class = self._auth_plugin_class - - logger.debug("Switching to strategy %s", new_strategy_name) - self._auth_strategy = get_auth_plugin( - plugin_name=new_strategy_name, auth_plugin_class=strategy_class - )( - username, - self._passwords.get(password_factor, ""), - ssl_enabled=self.ssl_enabled, - ) - - def _mfa_n_factor( - self, - sock: MySQLSocket, - pkt: bytes, - ) -> Optional[bytes]: - """Handles MFA (Multi-Factor Authentication) response. - - Up to three levels of authentication (MFA) are allowed. - - Args: - sock: Pointer to the socket connection. - pkt: MFA response. - - Returns: - ok_packet: If last server's response is an OK packet. - None: If last server's response isn't an OK packet and no ERROR was raised. - - Raises: - InterfaceError: If got an invalid N factor. - errors.ErrorTypes: If got an ERROR response. - """ - n_factor = 2 - while pkt[4] == MFA_STATUS: - if n_factor not in self._passwords: - raise InterfaceError( - "Failed Multi Factor Authentication (invalid N factor)" - ) - - new_strategy_name, auth_data = MySQLProtocol.parse_auth_next_factor(pkt) - self._switch_auth_strategy(new_strategy_name, password_factor=n_factor) - logger.debug("MFA %i factor %s", n_factor, self._auth_strategy.name) - - pkt = self._auth_strategy.auth_switch_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == EXCHANGE_FURTHER_STATUS: - auth_data = MySQLProtocol.parse_auth_more_data(pkt) - pkt = self._auth_strategy.auth_more_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == OK_STATUS: - logger.debug("MFA completed succesfully") - return pkt - - if pkt[4] == ERR_STATUS: - raise get_exception(pkt) - - n_factor += 1 - - logger.warning("MFA terminated with a no ok packet") - return None - - def _handle_server_response( - self, - sock: MySQLSocket, - pkt: bytes, - ) -> Optional[bytes]: - """Handles server's response. - - Args: - sock: Pointer to the socket connection. - pkt: Server's response after completing the `HandShakeResponse`. - - Returns: - ok_packet: If last server's response is an OK packet. - None: If last server's response isn't an OK packet and no ERROR was raised. - - Raises: - errors.ErrorTypes: If got an ERROR response. - NotSupportedError: If got Authentication with old (insecure) passwords. - """ - if pkt[4] == AUTH_SWITCH_STATUS and len(pkt) == 5: - raise NotSupportedError( - "Authentication with old (insecure) passwords " - "is not supported. For more information, lookup " - "Password Hashing in the latest MySQL manual" - ) - - if pkt[4] == AUTH_SWITCH_STATUS: - logger.debug("Server's response is an auth switch request") - new_strategy_name, auth_data = MySQLProtocol.parse_auth_switch_request(pkt) - self._switch_auth_strategy(new_strategy_name) - pkt = self._auth_strategy.auth_switch_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == EXCHANGE_FURTHER_STATUS: - logger.debug("Exchanging further packets") - auth_data = MySQLProtocol.parse_auth_more_data(pkt) - pkt = self._auth_strategy.auth_more_response( - sock, auth_data, **self._plugin_config - ) - - if pkt[4] == OK_STATUS: - logger.debug("%s completed succesfully", self._auth_strategy.name) - return pkt - - if pkt[4] == MFA_STATUS: - logger.debug("Starting multi-factor authentication") - logger.debug("MFA 1 factor %s", self._auth_strategy.name) - return self._mfa_n_factor(sock, pkt) - - if pkt[4] == ERR_STATUS: - raise get_exception(pkt) - - return None - - def authenticate( - self, - sock: MySQLSocket, - handshake: HandShakeType, - username: str = "", - password1: str = "", - password2: str = "", - password3: str = "", - database: Optional[str] = None, - charset: int = DEFAULT_CHARSET_ID, - client_flags: int = 0, - max_allowed_packet: int = DEFAULT_MAX_ALLOWED_PACKET, - auth_plugin: Optional[str] = None, - auth_plugin_class: Optional[str] = None, - conn_attrs: Optional[Dict[str, str]] = None, - is_change_user_request: bool = False, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> bytes: - """Performs the authentication phase. - - During re-authentication you must set `is_change_user_request` to True. - - Args: - sock: Pointer to the socket connection. - handshake: Initial handshake. - username: Account's username. - password1: Account's password factor 1. - password2: Account's password factor 2. - password3: Account's password factor 3. - database: Initial database name for the connection. - charset: Client charset (see [1]), only the lower 8-bits. - client_flags: Integer representing client capabilities flags. - max_allowed_packet: Maximum packet size. - auth_plugin: Authorization plugin name. - auth_plugin_class: Authorization plugin class (has higher precedence - than the authorization plugin name). - conn_attrs: Connection attributes. - is_change_user_request: Whether is a `change user request` operation or not. - read_timeout: Timeout in seconds upto which the connector should wait for - the server to reply back before raising an ReadTimeoutError. - write_timeout: Timeout in seconds upto which the connector should spend to - send data to the server before raising an WriteTimeoutError. - Returns: - ok_packet: OK packet. - - Raises: - InterfaceError: If OK packet is NULL. - ReadTimeoutError: If the time taken for the server to reply back exceeds - 'read_timeout' (if set). - WriteTimeoutError: If the time taken to send data packets to the server - exceeds 'write_timeout' (if set). - - References: - [1]: https://dev.mysql.com/doc/dev/mysql-server/latest/\ - page_protocol_basic_character_set.html#a_protocol_character_set - """ - # update credentials, plugin config and plugin class - self._username = username - self._passwords = {1: password1, 2: password2, 3: password3} - self._auth_plugin_class = auth_plugin_class - - # client's handshake response - response_payload, self._auth_strategy = MySQLProtocol.make_auth( - handshake=handshake, - username=username, - password=password1, - database=database, - charset=charset, - client_flags=client_flags, - max_allowed_packet=max_allowed_packet, - auth_plugin=auth_plugin, - auth_plugin_class=auth_plugin_class, - conn_attrs=conn_attrs, - is_change_user_request=is_change_user_request, - ssl_enabled=self.ssl_enabled, - plugin_config=self.plugin_config, - ) - - # client sends transaction response - send_args = ( - (0, 0, write_timeout) - if is_change_user_request - else (None, None, write_timeout) - ) - sock.send(response_payload, *send_args) - - # server replies back - pkt = bytes(sock.recv(read_timeout)) - - ok_pkt = self._handle_server_response(sock, pkt) - if ok_pkt is None: - raise InterfaceError("Got a NULL ok_pkt") from None - - return ok_pkt diff --git a/server/venv/Lib/site-packages/mysql/connector/charsets.py b/server/venv/Lib/site-packages/mysql/connector/charsets.py deleted file mode 100644 index 5220b9d..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/charsets.py +++ /dev/null @@ -1,620 +0,0 @@ -# -*- coding: utf-8 -*- # pylint: disable=missing-module-docstring - -# Copyright (c) 2013, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -from typing import List, Optional, Tuple - -"""This module contains the MySQL Server Character Sets.""" # pylint: disable=pointless-string-statement - -# This file was auto-generated. -_GENERATED_ON: str = "2022-05-09" -_MYSQL_VERSION: Tuple[int, int, int] = (8, 0, 30) - -MYSQL_CHARACTER_SETS: List[Optional[Tuple[str, str, bool]]] = [ - # (character set name, collation, default) - None, - ("big5", "big5_chinese_ci", True), # 1 - ("latin2", "latin2_czech_cs", False), # 2 - ("dec8", "dec8_swedish_ci", True), # 3 - ("cp850", "cp850_general_ci", True), # 4 - ("latin1", "latin1_german1_ci", False), # 5 - ("hp8", "hp8_english_ci", True), # 6 - ("koi8r", "koi8r_general_ci", True), # 7 - ("latin1", "latin1_swedish_ci", True), # 8 - ("latin2", "latin2_general_ci", True), # 9 - ("swe7", "swe7_swedish_ci", True), # 10 - ("ascii", "ascii_general_ci", True), # 11 - ("ujis", "ujis_japanese_ci", True), # 12 - ("sjis", "sjis_japanese_ci", True), # 13 - ("cp1251", "cp1251_bulgarian_ci", False), # 14 - ("latin1", "latin1_danish_ci", False), # 15 - ("hebrew", "hebrew_general_ci", True), # 16 - None, - ("tis620", "tis620_thai_ci", True), # 18 - ("euckr", "euckr_korean_ci", True), # 19 - ("latin7", "latin7_estonian_cs", False), # 20 - ("latin2", "latin2_hungarian_ci", False), # 21 - ("koi8u", "koi8u_general_ci", True), # 22 - ("cp1251", "cp1251_ukrainian_ci", False), # 23 - ("gb2312", "gb2312_chinese_ci", True), # 24 - ("greek", "greek_general_ci", True), # 25 - ("cp1250", "cp1250_general_ci", True), # 26 - ("latin2", "latin2_croatian_ci", False), # 27 - ("gbk", "gbk_chinese_ci", True), # 28 - ("cp1257", "cp1257_lithuanian_ci", False), # 29 - ("latin5", "latin5_turkish_ci", True), # 30 - ("latin1", "latin1_german2_ci", False), # 31 - ("armscii8", "armscii8_general_ci", True), # 32 - ("utf8mb3", "utf8mb3_general_ci", True), # 33 - ("cp1250", "cp1250_czech_cs", False), # 34 - ("ucs2", "ucs2_general_ci", True), # 35 - ("cp866", "cp866_general_ci", True), # 36 - ("keybcs2", "keybcs2_general_ci", True), # 37 - ("macce", "macce_general_ci", True), # 38 - ("macroman", "macroman_general_ci", True), # 39 - ("cp852", "cp852_general_ci", True), # 40 - ("latin7", "latin7_general_ci", True), # 41 - ("latin7", "latin7_general_cs", False), # 42 - ("macce", "macce_bin", False), # 43 - ("cp1250", "cp1250_croatian_ci", False), # 44 - ("utf8mb4", "utf8mb4_general_ci", False), # 45 - ("utf8mb4", "utf8mb4_bin", False), # 46 - ("latin1", "latin1_bin", False), # 47 - ("latin1", "latin1_general_ci", False), # 48 - ("latin1", "latin1_general_cs", False), # 49 - ("cp1251", "cp1251_bin", False), # 50 - ("cp1251", "cp1251_general_ci", True), # 51 - ("cp1251", "cp1251_general_cs", False), # 52 - ("macroman", "macroman_bin", False), # 53 - ("utf16", "utf16_general_ci", True), # 54 - ("utf16", "utf16_bin", False), # 55 - ("utf16le", "utf16le_general_ci", True), # 56 - ("cp1256", "cp1256_general_ci", True), # 57 - ("cp1257", "cp1257_bin", False), # 58 - ("cp1257", "cp1257_general_ci", True), # 59 - ("utf32", "utf32_general_ci", True), # 60 - ("utf32", "utf32_bin", False), # 61 - ("utf16le", "utf16le_bin", False), # 62 - ("binary", "binary", True), # 63 - ("armscii8", "armscii8_bin", False), # 64 - ("ascii", "ascii_bin", False), # 65 - ("cp1250", "cp1250_bin", False), # 66 - ("cp1256", "cp1256_bin", False), # 67 - ("cp866", "cp866_bin", False), # 68 - ("dec8", "dec8_bin", False), # 69 - ("greek", "greek_bin", False), # 70 - ("hebrew", "hebrew_bin", False), # 71 - ("hp8", "hp8_bin", False), # 72 - ("keybcs2", "keybcs2_bin", False), # 73 - ("koi8r", "koi8r_bin", False), # 74 - ("koi8u", "koi8u_bin", False), # 75 - ("utf8mb3", "utf8mb3_tolower_ci", False), # 76 - ("latin2", "latin2_bin", False), # 77 - ("latin5", "latin5_bin", False), # 78 - ("latin7", "latin7_bin", False), # 79 - ("cp850", "cp850_bin", False), # 80 - ("cp852", "cp852_bin", False), # 81 - ("swe7", "swe7_bin", False), # 82 - ("utf8mb3", "utf8mb3_bin", False), # 83 - ("big5", "big5_bin", False), # 84 - ("euckr", "euckr_bin", False), # 85 - ("gb2312", "gb2312_bin", False), # 86 - ("gbk", "gbk_bin", False), # 87 - ("sjis", "sjis_bin", False), # 88 - ("tis620", "tis620_bin", False), # 89 - ("ucs2", "ucs2_bin", False), # 90 - ("ujis", "ujis_bin", False), # 91 - ("geostd8", "geostd8_general_ci", True), # 92 - ("geostd8", "geostd8_bin", False), # 93 - ("latin1", "latin1_spanish_ci", False), # 94 - ("cp932", "cp932_japanese_ci", True), # 95 - ("cp932", "cp932_bin", False), # 96 - ("eucjpms", "eucjpms_japanese_ci", True), # 97 - ("eucjpms", "eucjpms_bin", False), # 98 - ("cp1250", "cp1250_polish_ci", False), # 99 - None, - ("utf16", "utf16_unicode_ci", False), # 101 - ("utf16", "utf16_icelandic_ci", False), # 102 - ("utf16", "utf16_latvian_ci", False), # 103 - ("utf16", "utf16_romanian_ci", False), # 104 - ("utf16", "utf16_slovenian_ci", False), # 105 - ("utf16", "utf16_polish_ci", False), # 106 - ("utf16", "utf16_estonian_ci", False), # 107 - ("utf16", "utf16_spanish_ci", False), # 108 - ("utf16", "utf16_swedish_ci", False), # 109 - ("utf16", "utf16_turkish_ci", False), # 110 - ("utf16", "utf16_czech_ci", False), # 111 - ("utf16", "utf16_danish_ci", False), # 112 - ("utf16", "utf16_lithuanian_ci", False), # 113 - ("utf16", "utf16_slovak_ci", False), # 114 - ("utf16", "utf16_spanish2_ci", False), # 115 - ("utf16", "utf16_roman_ci", False), # 116 - ("utf16", "utf16_persian_ci", False), # 117 - ("utf16", "utf16_esperanto_ci", False), # 118 - ("utf16", "utf16_hungarian_ci", False), # 119 - ("utf16", "utf16_sinhala_ci", False), # 120 - ("utf16", "utf16_german2_ci", False), # 121 - ("utf16", "utf16_croatian_ci", False), # 122 - ("utf16", "utf16_unicode_520_ci", False), # 123 - ("utf16", "utf16_vietnamese_ci", False), # 124 - None, - None, - None, - ("ucs2", "ucs2_unicode_ci", False), # 128 - ("ucs2", "ucs2_icelandic_ci", False), # 129 - ("ucs2", "ucs2_latvian_ci", False), # 130 - ("ucs2", "ucs2_romanian_ci", False), # 131 - ("ucs2", "ucs2_slovenian_ci", False), # 132 - ("ucs2", "ucs2_polish_ci", False), # 133 - ("ucs2", "ucs2_estonian_ci", False), # 134 - ("ucs2", "ucs2_spanish_ci", False), # 135 - ("ucs2", "ucs2_swedish_ci", False), # 136 - ("ucs2", "ucs2_turkish_ci", False), # 137 - ("ucs2", "ucs2_czech_ci", False), # 138 - ("ucs2", "ucs2_danish_ci", False), # 139 - ("ucs2", "ucs2_lithuanian_ci", False), # 140 - ("ucs2", "ucs2_slovak_ci", False), # 141 - ("ucs2", "ucs2_spanish2_ci", False), # 142 - ("ucs2", "ucs2_roman_ci", False), # 143 - ("ucs2", "ucs2_persian_ci", False), # 144 - ("ucs2", "ucs2_esperanto_ci", False), # 145 - ("ucs2", "ucs2_hungarian_ci", False), # 146 - ("ucs2", "ucs2_sinhala_ci", False), # 147 - ("ucs2", "ucs2_german2_ci", False), # 148 - ("ucs2", "ucs2_croatian_ci", False), # 149 - ("ucs2", "ucs2_unicode_520_ci", False), # 150 - ("ucs2", "ucs2_vietnamese_ci", False), # 151 - None, - None, - None, - None, - None, - None, - None, - ("ucs2", "ucs2_general_mysql500_ci", False), # 159 - ("utf32", "utf32_unicode_ci", False), # 160 - ("utf32", "utf32_icelandic_ci", False), # 161 - ("utf32", "utf32_latvian_ci", False), # 162 - ("utf32", "utf32_romanian_ci", False), # 163 - ("utf32", "utf32_slovenian_ci", False), # 164 - ("utf32", "utf32_polish_ci", False), # 165 - ("utf32", "utf32_estonian_ci", False), # 166 - ("utf32", "utf32_spanish_ci", False), # 167 - ("utf32", "utf32_swedish_ci", False), # 168 - ("utf32", "utf32_turkish_ci", False), # 169 - ("utf32", "utf32_czech_ci", False), # 170 - ("utf32", "utf32_danish_ci", False), # 171 - ("utf32", "utf32_lithuanian_ci", False), # 172 - ("utf32", "utf32_slovak_ci", False), # 173 - ("utf32", "utf32_spanish2_ci", False), # 174 - ("utf32", "utf32_roman_ci", False), # 175 - ("utf32", "utf32_persian_ci", False), # 176 - ("utf32", "utf32_esperanto_ci", False), # 177 - ("utf32", "utf32_hungarian_ci", False), # 178 - ("utf32", "utf32_sinhala_ci", False), # 179 - ("utf32", "utf32_german2_ci", False), # 180 - ("utf32", "utf32_croatian_ci", False), # 181 - ("utf32", "utf32_unicode_520_ci", False), # 182 - ("utf32", "utf32_vietnamese_ci", False), # 183 - None, - None, - None, - None, - None, - None, - None, - None, - ("utf8mb3", "utf8mb3_unicode_ci", False), # 192 - ("utf8mb3", "utf8mb3_icelandic_ci", False), # 193 - ("utf8mb3", "utf8mb3_latvian_ci", False), # 194 - ("utf8mb3", "utf8mb3_romanian_ci", False), # 195 - ("utf8mb3", "utf8mb3_slovenian_ci", False), # 196 - ("utf8mb3", "utf8mb3_polish_ci", False), # 197 - ("utf8mb3", "utf8mb3_estonian_ci", False), # 198 - ("utf8mb3", "utf8mb3_spanish_ci", False), # 199 - ("utf8mb3", "utf8mb3_swedish_ci", False), # 200 - ("utf8mb3", "utf8mb3_turkish_ci", False), # 201 - ("utf8mb3", "utf8mb3_czech_ci", False), # 202 - ("utf8mb3", "utf8mb3_danish_ci", False), # 203 - ("utf8mb3", "utf8mb3_lithuanian_ci", False), # 204 - ("utf8mb3", "utf8mb3_slovak_ci", False), # 205 - ("utf8mb3", "utf8mb3_spanish2_ci", False), # 206 - ("utf8mb3", "utf8mb3_roman_ci", False), # 207 - ("utf8mb3", "utf8mb3_persian_ci", False), # 208 - ("utf8mb3", "utf8mb3_esperanto_ci", False), # 209 - ("utf8mb3", "utf8mb3_hungarian_ci", False), # 210 - ("utf8mb3", "utf8mb3_sinhala_ci", False), # 211 - ("utf8mb3", "utf8mb3_german2_ci", False), # 212 - ("utf8mb3", "utf8mb3_croatian_ci", False), # 213 - ("utf8mb3", "utf8mb3_unicode_520_ci", False), # 214 - ("utf8mb3", "utf8mb3_vietnamese_ci", False), # 215 - None, - None, - None, - None, - None, - None, - None, - ("utf8mb3", "utf8mb3_general_mysql500_ci", False), # 223 - ("utf8mb4", "utf8mb4_unicode_ci", False), # 224 - ("utf8mb4", "utf8mb4_icelandic_ci", False), # 225 - ("utf8mb4", "utf8mb4_latvian_ci", False), # 226 - ("utf8mb4", "utf8mb4_romanian_ci", False), # 227 - ("utf8mb4", "utf8mb4_slovenian_ci", False), # 228 - ("utf8mb4", "utf8mb4_polish_ci", False), # 229 - ("utf8mb4", "utf8mb4_estonian_ci", False), # 230 - ("utf8mb4", "utf8mb4_spanish_ci", False), # 231 - ("utf8mb4", "utf8mb4_swedish_ci", False), # 232 - ("utf8mb4", "utf8mb4_turkish_ci", False), # 233 - ("utf8mb4", "utf8mb4_czech_ci", False), # 234 - ("utf8mb4", "utf8mb4_danish_ci", False), # 235 - ("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236 - ("utf8mb4", "utf8mb4_slovak_ci", False), # 237 - ("utf8mb4", "utf8mb4_spanish2_ci", False), # 238 - ("utf8mb4", "utf8mb4_roman_ci", False), # 239 - ("utf8mb4", "utf8mb4_persian_ci", False), # 240 - ("utf8mb4", "utf8mb4_esperanto_ci", False), # 241 - ("utf8mb4", "utf8mb4_hungarian_ci", False), # 242 - ("utf8mb4", "utf8mb4_sinhala_ci", False), # 243 - ("utf8mb4", "utf8mb4_german2_ci", False), # 244 - ("utf8mb4", "utf8mb4_croatian_ci", False), # 245 - ("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246 - ("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247 - ("gb18030", "gb18030_chinese_ci", True), # 248 - ("gb18030", "gb18030_bin", False), # 249 - ("gb18030", "gb18030_unicode_520_ci", False), # 250 - None, - None, - None, - None, - ("utf8mb4", "utf8mb4_0900_ai_ci", True), # 255 - ("utf8mb4", "utf8mb4_de_pb_0900_ai_ci", False), # 256 - ("utf8mb4", "utf8mb4_is_0900_ai_ci", False), # 257 - ("utf8mb4", "utf8mb4_lv_0900_ai_ci", False), # 258 - ("utf8mb4", "utf8mb4_ro_0900_ai_ci", False), # 259 - ("utf8mb4", "utf8mb4_sl_0900_ai_ci", False), # 260 - ("utf8mb4", "utf8mb4_pl_0900_ai_ci", False), # 261 - ("utf8mb4", "utf8mb4_et_0900_ai_ci", False), # 262 - ("utf8mb4", "utf8mb4_es_0900_ai_ci", False), # 263 - ("utf8mb4", "utf8mb4_sv_0900_ai_ci", False), # 264 - ("utf8mb4", "utf8mb4_tr_0900_ai_ci", False), # 265 - ("utf8mb4", "utf8mb4_cs_0900_ai_ci", False), # 266 - ("utf8mb4", "utf8mb4_da_0900_ai_ci", False), # 267 - ("utf8mb4", "utf8mb4_lt_0900_ai_ci", False), # 268 - ("utf8mb4", "utf8mb4_sk_0900_ai_ci", False), # 269 - ("utf8mb4", "utf8mb4_es_trad_0900_ai_ci", False), # 270 - ("utf8mb4", "utf8mb4_la_0900_ai_ci", False), # 271 - None, - ("utf8mb4", "utf8mb4_eo_0900_ai_ci", False), # 273 - ("utf8mb4", "utf8mb4_hu_0900_ai_ci", False), # 274 - ("utf8mb4", "utf8mb4_hr_0900_ai_ci", False), # 275 - None, - ("utf8mb4", "utf8mb4_vi_0900_ai_ci", False), # 277 - ("utf8mb4", "utf8mb4_0900_as_cs", False), # 278 - ("utf8mb4", "utf8mb4_de_pb_0900_as_cs", False), # 279 - ("utf8mb4", "utf8mb4_is_0900_as_cs", False), # 280 - ("utf8mb4", "utf8mb4_lv_0900_as_cs", False), # 281 - ("utf8mb4", "utf8mb4_ro_0900_as_cs", False), # 282 - ("utf8mb4", "utf8mb4_sl_0900_as_cs", False), # 283 - ("utf8mb4", "utf8mb4_pl_0900_as_cs", False), # 284 - ("utf8mb4", "utf8mb4_et_0900_as_cs", False), # 285 - ("utf8mb4", "utf8mb4_es_0900_as_cs", False), # 286 - ("utf8mb4", "utf8mb4_sv_0900_as_cs", False), # 287 - ("utf8mb4", "utf8mb4_tr_0900_as_cs", False), # 288 - ("utf8mb4", "utf8mb4_cs_0900_as_cs", False), # 289 - ("utf8mb4", "utf8mb4_da_0900_as_cs", False), # 290 - ("utf8mb4", "utf8mb4_lt_0900_as_cs", False), # 291 - ("utf8mb4", "utf8mb4_sk_0900_as_cs", False), # 292 - ("utf8mb4", "utf8mb4_es_trad_0900_as_cs", False), # 293 - ("utf8mb4", "utf8mb4_la_0900_as_cs", False), # 294 - None, - ("utf8mb4", "utf8mb4_eo_0900_as_cs", False), # 296 - ("utf8mb4", "utf8mb4_hu_0900_as_cs", False), # 297 - ("utf8mb4", "utf8mb4_hr_0900_as_cs", False), # 298 - None, - ("utf8mb4", "utf8mb4_vi_0900_as_cs", False), # 300 - None, - None, - ("utf8mb4", "utf8mb4_ja_0900_as_cs", False), # 303 - ("utf8mb4", "utf8mb4_ja_0900_as_cs_ks", False), # 304 - ("utf8mb4", "utf8mb4_0900_as_ci", False), # 305 - ("utf8mb4", "utf8mb4_ru_0900_ai_ci", False), # 306 - ("utf8mb4", "utf8mb4_ru_0900_as_cs", False), # 307 - ("utf8mb4", "utf8mb4_zh_0900_as_cs", False), # 308 - ("utf8mb4", "utf8mb4_0900_bin", False), # 309 - ("utf8mb4", "utf8mb4_nb_0900_ai_ci", False), # 310 - ("utf8mb4", "utf8mb4_nb_0900_as_cs", False), # 311 - ("utf8mb4", "utf8mb4_nn_0900_ai_ci", False), # 312 - ("utf8mb4", "utf8mb4_nn_0900_as_cs", False), # 313 - ("utf8mb4", "utf8mb4_sr_latn_0900_ai_ci", False), # 314 - ("utf8mb4", "utf8mb4_sr_latn_0900_as_cs", False), # 315 - ("utf8mb4", "utf8mb4_bs_0900_ai_ci", False), # 316 - ("utf8mb4", "utf8mb4_bs_0900_as_cs", False), # 317 - ("utf8mb4", "utf8mb4_bg_0900_ai_ci", False), # 318 - ("utf8mb4", "utf8mb4_bg_0900_as_cs", False), # 319 - ("utf8mb4", "utf8mb4_gl_0900_ai_ci", False), # 320 - ("utf8mb4", "utf8mb4_gl_0900_as_cs", False), # 321 - ("utf8mb4", "utf8mb4_mn_cyrl_0900_ai_ci", False), # 322 - ("utf8mb4", "utf8mb4_mn_cyrl_0900_as_cs", False), # 323 -] - -MYSQL_CHARACTER_SETS_57: List[Optional[Tuple[str, str, bool]]] = [ - # (character set name, collation, default) - None, - ("big5", "big5_chinese_ci", True), # 1 - ("latin2", "latin2_czech_cs", False), # 2 - ("dec8", "dec8_swedish_ci", True), # 3 - ("cp850", "cp850_general_ci", True), # 4 - ("latin1", "latin1_german1_ci", False), # 5 - ("hp8", "hp8_english_ci", True), # 6 - ("koi8r", "koi8r_general_ci", True), # 7 - ("latin1", "latin1_swedish_ci", True), # 8 - ("latin2", "latin2_general_ci", True), # 9 - ("swe7", "swe7_swedish_ci", True), # 10 - ("ascii", "ascii_general_ci", True), # 11 - ("ujis", "ujis_japanese_ci", True), # 12 - ("sjis", "sjis_japanese_ci", True), # 13 - ("cp1251", "cp1251_bulgarian_ci", False), # 14 - ("latin1", "latin1_danish_ci", False), # 15 - ("hebrew", "hebrew_general_ci", True), # 16 - None, - ("tis620", "tis620_thai_ci", True), # 18 - ("euckr", "euckr_korean_ci", True), # 19 - ("latin7", "latin7_estonian_cs", False), # 20 - ("latin2", "latin2_hungarian_ci", False), # 21 - ("koi8u", "koi8u_general_ci", True), # 22 - ("cp1251", "cp1251_ukrainian_ci", False), # 23 - ("gb2312", "gb2312_chinese_ci", True), # 24 - ("greek", "greek_general_ci", True), # 25 - ("cp1250", "cp1250_general_ci", True), # 26 - ("latin2", "latin2_croatian_ci", False), # 27 - ("gbk", "gbk_chinese_ci", True), # 28 - ("cp1257", "cp1257_lithuanian_ci", False), # 29 - ("latin5", "latin5_turkish_ci", True), # 30 - ("latin1", "latin1_german2_ci", False), # 31 - ("armscii8", "armscii8_general_ci", True), # 32 - ("utf8", "utf8_general_ci", True), # 33 - ("cp1250", "cp1250_czech_cs", False), # 34 - ("ucs2", "ucs2_general_ci", True), # 35 - ("cp866", "cp866_general_ci", True), # 36 - ("keybcs2", "keybcs2_general_ci", True), # 37 - ("macce", "macce_general_ci", True), # 38 - ("macroman", "macroman_general_ci", True), # 39 - ("cp852", "cp852_general_ci", True), # 40 - ("latin7", "latin7_general_ci", True), # 41 - ("latin7", "latin7_general_cs", False), # 42 - ("macce", "macce_bin", False), # 43 - ("cp1250", "cp1250_croatian_ci", False), # 44 - ("utf8mb4", "utf8mb4_general_ci", True), # 45 - ("utf8mb4", "utf8mb4_bin", False), # 46 - ("latin1", "latin1_bin", False), # 47 - ("latin1", "latin1_general_ci", False), # 48 - ("latin1", "latin1_general_cs", False), # 49 - ("cp1251", "cp1251_bin", False), # 50 - ("cp1251", "cp1251_general_ci", True), # 51 - ("cp1251", "cp1251_general_cs", False), # 52 - ("macroman", "macroman_bin", False), # 53 - ("utf16", "utf16_general_ci", True), # 54 - ("utf16", "utf16_bin", False), # 55 - ("utf16le", "utf16le_general_ci", True), # 56 - ("cp1256", "cp1256_general_ci", True), # 57 - ("cp1257", "cp1257_bin", False), # 58 - ("cp1257", "cp1257_general_ci", True), # 59 - ("utf32", "utf32_general_ci", True), # 60 - ("utf32", "utf32_bin", False), # 61 - ("utf16le", "utf16le_bin", False), # 62 - ("binary", "binary", True), # 63 - ("armscii8", "armscii8_bin", False), # 64 - ("ascii", "ascii_bin", False), # 65 - ("cp1250", "cp1250_bin", False), # 66 - ("cp1256", "cp1256_bin", False), # 67 - ("cp866", "cp866_bin", False), # 68 - ("dec8", "dec8_bin", False), # 69 - ("greek", "greek_bin", False), # 70 - ("hebrew", "hebrew_bin", False), # 71 - ("hp8", "hp8_bin", False), # 72 - ("keybcs2", "keybcs2_bin", False), # 73 - ("koi8r", "koi8r_bin", False), # 74 - ("koi8u", "koi8u_bin", False), # 75 - None, - ("latin2", "latin2_bin", False), # 77 - ("latin5", "latin5_bin", False), # 78 - ("latin7", "latin7_bin", False), # 79 - ("cp850", "cp850_bin", False), # 80 - ("cp852", "cp852_bin", False), # 81 - ("swe7", "swe7_bin", False), # 82 - ("utf8", "utf8_bin", False), # 83 - ("big5", "big5_bin", False), # 84 - ("euckr", "euckr_bin", False), # 85 - ("gb2312", "gb2312_bin", False), # 86 - ("gbk", "gbk_bin", False), # 87 - ("sjis", "sjis_bin", False), # 88 - ("tis620", "tis620_bin", False), # 89 - ("ucs2", "ucs2_bin", False), # 90 - ("ujis", "ujis_bin", False), # 91 - ("geostd8", "geostd8_general_ci", True), # 92 - ("geostd8", "geostd8_bin", False), # 93 - ("latin1", "latin1_spanish_ci", False), # 94 - ("cp932", "cp932_japanese_ci", True), # 95 - ("cp932", "cp932_bin", False), # 96 - ("eucjpms", "eucjpms_japanese_ci", True), # 97 - ("eucjpms", "eucjpms_bin", False), # 98 - ("cp1250", "cp1250_polish_ci", False), # 99 - None, - ("utf16", "utf16_unicode_ci", False), # 101 - ("utf16", "utf16_icelandic_ci", False), # 102 - ("utf16", "utf16_latvian_ci", False), # 103 - ("utf16", "utf16_romanian_ci", False), # 104 - ("utf16", "utf16_slovenian_ci", False), # 105 - ("utf16", "utf16_polish_ci", False), # 106 - ("utf16", "utf16_estonian_ci", False), # 107 - ("utf16", "utf16_spanish_ci", False), # 108 - ("utf16", "utf16_swedish_ci", False), # 109 - ("utf16", "utf16_turkish_ci", False), # 110 - ("utf16", "utf16_czech_ci", False), # 111 - ("utf16", "utf16_danish_ci", False), # 112 - ("utf16", "utf16_lithuanian_ci", False), # 113 - ("utf16", "utf16_slovak_ci", False), # 114 - ("utf16", "utf16_spanish2_ci", False), # 115 - ("utf16", "utf16_roman_ci", False), # 116 - ("utf16", "utf16_persian_ci", False), # 117 - ("utf16", "utf16_esperanto_ci", False), # 118 - ("utf16", "utf16_hungarian_ci", False), # 119 - ("utf16", "utf16_sinhala_ci", False), # 120 - ("utf16", "utf16_german2_ci", False), # 121 - ("utf16", "utf16_croatian_ci", False), # 122 - ("utf16", "utf16_unicode_520_ci", False), # 123 - ("utf16", "utf16_vietnamese_ci", False), # 124 - None, - None, - None, - ("ucs2", "ucs2_unicode_ci", False), # 128 - ("ucs2", "ucs2_icelandic_ci", False), # 129 - ("ucs2", "ucs2_latvian_ci", False), # 130 - ("ucs2", "ucs2_romanian_ci", False), # 131 - ("ucs2", "ucs2_slovenian_ci", False), # 132 - ("ucs2", "ucs2_polish_ci", False), # 133 - ("ucs2", "ucs2_estonian_ci", False), # 134 - ("ucs2", "ucs2_spanish_ci", False), # 135 - ("ucs2", "ucs2_swedish_ci", False), # 136 - ("ucs2", "ucs2_turkish_ci", False), # 137 - ("ucs2", "ucs2_czech_ci", False), # 138 - ("ucs2", "ucs2_danish_ci", False), # 139 - ("ucs2", "ucs2_lithuanian_ci", False), # 140 - ("ucs2", "ucs2_slovak_ci", False), # 141 - ("ucs2", "ucs2_spanish2_ci", False), # 142 - ("ucs2", "ucs2_roman_ci", False), # 143 - ("ucs2", "ucs2_persian_ci", False), # 144 - ("ucs2", "ucs2_esperanto_ci", False), # 145 - ("ucs2", "ucs2_hungarian_ci", False), # 146 - ("ucs2", "ucs2_sinhala_ci", False), # 147 - ("ucs2", "ucs2_german2_ci", False), # 148 - ("ucs2", "ucs2_croatian_ci", False), # 149 - ("ucs2", "ucs2_unicode_520_ci", False), # 150 - ("ucs2", "ucs2_vietnamese_ci", False), # 151 - None, - None, - None, - None, - None, - None, - None, - ("ucs2", "ucs2_general_mysql500_ci", False), # 159 - ("utf32", "utf32_unicode_ci", False), # 160 - ("utf32", "utf32_icelandic_ci", False), # 161 - ("utf32", "utf32_latvian_ci", False), # 162 - ("utf32", "utf32_romanian_ci", False), # 163 - ("utf32", "utf32_slovenian_ci", False), # 164 - ("utf32", "utf32_polish_ci", False), # 165 - ("utf32", "utf32_estonian_ci", False), # 166 - ("utf32", "utf32_spanish_ci", False), # 167 - ("utf32", "utf32_swedish_ci", False), # 168 - ("utf32", "utf32_turkish_ci", False), # 169 - ("utf32", "utf32_czech_ci", False), # 170 - ("utf32", "utf32_danish_ci", False), # 171 - ("utf32", "utf32_lithuanian_ci", False), # 172 - ("utf32", "utf32_slovak_ci", False), # 173 - ("utf32", "utf32_spanish2_ci", False), # 174 - ("utf32", "utf32_roman_ci", False), # 175 - ("utf32", "utf32_persian_ci", False), # 176 - ("utf32", "utf32_esperanto_ci", False), # 177 - ("utf32", "utf32_hungarian_ci", False), # 178 - ("utf32", "utf32_sinhala_ci", False), # 179 - ("utf32", "utf32_german2_ci", False), # 180 - ("utf32", "utf32_croatian_ci", False), # 181 - ("utf32", "utf32_unicode_520_ci", False), # 182 - ("utf32", "utf32_vietnamese_ci", False), # 183 - None, - None, - None, - None, - None, - None, - None, - None, - ("utf8", "utf8_unicode_ci", False), # 192 - ("utf8", "utf8_icelandic_ci", False), # 193 - ("utf8", "utf8_latvian_ci", False), # 194 - ("utf8", "utf8_romanian_ci", False), # 195 - ("utf8", "utf8_slovenian_ci", False), # 196 - ("utf8", "utf8_polish_ci", False), # 197 - ("utf8", "utf8_estonian_ci", False), # 198 - ("utf8", "utf8_spanish_ci", False), # 199 - ("utf8", "utf8_swedish_ci", False), # 200 - ("utf8", "utf8_turkish_ci", False), # 201 - ("utf8", "utf8_czech_ci", False), # 202 - ("utf8", "utf8_danish_ci", False), # 203 - ("utf8", "utf8_lithuanian_ci", False), # 204 - ("utf8", "utf8_slovak_ci", False), # 205 - ("utf8", "utf8_spanish2_ci", False), # 206 - ("utf8", "utf8_roman_ci", False), # 207 - ("utf8", "utf8_persian_ci", False), # 208 - ("utf8", "utf8_esperanto_ci", False), # 209 - ("utf8", "utf8_hungarian_ci", False), # 210 - ("utf8", "utf8_sinhala_ci", False), # 211 - ("utf8", "utf8_german2_ci", False), # 212 - ("utf8", "utf8_croatian_ci", False), # 213 - ("utf8", "utf8_unicode_520_ci", False), # 214 - ("utf8", "utf8_vietnamese_ci", False), # 215 - None, - None, - None, - None, - None, - None, - None, - ("utf8", "utf8_general_mysql500_ci", False), # 223 - ("utf8mb4", "utf8mb4_unicode_ci", False), # 224 - ("utf8mb4", "utf8mb4_icelandic_ci", False), # 225 - ("utf8mb4", "utf8mb4_latvian_ci", False), # 226 - ("utf8mb4", "utf8mb4_romanian_ci", False), # 227 - ("utf8mb4", "utf8mb4_slovenian_ci", False), # 228 - ("utf8mb4", "utf8mb4_polish_ci", False), # 229 - ("utf8mb4", "utf8mb4_estonian_ci", False), # 230 - ("utf8mb4", "utf8mb4_spanish_ci", False), # 231 - ("utf8mb4", "utf8mb4_swedish_ci", False), # 232 - ("utf8mb4", "utf8mb4_turkish_ci", False), # 233 - ("utf8mb4", "utf8mb4_czech_ci", False), # 234 - ("utf8mb4", "utf8mb4_danish_ci", False), # 235 - ("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236 - ("utf8mb4", "utf8mb4_slovak_ci", False), # 237 - ("utf8mb4", "utf8mb4_spanish2_ci", False), # 238 - ("utf8mb4", "utf8mb4_roman_ci", False), # 239 - ("utf8mb4", "utf8mb4_persian_ci", False), # 240 - ("utf8mb4", "utf8mb4_esperanto_ci", False), # 241 - ("utf8mb4", "utf8mb4_hungarian_ci", False), # 242 - ("utf8mb4", "utf8mb4_sinhala_ci", False), # 243 - ("utf8mb4", "utf8mb4_german2_ci", False), # 244 - ("utf8mb4", "utf8mb4_croatian_ci", False), # 245 - ("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246 - ("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247 - ("gb18030", "gb18030_chinese_ci", True), # 248 - ("gb18030", "gb18030_bin", False), # 249 - ("gb18030", "gb18030_unicode_520_ci", False), # 250 -] diff --git a/server/venv/Lib/site-packages/mysql/connector/connection.py b/server/venv/Lib/site-packages/mysql/connector/connection.py deleted file mode 100644 index 662ebb6..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/connection.py +++ /dev/null @@ -1,1784 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="arg-type,operator,attr-defined,assignment" - -"""Implementing communication with MySQL servers.""" -from __future__ import annotations - -import datetime -import getpass -import os -import socket -import struct -import sys -import warnings - -from decimal import Decimal -from io import IOBase -from typing import ( - TYPE_CHECKING, - Any, - BinaryIO, - Dict, - Generator, - List, - Mapping, - Optional, - Sequence, - Tuple, - Type, - Union, -) - -from . import version -from ._decorating import cmd_refresh_verify_options, handle_read_write_timeout -from ._scripting import get_local_infile_filenames -from .abstracts import MySQLConnectionAbstract -from .authentication import MySQLAuthenticator, get_auth_plugin -from .constants import ( - ClientFlag, - FieldType, - RefreshOption, - ServerCmd, - ServerFlag, - flag_is_set, -) -from .conversion import MySQLConverter -from .cursor import ( - MySQLCursor, - MySQLCursorBuffered, - MySQLCursorBufferedDict, - MySQLCursorBufferedRaw, - MySQLCursorDict, - MySQLCursorPrepared, - MySQLCursorPreparedDict, - MySQLCursorRaw, -) -from .errors import ( - ConnectionTimeoutError, - DatabaseError, - Error, - InterfaceError, - InternalError, - NotSupportedError, - OperationalError, - ProgrammingError, - ReadTimeoutError, - WriteTimeoutError, - get_exception, -) -from .logger import logger -from .network import MySQLSocket, MySQLTCPSocket, MySQLUnixSocket -from .opentelemetry.constants import OTEL_ENABLED -from .opentelemetry.context_propagation import with_context_propagation -from .protocol import ( - EOF_STATUS, - ERR_STATUS, - LOCAL_INFILE_STATUS, - OK_STATUS, - MySQLProtocol, -) -from .types import ( - BinaryProtocolType, - DescriptionType, - EofPacketType, - HandShakeType, - OkPacketType, - ResultType, - RowType, - StatsPacketType, - StrOrBytes, -) -from .utils import ( - get_platform, - int1store, - int4store, - lc_int, - warn_ciphersuites_deprecated, - warn_tls_version_deprecated, -) - -if TYPE_CHECKING: - from .abstracts import CMySQLPrepStmt - -if OTEL_ENABLED: - from .opentelemetry.instrumentation import end_span, record_exception_event - - -class MySQLConnection(MySQLConnectionAbstract): - """Connection to a MySQL Server""" - - def __init__(self, **kwargs: Any) -> None: - self._protocol: Optional[MySQLProtocol] = None - self._socket: Optional[MySQLSocket] = None - self._handshake: Optional[HandShakeType] = None - super().__init__() - - self._converter_class: Type[MySQLConverter] = MySQLConverter - - self._client_flags: int = ClientFlag.get_default() - self._sql_mode: Optional[str] = None - self._time_zone: Optional[str] = None - self._autocommit: bool = False - - self._user: str = "" - self._password: str = "" - self._database: str = "" - self._host: str = "127.0.0.1" - self._port: int = 3306 - self._unix_socket: Optional[str] = None - self._client_host: str = "" - self._client_port: int = 0 - self._ssl: Dict[str, Optional[Union[str, bool, List[str]]]] = {} - self._force_ipv6: bool = False - - self._use_unicode: bool = True - self._get_warnings: bool = False - self._raise_on_warnings: bool = False - self._buffered: bool = False - self._unread_result: bool = False - self._have_next_result: bool = False - self._raw: bool = False - self._in_transaction: bool = False - - self._prepared_statements: Any = None - - self._ssl_active: bool = False - self._auth_plugin: Optional[str] = None - self._krb_service_principal: Optional[str] = None - self._pool_config_version: Any = None - self._query_attrs_supported: int = False - - self._columns_desc: List[DescriptionType] = [] - self._mfa_nfactor: int = 1 - - self._authenticator: MySQLAuthenticator = MySQLAuthenticator() - - if kwargs: - try: - self.connect(**kwargs) - except Exception: - # Tidy-up underlying socket on failure - self.close() - self._socket = None - raise - - def _add_default_conn_attrs(self) -> None: - """Add the default connection attributes.""" - platform = get_platform() - license_chunks = version.LICENSE.split(" ") - if license_chunks[0] == "GPLv2": - client_license = "GPL-2.0" - else: - client_license = "Commercial" - default_conn_attrs = { - "_pid": str(os.getpid()), - "_platform": platform["arch"], - "_source_host": socket.gethostname(), - "_client_name": "mysql-connector-python", - "_client_license": client_license, - "_client_version": ".".join([str(x) for x in version.VERSION[0:3]]), - "_os": platform["version"], - } - - self._conn_attrs.update((default_conn_attrs)) - - def _do_handshake(self) -> None: - """Get the handshake from the MySQL server""" - packet = bytes(self._socket.recv()) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - - self._handshake = None - handshake = self._protocol.parse_handshake(packet) - - server_version = handshake["server_version_original"] - - self._server_version = self._check_server_version( - server_version - if isinstance(server_version, (str, bytes, bytearray)) - else "Unknown" - ) - self._character_set.set_mysql_version(self._server_version) - - if not handshake["capabilities"] & ClientFlag.SSL: - if not self.is_secure: - if self._auth_plugin == "mysql_clear_password": - raise InterfaceError( - "Clear password authentication is not supported over " - "insecure channels" - ) - if self._auth_plugin == "authentication_openid_connect_client": - raise InterfaceError( - "OpenID Connect authentication is not supported over " - "insecure channels" - ) - if self._ssl.get("verify_cert"): - raise InterfaceError( - "SSL is required but the server doesn't support it", - errno=2026, - ) - self._client_flags &= ~ClientFlag.SSL - elif not self._ssl_disabled: - self._client_flags |= ClientFlag.SSL - - if handshake["capabilities"] & ClientFlag.PLUGIN_AUTH: - self.client_flags = [ClientFlag.PLUGIN_AUTH] - - if handshake["capabilities"] & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - self._query_attrs_supported = True - self.client_flags = [ClientFlag.CLIENT_QUERY_ATTRIBUTES] - - if handshake["capabilities"] & ClientFlag.MULTI_FACTOR_AUTHENTICATION: - self.client_flags = [ClientFlag.MULTI_FACTOR_AUTHENTICATION] - - self._handshake = handshake - - def _do_auth( - self, - username: Optional[str] = None, - password: Optional[str] = None, - database: Optional[str] = None, - client_flags: int = 0, - ssl_options: Optional[Dict[str, Optional[Union[str, bool, List[str]]]]] = None, - conn_attrs: Optional[Dict[str, str]] = None, - ) -> bool: - """Authenticate with the MySQL server - - Authentication happens in two parts. We first send a response to the - handshake. The MySQL server will then send either an AuthSwitchRequest - or an error packet. - - Raises NotSupportedError when we get the old, insecure password - reply back. Raises any error coming from MySQL. - """ - if ( - self._auth_plugin.startswith("authentication_oci") - or ( - self._auth_plugin.startswith("authentication_kerberos") - and os.name == "nt" - ) - ) and not username: - username = getpass.getuser() - logger.debug( - "MySQL user is empty, OS user: %s will be used for %s", - username, - self._auth_plugin, - ) - - if self._password1 and password != self._password1: - password = self._password1 - - self._ssl_active = False - if not self._ssl_disabled and (client_flags & ClientFlag.SSL): - self._authenticator.setup_ssl( - self._socket, - self.server_host, - ssl_options, - charset=self._charset_id, - client_flags=client_flags, - ) - self._ssl_active = True - - # Add the custom configurations required by specific auth plugins - self._authenticator.update_plugin_config( - config={ - "krb_service_principal": self._krb_service_principal, - "oci_config_file": self._oci_config_file, - "oci_config_profile": self._oci_config_profile, - "webauthn_callback": self._webauthn_callback, - "openid_token_file": self._openid_token_file, - } - ) - - ok_pkt = self._authenticator.authenticate( - sock=self._socket, - handshake=self._handshake, - username=username, - password1=password, - password2=self._password2, - password3=self._password3, - database=database, - charset=self._charset_id, - client_flags=client_flags, - auth_plugin=self._auth_plugin, - auth_plugin_class=self._auth_plugin_class, - conn_attrs=conn_attrs, - ) - self._handle_ok(ok_pkt) - - if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database: - self.cmd_init_db(database) - - return True - - def _get_connection(self) -> MySQLSocket: - """Get connection based on configuration - - This method will return the appropriated connection object using - the connection parameters. - - Returns subclass of MySQLBaseSocket. - """ - conn = None - if self._unix_socket and os.name == "posix": - conn = MySQLUnixSocket(unix_socket=self.unix_socket) - else: - conn = MySQLTCPSocket( - host=self.server_host, - port=self.server_port, - force_ipv6=self._force_ipv6, - ) - - conn.set_connection_timeout(self._connection_timeout) - return conn - - def _open_connection(self) -> None: - """Open the connection to the MySQL server - - This method sets up and opens the connection to the MySQL server. - - Raises on errors. - """ - # setting connection's read and write timeout to None temporarily - # till connections is established successfully. - stored_read_timeout = self.read_timeout - stored_write_timeout = self.write_timeout - self.read_timeout = self.write_timeout = None - - if self._auth_plugin == "authentication_kerberos_client" and not self._user: - cls = get_auth_plugin(self._auth_plugin, self._auth_plugin_class) - self._user = cls.get_user_from_credentials() - - self._protocol = MySQLProtocol() - self._socket = self._get_connection() - try: - self._socket.open_connection() - - # do initial handshake - self._do_handshake() - - # start authentication negotiation - self._do_auth( - self._user, - self._password, - self._database, - self._client_flags, - self._ssl, - self._conn_attrs, - ) - self.converter_class = self._converter_class - - if self._client_flags & ClientFlag.COMPRESS: - # update the network layer accordingly - self._socket.switch_to_compressed_mode() - - self._socket.set_connection_timeout(None) - except Exception as err: - # close socket - self._socket.close_connection() - if isinstance(err, (ReadTimeoutError, WriteTimeoutError)): - raise ConnectionTimeoutError( - errno=err.errno, - msg=err.msg, - ) from err - raise err - finally: - # as the connection is established, set back the read - # and write timeouts to the original value - self.read_timeout = stored_read_timeout - self.write_timeout = stored_write_timeout - - if ( - not self._ssl_disabled - and hasattr(self._socket.sock, "cipher") - and callable(self._socket.sock.cipher) - ): - # Raise a deprecation warning if deprecated TLS version - # or cipher is being used. - - # `cipher()` returns a three-value tuple containing the name - # of the cipher being used, the version of the SSL protocol - # that defines its use, and the number of secret bits being used. - cipher, tls_version, _ = self._socket.sock.cipher() - warn_tls_version_deprecated(tls_version) - warn_ciphersuites_deprecated(cipher, tls_version) - - def shutdown(self) -> None: - """Shut down connection to MySQL Server. - - This method closes the socket. It raises no exceptions. - - Unlike `disconnect()`, `shutdown()` closes the client connection without - attempting to send a QUIT command to the server first. Thus, it will not - block if the connection is disrupted for some reason such as network failure. - """ - if not self._socket: - return - - try: - self._socket.shutdown() - except (AttributeError, Error): - pass # Getting an exception would mean we are disconnected. - - def close(self) -> None: - if self._span and self._span.is_recording(): - # pylint: disable=possibly-used-before-assignment - record_exception_event(self._span, sys.exc_info()[1]) - - if not self._socket: - return - - try: - self.cmd_quit() - except (AttributeError, Error): - pass # Getting an exception would mean we are disconnected. - - try: - self._socket.close_connection() - except Exception as err: - if OTEL_ENABLED: - record_exception_event(self._span, err) - raise - finally: - if OTEL_ENABLED: - end_span(self._span) - - self._handshake = None - - disconnect = close - - @handle_read_write_timeout() - def _send_cmd( - self, - command: int, - argument: Optional[bytes] = None, - packet_number: int = 0, - packet: Optional[bytes] = None, - expect_response: bool = True, - compressed_packet_number: int = 0, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> Optional[bytearray]: - """Send a command to the MySQL server - - This method sends a command with an optional argument. - If packet is not None, it will be sent and the argument will be - ignored. - - The packet_number is optional and should usually not be used. - - Some commands might not result in the MySQL server returning - a response. If a command does not return anything, you should - set expect_response to False. The _send_cmd method will then - return None instead of a MySQL packet. - - Returns a MySQL packet or None. - """ - self.handle_unread_result() - - try: - self._socket.send( - self._protocol.make_command(command, packet or argument), - packet_number, - compressed_packet_number, - write_timeout or self._write_timeout, - ) - return ( - self._socket.recv(read_timeout or self._read_timeout) - if expect_response - else None - ) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - @handle_read_write_timeout() - def _send_data( - self, - data_file: BinaryIO, - send_empty_packet: bool = False, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> bytearray: - """Send data to the MySQL server - - This method accepts a file-like object and sends its data - as is to the MySQL server. If the send_empty_packet is - True, it will send an extra empty package (for example - when using LOAD LOCAL DATA INFILE). - - Returns a MySQL packet. - """ - self.handle_unread_result() - - if not hasattr(data_file, "read"): - raise ValueError("expecting a file-like object") - - chunk_size = 131072 # 128 KB - try: - buf = data_file.read(chunk_size - 16) - while buf: - self._socket.send( - buf, write_timeout=write_timeout or self._write_timeout - ) - buf = data_file.read(chunk_size - 16) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - if send_empty_packet: - try: - self._socket.send( - b"", write_timeout=write_timeout or self._write_timeout - ) - except WriteTimeoutError as err: - raise err - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - return self._socket.recv(read_timeout or self._read_timeout) - - def _handle_server_status(self, flags: int) -> None: - """Handle the server flags found in MySQL packets - - This method handles the server flags send by MySQL OK and EOF - packets. It, for example, checks whether there exists more result - sets or whether there is an ongoing transaction. - """ - self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags) - self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags) - - @property - def in_transaction(self) -> bool: - """MySQL session has started a transaction""" - return self._in_transaction - - def _handle_ok(self, packet: bytes) -> OkPacketType: - """Handle a MySQL OK packet - - This method handles a MySQL OK packet. When the packet is found to - be an Error packet, an error will be raised. If the packet is neither - an OK or an Error packet, InterfaceError will be raised. - - Returns a dict() - """ - if packet[4] == OK_STATUS: - ok_pkt = self._protocol.parse_ok(packet) - self._handle_server_status(ok_pkt["status_flag"]) - return ok_pkt - if packet[4] == ERR_STATUS: - raise get_exception(packet) - raise InterfaceError("Expected OK packet") - - def _handle_eof(self, packet: bytes) -> EofPacketType: - """Handle a MySQL EOF packet - - This method handles a MySQL EOF packet. When the packet is found to - be an Error packet, an error will be raised. If the packet is neither - and OK or an Error packet, InterfaceError will be raised. - - Returns a dict() - """ - if packet[4] == EOF_STATUS: - eof = self._protocol.parse_eof(packet) - self._handle_server_status(eof["status_flag"]) - return eof - if packet[4] == ERR_STATUS: - raise get_exception(packet) - raise InterfaceError("Expected EOF packet") - - @handle_read_write_timeout() - def _handle_load_data_infile( - self, - filename: str, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> OkPacketType: - """Handle a LOAD DATA INFILE LOCAL request""" - if self._local_infile_filenames is None: - self._local_infile_filenames = get_local_infile_filenames(self._query) - if not self._local_infile_filenames: - raise InterfaceError( - "No `LOCAL INFILE` statements found in the client's request. " - "Check your request includes valid `LOCAL INFILE` statements." - ) - elif not self._local_infile_filenames: - raise InterfaceError( - "Got more `LOCAL INFILE` responses than number of `LOCAL INFILE` " - "statements specified in the client's request. Please, report this " - "issue to the development team." - ) - - file_name = os.path.abspath(filename) - file_name_from_request = os.path.abspath(self._local_infile_filenames.popleft()) - - # Verify the file location specified by `filename` from client's request exists - if not os.path.exists(file_name_from_request): - raise InterfaceError( - f"Location specified by filename {file_name_from_request} " - "from client's request does not exist." - ) - - # Verify the file location specified by `filename` from server's response exists - if not os.path.exists(file_name): - raise InterfaceError( - f"Location specified by filename {file_name} from server's " - "response does not exist." - ) - - # Verify the `filename` specified by server's response matches the one from - # the client's request. - try: - if not os.path.samefile(file_name, file_name_from_request): - raise InterfaceError( - f"Filename {file_name} from the server's response is not the same " - f"as filename {file_name_from_request} from the " - "client's request." - ) - except OSError as err: - raise InterfaceError from err - - if os.path.islink(file_name): - raise OperationalError("Use of symbolic link is not allowed") - if not self._allow_local_infile and not self._allow_local_infile_in_path: - raise DatabaseError( - "LOAD DATA LOCAL INFILE file request rejected due to " - "restrictions on access." - ) - if not self._allow_local_infile and self._allow_local_infile_in_path: - # validate filename is inside of allow_local_infile_in_path path. - infile_path = os.path.abspath(self._allow_local_infile_in_path) - c_path = None - try: - c_path = os.path.commonpath([infile_path, file_name]) - except ValueError as err: - err_msg = ( - "{} while loading file `{}` and path `{}` given" - " in allow_local_infile_in_path" - ) - raise InterfaceError( - err_msg.format(str(err), file_name, infile_path) - ) from err - - if c_path != infile_path: - err_msg = ( - "The file `{}` is not found in the given " - "allow_local_infile_in_path {}" - ) - raise DatabaseError(err_msg.format(file_name, infile_path)) - - try: - data_file = open(file_name, "rb") # pylint: disable=consider-using-with - return self._handle_ok( - self._send_data(data_file, True, read_timeout, write_timeout) - ) - except IOError: - # Send a empty packet to cancel the operation - try: - self._socket.send( - b"", write_timeout=write_timeout or self._write_timeout - ) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - raise InterfaceError(f"File '{file_name}' could not be read") from None - finally: - try: - data_file.close() - except (IOError, NameError): - pass - - @handle_read_write_timeout() - def _handle_result( - self, - packet: bytes, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> ResultType: - """Handle a MySQL Result - - This method handles a MySQL result, for example, after sending the - query command. OK and EOF packets will be handled and returned. If - the packet is an Error packet, an Error-exception will be - raised. - - The dictionary returned of: - - columns: column information - - eof: the EOF-packet information - - Returns a dict() - """ - if not packet or len(packet) < 4: - raise InterfaceError("Empty response") - if packet[4] == OK_STATUS: - return self._handle_ok(packet) - if packet[4] == LOCAL_INFILE_STATUS: - filename = packet[5:].decode() - return self._handle_load_data_infile(filename, read_timeout, write_timeout) - if packet[4] == EOF_STATUS: - return self._handle_eof(packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - - # We have a text result set - column_count = self._protocol.parse_column_count(packet) - if not column_count or not isinstance(column_count, int): - raise InterfaceError("Illegal result set") - - self._columns_desc = [ - None, - ] * column_count - for i in range(0, column_count): - self._columns_desc[i] = self._protocol.parse_column( - self._socket.recv(read_timeout or self._read_timeout), - self.python_charset, - ) - - eof = self._handle_eof(self._socket.recv(read_timeout or self._read_timeout)) - self.unread_result = True - return {"columns": self._columns_desc, "eof": eof} - - def get_row( - self, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Optional[CMySQLPrepStmt] = None, - **kwargs: Any, - ) -> Tuple[Optional[RowType], Optional[EofPacketType]]: - """Get the next rows returned by the MySQL server - - This method gets one row from the result set after sending, for - example, the query command. The result is a tuple consisting of the - row and the EOF packet. - If no row was available in the result set, the row data will be None. - - Returns a tuple. - """ - read_timeout = kwargs.get("read_timeout", None) - (rows, eof) = self.get_rows( - count=1, - binary=binary, - columns=columns, - raw=raw, - read_timeout=read_timeout, - ) - if rows: - return (rows[0], eof) - return (None, eof) - - @handle_read_write_timeout() - def get_rows( - self, - count: Optional[int] = None, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Optional[CMySQLPrepStmt] = None, - **kwargs: Any, - ) -> Tuple[List[RowType], Optional[EofPacketType]]: - """Get all rows returned by the MySQL server - - This method gets all rows returned by the MySQL server after sending, - for example, the query command. The result is a tuple consisting of - a list of rows and the EOF packet. - - Returns a tuple() - """ - if raw is None: - raw = self._raw - - if not self.unread_result: - raise InternalError("No result set available") - - rows = ([], None) # type: ignore[var-annotated] - try: - read_timeout = kwargs.get("read_timeout", None) - if binary: - charset = self.charset - if charset == "utf8mb4": - charset = "utf8" - rows = self._protocol.read_binary_result( - self._socket, - columns, - count, - charset, - read_timeout or self._read_timeout, - ) - else: - rows = self._protocol.read_text_result( - self._socket, - self._server_version, - count, - read_timeout or self._read_timeout, - ) - except Error as err: - self.unread_result = False - raise err - - rows, eof_p = rows - if ( - not (binary or raw) - and self._columns_desc is not None - and rows - and hasattr(self, "converter") - ): - row_to_python = self.converter.row_to_python - rows = [row_to_python(row, self._columns_desc) for row in rows] - - if eof_p is not None: - self._handle_server_status( - eof_p["status_flag"] - if "status_flag" in eof_p - else eof_p["server_status"] - ) - self.unread_result = False - - return rows, eof_p - - def consume_results(self) -> None: - """Consume results""" - if self.unread_result: - self.get_rows() - - def cmd_init_db(self, database: str) -> OkPacketType: - """Change the current database - - This method changes the current (default) database by sending the - INIT_DB command. The result is a dictionary containing the OK packet - information. - - Returns a dict() - """ - return self._handle_ok( - self._send_cmd(ServerCmd.INIT_DB, database.encode("utf-8")) - ) - - @with_context_propagation - @handle_read_write_timeout() - def cmd_query( - self, - query: StrOrBytes, - raw: bool = False, - buffered: bool = False, - raw_as_string: bool = False, - **kwargs: Any, - ) -> ResultType: - if not isinstance(query, bytearray): - if isinstance(query, str): - query = query.encode("utf-8") - query = bytearray(query) - - # Set/Reset internal state related to query execution - self._query = query - self._local_infile_filenames = None - - # Prepare query attrs - charset = self.charset if self.charset != "utf8mb4" else "utf8" - packet = bytearray() - if not self._query_attrs_supported and self._query_attrs: - warnings.warn( - "This version of the server does not support Query Attributes", - category=Warning, - ) - if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - names = [] - types = [] - values: List[bytes] = [] - null_bitmap = [0] * ((len(self._query_attrs) + 7) // 8) - for pos, attr_tuple in enumerate(self._query_attrs.items()): - value = attr_tuple[1] - flags = 0 - if value is None: - null_bitmap[(pos // 8)] |= 1 << (pos % 8) - types.append(int1store(FieldType.NULL) + int1store(flags)) - continue - if isinstance(value, int): - ( - packed, - field_type, - flags, - ) = self._protocol.prepare_binary_integer(value) - values.append(packed) - elif isinstance(value, str): - value = value.encode(charset) - values.append(lc_int(len(value)) + value) - field_type = FieldType.STRING - elif isinstance(value, bytes): - values.append(lc_int(len(value)) + value) - field_type = FieldType.STRING - elif isinstance(value, Decimal): - values.append( - lc_int(len(str(value).encode(charset))) - + str(value).encode(charset) - ) - field_type = FieldType.DECIMAL - elif isinstance(value, float): - values.append(struct.pack(" parameter_count Number of parameters - packet.extend(lc_int(len(self._query_attrs))) - # int parameter_set_count Number of parameter sets. - # Currently always 1 - packet.extend(lc_int(1)) - if values: - packet.extend( - b"".join([struct.pack("B", bit) for bit in null_bitmap]) - + int1store(1) - ) - for _type, name in zip(types, names): - packet.extend(_type) - packet.extend(name) - - for value in values: - packet.extend(value) - - packet.extend(query) - query = bytes(packet) - try: - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - - result = self._handle_result( - self._send_cmd( - ServerCmd.QUERY, - query, - read_timeout=read_timeout, - write_timeout=write_timeout, - ), - read_timeout, - write_timeout, - ) - except ProgrammingError as err: - if err.errno == 3948 and "Loading local data is disabled" in err.msg: - err_msg = ( - "LOAD DATA LOCAL INFILE file request rejected due " - "to restrictions on access." - ) - raise DatabaseError(err_msg) from err - raise - return result - - @handle_read_write_timeout() - def cmd_query_iter( - self, - statements: StrOrBytes, - **kwargs: Any, - ) -> Generator[ResultType, None, None]: - """Send one or more statements to the MySQL server - - Similar to the cmd_query method, but instead returns a generator - object to iterate through results. It sends the statements to the - MySQL server and through the iterator you can get the results. - - statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' - for result in cnx.cmd_query(statement, iterate=True): - if 'columns' in result: - columns = result['columns'] - rows = cnx.get_rows() - else: - # do something useful with INSERT result - - Returns a generator. - """ - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - packet = bytearray() - if not isinstance(statements, bytearray): - if isinstance(statements, str): - statements = statements.encode("utf8") - statements = bytearray(statements) - - if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - # int parameter_count Number of parameters - packet.extend(lc_int(0)) - # int parameter_set_count Number of parameter sets. - # Currently always 1 - packet.extend(lc_int(1)) - - packet.extend(statements) - query = bytes(packet) - # Handle the first query result - yield self._handle_result( - self._send_cmd( - ServerCmd.QUERY, - query, - read_timeout=read_timeout, - write_timeout=write_timeout, - ), - read_timeout, - write_timeout, - ) - - # Handle next results, if any - while self._have_next_result: - self.handle_unread_result() - yield self._handle_result( - self._socket.recv(read_timeout=read_timeout or self._read_timeout), - read_timeout, - write_timeout, - ) - - @cmd_refresh_verify_options() - def cmd_refresh(self, options: int) -> OkPacketType: - if not options & ( - RefreshOption.GRANT - | RefreshOption.LOG - | RefreshOption.TABLES - | RefreshOption.HOST - | RefreshOption.STATUS - | RefreshOption.REPLICA - ): - raise ValueError("Invalid command REFRESH option") - - res = None - if options & RefreshOption.GRANT: - res = self.cmd_query("FLUSH PRIVILEGES") - if options & RefreshOption.LOG: - res = self.cmd_query("FLUSH LOGS") - if options & RefreshOption.TABLES: - res = self.cmd_query("FLUSH TABLES") - if options & RefreshOption.HOST: - res = self.cmd_query("TRUNCATE TABLE performance_schema.host_cache") - if options & RefreshOption.STATUS: - res = self.cmd_query("FLUSH STATUS") - if options & RefreshOption.REPLICA: - res = self.cmd_query( - "RESET SLAVE" if self._server_version < (8, 0, 22) else "RESET REPLICA" - ) - - return res - - def cmd_quit(self) -> bytes: - """Close the current connection with the server - - This method sends the `QUIT` command to the MySQL server, closing the - current connection. Since there is no response from the MySQL server, - the packet that was sent is returned. - - Returns a str() - """ - self.handle_unread_result() - - packet = self._protocol.make_command(ServerCmd.QUIT) - try: - self._socket.send(packet, 0, 0, self._write_timeout) - except WriteTimeoutError as _: - pass - return packet - - def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None: - """Shut down the MySQL Server - - This method sends the SHUTDOWN command to the MySQL server. - The `shutdown_type` is not used, and it's kept for backward compatibility. - """ - self.cmd_query("SHUTDOWN") - - @handle_read_write_timeout() - def cmd_statistics(self) -> StatsPacketType: - """Send the statistics command to the MySQL Server - - This method sends the STATISTICS command to the MySQL server. The - result is a dictionary with various statistical information. - - Returns a dict() - """ - self.handle_unread_result() - - packet = self._protocol.make_command(ServerCmd.STATISTICS) - self._socket.send(packet, 0, 0, self._write_timeout) - return self._protocol.parse_statistics(self._socket.recv(self._read_timeout)) - - def cmd_process_kill(self, mysql_pid: int) -> OkPacketType: - """Kill a MySQL process - - This method send the PROCESS_KILL command to the server along with - the process ID. The result is a dictionary with the OK packet - information. - """ - if not isinstance(mysql_pid, int): - raise ValueError("MySQL PID must be int") - return self.cmd_query(f"KILL {mysql_pid}") - - def cmd_debug(self) -> EofPacketType: - """Send the DEBUG command - - This method sends the DEBUG command to the MySQL server, which - requires the MySQL user to have SUPER privilege. The output will go - to the MySQL server error log and the result of this method is a - dictionary with EOF packet information. - - Returns a dict() - """ - return self._handle_eof(self._send_cmd(ServerCmd.DEBUG)) - - def cmd_ping(self) -> OkPacketType: - """Send the PING command - - This method sends the PING command to the MySQL server. It is used to - check if the the connection is still valid. The result of this - method is dictionary with OK packet information. - - Returns a dict() - """ - return self._handle_ok(self._send_cmd(ServerCmd.PING)) - - @handle_read_write_timeout() - def cmd_change_user( - self, - username: str = "", - password: str = "", - database: str = "", - charset: Optional[int] = None, - password1: str = "", - password2: str = "", - password3: str = "", - oci_config_file: str = "", - oci_config_profile: str = "", - openid_token_file: str = "", - ) -> Optional[OkPacketType]: - """Change the current logged in user - - This method allows to change the current logged in user information. - The result is a dictionary with OK packet information. - - Returns a dict() - """ - # If charset isn't defined, we use the same charset ID defined previously, - # otherwise, we run a verification and update the charset ID. - if charset is not None: - if not isinstance(charset, int): - raise ValueError("charset must be an integer") - if charset < 0: - raise ValueError("charset should be either zero or a postive integer") - self._charset_id = charset - - self._mfa_nfactor = 1 - self._user = username - self._password = password - self._password1 = password1 - self._password2 = password2 - self._password3 = password3 - - if self._password1 and password != self._password1: - self._password = self._password1 - - self.handle_unread_result() - - if self._compress: - raise NotSupportedError("Change user is not supported with compression") - - if oci_config_file: - self._oci_config_file = oci_config_file - if openid_token_file: - self._openid_token_file = openid_token_file - self._oci_config_profile = oci_config_profile - - # Update the custom configurations needed by specific auth plugins - self._authenticator.update_plugin_config( - config={ - "oci_config_file": self._oci_config_file, - "oci_config_profile": self._oci_config_profile, - "openid_token_file": self._openid_token_file, - } - ) - - ok_pkt = self._authenticator.authenticate( - sock=self._socket, - handshake=self._handshake, - username=self._user, - password1=self._password, - password2=self._password2, - password3=self._password3, - database=database, - charset=self._charset_id, - client_flags=self._client_flags, - auth_plugin=self._auth_plugin, - auth_plugin_class=self._auth_plugin_class, - conn_attrs=self._conn_attrs, - is_change_user_request=True, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - - if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) and database: - self.cmd_init_db(database) - - self._post_connection() - - # return ok_pkt - return self._handle_ok(ok_pkt) - - @property - def database(self) -> str: - """Get the current database""" - return self.info_query("SELECT DATABASE()")[0] # type: ignore[return-value] - - @database.setter - def database(self, value: str) -> None: - """Set the current database""" - self.cmd_init_db(value) - - def is_connected(self) -> bool: - """Reports whether the connection to MySQL Server is available - - This method checks whether the connection to MySQL is available. - It is similar to ping(), but unlike the ping()-method, either True - or False is returned and no exception is raised. - - Returns True or False. - """ - try: - self.cmd_ping() - except Error: - return False # This method does not raise - return True - - def set_allow_local_infile_in_path(self, path: str) -> None: - """Set the path that user can upload files. - - Args: - path (str): Path that user can upload files. - """ - self._allow_local_infile_in_path = path - - @MySQLConnectionAbstract.use_unicode.setter - def use_unicode(self, value: bool) -> None: - self._use_unicode = value - if self.converter: - self.converter.set_unicode(value) - - def reset_session( - self, - user_variables: Optional[Dict[str, Any]] = None, - session_variables: Optional[Dict[str, Any]] = None, - ) -> None: - """Clears the current active session - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - This method takes two arguments user_variables and session_variables - which are dictionaries. - - Raises OperationalError if not connected, InternalError if there are - unread results and InterfaceError on errors. - """ - if not self.is_connected(): - raise OperationalError("MySQL Connection not available.") - - if not self.cmd_reset_connection(): - try: - self.cmd_change_user( - self._user, - self._password, - self._database, - self._charset_id, - self._password1, - self._password2, - self._password3, - self._oci_config_file, - self._oci_config_profile, - self._openid_token_file, - ) - except ProgrammingError: - self.reconnect() - - cur = self.cursor() - if user_variables: - for key, value in user_variables.items(): - cur.execute(f"SET @`{key}` = %s", (value,)) - if session_variables: - for key, value in session_variables.items(): - cur.execute(f"SET SESSION `{key}` = %s", (value,)) - cur.close() - - def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None: - """Check availability of the MySQL server - - When reconnect is set to True, one or more attempts are made to try - to reconnect to the MySQL server using the reconnect()-method. - - delay is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. Use - the is_connected()-method if you just want to check the connection - without raising an error. - - Raises InterfaceError on errors. - """ - try: - self.cmd_ping() - except Error as err: - if reconnect: - self.reconnect(attempts=attempts, delay=delay) - else: - raise InterfaceError("Connection to MySQL is not available") from err - - @property - def connection_id(self) -> Optional[int]: - """MySQL connection ID""" - if self._handshake: - return self._handshake.get("server_threadid") # type: ignore[return-value] - return None - - def cursor( - self, - buffered: Optional[bool] = None, - raw: Optional[bool] = None, - prepared: Optional[bool] = None, - cursor_class: Optional[Type[MySQLCursor]] = None, # type: ignore[override] - dictionary: Optional[bool] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> MySQLCursor: - """Instantiates and returns a cursor - - By default, MySQLCursor is returned. Depending on the options - while connecting, a buffered and/or raw cursor is instantiated - instead. Also depending upon the cursor options, rows can be - returned as a dictionary or a tuple. - - Dictionary based cursors are available with buffered - output but not raw. - - It is possible to also give a custom cursor through the - cursor_class parameter, but it needs to be a subclass of - mysql.connector.cursor.MySQLCursor. - - Raises ProgrammingError when cursor_class is not a subclass of - MySQLCursor. Raises ValueError when cursor is not available. - Raises InterfaceError when read_timeout or write_timeout is not - a positive integer. - - Returns a cursor-object - """ - self.handle_unread_result() - - if not self.is_connected(): - raise OperationalError("MySQL Connection not available") - if read_timeout is not None and ( - not isinstance(read_timeout, int) or read_timeout < 0 - ): - raise InterfaceError("Option read_timeout must be a positive integer") - if write_timeout is not None and ( - not isinstance(write_timeout, int) or write_timeout < 0 - ): - raise InterfaceError("Option write_timeout must be a positive integer") - if cursor_class is not None: - if not issubclass(cursor_class, MySQLCursor): - raise ProgrammingError( - "Cursor class needs be to subclass of MySQLCursor" - ) - return (cursor_class)(self, read_timeout, write_timeout) - - buffered = buffered if buffered is not None else self._buffered - raw = raw if raw is not None else self._raw - - cursor_type = 0 - if buffered is True: - cursor_type |= 1 - if raw is True: - cursor_type |= 2 - if dictionary is True: - cursor_type |= 4 - if prepared is True: - cursor_type |= 16 - - types = { - 0: MySQLCursor, # 0 - 1: MySQLCursorBuffered, - 2: MySQLCursorRaw, - 3: MySQLCursorBufferedRaw, - 4: MySQLCursorDict, - 5: MySQLCursorBufferedDict, - 16: MySQLCursorPrepared, - 20: MySQLCursorPreparedDict, - } - try: - return (types[cursor_type])(self, read_timeout, write_timeout) - except KeyError: - args = ("buffered", "raw", "dictionary", "prepared") - raise ValueError( - "Cursor not available with given criteria: " - + ", ".join([args[i] for i in range(4) if cursor_type & (1 << i) != 0]) - ) from None - - def commit(self) -> None: - """Commit current transaction""" - self._execute_query("COMMIT") - - def rollback(self) -> None: - """Rollback current transaction""" - if self.unread_result: - self.get_rows() - - self._execute_query("ROLLBACK") - - def _execute_query(self, query: str) -> None: - """Execute a query - - This method simply calls cmd_query() after checking for unread - result. If there are still unread result, an InterfaceError - is raised. Otherwise whatever cmd_query() returns is returned. - - Returns a dict() - """ - self.handle_unread_result() - self.cmd_query(query) - - def info_query(self, query: str) -> Optional[RowType]: - """Send a query which only returns 1 row""" - cursor = self.cursor( - buffered=True, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - cursor.execute(query) - return cursor.fetchone() - - def _handle_binary_ok(self, packet: bytes) -> Dict[str, int]: - """Handle a MySQL Binary Protocol OK packet - - This method handles a MySQL Binary Protocol OK packet. When the - packet is found to be an Error packet, an error will be raised. If - the packet is neither an OK or an Error packet, InterfaceError - will be raised. - - Returns a dict() - """ - if packet[4] == OK_STATUS: - return self._protocol.parse_binary_prepare_ok(packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - raise InterfaceError("Expected Binary OK packet") - - @handle_read_write_timeout() - def _handle_binary_result( - self, packet: bytes, read_timeout: Optional[int] = None - ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]: - """Handle a MySQL Result - - This method handles a MySQL result, for example, after sending the - query command. OK and EOF packets will be handled and returned. If - the packet is an Error packet, an Error exception will be raised. - - The tuple returned by this method consist of: - - the number of columns in the result, - - a list of tuples with information about the columns, - - the EOF packet information as a dictionary. - - Returns tuple() or dict() - """ - if not packet or len(packet) < 4: - raise InterfaceError("Empty response") - if packet[4] == OK_STATUS: - return self._handle_ok(packet) - if packet[4] == EOF_STATUS: - return self._handle_eof(packet) - if packet[4] == ERR_STATUS: - raise get_exception(packet) - - # We have a binary result set - column_count = self._protocol.parse_column_count(packet) - if not column_count or not isinstance(column_count, int): - raise InterfaceError("Illegal result set.") - - columns: List[DescriptionType] = [None] * column_count - for i in range(0, column_count): - columns[i] = self._protocol.parse_column( - self._socket.recv(read_timeout or self._read_timeout), - self.python_charset, - ) - - eof = self._handle_eof(self._socket.recv(read_timeout or self._read_timeout)) - return (column_count, columns, eof) - - def cmd_stmt_fetch( - self, - statement_id: int, - rows: int = 1, - **kwargs: Any, - ) -> None: - """Fetch a MySQL statement Result Set - - This method will send the FETCH command to MySQL together with the - given statement id and the number of rows to fetch. - """ - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - packet = self._protocol.make_stmt_fetch(statement_id, rows) - self.unread_result = False - self._send_cmd( - ServerCmd.STMT_FETCH, - packet, - expect_response=False, - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - self.unread_result = True - - @handle_read_write_timeout() - def cmd_stmt_prepare( - self, - statement: bytes, - **kwargs: Any, - ) -> Mapping[str, Union[int, List[DescriptionType]]]: - """Prepare a MySQL statement - - This method will send the PREPARE command to MySQL together with the - given statement. - - Returns a dict() - """ - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - - packet = self._send_cmd( - ServerCmd.STMT_PREPARE, - statement, - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - result = self._handle_binary_ok(packet) - - result["columns"] = [] - result["parameters"] = [] - if result["num_params"] > 0: - for _ in range(0, result["num_params"]): - result["parameters"].append( - self._protocol.parse_column( - self._socket.recv(read_timeout or self._read_timeout), - self.python_charset, - ) - ) - self._handle_eof(self._socket.recv(read_timeout or self._read_timeout)) - if result["num_columns"] > 0: - for _ in range(0, result["num_columns"]): - result["columns"].append( - self._protocol.parse_column( - self._socket.recv(read_timeout or self._read_timeout), - self.python_charset, - ) - ) - self._handle_eof(self._socket.recv(read_timeout or self._read_timeout)) - return result - - @with_context_propagation - def cmd_stmt_execute( - self, - statement_id: int, - data: Sequence[BinaryProtocolType] = (), - parameters: Sequence = (), - flags: int = 0, - **kwargs: Any, - ) -> Union[OkPacketType, Tuple[int, List[DescriptionType], EofPacketType]]: - """Execute a prepared MySQL statement""" - parameters = list(parameters) - long_data_used = {} - read_timeout = kwargs.get("read_timeout", None) - write_timeout = kwargs.get("write_timeout", None) - - if data: - for param_id, _ in enumerate(parameters): - if isinstance(data[param_id], IOBase): - binary = True - try: - binary = "b" not in data[param_id].mode # type: ignore[union-attr] - except AttributeError: - pass - self.cmd_stmt_send_long_data( - statement_id, - param_id, - data[param_id], - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - long_data_used[param_id] = (binary,) - if not self._query_attrs_supported and self._query_attrs: - warnings.warn( - "This version of the server does not support Query Attributes", - category=Warning, - ) - if self._client_flags & ClientFlag.CLIENT_QUERY_ATTRIBUTES: - execute_packet = self._protocol.make_stmt_execute( - statement_id, - data, - tuple(parameters), - flags, - long_data_used, - self.charset, - self.query_attrs, - self._converter_str_fallback, - ) - else: - execute_packet = self._protocol.make_stmt_execute( - statement_id, - data, - tuple(parameters), - flags, - long_data_used, - self.charset, - converter_str_fallback=self._converter_str_fallback, - ) - packet = self._send_cmd( - ServerCmd.STMT_EXECUTE, - packet=execute_packet, - read_timeout=read_timeout, - write_timeout=write_timeout, - ) - result = self._handle_binary_result(packet, read_timeout) - return result - - def cmd_stmt_close( - self, - statement_id: int, # type: ignore[override] - **kwargs: Any, - ) -> None: - """Deallocate a prepared MySQL statement - - This method deallocates the prepared statement using the - statement_id. Note that the MySQL server does not return - anything. - """ - self._send_cmd( - ServerCmd.STMT_CLOSE, - int4store(statement_id), - expect_response=False, - read_timeout=kwargs.get("read_timeout", None), - write_timeout=kwargs.get("write_timeout", None), - ) - - def cmd_stmt_send_long_data( - self, - statement_id: int, # type: ignore[override] - param_id: int, - data: BinaryIO, - **kwargs: Any, - ) -> int: - """Send data for a column - - This methods send data for a column (for example BLOB) for statement - identified by statement_id. The param_id indicate which parameter - the data belongs too. - The data argument should be a file-like object. - - Since MySQL does not send anything back, no error is raised. When - the MySQL server is not reachable, an OperationalError is raised. - - cmd_stmt_send_long_data should be called before cmd_stmt_execute. - - The total bytes send is returned. - - Returns int. - """ - chunk_size = 131072 # 128 KB - total_sent = 0 - try: - buf = data.read(chunk_size) - while buf: - packet = self._protocol.prepare_stmt_send_long_data( - statement_id, param_id, buf - ) - self._send_cmd( - ServerCmd.STMT_SEND_LONG_DATA, - packet=packet, - expect_response=False, - read_timeout=kwargs.get("read_timeout", None), - write_timeout=kwargs.get("write_timeout", None), - ) - total_sent += len(buf) - buf = data.read(chunk_size) - except AttributeError as err: - raise OperationalError("MySQL Connection not available") from err - - return total_sent - - def cmd_stmt_reset( - self, - statement_id: int, # type: ignore[override] - **kwargs: Any, - ) -> None: - """Reset data for prepared statement sent as long data - - The result is a dictionary with OK packet information. - - Returns a dict() - """ - self._handle_ok( - self._send_cmd( - ServerCmd.STMT_RESET, - int4store(statement_id), - read_timeout=kwargs.get("read_timeout", None), - write_timeout=kwargs.get("write_timeout", None), - ) - ) - - def cmd_reset_connection(self) -> bool: - """Resets the session state without re-authenticating - - Reset command only works on MySQL server 5.7.3 or later. - The result is True for a successful reset otherwise False. - - Returns bool - """ - try: - self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION)) - self._post_connection() - return True - except (NotSupportedError, OperationalError): - return False - - def handle_unread_result(self) -> None: - """Check whether there is an unread result""" - if self.can_consume_results: - self.consume_results() - elif self.unread_result: - raise InternalError("Unread result found") diff --git a/server/venv/Lib/site-packages/mysql/connector/connection_cext.py b/server/venv/Lib/site-packages/mysql/connector/connection_cext.py deleted file mode 100644 index 2c3e94f..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/connection_cext.py +++ /dev/null @@ -1,1170 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="arg-type" - -"""Connection class using the C Extension.""" - -import os -import platform -import socket -import sys -import warnings - -from typing import ( - Any, - BinaryIO, - Dict, - List, - NoReturn, - Optional, - Sequence, - Tuple, - Type, - Union, -) - -from . import version -from ._decorating import cmd_refresh_verify_options -from .abstracts import CMySQLPrepStmt, MySQLConnectionAbstract -from .constants import ClientFlag, FieldFlag, FieldType, ServerFlag, ShutdownType -from .conversion import MySQLConverter -from .errors import ( - InterfaceError, - InternalError, - OperationalError, - ProgrammingError, - get_mysql_exception, -) -from .protocol import MySQLProtocol -from .types import ( - CextEofPacketType, - CextResultType, - DescriptionType, - ParamsSequenceOrDictType, - RowType, - StatsPacketType, - StrOrBytes, -) -from .utils import ( - import_object, - warn_ciphersuites_deprecated, - warn_tls_version_deprecated, -) - -HAVE_CMYSQL = False - -try: - import _mysql_connector - - from _mysql_connector import MySQLInterfaceError - - from .cursor_cext import ( - CMySQLCursor, - CMySQLCursorBuffered, - CMySQLCursorBufferedDict, - CMySQLCursorBufferedRaw, - CMySQLCursorDict, - CMySQLCursorPrepared, - CMySQLCursorPreparedDict, - CMySQLCursorRaw, - ) - - HAVE_CMYSQL = True -except ImportError as exc: - raise ImportError( - f"MySQL Connector/Python C Extension not available ({exc})" - ) from exc - -from .opentelemetry.constants import OTEL_ENABLED -from .opentelemetry.context_propagation import with_context_propagation - -if OTEL_ENABLED: - from .opentelemetry.instrumentation import end_span, record_exception_event - - -class CMySQLConnection(MySQLConnectionAbstract): - """Class initiating a MySQL Connection using Connector/C.""" - - def __init__(self, **kwargs: Any) -> None: - """Initialization""" - if not HAVE_CMYSQL: - raise RuntimeError("MySQL Connector/Python C Extension not available") - self._cmysql: Optional[ - _mysql_connector.MySQL # pylint: disable=c-extension-no-member - ] = None - self._columns: List[DescriptionType] = [] - self._plugin_dir: str = os.path.join( - os.path.dirname(os.path.abspath(_mysql_connector.__file__)), - "mysql", - "vendor", - "plugin", - ) - if platform.system() == "Linux": - # Use the authentication plugins from system if they aren't bundled - if not os.path.exists(self._plugin_dir): - self._plugin_dir = ( - "/usr/lib64/mysql/plugin" - if os.path.exists("/usr/lib64/mysql/plugin") - else "/usr/lib/mysql/plugin" - ) - - self.converter: Optional[MySQLConverter] = None - super().__init__() - - if kwargs: - try: - self.connect(**kwargs) - except Exception: - self.close() - raise - - def _add_default_conn_attrs(self) -> None: - """Add default connection attributes""" - license_chunks = version.LICENSE.split(" ") - if license_chunks[0] == "GPLv2": - client_license = "GPL-2.0" - else: - client_license = "Commercial" - - self._conn_attrs.update( - { - "_connector_name": "mysql-connector-python", - "_connector_license": client_license, - "_connector_version": ".".join([str(x) for x in version.VERSION[0:3]]), - "_source_host": socket.gethostname(), - } - ) - - def _do_handshake(self) -> None: - """Gather information of the MySQL server before authentication""" - self._handshake = { - "protocol": self._cmysql.get_proto_info(), - "server_version_original": self._cmysql.get_server_info(), - "server_threadid": self._cmysql.thread_id(), - "charset": None, - "server_status": None, - "auth_plugin": None, - "auth_data": None, - "capabilities": self._cmysql.st_server_capabilities(), - } - - self._server_version = self._check_server_version( - self._handshake["server_version_original"] - ) - self._character_set.set_mysql_version(self._server_version) - - @property - def _server_status(self) -> int: - """Returns the server status attribute of MYSQL structure""" - return self._cmysql.st_server_status() - - def set_allow_local_infile_in_path(self, path: str) -> None: - """set local_infile_in_path - - Set allow_local_infile_in_path. - """ - - if self._cmysql: - self._cmysql.set_load_data_local_infile_option(path) - - @MySQLConnectionAbstract.use_unicode.setter # type: ignore - def use_unicode(self, value: bool) -> None: - self._use_unicode = value - if self._cmysql: - self._cmysql.use_unicode(value) - if self.converter: - self.converter.set_unicode(value) - - @property - def autocommit(self) -> bool: - """Get whether autocommit is on or off""" - value = self.info_query("SELECT @@session.autocommit")[0] - return value == 1 - - @autocommit.setter - def autocommit(self, value: bool) -> None: - """Toggle autocommit""" - try: - self._cmysql.autocommit(value) - self._autocommit = value - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - @property - def read_timeout(self) -> Optional[int]: - return self._read_timeout - - @read_timeout.setter - def read_timeout(self, timeout: int) -> None: - raise ProgrammingError( - """ - The use of read_timeout after the connection has been established is unsupported - in the C-Extension - """ - ) - - @property - def write_timeout(self) -> Optional[int]: - return self._write_timeout - - @write_timeout.setter - def write_timeout(self, timeout: int) -> None: - raise ProgrammingError( - """ - Changes in write_timeout after the connection has been established is unsupported - in the C-Extension - """ - ) - - @property - def database(self) -> str: - """Get the current database""" - return self.info_query("SELECT DATABASE()")[0] # type: ignore[return-value] - - @database.setter - def database(self, value: str) -> None: - """Set the current database""" - try: - self._cmysql.select_db(value) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - @property - def in_transaction(self) -> bool: - """MySQL session has started a transaction""" - return bool(self._server_status & ServerFlag.STATUS_IN_TRANS) - - def _open_connection(self) -> None: - charset_name = self._character_set.get_info(self._charset_id)[0] - # pylint: disable=c-extension-no-member - self._cmysql = _mysql_connector.MySQL( - buffered=self._buffered, - raw=self._raw, - charset_name=charset_name, - connection_timeout=(self._connection_timeout or 0), - use_unicode=self.use_unicode, - auth_plugin=self._auth_plugin, - plugin_dir=self._plugin_dir, - ) - # pylint: enable=c-extension-no-member - if not self.isset_client_flag(ClientFlag.CONNECT_ARGS): - self._conn_attrs = {} - - cnx_kwargs = { - "host": self._host, - "user": self._user, - "password": self._password, - "password1": self._password1, - "password2": self._password2, - "password3": self._password3, - "database": self._database, - "port": self._port, - "client_flags": self.client_flags, - "unix_socket": self._unix_socket, - "compress": self._compress, - "ssl_disabled": True, - "conn_attrs": self._conn_attrs, - "local_infile": self._allow_local_infile, - "load_data_local_dir": self._allow_local_infile_in_path, - "oci_config_file": self._oci_config_file, - "oci_config_profile": self._oci_config_profile, - "webauthn_callback": ( - import_object(self._webauthn_callback) - if isinstance(self._webauthn_callback, str) - else self._webauthn_callback - ), - "openid_token_file": self._openid_token_file, - "read_timeout": self._read_timeout if self._read_timeout else 0, - "write_timeout": self._write_timeout if self._write_timeout else 0, - } - - tls_versions = self._ssl.get("tls_versions") - if tls_versions is not None: - tls_versions.sort(reverse=True) # type: ignore[union-attr] - tls_versions = ",".join(tls_versions) - if self._ssl.get("tls_ciphersuites") is not None: - ssl_ciphersuites = ( - self._ssl.get("tls_ciphersuites")[0] or None # type: ignore[index] - ) # if it's the empty string, then use `None` instead - tls_ciphersuites = self._ssl.get("tls_ciphersuites")[ # type: ignore[index] - 1 - ] - else: - ssl_ciphersuites = None - tls_ciphersuites = None - if ( - tls_versions is not None - and "TLSv1.3" in tls_versions - and not tls_ciphersuites - ): - tls_ciphersuites = "TLS_AES_256_GCM_SHA384" - if not self._ssl_disabled: - cnx_kwargs.update( - { - "ssl_ca": self._ssl.get("ca"), - "ssl_cert": self._ssl.get("cert"), - "ssl_key": self._ssl.get("key"), - "ssl_cipher_suites": ssl_ciphersuites, - "tls_versions": tls_versions, - "tls_cipher_suites": tls_ciphersuites, - "ssl_verify_cert": self._ssl.get("verify_cert") or False, - "ssl_verify_identity": self._ssl.get("verify_identity") or False, - "ssl_disabled": self._ssl_disabled, - } - ) - - if os.name == "nt" and self._auth_plugin_class == "MySQLKerberosAuthPlugin": - cnx_kwargs["use_kerberos_gssapi"] = True - - try: - self._cmysql.connect(**cnx_kwargs) - self._cmysql.converter_str_fallback = self._converter_str_fallback - if self.converter: - self.converter.str_fallback = self._converter_str_fallback - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - self._do_handshake() - - if ( - not self._ssl_disabled - and hasattr(self._cmysql, "get_ssl_cipher") - and callable(self._cmysql.get_ssl_cipher) - ): - # Raise a deprecation warning if deprecated TLS version - # or cipher is being used. - - # `get_ssl_cipher()` returns the name of the cipher being used. - cipher = self._cmysql.get_ssl_cipher() - for tls_version in set(self._ssl.get("tls_versions", [])): - warn_tls_version_deprecated(tls_version) - warn_ciphersuites_deprecated(cipher, tls_version) - - def close(self) -> None: - if self._span and self._span.is_recording(): - # pylint: disable=possibly-used-before-assignment - record_exception_event(self._span, sys.exc_info()[1]) - - if not self._cmysql: - return - - try: - self.free_result() - self._cmysql.close() - except MySQLInterfaceError as err: - if OTEL_ENABLED: - record_exception_event(self._span, err) - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - finally: - if OTEL_ENABLED: - end_span(self._span) - - disconnect = close - - def is_closed(self) -> bool: - """Return True if the connection to MySQL Server is closed.""" - return not self._cmysql.connected() - - def is_connected(self) -> bool: - """Reports whether the connection to MySQL Server is available""" - if self._cmysql: - self.handle_unread_result() - return self._cmysql.ping() - - return False - - def ping(self, reconnect: bool = False, attempts: int = 1, delay: int = 0) -> None: - """Check availability of the MySQL server - - When reconnect is set to True, one or more attempts are made to try - to reconnect to the MySQL server using the reconnect()-method. - - delay is the number of seconds to wait between each retry. - - When the connection is not available, an InterfaceError is raised. Use - the is_connected()-method if you just want to check the connection - without raising an error. - - Raises InterfaceError on errors. - """ - self.handle_unread_result() - - try: - connected = self._cmysql.ping() - except AttributeError: - pass # Raise or reconnect later - else: - if connected: - return - - if reconnect: - self.reconnect(attempts=attempts, delay=delay) - else: - raise InterfaceError("Connection to MySQL is not available") - - def set_character_set_name(self, charset: str) -> None: - """Sets the default character set name for current connection.""" - self._cmysql.set_character_set(charset) - - def info_query(self, query: StrOrBytes) -> Optional[RowType]: - """Send a query which only returns 1 row""" - first_row = () - try: - self._cmysql.query(query) - if self._cmysql.have_result_set: - first_row = self._cmysql.fetch_row() - if self._cmysql.fetch_row(): - self._cmysql.free_result() - raise InterfaceError("Query should not return more than 1 row") - self._cmysql.free_result() - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - return first_row - - @property - def connection_id(self) -> Optional[int]: - """MySQL connection ID""" - try: - return self._cmysql.thread_id() - except MySQLInterfaceError: - pass # Just return None - - return None - - def get_rows( - self, - count: Optional[int] = None, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Optional[CMySQLPrepStmt] = None, - **kwargs: Any, - ) -> Tuple[List[RowType], Optional[CextEofPacketType]]: - """Get all or a subset of rows returned by the MySQL server""" - unread_result = prep_stmt.have_result_set if prep_stmt else self.unread_result - if not (self._cmysql and unread_result): - raise InternalError("No result set available") - - if raw is None: - raw = self._raw - - rows: List[Tuple] = [] - if count is not None and count <= 0: - raise AttributeError("count should be 1 or higher, or None") - - counter = 0 - try: - fetch_row = prep_stmt.fetch_row if prep_stmt else self._cmysql.fetch_row - if self.converter or raw: - # When using a converter class or `raw`, the C extension should not - # convert the values. This can be accomplished by setting - # the raw option to True. - self._cmysql.raw(True) - - row = fetch_row() - while row: - row = list(row) - - if not self._cmysql.raw() and not raw: - # `not _cmysql.raw()` means the c-ext conversion layer will happen. - # `not raw` means the caller wants conversion to happen. - # For a VECTOR type, the c-ext conversion layer cannot return - # an array.array type since such a type isn't part of the Python/C - # API. Therefore, the c-ext will treat VECTOR types as if they - # were BLOB types - be returned as `bytes` always. - # Hence, a VECTOR type must be cast to an array.array type using the - # built-in python conversion layer. - # pylint: disable=protected-access - for i, dsc in enumerate(self._columns): - if dsc[1] == FieldType.VECTOR: - row[i] = MySQLConverter._vector_to_python(row[i]) - - if not self._raw and self.converter: - for i, _ in enumerate(row): - if not raw: - row[i] = self.converter.to_python(self._columns[i], row[i]) - - rows.append(tuple(row)) - counter += 1 - - if count and counter == count: - break - - row = fetch_row() - if not row: - _eof: Optional[CextEofPacketType] = self.fetch_eof_columns(prep_stmt)[ - "eof" - ] # type: ignore[assignment] - if prep_stmt: - prep_stmt.free_result() - self._unread_result = False - else: - self.free_result() - else: - _eof = None - except MySQLInterfaceError as err: - if prep_stmt: - prep_stmt.free_result() - else: - self.free_result() - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - return rows, _eof - - def get_row( - self, - binary: bool = False, - columns: Optional[List[DescriptionType]] = None, - raw: Optional[bool] = None, - prep_stmt: Optional[CMySQLPrepStmt] = None, - **kwargs: Any, - ) -> Tuple[Optional[RowType], Optional[CextEofPacketType]]: - """Get the next rows returned by the MySQL server""" - try: - rows, eof = self.get_rows( - count=1, - binary=binary, - columns=columns, - raw=raw, - prep_stmt=prep_stmt, - ) - if rows: - return (rows[0], eof) - return (None, eof) - except IndexError: - # No row available - return (None, None) - - def next_result(self) -> Optional[bool]: - """Reads the next result""" - if self._cmysql: - self._cmysql.consume_result() - return self._cmysql.next_result() - return None - - def free_result(self) -> None: - """Frees the result""" - if self._cmysql: - self._cmysql.free_result() - - def commit(self) -> None: - """Commit current transaction""" - if self._cmysql: - self.handle_unread_result() - self._cmysql.commit() - - def rollback(self) -> None: - """Rollback current transaction""" - if self._cmysql: - self._cmysql.consume_result() - self._cmysql.rollback() - - def cmd_init_db(self, database: str) -> None: - """Change the current database""" - try: - self._cmysql.select_db(database) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - def fetch_eof_columns( - self, prep_stmt: Optional[CMySQLPrepStmt] = None - ) -> CextResultType: - """Fetch EOF and column information""" - have_result_set = ( - prep_stmt.have_result_set if prep_stmt else self._cmysql.have_result_set - ) - if not have_result_set: - raise InterfaceError("No result set") - - fields = prep_stmt.fetch_fields() if prep_stmt else self._cmysql.fetch_fields() - self._columns = [] - for col in fields: - self._columns.append( - ( - col[4], - int(col[8]), - None, - None, - None, - None, - ~int(col[9]) & FieldFlag.NOT_NULL, - int(col[9]), - int(col[6]), - ) - ) - - return { - "eof": { - "status_flag": self._server_status, - "warning_count": self._cmysql.st_warning_count(), - }, - "columns": self._columns, - } - - def fetch_eof_status(self) -> Optional[CextEofPacketType]: - """Fetch EOF and status information""" - if self._cmysql: - return { - "warning_count": self._cmysql.st_warning_count(), - "field_count": self._cmysql.st_field_count(), - "insert_id": self._cmysql.insert_id(), - "affected_rows": self._cmysql.affected_rows(), - "server_status": self._server_status, - } - - return None - - def cmd_stmt_prepare( - self, - statement: bytes, - **kwargs: Any, - ) -> CMySQLPrepStmt: - """Prepares the SQL statement""" - if not self._cmysql: - raise OperationalError("MySQL Connection not available") - - try: - stmt = self._cmysql.stmt_prepare(statement) - stmt.converter_str_fallback = self._converter_str_fallback - return CMySQLPrepStmt(stmt) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - @with_context_propagation - def cmd_stmt_execute( - self, - statement_id: CMySQLPrepStmt, - *args: Any, - **kwargs: Any, - ) -> Optional[Union[CextEofPacketType, CextResultType]]: - """Executes the prepared statement""" - try: - statement_id.stmt_execute(*args, query_attrs=self.query_attrs) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - self._columns = [] - if not statement_id.have_result_set: - # No result - self._unread_result = False - return self.fetch_eof_status() - - self._unread_result = True - return self.fetch_eof_columns(statement_id) - - def cmd_stmt_close( - self, - statement_id: CMySQLPrepStmt, # type: ignore[override] - **kwargs: Any, - ) -> None: - """Closes the prepared statement""" - if self._unread_result: - raise InternalError("Unread result found") - try: - statement_id.stmt_close() - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - def cmd_stmt_reset( - self, - statement_id: CMySQLPrepStmt, # type: ignore[override] - **kwargs: Any, - ) -> None: - """Resets the prepared statement""" - if self._unread_result: - raise InternalError("Unread result found") - try: - statement_id.stmt_reset() - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - @with_context_propagation - def cmd_query( - self, - query: StrOrBytes, - raw: Optional[bool] = None, - buffered: bool = False, - raw_as_string: bool = False, - **kwargs: Any, - ) -> Optional[Union[CextEofPacketType, CextResultType]]: - self.handle_unread_result() - if raw is None: - raw = self._raw - try: - if not isinstance(query, bytes): - query = query.encode("utf-8") - - # Set/Reset internal state related to query execution - self._query = query - self._local_infile_filenames = None - - self._cmysql.query( - query, - raw=raw, - buffered=buffered, - raw_as_string=raw_as_string, - query_attrs=self.query_attrs, - ) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - except AttributeError as err: - addr = ( - self._unix_socket if self._unix_socket else f"{self._host}:{self._port}" - ) - raise OperationalError( - errno=2055, values=(addr, "Connection not available.") - ) from err - - self._columns = [] - if not self._cmysql.have_result_set: - # No result - return self.fetch_eof_status() - - return self.fetch_eof_columns() - - _execute_query = cmd_query - - def cursor( - self, - buffered: Optional[bool] = None, - raw: Optional[bool] = None, - prepared: Optional[bool] = None, - cursor_class: Optional[Type[CMySQLCursor]] = None, # type: ignore[override] - dictionary: Optional[bool] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> CMySQLCursor: - """Instantiates and returns a cursor using C Extension - - By default, CMySQLCursor is returned. Depending on the options - while connecting, a buffered and/or raw cursor is instantiated - instead. Also depending upon the cursor options, rows can be - returned as a dictionary or a tuple. - - Dictionary based cursors are available with buffered - output but not raw. - - It is possible to also give a custom cursor through the - cursor_class parameter, but it needs to be a subclass of - mysql.connector.cursor_cext.CMySQLCursor. - - Raises ProgrammingError when cursor_class is not a subclass of - CMySQLCursor. Raises ValueError when cursor is not available. - - Returns instance of CMySQLCursor or subclass. - - :param buffered: Return a buffering cursor - :param raw: Return a raw cursor - :param prepared: Return a cursor which uses prepared statements - :param cursor_class: Use a custom cursor class - :param dictionary: Rows are returned as dictionary - :return: Subclass of CMySQLCursor - :rtype: CMySQLCursor or subclass - """ - self.handle_unread_result(prepared) - if not self.is_connected(): - raise OperationalError("MySQL Connection not available.") - if read_timeout or write_timeout: - warnings.warn( - """The use of read_timeout after the connection has been established is unsupported - in the C-Extension""", - category=Warning, - ) - if cursor_class is not None: - if not issubclass(cursor_class, CMySQLCursor): - raise ProgrammingError( - "Cursor class needs be to subclass of cursor_cext.CMySQLCursor" - ) - return (cursor_class)(self) - - buffered = buffered or self._buffered - raw = raw or self._raw - - cursor_type = 0 - if buffered is True: - cursor_type |= 1 - if raw is True: - cursor_type |= 2 - if dictionary is True: - cursor_type |= 4 - if prepared is True: - cursor_type |= 16 - - types = { - 0: CMySQLCursor, # 0 - 1: CMySQLCursorBuffered, - 2: CMySQLCursorRaw, - 3: CMySQLCursorBufferedRaw, - 4: CMySQLCursorDict, - 5: CMySQLCursorBufferedDict, - 16: CMySQLCursorPrepared, - 20: CMySQLCursorPreparedDict, - } - try: - return (types[cursor_type])(self) - except KeyError: - args = ("buffered", "raw", "dictionary", "prepared") - raise ValueError( - "Cursor not available with given criteria: " - + ", ".join([args[i] for i in range(4) if cursor_type & (1 << i) != 0]) - ) from None - - @property - def num_rows(self) -> int: - """Returns number of rows of current result set""" - if not self._cmysql.have_result_set: - raise InterfaceError("No result set") - - return self._cmysql.num_rows() - - @property - def warning_count(self) -> int: - """Returns number of warnings""" - if not self._cmysql: - return 0 - - return self._cmysql.warning_count() - - @property - def result_set_available(self) -> bool: - """Check if a result set is available""" - if not self._cmysql: - return False - - return self._cmysql.have_result_set - - @property # type: ignore[misc] - def unread_result(self) -> bool: - """Check if there are unread results or rows""" - return self.result_set_available - - @property - def more_results(self) -> bool: - """Check if there are more results""" - return self._cmysql.more_results() - - def prepare_for_mysql( - self, params: ParamsSequenceOrDictType - ) -> Union[Sequence[bytes], Dict[bytes, bytes]]: - """Prepare parameters for statements - - This method is use by cursors to prepared parameters found in the - list (or tuple) params. - - Returns dict. - """ - result: Union[List[bytes], Dict[bytes, bytes]] = [] - if isinstance(params, (list, tuple)): - if self.converter: - result = [ - self.converter.quote( - self.converter.escape( - self.converter.to_mysql(value), self._sql_mode - ) - ) - for value in params - ] - else: - result = self._cmysql.convert_to_mysql(*params) - elif isinstance(params, dict): - result = {} - if self.converter: - for key, value in params.items(): - result[key.encode()] = self.converter.quote( - self.converter.escape( - self.converter.to_mysql(value), self._sql_mode - ) - ) - else: - for key, value in params.items(): - result[key.encode()] = self._cmysql.convert_to_mysql(value)[0] - else: - raise ProgrammingError( - f"Could not process parameters: {type(params).__name__}({params})," - " it must be of type list, tuple or dict" - ) - - return result - - def consume_results(self) -> None: - """Consume the current result - - This method consume the result by reading (consuming) all rows. - """ - self._cmysql.consume_result() - - def cmd_change_user( - self, - username: str = "", - password: str = "", - database: str = "", - charset: Optional[int] = None, - password1: str = "", - password2: str = "", - password3: str = "", - oci_config_file: Optional[str] = None, - oci_config_profile: Optional[str] = None, - openid_token_file: Optional[str] = None, - ) -> None: - """Change the current logged in user""" - try: - self._cmysql.change_user( - username, - password, - database, - password1, - password2, - password3, - oci_config_file, - oci_config_profile, - openid_token_file, - ) - - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - # If charset isn't defined, we use the same charset ID defined previously, - # otherwise, we run a verification and update the charset ID. - if charset is not None: - if not isinstance(charset, int): - raise ValueError("charset must be an integer") - if charset < 0: - raise ValueError("charset should be either zero or a postive integer") - self._charset_id = charset - self._user = username # updating user accordingly - self._post_connection() - - def cmd_reset_connection(self) -> bool: - """Resets the session state without re-authenticating - - Reset command only works on MySQL server 5.7.3 or later. - The result is True for a successful reset otherwise False. - - Returns bool - """ - res = self._cmysql.reset_connection() - if res: - self._post_connection() - return res - - @cmd_refresh_verify_options() - def cmd_refresh(self, options: int) -> Optional[CextEofPacketType]: - try: - self.handle_unread_result() - self._cmysql.refresh(options) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - return self.fetch_eof_status() - - def cmd_quit(self) -> None: - """Close the current connection with the server""" - self.close() - - def cmd_shutdown(self, shutdown_type: Optional[int] = None) -> None: - """Shut down the MySQL Server - - This method sends the SHUTDOWN command to the MySQL server. - The `shutdown_type` is not used, and it's kept for backward compatibility. - """ - if not self._cmysql: - raise OperationalError("MySQL Connection not available") - - if shutdown_type: - if not ShutdownType.get_info(shutdown_type): - raise InterfaceError("Invalid shutdown type") - level = shutdown_type - else: - level = ShutdownType.SHUTDOWN_DEFAULT - - try: - self._cmysql.shutdown(level) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - self.close() - - def cmd_statistics(self) -> StatsPacketType: - """Return statistics from the MySQL server""" - self.handle_unread_result() - - try: - stat = self._cmysql.stat() - return MySQLProtocol().parse_statistics(stat, with_header=False) - except (MySQLInterfaceError, InterfaceError) as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - - def cmd_process_kill(self, mysql_pid: int) -> None: - """Kill a MySQL process""" - if not isinstance(mysql_pid, int): - raise ValueError("MySQL PID must be int") - self.cmd_query(f"KILL {mysql_pid}") - - def cmd_debug(self) -> NoReturn: - """Send the DEBUG command""" - raise NotImplementedError - - def cmd_ping(self) -> NoReturn: - """Send the PING command""" - raise NotImplementedError - - def cmd_query_iter(self, statements: str, **kwargs: Any) -> NoReturn: - """Send one or more statements to the MySQL server""" - raise NotImplementedError - - def cmd_stmt_send_long_data( - self, - statement_id: CMySQLPrepStmt, # type: ignore[override] - param_id: int, - data: BinaryIO, - **kwargs: Any, - ) -> NoReturn: - """Send data for a column""" - raise NotImplementedError - - def handle_unread_result(self, prepared: bool = False) -> None: - """Check whether there is an unread result""" - unread_result = self._unread_result if prepared is True else self.unread_result - if self.can_consume_results: - self.consume_results() - elif unread_result: - raise InternalError("Unread result found") - - def reset_session( - self, - user_variables: Optional[Dict[str, Any]] = None, - session_variables: Optional[Dict[str, Any]] = None, - ) -> None: - """Clears the current active session - - This method resets the session state, if the MySQL server is 5.7.3 - or later active session will be reset without re-authenticating. - For other server versions session will be reset by re-authenticating. - - It is possible to provide a sequence of variables and their values to - be set after clearing the session. This is possible for both user - defined variables and session variables. - This method takes two arguments user_variables and session_variables - which are dictionaries. - - Raises OperationalError if not connected, InternalError if there are - unread results and InterfaceError on errors. - """ - if not self.is_connected(): - raise OperationalError("MySQL Connection not available.") - - if not self.cmd_reset_connection(): - try: - self.cmd_change_user( - self._user, - self._password, - self._database, - self._charset_id, - self._password1, - self._password2, - self._password3, - self._oci_config_file, - self._oci_config_profile, - ) - except ProgrammingError: - self.reconnect() - - if user_variables or session_variables: - cur = self.cursor() - if user_variables: - for key, value in user_variables.items(): - cur.execute(f"SET @`{key}` = %s", (value,)) - if session_variables: - for key, value in session_variables.items(): - cur.execute(f"SET SESSION `{key}` = %s", (value,)) - cur.close() diff --git a/server/venv/Lib/site-packages/mysql/connector/constants.py b/server/venv/Lib/site-packages/mysql/connector/constants.py deleted file mode 100644 index 99d2d51..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/constants.py +++ /dev/null @@ -1,1165 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Various MySQL constants and character sets.""" - -import warnings - -from abc import ABC, ABCMeta -from datetime import date, datetime, time, timedelta -from decimal import Decimal -from time import struct_time -from typing import Any, Dict, List, Optional, Sequence, Tuple, Union, ValuesView - -from .charsets import MYSQL_CHARACTER_SETS, MYSQL_CHARACTER_SETS_57 -from .errors import ProgrammingError -from .tls_ciphers import APPROVED_TLS_VERSIONS, DEPRECATED_TLS_VERSIONS - -NET_BUFFER_LENGTH: int = 8192 -MAX_MYSQL_TABLE_COLUMNS: int = 4096 -PARAMETER_COUNT_AVAILABLE: int = 8 -"""Flag used to send the Query Attributes with 0 (or more) parameters.""" -MYSQL_VECTOR_TYPE_CODE = "f" -"""Expected `typecode` when decoding VECTOR values from -MySQL (blob) to Python (array.array). -""" -MYSQL_DEFAULT_CHARSET_ID_57 = 45 -MYSQL_DEFAULT_CHARSET_ID_80 = 255 - -DEFAULT_CONFIGURATION: Dict[str, Optional[Union[str, bool, int]]] = { - "database": None, - "user": "", - "password": "", - "password1": "", - "password2": "", - "password3": "", - "host": "127.0.0.1", - "port": 3306, - "unix_socket": None, - "use_unicode": True, - "charset": "utf8mb4", - "collation": None, - "converter_class": None, - "converter_str_fallback": False, - "autocommit": False, - "time_zone": None, - "sql_mode": None, - "get_warnings": False, - "raise_on_warnings": False, - "connection_timeout": None, - "read_timeout": None, - "write_timeout": None, - "client_flags": 0, - "compress": False, - "buffered": False, - "raw": False, - "ssl_ca": None, - "ssl_cert": None, - "ssl_key": None, - "ssl_verify_cert": False, - "ssl_verify_identity": False, - "ssl_cipher": None, - "tls_ciphersuites": None, - "ssl_disabled": False, - "tls_versions": None, - "passwd": None, - "db": None, - "connect_timeout": None, - "dsn": None, - "force_ipv6": False, - "auth_plugin": None, - "allow_local_infile": False, - "allow_local_infile_in_path": None, - "consume_results": False, - "conn_attrs": None, - "dns_srv": False, - "use_pure": False, - "krb_service_principal": None, - "oci_config_file": None, - "oci_config_profile": None, - "webauthn_callback": None, - "kerberos_auth_mode": None, - "init_command": None, - "openid_token_file": None, -} - -CNX_POOL_ARGS: Tuple[str, str, str] = ("pool_name", "pool_size", "pool_reset_session") - -CONN_ATTRS_DN: Tuple[str, ...] = ( - "_pid", - "_platform", - "_source_host", - "_client_name", - "_client_license", - "_client_version", - "_os", - "_connector_name", - "_connector_license", - "_connector_version", -) - -DEPRECATED_METHOD_WARNING: str = """ - The property counterpart '{property_name}' should be used instead. -""" - -TLS_VERSIONS: List[str] = APPROVED_TLS_VERSIONS + DEPRECATED_TLS_VERSIONS -"""Accepted TLS versions. A warning is raised when using a deprecated version.""" - -# TLS v1.2 cipher suites IANI to OpenSSL name translation -TLSV1_2_CIPHER_SUITES: Dict[str, str] = { - "TLS_RSA_WITH_NULL_SHA256": "NULL-SHA256", - "TLS_RSA_WITH_AES_128_CBC_SHA256": "AES128-SHA256", - "TLS_RSA_WITH_AES_256_CBC_SHA256": "AES256-SHA256", - "TLS_RSA_WITH_AES_128_GCM_SHA256": "AES128-GCM-SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384": "AES256-GCM-SHA384", - "TLS_DH_RSA_WITH_AES_128_CBC_SHA256": "DH-RSA-AES128-SHA256", - "TLS_DH_RSA_WITH_AES_256_CBC_SHA256": "DH-RSA-AES256-SHA256", - "TLS_DH_RSA_WITH_AES_128_GCM_SHA256": "DH-RSA-AES128-GCM-SHA256", - "TLS_DH_RSA_WITH_AES_256_GCM_SHA384": "DH-RSA-AES256-GCM-SHA384", - "TLS_DH_DSS_WITH_AES_128_CBC_SHA256": "DH-DSS-AES128-SHA256", - "TLS_DH_DSS_WITH_AES_256_CBC_SHA256": "DH-DSS-AES256-SHA256", - "TLS_DH_DSS_WITH_AES_128_GCM_SHA256": "DH-DSS-AES128-GCM-SHA256", - "TLS_DH_DSS_WITH_AES_256_GCM_SHA384": "DH-DSS-AES256-GCM-SHA384", - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256": "DHE-RSA-AES128-SHA256", - "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256": "DHE-RSA-AES256-SHA256", - "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256": "DHE-RSA-AES128-GCM-SHA256", - "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384": "DHE-RSA-AES256-GCM-SHA384", - "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256": "DHE-DSS-AES128-SHA256", - "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256": "DHE-DSS-AES256-SHA256", - "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256": "DHE-DSS-AES128-GCM-SHA256", - "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384": "DHE-DSS-AES256-GCM-SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": "ECDHE-RSA-AES128-SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384": "ECDHE-RSA-AES256-SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": "ECDHE-RSA-AES128-GCM-SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": "ECDHE-RSA-AES256-GCM-SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": "ECDHE-ECDSA-AES128-SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384": "ECDHE-ECDSA-AES256-SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": "ECDHE-ECDSA-AES128-GCM-SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": "ECDHE-ECDSA-AES256-GCM-SHA384", - "TLS_DH_anon_WITH_AES_128_CBC_SHA256": "ADH-AES128-SHA256", - "TLS_DH_anon_WITH_AES_256_CBC_SHA256": "ADH-AES256-SHA256", - "TLS_DH_anon_WITH_AES_128_GCM_SHA256": "ADH-AES128-GCM-SHA256", - "TLS_DH_anon_WITH_AES_256_GCM_SHA384": "ADH-AES256-GCM-SHA384", - "RSA_WITH_AES_128_CCM": "AES128-CCM", - "RSA_WITH_AES_256_CCM": "AES256-CCM", - "DHE_RSA_WITH_AES_128_CCM": "DHE-RSA-AES128-CCM", - "DHE_RSA_WITH_AES_256_CCM": "DHE-RSA-AES256-CCM", - "RSA_WITH_AES_128_CCM_8": "AES128-CCM8", - "RSA_WITH_AES_256_CCM_8": "AES256-CCM8", - "DHE_RSA_WITH_AES_128_CCM_8": "DHE-RSA-AES128-CCM8", - "DHE_RSA_WITH_AES_256_CCM_8": "DHE-RSA-AES256-CCM8", - "ECDHE_ECDSA_WITH_AES_128_CCM": "ECDHE-ECDSA-AES128-CCM", - "ECDHE_ECDSA_WITH_AES_256_CCM": "ECDHE-ECDSA-AES256-CCM", - "ECDHE_ECDSA_WITH_AES_128_CCM_8": "ECDHE-ECDSA-AES128-CCM8", - "ECDHE_ECDSA_WITH_AES_256_CCM_8": "ECDHE-ECDSA-AES256-CCM8", - # ARIA cipher suites from RFC6209, extending TLS v1.2 - "TLS_RSA_WITH_ARIA_128_GCM_SHA256": "ARIA128-GCM-SHA256", - "TLS_RSA_WITH_ARIA_256_GCM_SHA384": "ARIA256-GCM-SHA384", - "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256": "DHE-RSA-ARIA128-GCM-SHA256", - "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384": "DHE-RSA-ARIA256-GCM-SHA384", - "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256": "DHE-DSS-ARIA128-GCM-SHA256", - "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384": "DHE-DSS-ARIA256-GCM-SHA384", - "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ECDSA-ARIA128-GCM-SHA256", - "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ECDSA-ARIA256-GCM-SHA384", - "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256": "ECDHE-ARIA128-GCM-SHA256", - "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384": "ECDHE-ARIA256-GCM-SHA384", - "TLS_PSK_WITH_ARIA_128_GCM_SHA256": "PSK-ARIA128-GCM-SHA256", - "TLS_PSK_WITH_ARIA_256_GCM_SHA384": "PSK-ARIA256-GCM-SHA384", - "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256": "DHE-PSK-ARIA128-GCM-SHA256", - "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384": "DHE-PSK-ARIA256-GCM-SHA384", - "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256": "RSA-PSK-ARIA128-GCM-SHA256", - "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384": "RSA-PSK-ARIA256-GCM-SHA384", - # Camellia HMAC-Based cipher suites from RFC6367, extending TLS v1.2 - "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-ECDSA-CAMELLIA128-SHA256", - "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-ECDSA-CAMELLIA256-SHA384", - "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-RSA-CAMELLIA128-SHA256", - "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-RSA-CAMELLIA256-SHA384", - # Pre-shared keying (PSK) cipher suites", - "PSK_WITH_NULL_SHA": "PSK-NULL-SHA", - "DHE_PSK_WITH_NULL_SHA": "DHE-PSK-NULL-SHA", - "RSA_PSK_WITH_NULL_SHA": "RSA-PSK-NULL-SHA", - "PSK_WITH_RC4_128_SHA": "PSK-RC4-SHA", - "PSK_WITH_3DES_EDE_CBC_SHA": "PSK-3DES-EDE-CBC-SHA", - "PSK_WITH_AES_128_CBC_SHA": "PSK-AES128-CBC-SHA", - "PSK_WITH_AES_256_CBC_SHA": "PSK-AES256-CBC-SHA", - "DHE_PSK_WITH_RC4_128_SHA": "DHE-PSK-RC4-SHA", - "DHE_PSK_WITH_3DES_EDE_CBC_SHA": "DHE-PSK-3DES-EDE-CBC-SHA", - "DHE_PSK_WITH_AES_128_CBC_SHA": "DHE-PSK-AES128-CBC-SHA", - "DHE_PSK_WITH_AES_256_CBC_SHA": "DHE-PSK-AES256-CBC-SHA", - "RSA_PSK_WITH_RC4_128_SHA": "RSA-PSK-RC4-SHA", - "RSA_PSK_WITH_3DES_EDE_CBC_SHA": "RSA-PSK-3DES-EDE-CBC-SHA", - "RSA_PSK_WITH_AES_128_CBC_SHA": "RSA-PSK-AES128-CBC-SHA", - "RSA_PSK_WITH_AES_256_CBC_SHA": "RSA-PSK-AES256-CBC-SHA", - "PSK_WITH_AES_128_GCM_SHA256": "PSK-AES128-GCM-SHA256", - "PSK_WITH_AES_256_GCM_SHA384": "PSK-AES256-GCM-SHA384", - "DHE_PSK_WITH_AES_128_GCM_SHA256": "DHE-PSK-AES128-GCM-SHA256", - "DHE_PSK_WITH_AES_256_GCM_SHA384": "DHE-PSK-AES256-GCM-SHA384", - "RSA_PSK_WITH_AES_128_GCM_SHA256": "RSA-PSK-AES128-GCM-SHA256", - "RSA_PSK_WITH_AES_256_GCM_SHA384": "RSA-PSK-AES256-GCM-SHA384", - "PSK_WITH_AES_128_CBC_SHA256": "PSK-AES128-CBC-SHA256", - "PSK_WITH_AES_256_CBC_SHA384": "PSK-AES256-CBC-SHA384", - "PSK_WITH_NULL_SHA256": "PSK-NULL-SHA256", - "PSK_WITH_NULL_SHA384": "PSK-NULL-SHA384", - "DHE_PSK_WITH_AES_128_CBC_SHA256": "DHE-PSK-AES128-CBC-SHA256", - "DHE_PSK_WITH_AES_256_CBC_SHA384": "DHE-PSK-AES256-CBC-SHA384", - "DHE_PSK_WITH_NULL_SHA256": "DHE-PSK-NULL-SHA256", - "DHE_PSK_WITH_NULL_SHA384": "DHE-PSK-NULL-SHA384", - "RSA_PSK_WITH_AES_128_CBC_SHA256": "RSA-PSK-AES128-CBC-SHA256", - "RSA_PSK_WITH_AES_256_CBC_SHA384": "RSA-PSK-AES256-CBC-SHA384", - "RSA_PSK_WITH_NULL_SHA256": "RSA-PSK-NULL-SHA256", - "RSA_PSK_WITH_NULL_SHA384": "RSA-PSK-NULL-SHA384", - "ECDHE_PSK_WITH_RC4_128_SHA": "ECDHE-PSK-RC4-SHA", - "ECDHE_PSK_WITH_3DES_EDE_CBC_SHA": "ECDHE-PSK-3DES-EDE-CBC-SHA", - "ECDHE_PSK_WITH_AES_128_CBC_SHA": "ECDHE-PSK-AES128-CBC-SHA", - "ECDHE_PSK_WITH_AES_256_CBC_SHA": "ECDHE-PSK-AES256-CBC-SHA", - "ECDHE_PSK_WITH_AES_128_CBC_SHA256": "ECDHE-PSK-AES128-CBC-SHA256", - "ECDHE_PSK_WITH_AES_256_CBC_SHA384": "ECDHE-PSK-AES256-CBC-SHA384", - "ECDHE_PSK_WITH_NULL_SHA": "ECDHE-PSK-NULL-SHA", - "ECDHE_PSK_WITH_NULL_SHA256": "ECDHE-PSK-NULL-SHA256", - "ECDHE_PSK_WITH_NULL_SHA384": "ECDHE-PSK-NULL-SHA384", - "PSK_WITH_CAMELLIA_128_CBC_SHA256": "PSK-CAMELLIA128-SHA256", - "PSK_WITH_CAMELLIA_256_CBC_SHA384": "PSK-CAMELLIA256-SHA384", - "DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "DHE-PSK-CAMELLIA128-SHA256", - "DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "DHE-PSK-CAMELLIA256-SHA384", - "RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256": "RSA-PSK-CAMELLIA128-SHA256", - "RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384": "RSA-PSK-CAMELLIA256-SHA384", - "ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256": "ECDHE-PSK-CAMELLIA128-SHA256", - "ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384": "ECDHE-PSK-CAMELLIA256-SHA384", - "PSK_WITH_AES_128_CCM": "PSK-AES128-CCM", - "PSK_WITH_AES_256_CCM": "PSK-AES256-CCM", - "DHE_PSK_WITH_AES_128_CCM": "DHE-PSK-AES128-CCM", - "DHE_PSK_WITH_AES_256_CCM": "DHE-PSK-AES256-CCM", - "PSK_WITH_AES_128_CCM_8": "PSK-AES128-CCM8", - "PSK_WITH_AES_256_CCM_8": "PSK-AES256-CCM8", - "DHE_PSK_WITH_AES_128_CCM_8": "DHE-PSK-AES128-CCM8", - "DHE_PSK_WITH_AES_256_CCM_8": "DHE-PSK-AES256-CCM8", - # ChaCha20-Poly1305 cipher suites, extending TLS v1.2 - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-RSA-CHACHA20-POLY1305", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-ECDSA-CHACHA20-POLY1305", - "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256": "DHE-RSA-CHACHA20-POLY1305", - "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256": "PSK-CHACHA20-POLY1305", - "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "ECDHE-PSK-CHACHA20-POLY1305", - "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256": "DHE-PSK-CHACHA20-POLY1305", - "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256": "RSA-PSK-CHACHA20-POLY1305", -} - -# TLS v1.3 cipher suites IANI to OpenSSL name translation -TLSV1_3_CIPHER_SUITES: Dict[str, str] = { - "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256", - "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384", - "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256", - "TLS_AES_128_CCM_SHA256": "TLS_AES_128_CCM_SHA256", - "TLS_AES_128_CCM_8_SHA256": "TLS_AES_128_CCM_8_SHA256", -} - -TLS_CIPHER_SUITES: Dict[str, Dict[str, str]] = { - "TLSv1.2": TLSV1_2_CIPHER_SUITES, - "TLSv1.3": TLSV1_3_CIPHER_SUITES, -} - -OPENSSL_CS_NAMES: Dict[str, ValuesView[str]] = { - "TLSv1.2": TLSV1_2_CIPHER_SUITES.values(), - "TLSv1.3": TLSV1_3_CIPHER_SUITES.values(), -} - -NATIVE_SUPPORTED_CONVERSION_TYPES = { - int: "int", - float: "float", - str: "str", - bytes: "bytes", - bytearray: "bytearray", - bool: "bool", - type(None): "nonetype", - datetime: "datetime", - date: "date", - time: "time", - struct_time: "struct_time", - timedelta: "timedelta", - Decimal: "decimal", -} -"""Dictionary of the supported data types of the default MySQLConverter class -and the corresponding tag names used to map against their respective methods -'_{tag_name}_to_mysql(...)'. These converter methods are then used to convert -the matched data type to mysql recognizable type.""" - - -def flag_is_set(flag: int, flags: int) -> bool: - """Checks if the flag is set - - Returns boolean""" - if (flags & flag) > 0: - return True - return False - - -def _obsolete_option(name: str, new_name: str, value: int) -> int: - """Raise a deprecation warning and advise a new option name. - - Args: - name (str): The name of the option. - new_name (str): The new option name. - value (int): The value of the option. - - Returns: - int: The value of the option. - """ - warnings.warn( - f"The option '{name}' has been deprecated, use '{new_name}' instead.", - category=DeprecationWarning, - ) - return value - - -class _Constants(ABC): - """Base class for constants.""" - - prefix: str = "" - desc: Dict[str, Tuple[int, str]] = {} - - @classmethod - def get_desc(cls, name: str) -> Optional[str]: - """Get description of given constant""" - try: - return cls.desc[name][1] - except (IndexError, KeyError): - return None - - @classmethod - def get_info(cls, setid: int) -> Union[Optional[str], Tuple[str, str]]: - """Get information about given constant""" - for name, info in cls.desc.items(): - if info[0] == setid: - return name - return None - - @classmethod - def get_full_info(cls) -> Union[str, Sequence[str]]: - """get full information about given constant""" - res: Union[str, List[str]] = [] - try: - res = [f"{k} : {v[1]}" for k, v in cls.desc.items()] - except (AttributeError, IndexError) as err: - res = f"No information found in constant class. {err}" - - return res - - -class _Flags(_Constants): - """Base class for classes describing flags""" - - @classmethod - def get_bit_info(cls, value: int) -> List[str]: - """Get the name of all bits set - - Returns a list of strings.""" - res = [] - for name, info in cls.desc.items(): - if value & info[0]: - res.append(name) - return res - - -class FieldType(_Constants): - """MySQL Field Types. - - This class provides all supported MySQL field or data types. They can be useful - when dealing with raw data or defining your own converters. The field type is - stored with every cursor in the description for each column. - - The `FieldType` class shouldn't be instantiated. - - Examples: - The following example shows how to print the name of the data type for - each column in a result set. - - ``` - from __future__ import print_function - import mysql.connector - from mysql.connector import FieldType - - cnx = mysql.connector.connect(user='scott', database='test') - cursor = cnx.cursor() - - cursor.execute( - "SELECT DATE(NOW()) AS `c1`, TIME(NOW()) AS `c2`, " - "NOW() AS `c3`, 'a string' AS `c4`, 42 AS `c5`") - rows = cursor.fetchall() - - for desc in cursor.description: - colname = desc[0] - coltype = desc[1] - print("Column {} has type {}".format( - colname, FieldType.get_info(coltype))) - - cursor.close() - cnx.close() - ``` - """ - - prefix: str = "FIELD_TYPE_" - DECIMAL: int = 0x00 - TINY: int = 0x01 - SHORT: int = 0x02 - LONG: int = 0x03 - FLOAT: int = 0x04 - DOUBLE: int = 0x05 - NULL: int = 0x06 - TIMESTAMP: int = 0x07 - LONGLONG: int = 0x08 - INT24: int = 0x09 - DATE: int = 0x0A - TIME: int = 0x0B - DATETIME: int = 0x0C - YEAR: int = 0x0D - NEWDATE: int = 0x0E - VARCHAR: int = 0x0F - BIT: int = 0x10 - VECTOR: int = 0xF2 - JSON: int = 0xF5 - NEWDECIMAL: int = 0xF6 - ENUM: int = 0xF7 - SET: int = 0xF8 - TINY_BLOB: int = 0xF9 - MEDIUM_BLOB: int = 0xFA - LONG_BLOB: int = 0xFB - BLOB: int = 0xFC - VAR_STRING: int = 0xFD - STRING: int = 0xFE - GEOMETRY: int = 0xFF - - desc: Dict[str, Tuple[int, str]] = { - "DECIMAL": (DECIMAL, "DECIMAL"), - "TINY": (TINY, "TINY"), - "SHORT": (SHORT, "SHORT"), - "LONG": (LONG, "LONG"), - "FLOAT": (FLOAT, "FLOAT"), - "DOUBLE": (DOUBLE, "DOUBLE"), - "NULL": (NULL, "NULL"), - "TIMESTAMP": (TIMESTAMP, "TIMESTAMP"), - "LONGLONG": (LONGLONG, "LONGLONG"), - "INT24": (INT24, "INT24"), - "DATE": (DATE, "DATE"), - "TIME": (TIME, "TIME"), - "DATETIME": (DATETIME, "DATETIME"), - "YEAR": (YEAR, "YEAR"), - "NEWDATE": (NEWDATE, "NEWDATE"), - "VARCHAR": (VARCHAR, "VARCHAR"), - "BIT": (BIT, "BIT"), - "VECTOR": (VECTOR, "VECTOR"), - "JSON": (JSON, "JSON"), - "NEWDECIMAL": (NEWDECIMAL, "NEWDECIMAL"), - "ENUM": (ENUM, "ENUM"), - "SET": (SET, "SET"), - "TINY_BLOB": (TINY_BLOB, "TINY_BLOB"), - "MEDIUM_BLOB": (MEDIUM_BLOB, "MEDIUM_BLOB"), - "LONG_BLOB": (LONG_BLOB, "LONG_BLOB"), - "BLOB": (BLOB, "BLOB"), - "VAR_STRING": (VAR_STRING, "VAR_STRING"), - "STRING": (STRING, "STRING"), - "GEOMETRY": (GEOMETRY, "GEOMETRY"), - } - - @classmethod - def get_string_types(cls) -> List[int]: - """Get the list of all string types""" - return [ - cls.VARCHAR, - cls.ENUM, - cls.VAR_STRING, - cls.STRING, - ] - - @classmethod - def get_binary_types(cls) -> List[int]: - """Get the list of all binary types""" - return [ - cls.TINY_BLOB, - cls.MEDIUM_BLOB, - cls.LONG_BLOB, - cls.BLOB, - ] - - @classmethod - def get_number_types(cls) -> List[int]: - """Get the list of all number types""" - return [ - cls.DECIMAL, - cls.NEWDECIMAL, - cls.TINY, - cls.SHORT, - cls.LONG, - cls.FLOAT, - cls.DOUBLE, - cls.LONGLONG, - cls.INT24, - cls.BIT, - cls.YEAR, - ] - - @classmethod - def get_timestamp_types(cls) -> List[int]: - """Get the list of all timestamp types""" - return [ - cls.DATETIME, - cls.TIMESTAMP, - ] - - -class FieldFlag(_Flags): - """MySQL Field Flags - - Field flags as found in MySQL sources mysql-src/include/mysql_com.h - """ - - _prefix: str = "" - NOT_NULL: int = 1 << 0 - PRI_KEY: int = 1 << 1 - UNIQUE_KEY: int = 1 << 2 - MULTIPLE_KEY: int = 1 << 3 - BLOB: int = 1 << 4 - UNSIGNED: int = 1 << 5 - ZEROFILL: int = 1 << 6 - BINARY: int = 1 << 7 - - ENUM: int = 1 << 8 - AUTO_INCREMENT: int = 1 << 9 - TIMESTAMP: int = 1 << 10 - SET: int = 1 << 11 - - NO_DEFAULT_VALUE: int = 1 << 12 - ON_UPDATE_NOW: int = 1 << 13 - NUM: int = 1 << 14 - PART_KEY: int = 1 << 15 - GROUP: int = 1 << 14 # SAME AS NUM !!!!!!!???? - UNIQUE: int = 1 << 16 - BINCMP: int = 1 << 17 - - GET_FIXED_FIELDS: int = 1 << 18 - FIELD_IN_PART_FUNC: int = 1 << 19 - FIELD_IN_ADD_INDEX: int = 1 << 20 - FIELD_IS_RENAMED: int = 1 << 21 - - desc: Dict[str, Tuple[int, str]] = { - "NOT_NULL": (1 << 0, "Field can't be NULL"), - "PRI_KEY": (1 << 1, "Field is part of a primary key"), - "UNIQUE_KEY": (1 << 2, "Field is part of a unique key"), - "MULTIPLE_KEY": (1 << 3, "Field is part of a key"), - "BLOB": (1 << 4, "Field is a blob"), - "UNSIGNED": (1 << 5, "Field is unsigned"), - "ZEROFILL": (1 << 6, "Field is zerofill"), - "BINARY": (1 << 7, "Field is binary "), - "ENUM": (1 << 8, "field is an enum"), - "AUTO_INCREMENT": (1 << 9, "field is a autoincrement field"), - "TIMESTAMP": (1 << 10, "Field is a timestamp"), - "SET": (1 << 11, "field is a set"), - "NO_DEFAULT_VALUE": (1 << 12, "Field doesn't have default value"), - "ON_UPDATE_NOW": (1 << 13, "Field is set to NOW on UPDATE"), - "NUM": (1 << 14, "Field is num (for clients)"), - "PART_KEY": (1 << 15, "Intern; Part of some key"), - "GROUP": (1 << 14, "Intern: Group field"), # Same as NUM - "UNIQUE": (1 << 16, "Intern: Used by sql_yacc"), - "BINCMP": (1 << 17, "Intern: Used by sql_yacc"), - "GET_FIXED_FIELDS": (1 << 18, "Used to get fields in item tree"), - "FIELD_IN_PART_FUNC": (1 << 19, "Field part of partition func"), - "FIELD_IN_ADD_INDEX": (1 << 20, "Intern: Field used in ADD INDEX"), - "FIELD_IS_RENAMED": (1 << 21, "Intern: Field is being renamed"), - } - - -class ServerCmdMeta(ABCMeta): - """ClientFlag Metaclass.""" - - def __getattribute__(cls, name: str) -> Any: - deprecated_options = ( - "FIELD_LIST", - "REFRESH", - "SHUTDOWN", - "PROCESS_INFO", - "PROCESS_KILL", - ) - if name in deprecated_options: - warnings.warn( - f"The option 'ServerCmd.{name}' is deprecated and will be removed in " - "a future release.", - category=DeprecationWarning, - ) - return super().__getattribute__(name) - - -class ServerCmd(_Constants, metaclass=ServerCmdMeta): - """MySQL Server Commands""" - - _prefix: str = "COM_" - SLEEP: int = 0 - QUIT: int = 1 - INIT_DB: int = 2 - QUERY: int = 3 - FIELD_LIST: int = 4 - CREATE_DB: int = 5 - DROP_DB: int = 6 - REFRESH: int = 7 - SHUTDOWN: int = 8 - STATISTICS: int = 9 - PROCESS_INFO: int = 10 - CONNECT: int = 11 - PROCESS_KILL: int = 12 - DEBUG: int = 13 - PING: int = 14 - TIME: int = 15 - DELAYED_INSERT: int = 16 - CHANGE_USER: int = 17 - BINLOG_DUMP: int = 18 - TABLE_DUMP: int = 19 - CONNECT_OUT: int = 20 - REGISTER_REPLICA: int = 21 - STMT_PREPARE: int = 22 - STMT_EXECUTE: int = 23 - STMT_SEND_LONG_DATA: int = 24 - STMT_CLOSE: int = 25 - STMT_RESET: int = 26 - SET_OPTION: int = 27 - STMT_FETCH: int = 28 - DAEMON: int = 29 - BINLOG_DUMP_GTID: int = 30 - RESET_CONNECTION: int = 31 - - desc: Dict[str, Tuple[int, str]] = { - "SLEEP": (0, "SLEEP"), - "QUIT": (1, "QUIT"), - "INIT_DB": (2, "INIT_DB"), - "QUERY": (3, "QUERY"), - "FIELD_LIST": (4, "FIELD_LIST"), - "CREATE_DB": (5, "CREATE_DB"), - "DROP_DB": (6, "DROP_DB"), - "REFRESH": (7, "REFRESH"), - "SHUTDOWN": (8, "SHUTDOWN"), - "STATISTICS": (9, "STATISTICS"), - "PROCESS_INFO": (10, "PROCESS_INFO"), - "CONNECT": (11, "CONNECT"), - "PROCESS_KILL": (12, "PROCESS_KILL"), - "DEBUG": (13, "DEBUG"), - "PING": (14, "PING"), - "TIME": (15, "TIME"), - "DELAYED_INSERT": (16, "DELAYED_INSERT"), - "CHANGE_USER": (17, "CHANGE_USER"), - "BINLOG_DUMP": (18, "BINLOG_DUMP"), - "TABLE_DUMP": (19, "TABLE_DUMP"), - "CONNECT_OUT": (20, "CONNECT_OUT"), - "REGISTER_REPLICA": (21, "REGISTER_REPLICA"), - "STMT_PREPARE": (22, "STMT_PREPARE"), - "STMT_EXECUTE": (23, "STMT_EXECUTE"), - "STMT_SEND_LONG_DATA": (24, "STMT_SEND_LONG_DATA"), - "STMT_CLOSE": (25, "STMT_CLOSE"), - "STMT_RESET": (26, "STMT_RESET"), - "SET_OPTION": (27, "SET_OPTION"), - "STMT_FETCH": (28, "STMT_FETCH"), - "DAEMON": (29, "DAEMON"), - "BINLOG_DUMP_GTID": (30, "BINLOG_DUMP_GTID"), - "RESET_CONNECTION": (31, "RESET_CONNECTION"), - } - - -class ClientFlag(_Flags): - """MySQL Client Flags. - - Client options as found in the MySQL sources mysql-src/include/mysql_com.h. - - This class provides constants defining MySQL client flags that can be used - when the connection is established to configure the session. The `ClientFlag` - class is available when importing mysql.connector. - - The `ClientFlag` class shouldn't be instantiated. - - Examples: - ``` - >>> import mysql.connector - >>> mysql.connector.ClientFlag.FOUND_ROWS - 2 - ``` - """ - - LONG_PASSWD: int = 1 << 0 - FOUND_ROWS: int = 1 << 1 - LONG_FLAG: int = 1 << 2 - CONNECT_WITH_DB: int = 1 << 3 - NO_SCHEMA: int = 1 << 4 - COMPRESS: int = 1 << 5 - ODBC: int = 1 << 6 - LOCAL_FILES: int = 1 << 7 - IGNORE_SPACE: int = 1 << 8 - PROTOCOL_41: int = 1 << 9 - INTERACTIVE: int = 1 << 10 - SSL: int = 1 << 11 - IGNORE_SIGPIPE: int = 1 << 12 - TRANSACTIONS: int = 1 << 13 - RESERVED: int = 1 << 14 - SECURE_CONNECTION: int = 1 << 15 - MULTI_STATEMENTS: int = 1 << 16 - MULTI_RESULTS: int = 1 << 17 - PS_MULTI_RESULTS: int = 1 << 18 - PLUGIN_AUTH: int = 1 << 19 - CONNECT_ARGS: int = 1 << 20 - PLUGIN_AUTH_LENENC_CLIENT_DATA: int = 1 << 21 - CAN_HANDLE_EXPIRED_PASSWORDS: int = 1 << 22 - SESION_TRACK: int = 1 << 23 # deprecated - SESSION_TRACK: int = 1 << 23 - DEPRECATE_EOF: int = 1 << 24 - CLIENT_QUERY_ATTRIBUTES: int = 1 << 27 - SSL_VERIFY_SERVER_CERT: int = 1 << 30 - REMEMBER_OPTIONS: int = 1 << 31 - MULTI_FACTOR_AUTHENTICATION: int = 1 << 28 - - desc: Dict[str, Tuple[int, str]] = { - "LONG_PASSWD": (1 << 0, "New more secure passwords"), - "FOUND_ROWS": (1 << 1, "Found instead of affected rows"), - "LONG_FLAG": (1 << 2, "Get all column flags"), - "CONNECT_WITH_DB": (1 << 3, "One can specify db on connect"), - "NO_SCHEMA": (1 << 4, "Don't allow database.table.column"), - "COMPRESS": (1 << 5, "Can use compression protocol"), - "ODBC": (1 << 6, "ODBC client"), - "LOCAL_FILES": (1 << 7, "Can use LOAD DATA LOCAL"), - "IGNORE_SPACE": (1 << 8, "Ignore spaces before ''"), - "PROTOCOL_41": (1 << 9, "New 4.1 protocol"), - "INTERACTIVE": (1 << 10, "This is an interactive client"), - "SSL": (1 << 11, "Switch to SSL after handshake"), - "IGNORE_SIGPIPE": (1 << 12, "IGNORE sigpipes"), - "TRANSACTIONS": (1 << 13, "Client knows about transactions"), - "RESERVED": (1 << 14, "Old flag for 4.1 protocol"), - "SECURE_CONNECTION": (1 << 15, "New 4.1 authentication"), - "MULTI_STATEMENTS": (1 << 16, "Enable/disable multi-stmt support"), - "MULTI_RESULTS": (1 << 17, "Enable/disable multi-results"), - "PS_MULTI_RESULTS": (1 << 18, "Multi-results in PS-protocol"), - "PLUGIN_AUTH": (1 << 19, "Client supports plugin authentication"), - "CONNECT_ARGS": (1 << 20, "Client supports connection attributes"), - "PLUGIN_AUTH_LENENC_CLIENT_DATA": ( - 1 << 21, - "Enable authentication response packet to be larger than 255 bytes", - ), - "CAN_HANDLE_EXPIRED_PASSWORDS": ( - 1 << 22, - "Don't close the connection for a connection with expired password", - ), - "SESION_TRACK": ( # deprecated - 1 << 23, - "Capable of handling server state change information", - ), - "SESSION_TRACK": ( - 1 << 23, - "Capable of handling server state change information", - ), - "DEPRECATE_EOF": (1 << 24, "Client no longer needs EOF packet"), - "CLIENT_QUERY_ATTRIBUTES": ( - 1 << 27, - "Support optional extension for query parameters", - ), - "SSL_VERIFY_SERVER_CERT": (1 << 30, ""), - "REMEMBER_OPTIONS": (1 << 31, ""), - } - - default: List[int] = [ - LONG_PASSWD, - LONG_FLAG, - CONNECT_WITH_DB, - PROTOCOL_41, - TRANSACTIONS, - SECURE_CONNECTION, - MULTI_STATEMENTS, - MULTI_RESULTS, - CONNECT_ARGS, - PLUGIN_AUTH_LENENC_CLIENT_DATA, - ] - - @classmethod - def get_default(cls) -> int: - """Get the default client options set - - Returns a flag with all the default client options set""" - flags = 0 - for option in cls.default: - flags |= option - return flags - - -class ServerFlag(_Flags): - """MySQL Server Flags - - Server flags as found in the MySQL sources mysql-src/include/mysql_com.h - """ - - _prefix: str = "SERVER_" - STATUS_IN_TRANS: int = 1 << 0 - STATUS_AUTOCOMMIT: int = 1 << 1 - MORE_RESULTS_EXISTS: int = 1 << 3 - QUERY_NO_GOOD_INDEX_USED: int = 1 << 4 - QUERY_NO_INDEX_USED: int = 1 << 5 - STATUS_CURSOR_EXISTS: int = 1 << 6 - STATUS_LAST_ROW_SENT: int = 1 << 7 - STATUS_DB_DROPPED: int = 1 << 8 - STATUS_NO_BACKSLASH_ESCAPES: int = 1 << 9 - SERVER_STATUS_METADATA_CHANGED: int = 1 << 10 - SERVER_QUERY_WAS_SLOW: int = 1 << 11 - SERVER_PS_OUT_PARAMS: int = 1 << 12 - SERVER_STATUS_IN_TRANS_READONLY: int = 1 << 13 - SERVER_SESSION_STATE_CHANGED: int = 1 << 14 - - desc: Dict[str, Tuple[int, str]] = { - "SERVER_STATUS_IN_TRANS": (1 << 0, "Transaction has started"), - "SERVER_STATUS_AUTOCOMMIT": (1 << 1, "Server in auto_commit mode"), - "SERVER_MORE_RESULTS_EXISTS": ( - 1 << 3, - "Multi query - next query exists", - ), - "SERVER_QUERY_NO_GOOD_INDEX_USED": (1 << 4, ""), - "SERVER_QUERY_NO_INDEX_USED": (1 << 5, ""), - "SERVER_STATUS_CURSOR_EXISTS": ( - 1 << 6, - "Set when server opened a read-only non-scrollable cursor for a query.", - ), - "SERVER_STATUS_LAST_ROW_SENT": ( - 1 << 7, - "Set when a read-only cursor is exhausted", - ), - "SERVER_STATUS_DB_DROPPED": (1 << 8, "A database was dropped"), - "SERVER_STATUS_NO_BACKSLASH_ESCAPES": (1 << 9, ""), - "SERVER_STATUS_METADATA_CHANGED": ( - 1024, - "Set if after a prepared statement " - "reprepare we discovered that the " - "new statement returns a different " - "number of result set columns.", - ), - "SERVER_QUERY_WAS_SLOW": (2048, ""), - "SERVER_PS_OUT_PARAMS": ( - 4096, - "To mark ResultSet containing output parameter values.", - ), - "SERVER_STATUS_IN_TRANS_READONLY": ( - 8192, - "Set if multi-statement transaction is a read-only transaction.", - ), - "SERVER_SESSION_STATE_CHANGED": ( - 1 << 14, - "Session state has changed on the " - "server because of the execution of " - "the last statement", - ), - } - - -class RefreshOptionMeta(ABCMeta): - """RefreshOption Metaclass.""" - - @property - def SLAVE(self) -> int: # pylint: disable=bad-mcs-method-argument,invalid-name - """Return the deprecated alias of RefreshOption.REPLICA. - - Raises a warning about this attribute deprecation. - """ - return _obsolete_option( - "RefreshOption.SLAVE", - "RefreshOption.REPLICA", - RefreshOption.REPLICA, - ) - - -class RefreshOption(_Constants, metaclass=RefreshOptionMeta): - """MySQL Refresh command options. - - Options used when sending the COM_REFRESH server command. - """ - - _prefix: str = "REFRESH_" - GRANT: int = 1 << 0 - LOG: int = 1 << 1 - TABLES: int = 1 << 2 - HOST: int = 1 << 3 - STATUS: int = 1 << 4 - REPLICA: int = 1 << 6 - - desc: Dict[str, Tuple[int, str]] = { - "GRANT": (1 << 0, "Refresh grant tables"), - "LOG": (1 << 1, "Start on new log file"), - "TABLES": (1 << 2, "close all tables"), - "HOST": (1 << 3, "Flush host cache"), - "STATUS": (1 << 4, "Flush status variables"), - "REPLICA": (1 << 6, "Reset source info and restart replica thread"), - "SLAVE": (1 << 6, "Deprecated option; use REPLICA instead."), - } - - -class ShutdownType(_Constants): - """MySQL Shutdown types - - Shutdown types used by the COM_SHUTDOWN server command. - """ - - _prefix: str = "" - SHUTDOWN_DEFAULT: int = 0 - SHUTDOWN_WAIT_CONNECTIONS: int = 1 - SHUTDOWN_WAIT_TRANSACTIONS: int = 2 - SHUTDOWN_WAIT_UPDATES: int = 8 - SHUTDOWN_WAIT_ALL_BUFFERS: int = 16 - SHUTDOWN_WAIT_CRITICAL_BUFFERS: int = 17 - KILL_QUERY: int = 254 - KILL_CONNECTION: int = 255 - - desc: Dict[str, Tuple[int, str]] = { - "SHUTDOWN_DEFAULT": ( - SHUTDOWN_DEFAULT, - "defaults to SHUTDOWN_WAIT_ALL_BUFFERS", - ), - "SHUTDOWN_WAIT_CONNECTIONS": ( - SHUTDOWN_WAIT_CONNECTIONS, - "wait for existing connections to finish", - ), - "SHUTDOWN_WAIT_TRANSACTIONS": ( - SHUTDOWN_WAIT_TRANSACTIONS, - "wait for existing trans to finish", - ), - "SHUTDOWN_WAIT_UPDATES": ( - SHUTDOWN_WAIT_UPDATES, - "wait for existing updates to finish", - ), - "SHUTDOWN_WAIT_ALL_BUFFERS": ( - SHUTDOWN_WAIT_ALL_BUFFERS, - "flush InnoDB and other storage engine buffers", - ), - "SHUTDOWN_WAIT_CRITICAL_BUFFERS": ( - SHUTDOWN_WAIT_CRITICAL_BUFFERS, - "don't flush InnoDB buffers, flush other storage engines' buffers", - ), - "KILL_QUERY": (KILL_QUERY, "(no description)"), - "KILL_CONNECTION": (KILL_CONNECTION, "(no description)"), - } - - -class CharacterSet: - """MySQL supported character sets and collations - - List of character sets with their collations supported by MySQL. This - maps to the character set we get from the server within the handshake - packet. - - The list is hardcode so we avoid a database query when getting the - name of the used character set or collation. - """ - - # Multi-byte character sets which use 5c (backslash) in characters - slash_charsets: Tuple[int, ...] = (1, 13, 28, 84, 87, 88) - - def __init__(self) -> None: - # Use LTS character set as default - self._desc: List[Optional[Tuple[str, str, bool]]] = MYSQL_CHARACTER_SETS_57 - self._mysql_version: Tuple[int, ...] = (5, 7) - - def set_mysql_version(self, version: Tuple[int, ...]) -> None: - """Set the MySQL major version and change the charset mapping if is 5.7. - - Args: - version (tuple): MySQL version tuple. - """ - self._mysql_version = version[:2] - if self._mysql_version >= (8, 0): - self._desc = MYSQL_CHARACTER_SETS - - def get_info(self, setid: int) -> Tuple[str, str]: - """Retrieves character set information as tuple using an ID - - Retrieves character set and collation information based on the - given MySQL ID. - - Raises ProgrammingError when character set is not supported. - - Returns a tuple. - """ - try: - return self._desc[setid][0:2] - except IndexError: - raise ProgrammingError(f"Character set '{setid}' unsupported") from None - - def get_desc(self, name: int) -> str: - """Retrieves character set information as string using an ID - - Retrieves character set and collation information based on the - given MySQL ID. - - Returns a tuple. - """ - charset, collation = self.get_info(name) - return f"{charset}/{collation}" - - def get_default_collation(self, charset: Union[int, str]) -> Tuple[str, str, int]: - """Retrieves the default collation for given character set - - Raises ProgrammingError when character set is not supported. - - Returns list (collation, charset, index) - """ - if isinstance(charset, int): - try: - info = self._desc[charset] - return info[1], info[0], charset - except (IndexError, KeyError) as err: - raise ProgrammingError( - f"Character set ID '{charset}' unsupported" - ) from err - - for cid, info in enumerate(self._desc): - if info is None: - continue - if info[0] == charset and info[2] is True: - return info[1], info[0], cid - - raise ProgrammingError(f"Character set '{charset}' unsupported") - - def get_charset_info( - self, charset: Optional[Union[int, str]] = None, collation: Optional[str] = None - ) -> Tuple[int, str, str]: - """Get character set information using charset name and/or collation - - Retrieves character set and collation information given character - set name and/or a collation name. - If charset is an integer, it will look up the character set based - on the MySQL's ID. - For example: - get_charset_info('utf8',None) - get_charset_info(collation='utf8_general_ci') - get_charset_info(47) - - Raises ProgrammingError when character set is not supported. - - Returns a tuple with (id, characterset name, collation) - """ - info: Optional[Union[Tuple[str, str, bool], Tuple[str, str, int]]] = None - if isinstance(charset, int): - try: - info = self._desc[charset] - return (charset, info[0], info[1]) - except IndexError as err: - raise ProgrammingError(f"Character set ID {charset} unknown") from err - - if charset in ("utf8", "utf-8") and self._mysql_version >= (8, 0): - charset = "utf8mb4" - if charset is not None and collation is None: - info = self.get_default_collation(charset) - return (info[2], info[1], info[0]) - if charset is None and collation is not None: - for cid, info in enumerate(self._desc): - if info is None: - continue - if collation == info[1]: - return (cid, info[0], info[1]) - raise ProgrammingError(f"Collation '{collation}' unknown") - for cid, info in enumerate(self._desc): - if info is None: - continue - if info[0] == charset and info[1] == collation: - return (cid, info[0], info[1]) - _ = self.get_default_collation(charset) - raise ProgrammingError(f"Collation '{collation}' unknown") - - def get_supported(self) -> Tuple[str, ...]: - """Retrieves a list with names of all supproted character sets - - Returns a tuple. - """ - res = [] - for info in self._desc: - if info and info[0] not in res: - res.append(info[0]) - return tuple(res) - - -class SQLMode(_Constants): - """MySQL SQL Modes - - The numeric values of SQL Modes are not interesting, only the names - are used when setting the SQL_MODE system variable using the MySQL - SET command. - - The `SQLMode` class shouldn't be instantiated. - - See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html - """ - - _prefix: str = "MODE_" - REAL_AS_FLOAT: str = "REAL_AS_FLOAT" - PIPES_AS_CONCAT: str = "PIPES_AS_CONCAT" - ANSI_QUOTES: str = "ANSI_QUOTES" - IGNORE_SPACE: str = "IGNORE_SPACE" - NOT_USED: str = "NOT_USED" - ONLY_FULL_GROUP_BY: str = "ONLY_FULL_GROUP_BY" - NO_UNSIGNED_SUBTRACTION: str = "NO_UNSIGNED_SUBTRACTION" - NO_DIR_IN_CREATE: str = "NO_DIR_IN_CREATE" - POSTGRESQL: str = "POSTGRESQL" - ORACLE: str = "ORACLE" - MSSQL: str = "MSSQL" - DB2: str = "DB2" - MAXDB: str = "MAXDB" - NO_KEY_OPTIONS: str = "NO_KEY_OPTIONS" - NO_TABLE_OPTIONS: str = "NO_TABLE_OPTIONS" - NO_FIELD_OPTIONS: str = "NO_FIELD_OPTIONS" - MYSQL323: str = "MYSQL323" - MYSQL40: str = "MYSQL40" - ANSI: str = "ANSI" - NO_AUTO_VALUE_ON_ZERO: str = "NO_AUTO_VALUE_ON_ZERO" - NO_BACKSLASH_ESCAPES: str = "NO_BACKSLASH_ESCAPES" - STRICT_TRANS_TABLES: str = "STRICT_TRANS_TABLES" - STRICT_ALL_TABLES: str = "STRICT_ALL_TABLES" - NO_ZERO_IN_DATE: str = "NO_ZERO_IN_DATE" - NO_ZERO_DATE: str = "NO_ZERO_DATE" - INVALID_DATES: str = "INVALID_DATES" - ERROR_FOR_DIVISION_BY_ZERO: str = "ERROR_FOR_DIVISION_BY_ZERO" - TRADITIONAL: str = "TRADITIONAL" - NO_AUTO_CREATE_USER: str = "NO_AUTO_CREATE_USER" - HIGH_NOT_PRECEDENCE: str = "HIGH_NOT_PRECEDENCE" - NO_ENGINE_SUBSTITUTION: str = "NO_ENGINE_SUBSTITUTION" - PAD_CHAR_TO_FULL_LENGTH: str = "PAD_CHAR_TO_FULL_LENGTH" - - @classmethod - def get_desc(cls, name: str) -> Optional[str]: - raise NotImplementedError - - @classmethod - def get_info(cls, setid: int) -> Optional[str]: - raise NotImplementedError - - @classmethod - def get_full_info(cls) -> Tuple[str, ...]: - """Returns a sequence of all available SQL Modes - - This class method returns a tuple containing all SQL Mode names. The - names will be alphabetically sorted. - - Returns a tuple. - """ - res = [] - for key in vars(cls).keys(): - if not key.startswith("_") and not hasattr(getattr(cls, key), "__call__"): - res.append(key) - return tuple(sorted(res)) diff --git a/server/venv/Lib/site-packages/mysql/connector/conversion.py b/server/venv/Lib/site-packages/mysql/connector/conversion.py deleted file mode 100644 index 0e92a71..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/conversion.py +++ /dev/null @@ -1,789 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Converting MySQL and Python types""" - -import array -import datetime -import math -import struct -import time - -from decimal import Decimal -from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union - -from .constants import ( - MYSQL_VECTOR_TYPE_CODE, - NATIVE_SUPPORTED_CONVERSION_TYPES, - CharacterSet, - FieldFlag, - FieldType, - SQLMode, -) -from .custom_types import HexLiteral -from .types import ( - DescriptionType, - MySQLConvertibleType, - MySQLProducedType, - PythonProducedType, - StrOrBytes, -) -from .utils import NUMERIC_TYPES - -CONVERT_ERROR = "Could not convert '{value}' to python {pytype}" - - -class MySQLConverterBase: - """Base class for conversion classes - - All class dealing with converting to and from MySQL data types must - be a subclass of this class. - """ - - def __init__( - self, - charset: Optional[str] = "utf8", - use_unicode: bool = True, - str_fallback: bool = False, - ) -> None: - self._character_set: CharacterSet = CharacterSet() - self.python_types: Optional[Tuple] = None - self.mysql_types: Optional[Tuple] = None - self.charset: Optional[str] = None - self.charset_id: int = 0 - self.set_charset(charset) - self.use_unicode: bool = use_unicode - self.str_fallback: bool = str_fallback - self._cache_field_types: Dict[ - int, - Callable[[bytes, DescriptionType], PythonProducedType], - ] = {} - - def set_charset( - self, charset: Optional[str], character_set: Optional[CharacterSet] = None - ) -> None: - """Set character set""" - if charset in ("utf8mb4", "utf8mb3"): - charset = "utf8" - if charset is not None: - self.charset = charset - else: - # default to utf8 - self.charset = "utf8" - - if character_set: - self._character_set = character_set - - self.charset_id = self._character_set.get_charset_info(self.charset)[0] - - def set_unicode(self, value: bool = True) -> None: - """Set whether to use Unicode""" - self.use_unicode = value - - def to_mysql( - self, value: MySQLConvertibleType - ) -> Union[MySQLConvertibleType, HexLiteral]: - """Convert Python data type to MySQL""" - type_name = value.__class__.__name__.lower() - try: - converted: MySQLConvertibleType = getattr(self, f"_{type_name}_to_mysql")( - value - ) - return converted - except AttributeError: - return value - - def to_python( - self, vtype: DescriptionType, value: Optional[bytes] - ) -> PythonProducedType: - """Convert MySQL data type to Python""" - - if (value == b"\x00" or value is None) and vtype[1] != FieldType.BIT: - # Don't go further when we hit a NULL value - return None - - if not self._cache_field_types: - self._cache_field_types = {} - for name, info in FieldType.desc.items(): - try: - self._cache_field_types[info[0]] = getattr( - self, f"_{name.lower()}_to_python" - ) - except AttributeError: - # We ignore field types which has no method - pass - if value is None: - return None - try: - return self._cache_field_types[vtype[1]](value, vtype) - except KeyError: - return value - - @staticmethod - def escape( - value: Any, - sql_mode: Optional[Union[str, bytes]] = None, # pylint: disable=unused-argument - ) -> Any: - """Escape buffer for sending to MySQL""" - return value - - @staticmethod - def quote(buf: Any) -> StrOrBytes: - """Quote buffer for sending to MySQL""" - return str(buf) - - -class MySQLConverter(MySQLConverterBase): - """Default conversion class for MySQL Connector/Python. - - o escape method: for escaping values send to MySQL - o quoting method: for quoting values send to MySQL in statements - o conversion mapping: maps Python and MySQL data types to - function for converting them. - - Whenever one needs to convert values differently, a converter_class - argument can be given while instantiating a new connection like - cnx.connect(converter_class=CustomMySQLConverterClass). - - """ - - def __init__( - self, - charset: Optional[str] = None, - use_unicode: bool = True, - str_fallback: bool = False, - ) -> None: - super().__init__(charset, use_unicode, str_fallback) - self._cache_field_types: Dict[ - int, - Callable[[bytes, DescriptionType], PythonProducedType], - ] = {} - - @staticmethod - def escape(value: Any, sql_mode: Optional[Union[str, bytes]] = None) -> Any: - """ - Escapes special characters as they are expected to by when MySQL - receives them. - As found in MySQL source mysys/charset.c - - Returns the value if not a string, or the escaped string. - """ - if isinstance(sql_mode, bytes): - # sql_mode is returned as bytes if use_unicode is set to False during connect() - sql_mode = sql_mode.decode() - if isinstance(value, (bytes, bytearray)): - if sql_mode is not None and SQLMode.NO_BACKSLASH_ESCAPES in sql_mode: - return value.replace(b"'", b"''") - value = value.replace(b"\\", b"\\\\") - value = value.replace(b"\n", b"\\n") - value = value.replace(b"\r", b"\\r") - value = value.replace(b"\047", b"\134\047") # single quotes - value = value.replace(b"\042", b"\134\042") # double quotes - value = value.replace(b"\032", b"\134\032") # for Win32 - elif isinstance(value, str) and not isinstance(value, HexLiteral): - if sql_mode is not None and SQLMode.NO_BACKSLASH_ESCAPES in sql_mode: - return value.replace("'", "''") - value = value.replace("\\", "\\\\") - value = value.replace("\n", "\\n") - value = value.replace("\r", "\\r") - value = value.replace("\047", "\134\047") # single quotes - value = value.replace("\042", "\134\042") # double quotes - value = value.replace("\032", "\134\032") # for Win32 - return value - - @staticmethod - def quote(buf: Optional[Union[float, int, Decimal, HexLiteral, bytes]]) -> bytes: - """ - Quote the parameters for commands. General rules: - o numbers are returns as bytes using ascii codec - o None is returned as bytearray(b'NULL') - o Everything else is single quoted '' - - Returns a bytearray object. - """ - if isinstance(buf, NUMERIC_TYPES): - return str(buf).encode("ascii") - if isinstance(buf, type(None)): - return bytearray(b"NULL") - return bytearray(b"'" + buf + b"'") # type: ignore[operator] - - def to_mysql(self, value: MySQLConvertibleType) -> MySQLProducedType: - """Convert Python data type to MySQL""" - if isinstance(value, Enum): - value = value.value - # check if type of value object matches any one of the native supported conversion types - # most of the types will match the condition below - type_name: str = NATIVE_SUPPORTED_CONVERSION_TYPES.get(type(value), "") - if not type_name: - # check if the value object inherits from one of the native supported conversion types - type_name = next( - ( - name - for data_type, name in NATIVE_SUPPORTED_CONVERSION_TYPES.items() - if isinstance(value, data_type) - ), - value.__class__.__name__.lower(), - ) - try: - converted: MySQLProducedType = getattr(self, f"_{type_name}_to_mysql")( - value - ) - return converted - except AttributeError: - # Value type is not a native one, nor a subclass of a native one - if self.str_fallback: - return str(value).encode() - raise TypeError( - f"Python '{type_name}' cannot be converted to a MySQL type" - ) from None - - def to_python( - self, - vtype: DescriptionType, - value: Optional[bytes], - ) -> PythonProducedType: - """Convert MySQL data type to Python""" - # \x00 - if value == 0 and vtype[1] != FieldType.BIT: - # Don't go further when we hit a NULL value - return None - if value is None: - return None - - if not self._cache_field_types: - self._cache_field_types = {} - for name, info in FieldType.desc.items(): - try: - self._cache_field_types[info[0]] = getattr( - self, f"_{name.lower()}_to_python" - ) - except AttributeError: - # We ignore field types which has no method - pass - - try: - return self._cache_field_types[vtype[1]](value, vtype) - except KeyError: - # If one type is not defined, we just return the value as str - try: - return value.decode("utf-8") - except UnicodeDecodeError: - return value - except ValueError as err: - raise ValueError(f"{err} (field {vtype[0]})") from err - except TypeError as err: - raise TypeError(f"{err} (field {vtype[0]})") from err - - @staticmethod - def _int_to_mysql(value: int) -> int: - """Convert value to int""" - return int(value) - - @staticmethod - def _long_to_mysql(value: int) -> int: - """Convert value to int - - Note: There is no type "long" in Python 3 since integers are of unlimited size. - Since Python 2 is no longer supported, this method should be deprecated. - """ - return int(value) - - @staticmethod - def _float_to_mysql(value: float) -> Optional[float]: - """Convert value to float""" - if math.isnan(value): - return None - return float(value) - - def _str_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: - """Convert value to string""" - return self._unicode_to_mysql(value) - - def _unicode_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: - """Convert unicode""" - charset = self.charset - charset_id = self.charset_id - if charset == "binary": - charset = "utf8" - charset_id = self._character_set.get_charset_info(charset)[0] - encoded = value.encode(charset) - if charset_id in self._character_set.slash_charsets: - if b"\x5c" in encoded: - return HexLiteral(value, charset) - return encoded - - @staticmethod - def _bytes_to_mysql(value: bytes) -> bytes: - """Convert value to bytes""" - return value - - @staticmethod - def _bytearray_to_mysql(value: bytearray) -> bytes: - """Convert value to bytes""" - return bytes(value) - - @staticmethod - def _bool_to_mysql(value: bool) -> int: - """Convert value to boolean""" - return 1 if value else 0 - - @staticmethod - def _nonetype_to_mysql(value: None) -> None: # pylint: disable=unused-argument - """ - This would return what None would be in MySQL, but instead we - leave it None and return it right away. The actual conversion - from None to NULL happens in the quoting functionality. - - Return None. - """ - return None - - @staticmethod - def _datetime_to_mysql(value: datetime.datetime) -> bytes: - """ - Converts a datetime instance to a string suitable for MySQL. - The returned string has format: %Y-%m-%d %H:%M:%S[.%f] - - If the instance isn't a datetime.datetime type, it return None. - - Returns a bytes. - """ - if value.microsecond: - fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}" - return fmt.format( - value.year, - value.month, - value.day, - value.hour, - value.minute, - value.second, - value.microsecond, - ).encode("ascii") - - fmt = "{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}" - return fmt.format( - value.year, - value.month, - value.day, - value.hour, - value.minute, - value.second, - ).encode("ascii") - - @staticmethod - def _date_to_mysql(value: datetime.date) -> bytes: - """ - Converts a date instance to a string suitable for MySQL. - The returned string has format: %Y-%m-%d - - If the instance isn't a datetime.date type, it return None. - - Returns a bytes. - """ - return f"{value.year:04d}-{value.month:02d}-{value.day:02d}".encode("ascii") - - @staticmethod - def _time_to_mysql(value: datetime.time) -> bytes: - """ - Converts a time instance to a string suitable for MySQL. - The returned string has format: %H:%M:%S[.%f] - - If the instance isn't a datetime.time type, it return None. - - Returns a bytes. - """ - if value.microsecond: - return value.strftime("%H:%M:%S.%f").encode("ascii") - return value.strftime("%H:%M:%S").encode("ascii") - - @staticmethod - def _struct_time_to_mysql(value: time.struct_time) -> bytes: - """ - Converts a time.struct_time sequence to a string suitable - for MySQL. - The returned string has format: %Y-%m-%d %H:%M:%S - - Returns a bytes or None when not valid. - """ - return time.strftime("%Y-%m-%d %H:%M:%S", value).encode("ascii") - - @staticmethod - def _timedelta_to_mysql(value: datetime.timedelta) -> bytes: - """ - Converts a timedelta instance to a string suitable for MySQL. - The returned string has format: %H:%M:%S - - Returns a bytes. - """ - seconds = abs(value.days * 86400 + value.seconds) - - if value.microseconds: - fmt = "{0:02d}:{1:02d}:{2:02d}.{3:06d}" - if value.days < 0: - mcs = 1000000 - value.microseconds - seconds -= 1 - else: - mcs = value.microseconds - else: - fmt = "{0:02d}:{1:02d}:{2:02d}" - - if value.days < 0: - fmt = "-" + fmt - - (hours, remainder) = divmod(seconds, 3600) - (mins, secs) = divmod(remainder, 60) - - if value.microseconds: - result = fmt.format(hours, mins, secs, mcs) - else: - result = fmt.format(hours, mins, secs) - - return result.encode("ascii") - - @staticmethod - def _decimal_to_mysql(value: Decimal) -> Optional[bytes]: - """ - Converts a decimal.Decimal instance to a string suitable for - MySQL. - - Returns a bytes or None when not valid. - """ - if isinstance(value, Decimal): - return str(value).encode("ascii") - - return None - - def row_to_python( - self, row: Tuple[bytes, ...], fields: List[DescriptionType] - ) -> Tuple[PythonProducedType, ...]: - """Convert a MySQL text result row to Python types - - The row argument is a sequence containing text result returned - by a MySQL server. Each value of the row is converted to the - using the field type information in the fields argument. - - Returns a tuple. - """ - i = 0 - result: List[PythonProducedType] = [None] * len(fields) - - if not self._cache_field_types: - self._cache_field_types = {} - for name, info in FieldType.desc.items(): - try: - self._cache_field_types[info[0]] = getattr( - self, f"_{name.lower()}_to_python" - ) - except AttributeError: - # We ignore field types which has no method - pass - - for field in fields: - field_type = field[1] - - if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None: - # Don't convert NULL value - i += 1 - continue - - try: - result[i] = self._cache_field_types[field_type](row[i], field) - except KeyError: - # If one type is not defined, we just return the value as str - try: - result[i] = row[i].decode("utf-8") - except UnicodeDecodeError: - result[i] = row[i] - except (ValueError, TypeError) as err: - # Item "ValueError" of "Union[ValueError, TypeError]" has no attribute "message" - err.message = f"{err} (field {field[0]})" # type: ignore[union-attr] - raise - - i += 1 - - return tuple(result) - - # pylint: disable=unused-argument - @staticmethod - def _float_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> float: - """ - Returns value as float type. - """ - return float(value) - - _double_to_python = _float_to_python - - @staticmethod - def _int_to_python(value: bytes, desc: Optional[DescriptionType] = None) -> int: - """ - Returns value as int type. - """ - return int(value) - - _tiny_to_python = _int_to_python - _short_to_python = _int_to_python - _int24_to_python = _int_to_python - _long_to_python = _int_to_python - _longlong_to_python = _int_to_python - - def _decimal_to_python( - self, value: bytes, desc: Optional[DescriptionType] = None - ) -> Decimal: - """ - Returns value as a decimal.Decimal. - """ - val = value.decode(self.charset) - return Decimal(val) - - _newdecimal_to_python = _decimal_to_python - - @staticmethod - def _str(value: bytes, desc: Optional[DescriptionType] = None) -> str: - """ - Returns value as str type. - """ - return str(value) - - @staticmethod - def _bit_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int: - """Returns BIT columntype as integer""" - int_val = value - if len(int_val) < 8: - int_val = b"\x00" * (8 - len(int_val)) + int_val - return int(struct.unpack(">Q", int_val)[0]) - - @staticmethod - def _date_to_python( - value: bytes, dsc: Optional[DescriptionType] = None - ) -> Optional[datetime.date]: - """Converts TIME column MySQL to a python datetime.datetime type. - - Raises ValueError if the value can not be converted. - - Returns DATE column type as datetime.date type. - """ - if isinstance(value, datetime.date): - return value - try: - parts = value.split(b"-") - if len(parts) != 3: - raise ValueError(f"invalid datetime format: {parts} len: {len(parts)}") - try: - return datetime.date(int(parts[0]), int(parts[1]), int(parts[2])) - except ValueError: - return None - except (IndexError, ValueError): - raise ValueError( - f"Could not convert {repr(value)} to python datetime.timedelta" - ) from None - - _NEWDATE_to_python = _date_to_python - - @staticmethod - def _time_to_python( - value: bytes, dsc: Optional[DescriptionType] = None - ) -> datetime.timedelta: - """Converts TIME column value to python datetime.time value type. - - Converts the TIME column MySQL type passed as bytes to a python - datetime.datetime type. - - Raises ValueError if the value can not be converted. - - Returns datetime.timedelta type. - """ - mcs: Optional[Union[int, bytes]] = None - try: - (hms, mcs) = value.split(b".") - mcs = int(mcs.ljust(6, b"0")) - except (TypeError, ValueError): - hms = value - mcs = 0 - try: - (hours, mins, secs) = [int(d) for d in hms.split(b":")] - if value[0] == 45 or value[0] == "-": - mins, secs, mcs = ( - -mins, - -secs, - -mcs, # pylint: disable=invalid-unary-operand-type - ) - return datetime.timedelta( - hours=hours, minutes=mins, seconds=secs, microseconds=mcs - ) - except (IndexError, TypeError, ValueError): - raise ValueError( - CONVERT_ERROR.format(value=value, pytype="datetime.timedelta") - ) from None - - @staticmethod - def _datetime_to_python( - value: bytes, dsc: Optional[DescriptionType] = None - ) -> Optional[datetime.datetime]: - """Converts DATETIME column value to python datetime.time value type. - - Converts the DATETIME column MySQL type passed as bytes to a python - datetime.datetime type. - - Returns: datetime.datetime type. - """ - if isinstance(value, datetime.datetime): - return value - datetime_val = None - mcs: Optional[Union[int, bytes]] = None - try: - (date_, time_) = value.split(b" ") - if len(time_) > 8: - (hms, mcs) = time_.split(b".") - mcs = int(mcs.ljust(6, b"0")) - else: - hms = time_ - mcs = 0 - dtval = ( - [int(i) for i in date_.split(b"-")] - + [int(i) for i in hms.split(b":")] - + [ - mcs, - ] - ) - if len(dtval) < 6: - raise ValueError(f"invalid datetime format: {dtval} len: {len(dtval)}") - # Note that by default MySQL accepts invalid timestamps - # (this is also backward compatibility). - # Traditionaly C/py returns None for this well formed but - # invalid datetime for python like '0000-00-00 HH:MM:SS'. - try: - datetime_val = datetime.datetime(*dtval) # type: ignore[arg-type] - except ValueError: - return None - except (IndexError, TypeError): - raise ValueError( - CONVERT_ERROR.format(value=value, pytype="datetime.timedelta") - ) from None - - return datetime_val - - _timestamp_to_python = _datetime_to_python - - @staticmethod - def _year_to_python(value: bytes, dsc: Optional[DescriptionType] = None) -> int: - """Returns YEAR column type as integer""" - try: - year = int(value) - except ValueError as err: - raise ValueError(f"Failed converting YEAR to int ({repr(value)})") from err - - return year - - def _set_to_python( - self, value: bytes, dsc: Optional[DescriptionType] = None - ) -> Set[str]: - """Returns SET column type as set - - Actually, MySQL protocol sees a SET as a string type field. So this - code isn't called directly, but used by STRING_to_python() method. - - Returns SET column type as a set. - """ - set_type = None - val = value.decode(self.charset) - if not val: - return set() - try: - set_type = set(val.split(",")) - except ValueError as err: - raise ValueError( - f"Could not convert set {repr(value)} to a sequence" - ) from err - return set_type - - def _string_to_python( - self, value: bytes, dsc: Optional[DescriptionType] = None - ) -> Union[StrOrBytes, Set[str]]: - """ - Note that a SET is a string too, but using the FieldFlag we can see - whether we have to split it. - - Returns string typed columns as string type. - """ - if self.charset == "binary": - return value - if dsc is not None: - if dsc[1] == FieldType.JSON and self.use_unicode: - return value.decode(self.charset) - if dsc[7] & FieldFlag.SET: - return self._set_to_python(value, dsc) - # 'binary' charset - if dsc[8] == 63: - return value - if isinstance(value, (bytes, bytearray)) and self.use_unicode: - try: - return value.decode(self.charset) - except UnicodeDecodeError: - return value - - return value - - _var_string_to_python = _string_to_python - _json_to_python = _string_to_python - - def _blob_to_python( - self, value: bytes, dsc: Optional[DescriptionType] = None - ) -> Union[StrOrBytes, Set[str]]: - """Convert BLOB data type to Python.""" - if dsc is not None: - if ( - dsc[7] & FieldFlag.BLOB - and dsc[7] & FieldFlag.BINARY - # 'binary' charset - and dsc[8] == 63 - ): - return bytes(value) - return self._string_to_python(value, dsc) - - @staticmethod - def _vector_to_python( - value: Optional[bytes], desc: Optional[DescriptionType] = None - ) -> Optional[array.array]: - """ - Converts a MySQL VECTOR value to a Python array.array type. - - Returns an array of floats if `value` isn't `None`, otherwise `None`. - """ - if value is None or isinstance(value, array.array): - return value - - if isinstance(value, (bytes, bytearray)): - return array.array(MYSQL_VECTOR_TYPE_CODE, value) - - raise TypeError(f"Got unsupported type {value.__class__.__name__}") - - _long_blob_to_python = _blob_to_python - _medium_blob_to_python = _blob_to_python - _tiny_blob_to_python = _blob_to_python - # pylint: enable=unused-argument diff --git a/server/venv/Lib/site-packages/mysql/connector/cursor.py b/server/venv/Lib/site-packages/mysql/connector/cursor.py deleted file mode 100644 index c7364d7..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/cursor.py +++ /dev/null @@ -1,1489 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="assignment,arg-type,attr-defined,index,override,call-overload" - -"""Cursor classes.""" -from __future__ import annotations - -import re -import warnings - -from decimal import Decimal -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Iterator, - List, - NoReturn, - Optional, - Sequence, - Tuple, - Union, - cast, -) - -from ._decorating import deprecated -from ._scripting import split_multi_statement -from .abstracts import MySQLCursorAbstract -from .constants import ServerFlag -from .errors import ( - Error, - InterfaceError, - NotSupportedError, - ProgrammingError, - ReadTimeoutError, - WriteTimeoutError, - get_mysql_exception, -) -from .types import ( - DescriptionType, - EofPacketType, - ParamsDictType, - ParamsSequenceOrDictType, - ParamsSequenceType, - ResultType, - RowItemType, - RowType, - StrOrBytes, - WarningType, -) - -if TYPE_CHECKING: - from .connection import MySQLConnection - - -SQL_COMMENT = r"\/\*.*?\*\/" -RE_SQL_COMMENT = re.compile( - rf"""({SQL_COMMENT})|(["'`][^"'`]*?({SQL_COMMENT})[^"'`]*?["'`])""", - re.I | re.M | re.S, -) -RE_SQL_ON_DUPLICATE = re.compile( - r"""\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$""", - re.I | re.M | re.S, -) -RE_SQL_INSERT_STMT = re.compile( - rf"({SQL_COMMENT}|\s)*INSERT({SQL_COMMENT}|\s)" - r"*(?:IGNORE\s+)?INTO\s+[`'\"]?.+[`'\"]?(?:\.[`'\"]?.+[`'\"]?)" - r"{0,2}\s+VALUES\s*(\(.+\)).*", - re.I | re.M | re.S, -) -RE_SQL_INSERT_VALUES = re.compile(r".*VALUES\s*(\(.+\)).*", re.I | re.M | re.S) -RE_PY_PARAM = re.compile(b"(%s)") -RE_PY_MAPPING_PARAM = re.compile( - rb""" - % - \((?P[^)]+)\) - (?P[diouxXeEfFgGcrs%]) - """, - re.X, -) -RE_SQL_SPLIT_STMTS = re.compile( - b""";(?=(?:[^"'`]*(?:"[^"]*"|'[^']*'|`[^`]*`))*[^"'`]*$)""" -) -RE_SQL_FIND_PARAM = re.compile(b"""%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)""") -RE_SQL_PYTHON_REPLACE_PARAM = re.compile(r"%\(.*?\)s") -RE_SQL_PYTHON_CAPTURE_PARAM_NAME = re.compile(r"%\((.*?)\)s") - -ERR_NO_RESULT_TO_FETCH = "No result set to fetch from" - -MAX_RESULTS = 4294967295 - - -class _ParamSubstitutor: - """ - Substitutes parameters into SQL statement. - """ - - def __init__(self, params: Sequence[bytes]) -> None: - self.params: Sequence[bytes] = params - self.index: int = 0 - - def __call__(self, matchobj: re.Match) -> bytes: - index = self.index - self.index += 1 - try: - return bytes(self.params[index]) - except IndexError: - raise ProgrammingError( - "Not enough parameters for the SQL statement" - ) from None - - @property - def remaining(self) -> int: - """Returns number of parameters remaining to be substituted""" - return len(self.params) - self.index - - -def _bytestr_format_dict(bytestr: bytes, value_dict: Dict[bytes, bytes]) -> bytes: - """ - >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'}) - b'foobar - >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'}) - b'%%(a)s' - >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'}) - b'%%foobar' - >>> _bytestr_format_dict(b'%(x)s %(y)s', - ... {b'x': b'x=%(y)s', b'y': b'y=%(x)s'}) - b'x=%(y)s y=%(x)s' - """ - - def replace(matchobj: re.Match) -> bytes: - """Replace pattern.""" - value: Optional[bytes] = None - groups = matchobj.groupdict() - if groups["conversion_type"] == b"%": - value = b"%" - if groups["conversion_type"] == b"s": - key = groups["mapping_key"] - value = value_dict[key] - if value is None: - raise ValueError( - f"Unsupported conversion_type: {groups['conversion_type']}" - ) - return value - - stmt = RE_PY_MAPPING_PARAM.sub(replace, bytestr) - return stmt - - -class MySQLCursor(MySQLCursorAbstract): - """Default cursor for interacting with MySQL - - This cursor will execute statements and handle the result. It will - not automatically fetch all rows. - - MySQLCursor should be inherited whenever other functionallity is - required. An example would to change the fetch* member functions - to return dictionaries instead of lists of values. - - Implements the Python Database API Specification v2.0 (PEP-249) - """ - - def __init__( - self, - connection: Optional[MySQLConnection] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - """Initialize""" - super().__init__(connection, read_timeout, write_timeout) - self._connection: MySQLConnection = cast("MySQLConnection", self._connection) - - def __iter__(self) -> Iterator[RowType]: - """ - Iteration over the result set which calls self.fetchone() - and returns the next row. - """ - return iter(self.fetchone, None) - - def _reset_result(self, preserve_last_executed_stmt: bool = False) -> None: - """Reset the cursor to default. - - Args: - preserve_last_executed_stmt: If it is False, the last executed - statement value is reset. Otherwise, - such a value is preserved. - """ - self._rowcount: int = -1 - self._nextrow = (None, None) - self._stored_results: List[MySQLCursor] = [] - self._warnings: Optional[List[WarningType]] = None - self._warning_count: int = 0 - self._description: Optional[List[DescriptionType]] = None - - if not preserve_last_executed_stmt: - # reset inner state related to statement execution - self._executed = None - self._executed_list = [] - self._stmt_partitions = None - self._stmt_partition = None - self._stmt_map_results = False - - self.reset() - - def _have_unread_result(self) -> bool: - """Check whether there is an unread result""" - try: - return self._connection.unread_result - except AttributeError: - return False - - def _check_executed(self) -> None: - """Check if the statement has been executed. - - Raises an error if the statement has not been executed. - """ - if self._executed is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - - def __next__(self) -> RowType: - """ - Used for iterating over the result set. Calles self.fetchone() - to get the next row. - """ - try: - row = self.fetchone() - except InterfaceError: - raise StopIteration from None - if not row: - raise StopIteration - return row - - def close(self) -> bool: - """Close the cursor - - Returns True when successful, otherwise False. - """ - if self._connection is None: - return False - - self._connection.handle_unread_result() - self._reset_result() - self._connection = None - - return True - - def _process_params_dict( - self, params: ParamsDictType - ) -> Dict[bytes, Union[bytes, Decimal]]: - """Process query parameters given as dictionary""" - res: Dict[bytes, Any] = {} - try: - sql_mode = self._connection.sql_mode - to_mysql = self._connection.converter.to_mysql - escape = self._connection.converter.escape - quote = self._connection.converter.quote - for key, value in params.items(): - conv = value - conv = to_mysql(conv) - conv = escape(conv, sql_mode) - if not isinstance(value, Decimal): - conv = quote(conv) - res[key.encode()] = conv - except Exception as err: - raise ProgrammingError( - f"Failed processing pyformat-parameters; {err}" - ) from err - return res - - def _process_params( - self, params: ParamsSequenceType - ) -> Tuple[Union[bytes, Decimal], ...]: - """Process query parameters.""" - res = params[:] - try: - sql_mode = self._connection.sql_mode - to_mysql = self._connection.converter.to_mysql - escape = self._connection.converter.escape - quote = self._connection.converter.quote - res = [to_mysql(value) for value in res] - res = [escape(value, sql_mode) for value in res] - res = [ - quote(value) if not isinstance(params[i], Decimal) else value - for i, value in enumerate(res) - ] - except Exception as err: - raise ProgrammingError( - f"Failed processing format-parameters; {err}" - ) from err - return tuple(res) - - def _handle_noresultset(self, res: ResultType) -> None: - """Handles result of execute() when there is no result set""" - try: - self._rowcount = res["affected_rows"] - self._last_insert_id = res["insert_id"] - self._warning_count = res["warning_count"] - except (KeyError, TypeError) as err: - raise ProgrammingError(f"Failed handling non-resultset; {err}") from None - - self._handle_warnings() - - def _handle_resultset(self) -> None: - """Handles result set - - This method handles the result set and is called after reading - and storing column information in _handle_result(). For non-buffering - cursors, this method is usually doing nothing. - """ - - def _handle_result(self, result: ResultType) -> None: - """ - Handle the result after a command was send. The result can be either - an OK-packet or a dictionary containing column/eof information. - - Raises InterfaceError when result is not a dict() or result is - invalid. - """ - if not isinstance(result, dict): - raise InterfaceError("Result was not a dict()") - - if "columns" in result: - # Weak test, must be column/eof information - self._description = result["columns"] - self._connection.unread_result = True - self._handle_resultset() - elif "affected_rows" in result: - # Weak test, must be an OK-packet - self._connection.unread_result = False - self._handle_noresultset(result) - else: - raise InterfaceError("Invalid result") - - def execute( - self, - operation: str, - params: Optional[ParamsSequenceOrDictType] = None, - map_results: bool = False, - ) -> None: - if not operation: - return None - - try: - if not self._connection: - raise ProgrammingError - except (ProgrammingError, ReferenceError) as err: - raise ProgrammingError("Cursor is not connected") from err - - self._connection.handle_unread_result() - self._reset_result() - - stmt = b"" - try: - if isinstance(operation, str): - stmt = operation.encode(self._connection.python_charset) - else: - stmt = cast(bytes, operation) - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise ProgrammingError(str(err)) from err - - if params: - if isinstance(params, dict): - stmt = _bytestr_format_dict(stmt, self._process_params_dict(params)) - elif isinstance(params, (list, tuple)): - psub = _ParamSubstitutor(self._process_params(params)) - stmt = RE_PY_PARAM.sub(psub, stmt) - if psub.remaining != 0: - raise ProgrammingError( - "Not all parameters were used in the SQL statement" - ) - else: - raise ProgrammingError( - f"Could not process parameters: {type(params).__name__}({params})," - " it must be of type list, tuple or dict" - ) - - self._stmt_partitions = split_multi_statement( - sql_code=stmt, map_results=map_results - ) - self._stmt_partition = next(self._stmt_partitions) - self._stmt_map_results = map_results - self._executed_list = self._stmt_partition["single_stmts"] - self._executed = ( - self._stmt_partition["single_stmts"].popleft() - if map_results - else self._stmt_partition["mappable_stmt"] - ) - - self._handle_result( - self._connection.cmd_query( - self._stmt_partition["mappable_stmt"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - ) - - return None - - def _batch_insert( - self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType] - ) -> Optional[bytes]: - """Implements multi row insert""" - - def remove_comments(match: re.Match) -> str: - """Remove comments from INSERT statements. - - This function is used while removing comments from INSERT - statements. If the matched string is a comment not enclosed - by quotes, it returns an empty string, else the string itself. - """ - if match.group(1): - return "" - return match.group(2) - - tmp = re.sub( - RE_SQL_ON_DUPLICATE, - "", - re.sub(RE_SQL_COMMENT, remove_comments, operation), - ) - - matches = re.search(RE_SQL_INSERT_VALUES, tmp) - if not matches: - raise InterfaceError( - "Failed rewriting statement for multi-row INSERT. Check SQL syntax" - ) - fmt = matches.group(1).encode(self._connection.python_charset) - values = [] - - try: - stmt = operation.encode(self._connection.python_charset) - for params in seq_params: - tmp = fmt - if isinstance(params, dict): - tmp = _bytestr_format_dict(tmp, self._process_params_dict(params)) - else: - psub = _ParamSubstitutor(self._process_params(params)) - tmp = RE_PY_PARAM.sub(psub, tmp) - if psub.remaining != 0: - raise ProgrammingError( - "Not all parameters were used in the SQL statement" - ) - values.append(tmp) - if fmt in stmt: - stmt = stmt.replace(fmt, b",".join(values), 1) - self._executed = stmt - return stmt - return None - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise ProgrammingError(str(err)) from err - except Error: - raise - except Exception as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - - def executemany( - self, operation: str, seq_params: Sequence[ParamsSequenceOrDictType] - ) -> None: - """Execute the given operation multiple times - - The executemany() method will execute the operation iterating - over the list of parameters in seq_params. - - Example: Inserting 3 new employees and their phone number - - data = [ - ('Jane','555-001'), - ('Joe', '555-001'), - ('John', '555-003') - ] - stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)" - cursor.executemany(stmt, data) - - INSERT statements are optimized by batching the data, that is - using the MySQL multiple rows syntax. - - Results are discarded. If they are needed, consider looping over - data using the execute() method. - """ - if not operation or not seq_params: - return None - self._connection.handle_unread_result() - - try: - _ = iter(seq_params) - except TypeError as err: - raise ProgrammingError("Parameters for query must be an Iterable") from err - - # Optimize INSERTs by batching them - if re.match(RE_SQL_INSERT_STMT, operation): - if not seq_params: - self._rowcount = 0 - return None - stmt = self._batch_insert(operation, seq_params) - if stmt is not None: - self._executed = stmt - return self.execute(stmt) - - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self.with_rows and self._have_unread_result(): - self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - self._rowcount = rowcnt - return None - - @deprecated( - "The property counterpart 'stored_results' will be added in a future release, " - "and this method will be removed." - ) - def stored_results(self) -> Iterator[MySQLCursor]: - """Returns an iterator for stored results - - This method returns an iterator over results which are stored when - callproc() is called. The iterator will provide MySQLCursorBuffered - instances. - - Returns a iterator. - """ - return iter(self._stored_results) - - def callproc( - self, - procname: str, - args: Sequence = (), - ) -> Optional[Union[Dict[str, RowItemType], RowType]]: - """Calls a stored procedure with the given arguments - - The arguments will be set during this session, meaning - they will be called like ___arg where - is an enumeration (+1) of the arguments. - - Coding Example: - 1) Defining the Stored Routine in MySQL: - CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN - SET pProd := pFac1 * pFac2; - END - - 2) Executing in Python: - args = (5, 5, 0) # 0 is to hold pprod - cursor.callproc('multiply', args) - print(cursor.fetchone()) - - For OUT and INOUT parameters the user should provide the - type of the parameter as well. The argument should be a - tuple with first item as the value of the parameter to pass - and second argument the type of the argument. - - In the above example, one can call callproc method like: - args = (5, 5, (0, 'INT')) - cursor.callproc('multiply', args) - - The type of the argument given in the tuple will be used by - the MySQL CAST function to convert the values in the corresponding - MySQL type (See CAST in MySQL Reference for more information) - - Does not return a value, but a result set will be - available when the CALL-statement execute successfully. - Raises exceptions when something is wrong. - """ - if not procname or not isinstance(procname, str): - raise ValueError("procname must be a string") - - if not isinstance(args, (tuple, list)): - raise ValueError("args must be a sequence") - - argfmt = "@_{name}_arg{index}" - self._stored_results = [] - - results = [] - try: - argnames = [] - argtypes = [] - - # MySQL itself does support calling procedures with their full - # name .. It's necessary to split - # by '.' and grab the procedure name from procname. - procname_abs = procname.split(".")[-1] - if args: - argvalues = [] - for idx, arg in enumerate(args): - argname = argfmt.format(name=procname_abs, index=idx + 1) - argnames.append(argname) - if isinstance(arg, tuple): - argtypes.append(f" CAST({argname} AS {arg[1]})") - argvalues.append(arg[0]) - else: - argtypes.append(argname) - argvalues.append(arg) - - placeholders = ",".join(f"{arg}=%s" for arg in argnames) - self.execute(f"SET {placeholders}", argvalues) - - call = f"CALL {procname}({','.join(argnames)})" - - # We disable consuming results temporary to make sure we - # getting all results - can_consume_results = self._connection.can_consume_results - for result in self._connection.cmd_query_iter( - call, read_timeout=self._read_timeout, write_timeout=self._write_timeout - ): - self._connection.can_consume_results = False - if isinstance(self, (MySQLCursorDict, MySQLCursorBufferedDict)): - cursor_class = MySQLCursorBufferedDict - elif self._raw: - cursor_class = MySQLCursorBufferedRaw - else: - cursor_class = MySQLCursorBuffered - # pylint: disable=protected-access - cur = cursor_class(self._connection.get_self()) - cur._executed = f"(a result of {call})" - cur._handle_result(result) - # pylint: enable=protected-access - if cur.warnings is not None: - self._warnings = cur.warnings - if "columns" in result: - results.append(cur) - self._connection.can_consume_results = can_consume_results - - if argnames: - # Create names aliases to be compatible with namedtuples - args = [ - f"{name} AS {alias}" - for name, alias in zip( - argtypes, [arg.lstrip("@_") for arg in argnames] - ) - ] - select = f"SELECT {','.join(args)}" - self.execute(select) - self._stored_results = results - return self.fetchone() - - self._stored_results = results - return tuple() - - except Error: - raise - except Exception as err: - raise InterfaceError(f"Failed calling stored routine; {err}") from None - - def _fetch_warnings(self) -> Optional[List[WarningType]]: - """ - Fetch warnings doing a SHOW WARNINGS. Can be called after getting - the result. - - Returns a result set or None when there were no warnings. - """ - res = [] - try: - cur = self._connection.cursor( - raw=False, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - cur.execute("SHOW WARNINGS") - res = cur.fetchall() - cur.close() - except Exception as err: - raise InterfaceError(f"Failed getting warnings; {err}") from None - - if res: - return res # type: ignore[return-value] - - return None - - def _handle_warnings(self) -> None: - """Handle possible warnings after all results are consumed. - - Raises: - Error: Also raises exceptions if raise_on_warnings is set. - """ - if self._connection.get_warnings and self._warning_count: - self._warnings = self._fetch_warnings() - - if not self._warnings: - return - - err = get_mysql_exception( - self._warnings[0][1], - self._warnings[0][2], - warning=not self._connection.raise_on_warnings, - ) - - if self._connection.raise_on_warnings: - raise err - - warnings.warn(err, stacklevel=4) - - def _handle_eof(self, eof: EofPacketType) -> None: - """Handle EOF packet""" - self._connection.unread_result = False - self._nextrow = (None, None) - self._warning_count = eof["warning_count"] - self._handle_warnings() - - def _fetch_row(self, raw: bool = False) -> Optional[RowType]: - """Returns the next row in the result set - - Returns a tuple or None. - """ - if not self._have_unread_result(): - return None - row = None - - if self._nextrow == (None, None): - (row, eof) = self._connection.get_row( - binary=self._binary, - columns=self.description, - raw=raw, - read_timeout=self._read_timeout, - ) - else: - (row, eof) = self._nextrow - - if row: - self._nextrow = self._connection.get_row( - binary=self._binary, - columns=self.description, - raw=raw, - read_timeout=self._read_timeout, - ) - eof = self._nextrow[1] - if eof is not None: - self._handle_eof(eof) - if self._rowcount == -1: - self._rowcount = 1 - else: - self._rowcount += 1 - if eof: - self._handle_eof(eof) - - return row - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return self._fetch_row() - - def fetchmany(self, size: Optional[int] = None) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0 and self._have_unread_result(): - cnt -= 1 - row = self.fetchone() - if row: - res.append(row) - return res - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if not self._have_unread_result(): - return [] - - (rows, eof) = self._connection.get_rows(read_timeout=self._read_timeout) - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - - self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return rows - - def nextset(self) -> Optional[bool]: - if self._connection._have_next_result: - # prepare cursor to load the next result set, and ultimately, load it. - self._connection.handle_unread_result() - self._reset_result(preserve_last_executed_stmt=True) - self._handle_result( - self._connection._handle_result( - self._connection._socket.recv(read_timeout=self._read_timeout) - ) - ) - - # if mapping is enabled, run the if-block, otherwise simply return `True`. - if self._stmt_partitions is not None and self._stmt_map_results: - if not self._stmt_partition["single_stmts"]: - # It means there are still results to be consumed, but no more - # statements to relate these results to. - # In this case, we raise a no fatal error and don't clear - # `_executed` so its current value is reported when users - # access the property `statement`. - # If this case ever happens, a bug report should be filed, - # assuming it is happening on supported use cases. - warnings.warn( - "MappingWarning: Number of result sets greater than number " - "of single statements." - ) - else: - self._executed = self._stmt_partition["single_stmts"].popleft() - return True - if self._stmt_partitions is not None: - # Let's see if there are more mappable statements (partitions) - # to be executed. - # If there are no more partitions, we simply return `None`, otherwise - # we execute the correponding mappable multi statement and repeat the - # process all over again. - try: - self._stmt_partition = next(self._stmt_partitions) - except StopIteration: - pass - else: - # This block only happens when mapping is enabled because when it - # is disabled, only one partition is generated, and at this point, - # such partiton has already been processed. - self._executed = self._stmt_partition["single_stmts"].popleft() - self._handle_result( - self._connection.cmd_query( - self._stmt_partition["mappable_stmt"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - ) - return True - - self._reset_result() - return None - - @property - def column_names(self) -> Tuple[str, ...]: - """Returns column names - - This property returns the columns names as a tuple. - - Returns a tuple. - """ - if not self.description: - return tuple() - return tuple(d[0] for d in self.description) - - @property - def with_rows(self) -> bool: - """Returns whether the cursor could have rows returned - - This property returns True when column descriptions are available - and possibly also rows, which will need to be fetched. - - Returns True or False. - """ - if not self.description: - return False - return True - - def __str__(self) -> str: - fmt = "{class_name}: {stmt}" - if self._executed: - try: - executed = self._executed.decode("utf-8") - except AttributeError: - executed = self._executed - if len(executed) > 40: - executed = executed[:40] + ".." - else: - executed = "(Nothing executed yet)" - return fmt.format(class_name=self.__class__.__name__, stmt=executed) - - -class MySQLCursorBuffered(MySQLCursor): - """Cursor which fetches rows within execute()""" - - def __init__( - self, - connection: Optional[MySQLConnection] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - super().__init__(connection, read_timeout, write_timeout) - self._rows: Optional[List[RowType]] = None - self._next_row: int = 0 - - def _handle_resultset(self) -> None: - (self._rows, eof) = self._connection.get_rows(read_timeout=self._read_timeout) - self._rowcount = len(self._rows) - self._handle_eof(eof) - self._next_row = 0 - try: - self._connection.unread_result = False - except AttributeError: - pass - - def reset(self, free: bool = True) -> None: - self._rows = None - - def _fetch_row(self, raw: bool = False) -> Optional[RowType]: - row = None - try: - row = self._rows[self._next_row] - except (IndexError, TypeError): - return None - self._next_row += 1 - return row - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return self._fetch_row() - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - if self._executed is None or self._rows is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - res = [] - res = self._rows[self._next_row :] - self._next_row = len(self._rows) - return res - - def fetchmany(self, size: Optional[int] = None) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0: - cnt -= 1 - row = self.fetchone() - if row: - res.append(row) - - return res - - @property - def with_rows(self) -> bool: - return self._rows is not None - - -class MySQLCursorRaw(MySQLCursor): - """ - Skips conversion from MySQL datatypes to Python types when fetching rows. - """ - - def __init__( - self, - connection: Optional[MySQLConnection] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - super().__init__(connection, read_timeout, write_timeout) - self._raw: bool = True - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return self._fetch_row(raw=self._raw) - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if not self._have_unread_result(): - return [] - (rows, eof) = self._connection.get_rows( - raw=self._raw, read_timeout=self._read_timeout - ) - if self._nextrow[0]: - rows.insert(0, self._nextrow[0]) - self._handle_eof(eof) - rowcount = len(rows) - if rowcount >= 0 and self._rowcount == -1: - self._rowcount = 0 - self._rowcount += rowcount - return rows - - -class MySQLCursorBufferedRaw(MySQLCursorBuffered): - """ - Cursor which skips conversion from MySQL datatypes to Python types when - fetching rows and fetches rows within execute(). - """ - - def __init__( - self, - connection: Optional[MySQLConnection] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - super().__init__(connection, read_timeout, write_timeout) - self._raw: bool = True - - def _handle_resultset(self) -> None: - (self._rows, eof) = self._connection.get_rows( - raw=self._raw, read_timeout=self._read_timeout - ) - self._rowcount = len(self._rows) - self._handle_eof(eof) - self._next_row = 0 - try: - self._connection.unread_result = False - except AttributeError: - pass - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return self._fetch_row() - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if self._rows is None: - return [] - return list(self._rows[self._next_row :]) - - @property - def with_rows(self) -> bool: - return self._rows is not None - - -class MySQLCursorPrepared(MySQLCursor): - """Cursor using MySQL Prepared Statements""" - - def __init__( - self, - connection: Optional[MySQLConnection] = None, - read_timeout: Optional[int] = None, - write_timeout: Optional[int] = None, - ): - super().__init__(connection, read_timeout, write_timeout) - self._rows: Optional[List[RowType]] = None - self._next_row: int = 0 - self._prepared: Optional[Dict[str, Union[int, List[DescriptionType]]]] = None - self._binary: bool = True - self._have_result: Optional[bool] = None - self._last_row_sent: bool = False - self._cursor_exists: bool = False - - def reset(self, free: bool = True) -> None: - if self._prepared: - try: - self._connection.cmd_stmt_close( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - except Error: - # We tried to deallocate, but it's OK when we fail. - pass - self._prepared = None - self._executed = None - self._last_row_sent = False - self._cursor_exists = False - - def _handle_noresultset(self, res: ResultType) -> None: - self._handle_server_status(res.get("status_flag", res.get("server_status", 0))) - super()._handle_noresultset(res) - - def _handle_server_status(self, flags: int) -> None: - """Check for SERVER_STATUS_CURSOR_EXISTS and - SERVER_STATUS_LAST_ROW_SENT flags set by the server. - """ - self._cursor_exists = flags & ServerFlag.STATUS_CURSOR_EXISTS != 0 - self._last_row_sent = flags & ServerFlag.STATUS_LAST_ROW_SENT != 0 - - def _handle_eof(self, eof: EofPacketType) -> None: - self._handle_server_status(eof.get("status_flag", eof.get("server_status", 0))) - super()._handle_eof(eof) - - def callproc(self, procname: Any, args: Any = ()) -> NoReturn: - """Calls a stored procedue - - Not supported with MySQLCursorPrepared. - """ - raise NotSupportedError() - - def close(self) -> None: - """Close the cursor - - This method will try to deallocate the prepared statement and close - the cursor. - """ - self.reset() - super().close() - - def _row_to_python(self, rowdata: Any, desc: Any = None) -> Any: - """Convert row data from MySQL to Python types - - The conversion is done while reading binary data in the - protocol module. - """ - - def _handle_result(self, result: ResultType) -> None: - """Handle result after execution""" - if isinstance(result, dict): - self._connection.unread_result = False - self._have_result = False - self._handle_noresultset(result) - else: - self._description = result[1] - self._connection.unread_result = True - self._have_result = True - - if "status_flag" in result[2]: # type: ignore[operator] - self._handle_server_status(result[2]["status_flag"]) - elif "server_status" in result[2]: # type: ignore[operator] - self._handle_server_status(result[2]["server_status"]) - - def execute( - self, - operation: StrOrBytes, - params: Optional[ParamsSequenceOrDictType] = None, - map_results: bool = False, - ) -> None: - """Prepare and execute a MySQL Prepared Statement - - This method will prepare the given operation and execute it using - the optionally given parameters. - - If the cursor instance already had a prepared statement, it is - first closed. - - *Argument "map_results" is unused as multi statement execution - is not supported for prepared statements*. - - Raises: - ProgrammingError: When providing a multi statement operation - or setting *map_results* to True. - """ - if map_results: - raise ProgrammingError( - "Multi statement execution not supported for prepared statements." - ) - - charset = self._connection.charset - if charset == "utf8mb4": - charset = "utf8" - - if not isinstance(operation, str): - try: - operation = operation.decode(charset) - except UnicodeDecodeError as err: - raise ProgrammingError(str(err)) from err - - if isinstance(params, dict): - replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation) - try: - # Replace params dict with params tuple in correct order. - params = tuple(params[key] for key in replacement_keys) - except KeyError as err: - raise ProgrammingError( - "Not all placeholders were found in the parameters dict" - ) from err - # Convert %(name)s to ? before sending it to MySQL - operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation) - - if operation is not self._executed: - if self._prepared: - self._connection.cmd_stmt_close( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - self._executed = operation - - try: - operation = operation.encode(charset) - except UnicodeEncodeError as err: - raise ProgrammingError(str(err)) from err - - if b"%s" in operation: - # Convert %s to ? before sending it to MySQL - operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation) - - try: - self._prepared = self._connection.cmd_stmt_prepare( - operation, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - except Error: - self._executed = None - raise - - self._connection.cmd_stmt_reset( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - - if self._prepared["parameters"] and not params: - return - if params: - if not isinstance(params, (tuple, list)): - raise ProgrammingError( - errno=1210, - msg=f"Incorrect type of argument: {type(params).__name__}({params})" - ", it must be of type tuple or list the argument given to " - "the prepared statement", - ) - if len(self._prepared["parameters"]) != len(params): - raise ProgrammingError( - errno=1210, - msg="Incorrect number of arguments executing prepared statement", - ) - - if params is None: - params = () - try: - res = self._connection.cmd_stmt_execute( - self._prepared["statement_id"], - data=params, - parameters=self._prepared["parameters"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - self._handle_result(res) - except (ReadTimeoutError, WriteTimeoutError) as err: - self.reset() - raise err - - def executemany( - self, - operation: str, - seq_params: Sequence[ParamsSequenceType], - ) -> None: - """Prepare and execute a MySQL Prepared Statement many times - - This method will prepare the given operation and execute with each - tuple found the list seq_params. - - If the cursor instance already had a prepared statement, it is - first closed. - - executemany() simply calls execute(). - """ - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self.with_rows and self._have_unread_result(): - self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - self._rowcount = rowcnt - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - if self._cursor_exists: - self._connection.cmd_stmt_fetch( - self._prepared["statement_id"], - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - return self._fetch_row() or None - - def fetchmany(self, size: Optional[int] = None) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0 and self._have_unread_result(): - cnt -= 1 - row = self._fetch_row() - if row: - res.append(row) - return res - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - rows = [] - if self._nextrow[0]: - rows.append(self._nextrow[0]) - while self._have_unread_result(): - if self._cursor_exists: - self._connection.cmd_stmt_fetch( - self._prepared["statement_id"], - MAX_RESULTS, - read_timeout=self._read_timeout, - write_timeout=self._write_timeout, - ) - (tmp, eof) = self._connection.get_rows( - binary=self._binary, - columns=self.description, - read_timeout=self._read_timeout, - ) - rows.extend(tmp) - self._handle_eof(eof) - self._rowcount = len(rows) - return rows - - -class MySQLCursorDict(MySQLCursor): - """ - Cursor fetching rows as dictionaries. - - The fetch methods of this class will return dictionaries instead of tuples. - Each row is a dictionary that looks like: - row = { - "col1": value1, - "col2": value2 - } - """ - - def _row_to_python( - self, - rowdata: RowType, - desc: Optional[List[DescriptionType]] = None, # pylint: disable=unused-argument - ) -> Optional[Dict[str, RowItemType]]: - """Convert a MySQL text result row to Python types - - Returns a dictionary. - """ - return dict(zip(self.column_names, rowdata)) if rowdata else None - - def fetchone(self) -> Optional[Dict[str, RowItemType]]: - """Return next row of a query result set. - - Returns: - dict or None: A dict from query result set. - """ - return self._row_to_python(super().fetchone(), self.description) - - def fetchall(self) -> List[Optional[Dict[str, RowItemType]]]: - """Return all rows of a query result set. - - Returns: - list: A list of dictionaries with all rows of a query - result set where column names are used as keys. - """ - return [ - self._row_to_python(row, self.description) - for row in super().fetchall() - if row - ] - - -class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered): - """ - Buffered Cursor fetching rows as dictionaries. - """ - - def fetchone(self) -> Optional[Dict[str, RowItemType]]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - row = self._fetch_row() - if row: - return self._row_to_python(row, self.description) - return None - - def fetchall(self) -> List[Optional[Dict[str, RowItemType]]]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - if self._executed is None or self._rows is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - res = [] - for row in self._rows[self._next_row :]: - res.append(self._row_to_python(row, self.description)) - self._next_row = len(self._rows) - return res - - -class MySQLCursorPreparedDict(MySQLCursorDict, MySQLCursorPrepared): # type: ignore[misc] - """ - This class is a blend of features from MySQLCursorDict and MySQLCursorPrepared - - Multiple inheritance in python is allowed but care must be taken - when assuming methods resolution. In the case of multiple - inheritance, a given attribute is first searched in the current - class if it's not found then it's searched in the parent classes. - The parent classes are searched in a left-right fashion and each - class is searched once. - Based on python's attribute resolution, in this case, attributes - are searched as follows: - 1. MySQLCursorPreparedDict (current class) - 2. MySQLCursorDict (left parent class) - 3. MySQLCursorPrepared (right parent class) - 4. MySQLCursor (base class) - """ - - def fetchmany(self, size: Optional[int] = None) -> List[Dict[str, RowItemType]]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set represented - as a list of dictionaries where column names are used as keys. - """ - return [ - self._row_to_python(row, self.description) - for row in super().fetchmany(size=size) - if row - ] diff --git a/server/venv/Lib/site-packages/mysql/connector/cursor_cext.py b/server/venv/Lib/site-packages/mysql/connector/cursor_cext.py deleted file mode 100644 index f54dd62..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/cursor_cext.py +++ /dev/null @@ -1,1275 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="assignment,override,union-attr" - -"""Cursor classes using the C Extension.""" -from __future__ import annotations - -import re -import warnings - -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Generator, - Iterator, - List, - NoReturn, - Optional, - Sequence, - Tuple, - Union, - cast, -) - -# pylint: disable=import-error,no-name-in-module -from _mysql_connector import MySQLInterfaceError - -from ._decorating import deprecated -from .types import ( - CextEofPacketType, - CextResultType, - DescriptionType, - ParamsSequenceOrDictType, - ParamsSequenceType, - RowItemType, - RowType, - StrOrBytesAny, - WarningType, -) - -# pylint: enable=import-error,no-name-in-module -# isort: split - -from ._scripting import split_multi_statement -from .abstracts import CMySQLPrepStmt, MySQLCursorAbstract -from .cursor import ( - RE_PY_PARAM, - RE_SQL_COMMENT, - RE_SQL_FIND_PARAM, - RE_SQL_INSERT_STMT, - RE_SQL_INSERT_VALUES, - RE_SQL_ON_DUPLICATE, - RE_SQL_PYTHON_CAPTURE_PARAM_NAME, - RE_SQL_PYTHON_REPLACE_PARAM, - _bytestr_format_dict, -) -from .errors import ( - Error, - InterfaceError, - NotSupportedError, - ProgrammingError, - get_mysql_exception, -) - -if TYPE_CHECKING: - from .connection_cext import CMySQLConnection - -ERR_NO_RESULT_TO_FETCH = "No result set to fetch from" - - -class _ParamSubstitutor: - """ - Substitutes parameters into SQL statement. - """ - - def __init__(self, params: Sequence[bytes]) -> None: - self.params: Sequence[bytes] = params - self.index: int = 0 - - def __call__(self, matchobj: object) -> bytes: - index = self.index - self.index += 1 - try: - return self.params[index] - except IndexError: - raise ProgrammingError( - "Not enough parameters for the SQL statement" - ) from None - - @property - def remaining(self) -> int: - """Returns number of parameters remaining to be substituted""" - return len(self.params) - self.index - - -class CMySQLCursor(MySQLCursorAbstract): - """Default cursor for interacting with MySQL using C Extension""" - - def __init__( - self, - connection: CMySQLConnection, - ) -> None: - """Initialize""" - super().__init__(connection) - - self._affected_rows: int = -1 - self._raw_as_string: bool = False - self._buffered: bool = False - self._connection: CMySQLConnection = cast("CMySQLConnection", self._connection) - - def reset(self, free: bool = True) -> None: - self._rowcount = -1 - self._nextrow = None - self._affected_rows = -1 - self._last_insert_id: int = 0 - self._warning_count: int = 0 - self._warnings: Optional[List[WarningType]] = None - self._warnings = None - self._warning_count = 0 - self._description: Optional[List[DescriptionType]] = None - if free and self._connection: - self._connection.free_result() - - super().reset() - - def _reset_result( - self, free: bool = True, preserve_last_executed_stmt: bool = False - ) -> None: - """Resets the cursor to default. - - This method is similar to `reset()`. Unlike `reset()`, this hidden method - allows to customize the reset. - - Args: - free: If `True`, the result will be freed. - preserve_last_executed_stmt: If `False`, the last executed - statement value is reset. Otherwise, - such a value is preserved. - """ - if not preserve_last_executed_stmt: - # reset inner state related to statement execution - self._executed = None - self._executed_list = [] - self._stmt_partitions = None - self._stmt_partition = None - self._stmt_map_results = False - - self.reset(free=free) - - @property - def read_timeout(self) -> Optional[float]: - raise ProgrammingError( - """ - The use of read_timeout after the connection has been established is unsupported - in the C-Extension - """ - ) - - @read_timeout.setter - def read_timeout(self, timeout: int) -> None: - raise ProgrammingError( - """ - Changes in read_timeout after the connection has been established is unsupported - in the C-Extension - """ - ) - - @property - def write_timeout(self) -> Optional[float]: - raise ProgrammingError( - """ - The use of write_timeout after the connection has been established is unsupported - in the C-Extension - """ - ) - - @write_timeout.setter - def write_timeout(self, timeout: int) -> None: - raise ProgrammingError( - """ - Changes in write_timeout after the connection has been established is unsupported - in the C-Extension - """ - ) - - def _check_executed(self) -> None: - """Check if the statement has been executed. - - Raises an error if the statement has not been executed. - """ - if self._executed is None: - raise InterfaceError(ERR_NO_RESULT_TO_FETCH) - - def _fetch_warnings(self) -> Optional[List[WarningType]]: - """Fetch warnings - - Fetch warnings doing a SHOW WARNINGS. Can be called after getting - the result. - - Returns a result set or None when there were no warnings. - - Raises Error (or subclass) on errors. - - Returns list of tuples or None. - """ - warns = [] - try: - # force freeing result - self._connection.consume_results() - _ = self._connection.cmd_query("SHOW WARNINGS") - warns = self._connection.get_rows(raw=self._raw)[0] - self._connection.consume_results() - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - except Exception as err: - raise InterfaceError(f"Failed getting warnings; {err}") from None - - if warns: - return warns # type: ignore[return-value] - - return None - - def _handle_warnings(self) -> None: - """Handle possible warnings after all results are consumed. - - Raises: - Error: Also raises exceptions if raise_on_warnings is set. - """ - if self._connection.get_warnings and self._warning_count: - self._warnings = self._fetch_warnings() - - if not self._warnings: - return - - err = get_mysql_exception( - *self._warnings[0][1:3], warning=not self._connection.raise_on_warnings - ) - if self._connection.raise_on_warnings: - raise err - - warnings.warn(str(err), stacklevel=4) - - def _handle_result(self, result: Union[CextEofPacketType, CextResultType]) -> None: - """Handles the result after statement execution""" - if "columns" in result: - self._description = result["columns"] - self._rowcount = 0 - self._handle_resultset() - else: - self._last_insert_id = result["insert_id"] - self._warning_count = result["warning_count"] - self._affected_rows = result["affected_rows"] - self._rowcount = -1 - self._handle_warnings() - - def _handle_resultset(self) -> None: - """Handle a result set""" - - def _handle_eof(self) -> None: - """Handle end of reading the result - - Raises an Error on errors. - """ - self._warning_count = self._connection.warning_count - self._handle_warnings() - if not self._connection.more_results: - self._connection.free_result() - - def execute( - self, - operation: str, - params: ParamsSequenceOrDictType = (), - map_results: bool = False, - ) -> None: - if not operation: - return None - - try: - if not self._connection or self._connection.is_closed(): - raise ProgrammingError - except (ProgrammingError, ReferenceError) as err: - raise ProgrammingError("Cursor is not connected", 2055) from err - - self._connection.handle_unread_result() - self.reset() - - stmt = b"" - try: - if isinstance(operation, str): - stmt = operation.encode(self._connection.python_charset) - else: - stmt = cast(bytes, operation) - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise ProgrammingError(str(err)) from err - - if params: - prepared = self._connection.prepare_for_mysql(params) - if isinstance(prepared, dict): - stmt = _bytestr_format_dict(stmt, prepared) - elif isinstance(prepared, (list, tuple)): - psub = _ParamSubstitutor(prepared) - stmt = RE_PY_PARAM.sub(psub, stmt) - if psub.remaining != 0: - raise ProgrammingError( - "Not all parameters were used in the SQL statement" - ) - - self._stmt_partitions = split_multi_statement( - sql_code=stmt, map_results=map_results - ) - self._stmt_partition = next(self._stmt_partitions) - self._stmt_map_results = map_results - self._executed_list = self._stmt_partition["single_stmts"] - self._executed = ( - self._stmt_partition["single_stmts"].popleft() - if map_results - else self._stmt_partition["mappable_stmt"] - ) - - try: - self._handle_result( - self._connection.cmd_query( - self._stmt_partition["mappable_stmt"], - raw=self._raw, - buffered=self._buffered, - raw_as_string=self._raw_as_string, - ) - ) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - return None - - def _batch_insert( - self, - operation: str, - seq_params: Sequence[ParamsSequenceOrDictType], - ) -> Optional[bytes]: - """Implements multi row insert""" - - def remove_comments(match: re.Match) -> str: - """Remove comments from INSERT statements. - - This function is used while removing comments from INSERT - statements. If the matched string is a comment not enclosed - by quotes, it returns an empty string, else the string itself. - """ - if match.group(1): - return "" - return match.group(2) - - tmp = re.sub( - RE_SQL_ON_DUPLICATE, - "", - re.sub(RE_SQL_COMMENT, remove_comments, operation), - ) - - matches = re.search(RE_SQL_INSERT_VALUES, tmp) - if not matches: - raise InterfaceError( - "Failed rewriting statement for multi-row INSERT. Check SQL syntax" - ) - fmt = matches.group(1).encode(self._connection.python_charset) - values = [] - - try: - stmt = operation.encode(self._connection.python_charset) - for params in seq_params: - tmp = fmt - prepared = self._connection.prepare_for_mysql(params) - if isinstance(prepared, dict): - tmp = _bytestr_format_dict(cast(bytes, tmp), prepared) - elif isinstance(prepared, (list, tuple)): - psub = _ParamSubstitutor(prepared) - tmp = RE_PY_PARAM.sub(psub, tmp) # type: ignore[call-overload] - if psub.remaining != 0: - raise ProgrammingError( - "Not all parameters were used in the SQL statement" - ) - values.append(tmp) - - if fmt in stmt: - stmt = stmt.replace(fmt, b",".join(values), 1) # type: ignore[arg-type] - self._executed = stmt - return stmt - return None - except (UnicodeDecodeError, UnicodeEncodeError) as err: - raise ProgrammingError(str(err)) from err - except Exception as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - - def executemany( - self, - operation: str, - seq_params: Sequence[ParamsSequenceOrDictType], - ) -> None: - """Execute the given operation multiple times - - The executemany() method will execute the operation iterating - over the list of parameters in seq_params. - - Example: Inserting 3 new employees and their phone number - - data = [ - ('Jane','555-001'), - ('Joe', '555-001'), - ('John', '555-003') - ] - stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s)" - cursor.executemany(stmt, data) - - INSERT statements are optimized by batching the data, that is - using the MySQL multiple rows syntax. - - Results are discarded! If they are needed, consider looping over - data using the execute() method. - """ - if not operation or not seq_params: - return None - - try: - if not self._connection: - raise ProgrammingError - except (ProgrammingError, ReferenceError) as err: - raise ProgrammingError("Cursor is not connected") from err - self._connection.handle_unread_result() - - if not isinstance(seq_params, (list, tuple)): - raise ProgrammingError("Parameters for query must be list or tuple.") - - # Optimize INSERTs by batching them - if re.match(RE_SQL_INSERT_STMT, operation): - if not seq_params: - self._rowcount = 0 - return None - stmt = self._batch_insert(operation, seq_params) - if stmt is not None: - self._executed = stmt - return self.execute(cast(str, stmt)) - - rowcnt = 0 - try: - # When processing read ops (e.g., SELECT), rowcnt is updated - # based on self._rowcount. For write ops (e.g., INSERT) is - # updated based on self._affected_rows. - # The variable self._description is None for write ops, that's - # why we use it as indicator for updating rowcnt. - for params in seq_params: - self.execute(operation, params) - if self.with_rows and self._connection.unread_result: - self.fetchall() - rowcnt += self._rowcount if self.description else self._affected_rows - except (ValueError, TypeError) as err: - raise InterfaceError(f"Failed executing the operation; {err}") from None - - self._rowcount = rowcnt - return None - - @property - def description(self) -> Optional[List[DescriptionType]]: - """Returns description of columns in a result""" - return self._description - - @property - def rowcount(self) -> int: - """Returns the number of rows produced or affected""" - if self._rowcount == -1: - return self._affected_rows - return self._rowcount - - def close(self) -> bool: - """Close the cursor - - The result will be freed. - """ - if not self._connection: - return False - - self._connection.handle_unread_result() - self._warnings = None - self._connection = None - return True - - def callproc( - self, - procname: str, - args: Sequence = (), - ) -> Optional[Union[Dict[str, RowItemType], RowType]]: - """Calls a stored procedure with the given arguments""" - if not procname or not isinstance(procname, str): - raise ValueError("procname must be a string") - - if not isinstance(args, (tuple, list)): - raise ValueError("args must be a sequence") - - argfmt = "@_{name}_arg{index}" - self._stored_results = [] - - try: - argnames = [] - argtypes = [] - - # MySQL itself does support calling procedures with their full - # name .. It's necessary to split - # by '.' and grab the procedure name from procname. - procname_abs = procname.split(".")[-1] - if args: - argvalues = [] - for idx, arg in enumerate(args): - argname = argfmt.format(name=procname_abs, index=idx + 1) - argnames.append(argname) - if isinstance(arg, tuple): - argtypes.append(f" CAST({argname} AS {arg[1]})") - argvalues.append(arg[0]) - else: - argtypes.append(argname) - argvalues.append(arg) - - placeholders = ",".join(f"{arg}=%s" for arg in argnames) - self.execute(f"SET {placeholders}", argvalues) - - call = f"CALL {procname}({','.join(argnames)})" - - result = self._connection.cmd_query( - call, raw=self._raw, raw_as_string=self._raw_as_string - ) - - results = [] - while self._connection.result_set_available: - result = self._connection.fetch_eof_columns() - if isinstance(self, (CMySQLCursorDict, CMySQLCursorBufferedDict)): - cursor_class = CMySQLCursorBufferedDict - elif self._raw: - cursor_class = CMySQLCursorBufferedRaw - else: - cursor_class = CMySQLCursorBuffered - # pylint: disable=protected-access - cur = cursor_class(self._connection.get_self()) # type: ignore[arg-type] - cur._executed = f"(a result of {call})" - cur._handle_result(result) - # pylint: enable=protected-access - results.append(cur) - self._connection.next_result() - self._stored_results = results - self._handle_eof() - - if argnames: - self.reset() - # Create names aliases to be compatible with namedtuples - args = [ - f"{name} AS {alias}" - for name, alias in zip( - argtypes, [arg.lstrip("@_") for arg in argnames] - ) - ] - select = f"SELECT {','.join(args)}" - self.execute(select) - - return self.fetchone() - return tuple() - - except Error: - raise - except Exception as err: - raise InterfaceError(f"Failed calling stored routine; {err}") from None - - def nextset(self) -> Optional[bool]: - if self._connection.next_result(): - # prepare cursor to load the next result set, and ultimately, load it. - self._reset_result(free=False, preserve_last_executed_stmt=True) - if not self._connection.result_set_available: - self._handle_result(self._connection.fetch_eof_status()) - else: - self._handle_result(self._connection.fetch_eof_columns()) - - # if mapping is enabled, run the if-block, otherwise simply return `True`. - if self._stmt_partitions is not None and self._stmt_map_results: - if not self._stmt_partition["single_stmts"]: - # It means there are still results to be consumed, but no more - # statements to relate these results to. - # In this case, we raise a no fatal error and don't clear - # `_executed` so its current value is reported when users - # access the property `statement`. - # If this case ever happens, a bug report should be filed, - # assuming it is happening on supported use cases. - warnings.warn( - "MappingWarning: Number of result sets greater than number " - "of single statements." - ) - else: - self._executed = self._stmt_partition["single_stmts"].popleft() - return True - if self._stmt_partitions is not None: - # Let's see if there are more mappable statements (partitions) - # to be executed. - # If there are no more partitions, we simply return `None`, otherwise - # we execute the correponding mappable multi statement and repeat the - # process all over again. - try: - self._stmt_partition = next(self._stmt_partitions) - except StopIteration: - pass - else: - # This block only happens when mapping is enabled because when it - # is disabled, only one partition is generated, and at this point, - # such partiton has already been processed. - self._executed = self._stmt_partition["single_stmts"].popleft() - try: - self._handle_result( - self._connection.cmd_query( - self._stmt_partition["mappable_stmt"], - raw=self._raw, - buffered=self._buffered, - raw_as_string=self._raw_as_string, - ) - ) - except MySQLInterfaceError as err: - if hasattr(err, "errno"): - raise get_mysql_exception( - err.errno, msg=err.msg, sqlstate=err.sqlstate - ) from err - raise InterfaceError(str(err)) from err - return True - - self._reset_result(free=True) - return None - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if not self._connection.unread_result: - return [] - - rows = self._connection.get_rows(raw=self._raw) - if self._nextrow and self._nextrow[0]: - rows[0].insert(0, self._nextrow[0]) - - if not rows[0]: - self._handle_eof() - return [] - - self._rowcount += len(rows[0]) - self._handle_eof() - return rows[0] - - def fetchmany(self, size: int = 1) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - if self._nextrow and self._nextrow[0]: - rows = [self._nextrow[0]] - size -= 1 - else: - rows = [] - - if size and self._connection.unread_result: - rows.extend(self._connection.get_rows(size, raw=self._raw)[0]) - - if self._connection.unread_result: - self._nextrow = self._connection.get_row() - if ( - self._nextrow - and not self._nextrow[0] - and not self._connection.more_results - ): - self._connection.free_result() - else: - self._nextrow = (None, None) - - if not rows: - self._handle_eof() - return [] - - self._rowcount += len(rows) - return rows - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - row = self._nextrow - if not row and self._connection.unread_result: - row = self._connection.get_row() - - if row and row[0]: - self._nextrow = self._connection.get_row() - if not self._nextrow[0] and not self._connection.more_results: - self._connection.free_result() - else: - self._handle_eof() - return None - self._rowcount += 1 - return row[0] - - def __iter__(self) -> Iterator[RowType]: - """Iteration over the result set - - Iteration over the result set which calls self.fetchone() - and returns the next row. - """ - return iter(self.fetchone, None) - - @deprecated( - "The property counterpart 'stored_results' will be added in a future release, " - "and this method will be removed." - ) - def stored_results(self) -> Generator[CMySQLCursor, None, None]: - """Returns an iterator for stored results - - This method returns an iterator over results which are stored when - callproc() is called. The iterator will provide MySQLCursorBuffered - instances. - - Returns a iterator. - """ - # pylint: disable=use-yield-from - for result in self._stored_results: - yield result # type: ignore[misc] - self._stored_results = [] - - def __next__(self) -> RowType: - """Iteration over the result set - Used for iterating over the result set. Calls self.fetchone() - to get the next row. - - Raises StopIteration when no more rows are available. - """ - try: - row = self.fetchone() - except InterfaceError: - raise StopIteration from None - if not row: - raise StopIteration from None - return row - - @property - def column_names(self) -> Tuple[str, ...]: - """Returns column names - - This property returns the columns names as a tuple. - - Returns a tuple. - """ - if not self.description: - return () - return tuple(d[0] for d in self.description) - - @property - def with_rows(self) -> bool: - """Returns whether the cursor could have rows returned - - This property returns True when column descriptions are available - and possibly also rows, which will need to be fetched. - - Returns True or False. - """ - if self.description: - return True - return False - - def __str__(self) -> str: - fmt = "{class_name}: {stmt}" - if self._executed: - try: - executed = self._executed.decode("utf-8") - except AttributeError: - executed = self._executed - if len(executed) > 40: - executed = executed[:40] + ".." - else: - executed = "(Nothing executed yet)" - - return fmt.format(class_name=self.__class__.__name__, stmt=executed) - - -class CMySQLCursorBuffered(CMySQLCursor): - """Cursor using C Extension buffering results""" - - def __init__( - self, - connection: CMySQLConnection, - ): - """Initialize""" - super().__init__(connection) - - self._rows: Optional[List[RowType]] = None - self._next_row: int = 0 - - def _handle_resultset(self) -> None: - """Handle a result set""" - self._rows = self._connection.get_rows(raw=self._raw)[0] - self._next_row = 0 - self._rowcount: int = len(self._rows) - self._handle_eof() - - def reset(self, free: bool = True) -> None: - self._rows = None - self._next_row = 0 - super().reset(free=free) - - def _fetch_row(self) -> Optional[RowType]: - """Returns the next row in the result set - - Returns a tuple or None. - """ - row = None - try: - row = self._rows[self._next_row] - except IndexError: - return None - self._next_row += 1 - return row - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if self._rows is None: - return [] - res = self._rows[self._next_row :] - self._next_row = len(self._rows) - return res - - def fetchmany(self, size: int = 1) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0: - cnt -= 1 - row = self._fetch_row() - if row: - res.append(row) - else: - break - return res - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return self._fetch_row() - - @property - def with_rows(self) -> bool: - """Returns whether the cursor could have rows returned - - This property returns True when rows are available, - which will need to be fetched. - - Returns True or False. - """ - return self._rows is not None - - -class CMySQLCursorRaw(CMySQLCursor): - """Cursor using C Extension return raw results""" - - def __init__( - self, - connection: CMySQLConnection, - ) -> None: - super().__init__(connection) - self._raw = True - - -class CMySQLCursorBufferedRaw(CMySQLCursorBuffered): - """Cursor using C Extension buffering raw results""" - - def __init__( - self, - connection: CMySQLConnection, - ): - super().__init__(connection) - self._raw = True - - -class CMySQLCursorDict(CMySQLCursor): - """Cursor using C Extension returning rows as dictionaries""" - - def fetchone(self) -> Optional[Dict[str, RowItemType]]: - """Return next row of a query result set. - - Returns: - dict or None: A dict from query result set. - """ - row = super().fetchone() - return dict(zip(self.column_names, row)) if row else None - - def fetchmany(self, size: int = 1) -> List[Dict[str, RowItemType]]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set represented - as a list of dictionaries where column names are used as keys. - """ - res = super().fetchmany(size=size) - return [dict(zip(self.column_names, row)) for row in res] - - def fetchall(self) -> List[Dict[str, RowItemType]]: - """Return all rows of a query result set. - - Returns: - list: A list of dictionaries with all rows of a query - result set where column names are used as keys. - """ - res = super().fetchall() - return [dict(zip(self.column_names, row)) for row in res] - - -class CMySQLCursorBufferedDict(CMySQLCursorBuffered): - """Cursor using C Extension buffering and returning rows as dictionaries""" - - def _fetch_row(self) -> Optional[Dict[str, RowItemType]]: - row = super()._fetch_row() - if row: - return dict(zip(self.column_names, row)) - return None - - def fetchall(self) -> List[Dict[str, RowItemType]]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - res = super().fetchall() - return [dict(zip(self.column_names, row)) for row in res] - - -class CMySQLCursorPrepared(CMySQLCursor): - """Cursor using MySQL Prepared Statements""" - - def __init__( - self, - connection: CMySQLConnection, - ): - super().__init__(connection) - self._rows: Optional[List[RowType]] = None - self._rowcount: int = 0 - self._next_row: int = 0 - self._binary: bool = True - self._stmt: Optional[CMySQLPrepStmt] = None - - def _handle_eof(self) -> None: - """Handle EOF packet""" - self._nextrow = (None, None) - self._handle_warnings() - - def _fetch_row(self, raw: bool = False) -> Optional[RowType]: - """Returns the next row in the result set - - Returns a tuple or None. - """ - if not self._stmt or not self._stmt.have_result_set: - return None - row = None - - if self._nextrow == (None, None): - (row, eof) = self._connection.get_row( - binary=self._binary, - columns=self.description, - raw=raw, - prep_stmt=self._stmt, - ) - else: - (row, eof) = self._nextrow - - if row: - self._nextrow = self._connection.get_row( - binary=self._binary, - columns=self.description, - raw=raw, - prep_stmt=self._stmt, - ) - eof = self._nextrow[1] - if eof is not None: - self._warning_count = eof["warning_count"] - self._handle_eof() - if self._rowcount == -1: - self._rowcount = 1 - else: - self._rowcount += 1 - if eof: - self._warning_count = eof["warning_count"] - self._handle_eof() - - return row - - def callproc(self, procname: Any, args: Any = None) -> NoReturn: - """Calls a stored procedue - - Not supported with CMySQLCursorPrepared. - """ - raise NotSupportedError() - - def close(self) -> None: - """Close the cursor - - This method will try to deallocate the prepared statement and close - the cursor. - """ - if self._stmt: - self.reset() - self._connection.cmd_stmt_close(self._stmt) - self._stmt = None - super().close() - - def reset(self, free: bool = True) -> None: - """Resets the prepared statement. - - Args: - free: If `True`, the result will be freed. - preserve_last_executed_stmt: If `False`, the last executed - statement value is reset. Otherwise, - such a value is preserved. - """ - if self._stmt: - self._connection.cmd_stmt_reset(self._stmt) - super().reset(free=free) - - def execute( - self, - operation: StrOrBytesAny, - params: Optional[ParamsSequenceOrDictType] = None, - map_results: bool = False, - ) -> None: - """Prepare and execute a MySQL Prepared Statement - - This method will prepare the given operation and execute it using - the given parameters. - - If the cursor instance already had a prepared statement, it is - first closed. - - Note: argument "multi" is unused. - """ - if map_results: - raise ProgrammingError( - "Multi statement execution not supported for prepared statements." - ) - - if not operation: - return - - try: - if not self._connection or self._connection.is_closed(): - raise ProgrammingError - except (ProgrammingError, ReferenceError) as err: - raise ProgrammingError("Cursor is not connected", 2055) from err - - self._connection.handle_unread_result(prepared=True) - - charset = self._connection.charset - if charset == "utf8mb4": - charset = "utf8" - - if not isinstance(operation, str): - try: - operation = operation.decode(charset) - except UnicodeDecodeError as err: - raise ProgrammingError(str(err)) from err - - if isinstance(params, dict): - replacement_keys = re.findall(RE_SQL_PYTHON_CAPTURE_PARAM_NAME, operation) - try: - # Replace params dict with params tuple in correct order. - params = tuple(params[key] for key in replacement_keys) - except KeyError as err: - raise ProgrammingError( - "Not all placeholders were found in the parameters dict" - ) from err - # Convert %(name)s to ? before sending it to MySQL - operation = re.sub(RE_SQL_PYTHON_REPLACE_PARAM, "?", operation) - - if operation is not self._executed: - if self._stmt: - self._connection.cmd_stmt_close(self._stmt) - self._executed = operation - - try: - operation = operation.encode(charset) - except UnicodeEncodeError as err: - raise ProgrammingError(str(err)) from err - - if b"%s" in operation: - # Convert %s to ? before sending it to MySQL - operation = re.sub(RE_SQL_FIND_PARAM, b"?", operation) - - try: - self._stmt = self._connection.cmd_stmt_prepare(operation) - except Error: - self._executed = None - self._stmt = None - raise - - self._connection.cmd_stmt_reset(self._stmt) - - if self._stmt.param_count > 0 and not params: - return - if params: - if not isinstance(params, (tuple, list)): - raise ProgrammingError( - errno=1210, - msg=f"Incorrect type of argument: {type(params).__name__}({params})" - ", it must be of type tuple or list the argument given to " - "the prepared statement", - ) - if self._stmt.param_count != len(params): - raise ProgrammingError( - errno=1210, - msg="Incorrect number of arguments executing prepared statement", - ) - - if params is None: - params = () - res = self._connection.cmd_stmt_execute(self._stmt, *params) - if res: - self._handle_result(res) - - def executemany( - self, operation: str, seq_params: Sequence[ParamsSequenceType] - ) -> None: - """Prepare and execute a MySQL Prepared Statement many times - - This method will prepare the given operation and execute with each - tuple found the list seq_params. - - If the cursor instance already had a prepared statement, it is - first closed. - """ - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self.with_rows: - self.fetchall() - rowcnt += self._rowcount - except (ValueError, TypeError) as err: - raise InterfaceError(f"Failed executing the operation; {err}") from err - self._rowcount = rowcnt - - def fetchone(self) -> Optional[RowType]: - """Return next row of a query result set. - - Returns: - tuple or None: A row from query result set. - """ - self._check_executed() - return self._fetch_row() or None - - def fetchmany(self, size: Optional[int] = None) -> List[RowType]: - """Return the next set of rows of a query result set. - - When no more rows are available, it returns an empty list. - The number of rows returned can be specified using the size argument, - which defaults to one. - - Returns: - list: The next set of rows of a query result set. - """ - self._check_executed() - res = [] - cnt = size or self.arraysize - while cnt > 0 and self._stmt.have_result_set: - cnt -= 1 - row = self._fetch_row() - if row: - res.append(row) - return res - - def fetchall(self) -> List[RowType]: - """Return all rows of a query result set. - - Returns: - list: A list of tuples with all rows of a query result set. - """ - self._check_executed() - if not self._stmt.have_result_set: - return [] - - rows = self._connection.get_rows(prep_stmt=self._stmt) - if self._nextrow and self._nextrow[0]: - rows[0].insert(0, self._nextrow[0]) - - if not rows[0]: - self._handle_eof() - return [] - - self._rowcount += len(rows[0]) - self._handle_eof() - return rows[0] - - -class CMySQLCursorPreparedDict(CMySQLCursorDict, CMySQLCursorPrepared): # type: ignore[misc] - """This class is a blend of features from CMySQLCursorDict and CMySQLCursorPrepared - - Multiple inheritance in python is allowed but care must be taken - when assuming methods resolution. In the case of multiple - inheritance, a given attribute is first searched in the current - class if it's not found then it's searched in the parent classes. - The parent classes are searched in a left-right fashion and each - class is searched once. - Based on python's attribute resolution, in this case, attributes - are searched as follows: - 1. CMySQLCursorPreparedDict (current class) - 2. CMySQLCursorDict (left parent class) - 3. CMySQLCursorPrepared (right parent class) - 4. CMySQLCursor (base class) - """ diff --git a/server/venv/Lib/site-packages/mysql/connector/custom_types.py b/server/venv/Lib/site-packages/mysql/connector/custom_types.py deleted file mode 100644 index c61ae49..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/custom_types.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Custom Python types used by MySQL Connector/Python""" -from __future__ import annotations - -from typing import Type - - -class HexLiteral(str): - """Class holding MySQL hex literals""" - - charset: str = "" - original: str = "" - - def __new__(cls: Type[HexLiteral], str_: str, charset: str = "utf8") -> HexLiteral: - hexed = [f"{i:02x}" for i in str_.encode(charset)] - obj = str.__new__(cls, "".join(hexed)) - obj.charset = charset - obj.original = str_ - return obj - - def __str__(self) -> str: - return "0x" + self diff --git a/server/venv/Lib/site-packages/mysql/connector/dbapi.py b/server/venv/Lib/site-packages/mysql/connector/dbapi.py deleted file mode 100644 index c780ec1..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/dbapi.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -""" -This module implements some constructors and singletons as required by the -DB API v2.0 (PEP-249). -""" - -# Python Db API v2 -# pylint: disable=invalid-name -apilevel: str = "2.0" -"""This attribute is a string that indicates the supported DB API level.""" - -threadsafety: int = 1 -"""This attribute is an integer that indicates the supported level of thread safety -provided by Connector/Python.""" - -paramstyle: str = "pyformat" -"""This attribute is a string that indicates the Connector/Python default -parameter style.""" - -import datetime -import time - -from typing import Tuple - -from . import constants - - -class _DBAPITypeObject: - def __init__(self, *values: int) -> None: - self.values: Tuple[int, ...] = values - - def __eq__(self, other: object) -> bool: - return other in self.values - - def __ne__(self, other: object) -> bool: - return other not in self.values - - -Date = datetime.date -Time = datetime.time -Timestamp = datetime.datetime - - -def DateFromTicks(ticks: int) -> datetime.date: - """Construct an object holding a date value from the given ticks value.""" - return Date(*time.localtime(ticks)[:3]) - - -def TimeFromTicks(ticks: int) -> datetime.time: - """Construct an object holding a time value from the given ticks value.""" - return Time(*time.localtime(ticks)[3:6]) - - -def TimestampFromTicks(ticks: int) -> datetime.datetime: - """Construct an object holding a time stamp from the given ticks value.""" - return Timestamp(*time.localtime(ticks)[:6]) - - -Binary = bytes - -STRING = _DBAPITypeObject(*constants.FieldType.get_string_types()) -BINARY = _DBAPITypeObject(*constants.FieldType.get_binary_types()) -NUMBER = _DBAPITypeObject(*constants.FieldType.get_number_types()) -DATETIME = _DBAPITypeObject(*constants.FieldType.get_timestamp_types()) -ROWID = _DBAPITypeObject() diff --git a/server/venv/Lib/site-packages/mysql/connector/django/__init__.py b/server/venv/Lib/site-packages/mysql/connector/django/__init__.py deleted file mode 100644 index 1f2326a..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/server/venv/Lib/site-packages/mysql/connector/django/base.py b/server/venv/Lib/site-packages/mysql/connector/django/base.py deleted file mode 100644 index 8168303..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/base.py +++ /dev/null @@ -1,654 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="override" - -"""Django database Backend using MySQL Connector/Python. - -This Django database backend is heavily based on the MySQL backend from Django. - -Changes include: -* Support for microseconds (MySQL 5.6.3 and later) -* Using INFORMATION_SCHEMA where possible -* Using new defaults for, for example SQL_AUTO_IS_NULL - -Requires and comes with MySQL Connector/Python v8.0.22 and later: - http://dev.mysql.com/downloads/connector/python/ -""" - -import warnings - -from datetime import datetime, time -from typing import ( - TYPE_CHECKING, - Any, - Dict, - Generator, - Iterator, - List, - Optional, - Sequence, - Set, - Tuple, - Union, -) - -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from django.db import IntegrityError -from django.db.backends.base.base import BaseDatabaseWrapper -from django.utils import dateparse, timezone -from django.utils.functional import cached_property - -from mysql.connector.types import MySQLConvertibleType - -try: - import mysql.connector - - from mysql.connector.conversion import MySQLConverter - from mysql.connector.custom_types import HexLiteral - from mysql.connector.pooling import PooledMySQLConnection - from mysql.connector.types import ParamsSequenceOrDictType, RowType, StrOrBytes -except ImportError as err: - raise ImproperlyConfigured(f"Error loading mysql.connector module: {err}") from err - -try: - from _mysql_connector import datetime_to_mysql -except ImportError: - HAVE_CEXT = False -else: - HAVE_CEXT = True - -from .client import DatabaseClient -from .creation import DatabaseCreation -from .features import DatabaseFeatures -from .introspection import DatabaseIntrospection -from .operations import DatabaseOperations -from .schema import DatabaseSchemaEditor -from .validation import DatabaseValidation - -Error = mysql.connector.Error -DatabaseError = mysql.connector.DatabaseError -NotSupportedError = mysql.connector.NotSupportedError -OperationalError = mysql.connector.OperationalError -ProgrammingError = mysql.connector.ProgrammingError - -if TYPE_CHECKING: - from mysql.connector.abstracts import MySQLConnectionAbstract, MySQLCursorAbstract - - -def adapt_datetime_with_timezone_support(value: datetime) -> StrOrBytes: - """Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.""" - if settings.USE_TZ: - if timezone.is_naive(value): - warnings.warn( - f"MySQL received a naive datetime ({value})" - " while time zone support is active.", - RuntimeWarning, - ) - default_timezone = timezone.get_default_timezone() - value = timezone.make_aware(value, default_timezone) - # pylint: disable=no-member - value = value.astimezone(timezone.utc).replace( # type: ignore[attr-defined] - tzinfo=None - ) - if HAVE_CEXT: - mysql_datetime: bytes = datetime_to_mysql(value) - return mysql_datetime - return value.strftime("%Y-%m-%d %H:%M:%S.%f") - - -class CursorWrapper: - """Wrapper around MySQL Connector/Python's cursor class. - - The cursor class is defined by the options passed to MySQL - Connector/Python. If buffered option is True in those options, - MySQLCursorBuffered will be used. - """ - - codes_for_integrityerror = ( - 1048, # Column cannot be null - 1690, # BIGINT UNSIGNED value is out of range - 3819, # CHECK constraint is violated - 4025, # CHECK constraint failed - ) - - def __init__(self, cursor: "MySQLCursorAbstract") -> None: - self.cursor: "MySQLCursorAbstract" = cursor - - @staticmethod - def _adapt_execute_args_dict( - args: Dict[str, MySQLConvertibleType], - ) -> Dict[str, MySQLConvertibleType]: - if not args: - return args - new_args = dict(args) - for key, value in args.items(): - if isinstance(value, datetime): - new_args[key] = adapt_datetime_with_timezone_support(value) - - return new_args - - @staticmethod - def _adapt_execute_args( - args: Optional[Sequence[MySQLConvertibleType]], - ) -> Optional[Sequence[MySQLConvertibleType]]: - if not args: - return args - new_args = list(args) - for i, arg in enumerate(args): - if isinstance(arg, datetime): - new_args[i] = adapt_datetime_with_timezone_support(arg) - - return tuple(new_args) - - def execute( - self, - query: str, - args: Optional[ - Union[Sequence[MySQLConvertibleType], Dict[str, MySQLConvertibleType]] - ] = None, - ) -> Optional[Generator["MySQLCursorAbstract", None, None]]: - """Executes the given operation - - This wrapper method around the execute()-method of the cursor is - mainly needed to re-raise using different exceptions. - """ - new_args: Optional[ParamsSequenceOrDictType] = None - if isinstance(args, dict): - new_args = self._adapt_execute_args_dict(args) - else: - new_args = self._adapt_execute_args(args) - try: - return self.cursor.execute(query, new_args) - except mysql.connector.OperationalError as exc: - if exc.args[0] in self.codes_for_integrityerror: - raise IntegrityError(*tuple(exc.args)) from None - raise - - def executemany( - self, - query: str, - args: Sequence[ - Union[Sequence[MySQLConvertibleType], Dict[str, MySQLConvertibleType]] - ], - ) -> Optional[Generator["MySQLCursorAbstract", None, None]]: - """Executes the given operation - - This wrapper method around the executemany()-method of the cursor is - mainly needed to re-raise using different exceptions. - """ - try: - return self.cursor.executemany(query, args) - except mysql.connector.OperationalError as exc: - if exc.args[0] in self.codes_for_integrityerror: - raise IntegrityError(*tuple(exc.args)) from None - raise - - def __getattr__(self, attr: Any) -> Any: - """Return an attribute of wrapped cursor""" - return getattr(self.cursor, attr) - - def __iter__(self) -> Iterator[RowType]: - """Return an iterator over wrapped cursor""" - return iter(self.cursor) - - -class DatabaseWrapper(BaseDatabaseWrapper): # pylint: disable=abstract-method - """Represent a database connection.""" - - vendor = "mysql" - # This dictionary maps Field objects to their associated MySQL column - # types, as strings. Column-type strings can contain format strings; they'll - # be interpolated against the values of Field.__dict__ before being output. - # If a column type is set to None, it won't be included in the output. - data_types = { - "AutoField": "integer AUTO_INCREMENT", - "BigAutoField": "bigint AUTO_INCREMENT", - "BinaryField": "longblob", - "BooleanField": "bool", - "CharField": "varchar(%(max_length)s)", - "DateField": "date", - "DateTimeField": "datetime(6)", - "DecimalField": "numeric(%(max_digits)s, %(decimal_places)s)", - "DurationField": "bigint", - "FileField": "varchar(%(max_length)s)", - "FilePathField": "varchar(%(max_length)s)", - "FloatField": "double precision", - "IntegerField": "integer", - "BigIntegerField": "bigint", - "IPAddressField": "char(15)", - "GenericIPAddressField": "char(39)", - "JSONField": "json", - "NullBooleanField": "bool", - "OneToOneField": "integer", - "PositiveBigIntegerField": "bigint UNSIGNED", - "PositiveIntegerField": "integer UNSIGNED", - "PositiveSmallIntegerField": "smallint UNSIGNED", - "SlugField": "varchar(%(max_length)s)", - "SmallAutoField": "smallint AUTO_INCREMENT", - "SmallIntegerField": "smallint", - "TextField": "longtext", - "TimeField": "time(6)", - "UUIDField": "char(32)", - } - - # For these data types: - # - MySQL < 8.0.13 doesn't accept default values and - # implicitly treat them as nullable - # - all versions of MySQL doesn't support full width database - # indexes - _limited_data_types = ( - "tinyblob", - "blob", - "mediumblob", - "longblob", - "tinytext", - "text", - "mediumtext", - "longtext", - "json", - ) - - operators = { - "exact": "= %s", - "iexact": "LIKE %s", - "contains": "LIKE CAST(%s AS BINARY)", - "icontains": "LIKE %s", - "gt": "> %s", - "gte": ">= %s", - "lt": "< %s", - "lte": "<= %s", - "startswith": "LIKE CAST(%s AS BINARY)", - "endswith": "LIKE CAST(%s AS BINARY)", - "istartswith": "LIKE %s", - "iendswith": "LIKE %s", - } - - # The patterns below are used to generate SQL pattern lookup clauses when - # the right-hand side of the lookup isn't a raw string (it might be an expression - # or the result of a bilateral transformation). - # In those cases, special characters for LIKE operators (e.g. \, *, _) should be - # escaped on database side. - # - # Note: we use str.format() here for readability as '%' is used as a wildcard for - # the LIKE operator. - pattern_esc = r"REPLACE(REPLACE(REPLACE({}, '\\', '\\\\'), '%%', '\%%'), '_', '\_')" - pattern_ops = { - "contains": "LIKE CAST(CONCAT('%%', {}, '%%') AS BINARY)", - "icontains": "LIKE CONCAT('%%', {}, '%%')", - "startswith": "LIKE CAST(CONCAT({}, '%%') AS BINARY)", - "istartswith": "LIKE CONCAT({}, '%%')", - "endswith": "LIKE CAST(CONCAT('%%', {}) AS BINARY)", - "iendswith": "LIKE CONCAT('%%', {})", - } - - isolation_level: Optional[str] = None - isolation_levels = { - "read uncommitted", - "read committed", - "repeatable read", - "serializable", - } - - Database = mysql.connector - SchemaEditorClass = DatabaseSchemaEditor - # Classes instantiated in __init__(). - client_class = DatabaseClient - creation_class = DatabaseCreation - features_class = DatabaseFeatures - introspection_class = DatabaseIntrospection - ops_class = DatabaseOperations - validation_class = DatabaseValidation - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - - options = self.settings_dict.get("OPTIONS") - if options: - self._use_pure = options.get("use_pure", not HAVE_CEXT) - converter_class = options.get( - "converter_class", - DjangoMySQLConverter, - ) - if not issubclass(converter_class, DjangoMySQLConverter): - raise ProgrammingError( - "Converter class should be a subclass of " - "mysql.connector.django.base.DjangoMySQLConverter" - ) - self.converter = converter_class() - else: - self.converter = DjangoMySQLConverter() - self._use_pure = not HAVE_CEXT - - def __getattr__(self, attr: str) -> bool: - if attr.startswith("mysql_is"): - return False - raise AttributeError - - def get_connection_params(self) -> Dict[str, Any]: - kwargs: dict = { - "consume_results": True, - } - - settings_dict = self.settings_dict - - if settings_dict["USER"]: - kwargs["user"] = settings_dict["USER"] - if settings_dict["NAME"]: - kwargs["database"] = settings_dict["NAME"] - if settings_dict["PASSWORD"]: - kwargs["passwd"] = settings_dict["PASSWORD"] - if settings_dict["HOST"].startswith("/"): - kwargs["unix_socket"] = settings_dict["HOST"] - elif settings_dict["HOST"]: - kwargs["host"] = settings_dict["HOST"] - if settings_dict["PORT"]: - kwargs["port"] = int(settings_dict["PORT"]) - if settings_dict.get("OPTIONS", {}).get("init_command"): - kwargs["init_command"] = settings_dict["OPTIONS"]["init_command"] - - # Raise exceptions for database warnings if DEBUG is on - kwargs["raise_on_warnings"] = settings.DEBUG - - kwargs["client_flags"] = [ - # Need potentially affected rows on UPDATE - mysql.connector.constants.ClientFlag.FOUND_ROWS, - ] - - try: - options = settings_dict["OPTIONS"].copy() - isolation_level = options.pop("isolation_level", None) - if isolation_level: - isolation_level = isolation_level.lower() - if isolation_level not in self.isolation_levels: - valid_levels = ", ".join( - f"'{level}'" for level in sorted(self.isolation_levels) - ) - raise ImproperlyConfigured( - f"Invalid transaction isolation level '{isolation_level}' " - f"specified.\nUse one of {valid_levels}, or None." - ) - self.isolation_level = isolation_level - kwargs.update(options) - except KeyError: - # OPTIONS missing is OK - pass - return kwargs - - def get_new_connection( - self, conn_params: Dict[str, Any] - ) -> Union[PooledMySQLConnection, "MySQLConnectionAbstract"]: - if "converter_class" not in conn_params: - conn_params["converter_class"] = DjangoMySQLConverter - cnx = mysql.connector.connect(**conn_params) - - return cnx - - def init_connection_state(self) -> None: - assignments = [] - if self.features.is_sql_auto_is_null_enabled: # type: ignore[attr-defined] - # SQL_AUTO_IS_NULL controls whether an AUTO_INCREMENT column on - # a recently inserted row will return when the field is tested - # for NULL. Disabling this brings this aspect of MySQL in line - # with SQL standards. - assignments.append("SET SQL_AUTO_IS_NULL = 0") - - if self.isolation_level: - assignments.append( - "SET SESSION TRANSACTION ISOLATION LEVEL " - f"{self.isolation_level.upper()}" - ) - - if assignments: - with self.cursor() as cursor: - cursor.execute("; ".join(assignments)) - - if "AUTOCOMMIT" in self.settings_dict: - try: - self.set_autocommit(self.settings_dict["AUTOCOMMIT"]) - except AttributeError: - self._set_autocommit(self.settings_dict["AUTOCOMMIT"]) - - def create_cursor(self, name: Any = None) -> CursorWrapper: - cursor = self.connection.cursor() - return CursorWrapper(cursor) - - def _rollback(self) -> None: - try: - BaseDatabaseWrapper._rollback(self) # type: ignore[attr-defined] - except NotSupportedError: - pass - - def _set_autocommit(self, autocommit: bool) -> None: - with self.wrap_database_errors: - self.connection.autocommit = autocommit - - def disable_constraint_checking(self) -> bool: - """ - Disable foreign key checks, primarily for use in adding rows with - forward references. Always return True to indicate constraint checks - need to be re-enabled. - """ - with self.cursor() as cursor: - cursor.execute("SET foreign_key_checks=0") - return True - - def enable_constraint_checking(self) -> None: - """ - Re-enable foreign key checks after they have been disabled. - """ - # Override needs_rollback in case constraint_checks_disabled is - # nested inside transaction.atomic. - self.needs_rollback, needs_rollback = False, self.needs_rollback - try: - with self.cursor() as cursor: - cursor.execute("SET foreign_key_checks=1") - finally: - self.needs_rollback = needs_rollback - - def check_constraints(self, table_names: Optional[List[str]] = None) -> None: - """ - Check each table name in `table_names` for rows with invalid foreign - key references. This method is intended to be used in conjunction with - `disable_constraint_checking()` and `enable_constraint_checking()`, to - determine if rows with invalid references were entered while constraint - checks were off. - """ - with self.cursor() as cursor: - if table_names is None: - table_names = self.introspection.table_names(cursor) - for table_name in table_names: - primary_key_column_name = self.introspection.get_primary_key_column( - cursor, table_name - ) - if not primary_key_column_name: - continue - key_columns = self.introspection.get_key_columns( # type: ignore[attr-defined] - cursor, table_name - ) - for ( - column_name, - referenced_table_name, - referenced_column_name, - ) in key_columns: - cursor.execute( - f""" - SELECT REFERRING.`{primary_key_column_name}`, - REFERRING.`{column_name}` - FROM `{table_name}` as REFERRING - LEFT JOIN `{referenced_table_name}` as REFERRED - ON ( - REFERRING.`{column_name}` = - REFERRED.`{referenced_column_name}` - ) - WHERE REFERRING.`{column_name}` IS NOT NULL - AND REFERRED.`{referenced_column_name}` IS NULL - """ - ) - for bad_row in cursor.fetchall(): - raise IntegrityError( - f"The row in table '{table_name}' with primary " - f"key '{bad_row[0]}' has an invalid foreign key: " - f"{table_name}.{column_name} contains a value " - f"'{bad_row[1]}' that does not have a " - f"corresponding value in " - f"{referenced_table_name}." - f"{referenced_column_name}." - ) - - def is_usable(self) -> bool: - try: - self.connection.ping() - except Error: - return False - return True - - @cached_property # type: ignore[misc] - @staticmethod - def display_name() -> str: - """Display name.""" - return "MySQL" - - @cached_property - def data_type_check_constraints(self) -> Dict[str, str]: - """Mapping of Field objects to their SQL for CHECK constraints.""" - if self.features.supports_column_check_constraints: - check_constraints = { - "PositiveBigIntegerField": "`%(column)s` >= 0", - "PositiveIntegerField": "`%(column)s` >= 0", - "PositiveSmallIntegerField": "`%(column)s` >= 0", - } - return check_constraints - return {} - - @cached_property - def mysql_server_data(self) -> Dict[str, Any]: - """Return MySQL server data.""" - with self.temporary_connection() as cursor: - # Select some server variables and test if the time zone - # definitions are installed. CONVERT_TZ returns NULL if 'UTC' - # timezone isn't loaded into the mysql.time_zone table. - cursor.execute( - """ - SELECT VERSION(), - @@sql_mode, - @@default_storage_engine, - @@sql_auto_is_null, - @@lower_case_table_names, - CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL - """ - ) - row = cursor.fetchone() - return { - "version": row[0], - "sql_mode": row[1], - "default_storage_engine": row[2], - "sql_auto_is_null": bool(row[3]), - "lower_case_table_names": bool(row[4]), - "has_zoneinfo_database": bool(row[5]), - } - - @cached_property - def mysql_server_info(self) -> Any: - """Return MySQL version.""" - if self.connection: - return self.connection.server_info - with self.temporary_connection() as cursor: - cursor.execute("SELECT VERSION()") - return cursor.fetchone()[0] - - @cached_property - def mysql_version(self) -> Tuple[int, ...]: - """Return MySQL version.""" - if self.connection: - return self.connection.server_version - config = self.get_connection_params() - with mysql.connector.connect(**config) as conn: - server_version: Tuple[int, ...] = conn.server_version - return server_version - - @cached_property - def sql_mode(self) -> Set[str]: - """Return SQL mode.""" - if self.connection: - return set(self.connection.sql_mode.split(",")) - with self.cursor() as cursor: - cursor.execute("SELECT @@sql_mode") - sql_mode = cursor.fetchone() - return set(sql_mode[0].split(",") if sql_mode else ()) - - @property - def use_pure(self) -> bool: - """Return True if pure Python version is being used.""" - ans: bool = self._use_pure - return ans - - -class DjangoMySQLConverter(MySQLConverter): - """Custom converter for Django.""" - - # pylint: disable=unused-argument - - @staticmethod - def _time_to_python(value: bytes, dsc: Any = None) -> Optional[time]: - """Return MySQL TIME data type as datetime.time() - - Returns datetime.time() - """ - return dateparse.parse_time(value.decode("utf-8")) - - @staticmethod - def _datetime_to_python(value: bytes, dsc: Any = None) -> Optional[datetime]: - """Connector/Python always returns naive datetime.datetime - - Connector/Python always returns naive timestamps since MySQL has - no time zone support. - - - A naive datetime is a datetime that doesn't know its own timezone. - - Django needs a non-naive datetime, but in this method we don't need - to make a datetime value time zone aware since Django itself at some - point will make it aware (at least in versions 3.2.16 and 4.1.2) when - USE_TZ=True. This may change in a future release, we need to keep an - eye on this behaviour. - - Returns datetime.datetime() - """ - return MySQLConverter._datetime_to_python(value) if value else None - - # pylint: enable=unused-argument - - def _safestring_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: - return self._str_to_mysql(value) - - def _safetext_to_mysql(self, value: str) -> Union[bytes, HexLiteral]: - return self._str_to_mysql(value) - - def _safebytes_to_mysql(self, value: bytes) -> bytes: - return self._bytes_to_mysql(value) diff --git a/server/venv/Lib/site-packages/mysql/connector/django/client.py b/server/venv/Lib/site-packages/mysql/connector/django/client.py deleted file mode 100644 index 8080bbb..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/client.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Database Client.""" - -import os -import subprocess - -from typing import Any, Dict, Iterable, List, Optional, Tuple - -from django.db.backends.base.client import BaseDatabaseClient - - -class DatabaseClient(BaseDatabaseClient): - """Encapsulate backend-specific methods for opening a client shell.""" - - executable_name = "mysql" - - @classmethod - def settings_to_cmd_args_env( - cls, settings_dict: Dict[str, Any], parameters: Optional[Iterable[str]] = None - ) -> Tuple[List[str], Optional[Dict[str, Any]]]: - args = [cls.executable_name] - - db = settings_dict["OPTIONS"].get("database", settings_dict["NAME"]) - user = settings_dict["OPTIONS"].get("user", settings_dict["USER"]) - passwd = settings_dict["OPTIONS"].get("password", settings_dict["PASSWORD"]) - host = settings_dict["OPTIONS"].get("host", settings_dict["HOST"]) - port = settings_dict["OPTIONS"].get("port", settings_dict["PORT"]) - ssl_ca = settings_dict["OPTIONS"].get("ssl_ca") - ssl_cert = settings_dict["OPTIONS"].get("ssl_cert") - ssl_key = settings_dict["OPTIONS"].get("ssl_key") - defaults_file = settings_dict["OPTIONS"].get("read_default_file") - charset = settings_dict["OPTIONS"].get("charset") - - # --defaults-file should always be the first option - if defaults_file: - args.append(f"--defaults-file={defaults_file}") - - # Load any custom init_commands. We always force SQL_MODE to TRADITIONAL - init_command = settings_dict["OPTIONS"].get("init_command", "") - args.append(f"--init-command=SET @@session.SQL_MODE=TRADITIONAL;{init_command}") - - if user: - args.append(f"--user={user}") - if passwd: - args.append(f"--password={passwd}") - - if host: - if "/" in host: - args.append(f"--socket={host}") - else: - args.append(f"--host={host}") - - if port: - args.append(f"--port={port}") - - if db: - args.append(f"--database={db}") - - if ssl_ca: - args.append(f"--ssl-ca={ssl_ca}") - if ssl_cert: - args.append(f"--ssl-cert={ssl_cert}") - if ssl_key: - args.append(f"--ssl-key={ssl_key}") - - if charset: - args.append(f"--default-character-set={charset}") - - if parameters: - args.extend(parameters) - - return args, None - - def runshell(self, parameters: Optional[Iterable[str]] = None) -> None: - args, env = self.settings_to_cmd_args_env( - self.connection.settings_dict, parameters - ) - env = {**os.environ, **env} if env else None - subprocess.run(args, env=env, check=True) diff --git a/server/venv/Lib/site-packages/mysql/connector/django/compiler.py b/server/venv/Lib/site-packages/mysql/connector/django/compiler.py deleted file mode 100644 index 083758a..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/compiler.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""SQL Compiler classes.""" - -from django.db.backends.mysql.compiler import ( - SQLAggregateCompiler, - SQLCompiler, - SQLDeleteCompiler, - SQLInsertCompiler, - SQLUpdateCompiler, -) - -__all__ = [ - "SQLAggregateCompiler", - "SQLCompiler", - "SQLDeleteCompiler", - "SQLInsertCompiler", - "SQLUpdateCompiler", -] diff --git a/server/venv/Lib/site-packages/mysql/connector/django/creation.py b/server/venv/Lib/site-packages/mysql/connector/django/creation.py deleted file mode 100644 index 2a880e7..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/creation.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Backend specific database creation.""" - -from django.db.backends.mysql.creation import DatabaseCreation - -__all__ = ["DatabaseCreation"] diff --git a/server/venv/Lib/site-packages/mysql/connector/django/features.py b/server/venv/Lib/site-packages/mysql/connector/django/features.py deleted file mode 100644 index 5c1dc8d..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/features.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Database Features.""" - -from typing import Any, List - -from django.db.backends.mysql.features import DatabaseFeatures as MySQLDatabaseFeatures -from django.utils.functional import cached_property - - -class DatabaseFeatures(MySQLDatabaseFeatures): - """Database Features Specification class.""" - - empty_fetchmany_value: List[Any] = [] - - @cached_property - def can_introspect_check_constraints(self) -> bool: # type: ignore[override] - """Check if backend support introspection CHECK of constraints.""" - return self.connection.mysql_version >= (8, 0, 16) - - @cached_property - def supports_microsecond_precision(self) -> bool: - """Check if backend support microsecond precision.""" - return self.connection.mysql_version >= (5, 6, 3) diff --git a/server/venv/Lib/site-packages/mysql/connector/django/introspection.py b/server/venv/Lib/site-packages/mysql/connector/django/introspection.py deleted file mode 100644 index 8c7d773..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/introspection.py +++ /dev/null @@ -1,461 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="override,attr-defined,call-arg" - -"""Database Introspection.""" - -from collections import namedtuple -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple - -import sqlparse - -from django import VERSION as DJANGO_VERSION -from django.db.backends.base.introspection import ( - BaseDatabaseIntrospection, - FieldInfo as BaseFieldInfo, - TableInfo, -) -from django.db.models import Index -from django.utils.datastructures import OrderedSet - -from mysql.connector.constants import FieldType - -# from .base import CursorWrapper produces a circular import error, -# avoiding importing CursorWrapper explicitly, using a documented -# trick; write the imports inside if TYPE_CHECKING: so that they -# are not executed at runtime. -# Ref: https://buildmedia.readthedocs.org/media/pdf/mypy/stable/mypy.pdf [page 42] -if TYPE_CHECKING: - # CursorWraper is used exclusively for type hinting - from mysql.connector.django.base import CursorWrapper - -# Based on my investigation, named tuples to -# comply with mypy need to define a static list or tuple -# for field_names (second argument). In this case, the field -# names are created dynamically for FieldInfo which triggers -# a mypy error. The solution is not straightforward since -# FieldInfo attributes are Django version dependent. Code -# refactory is needed to fix this issue. -FieldInfo = namedtuple( # type: ignore[misc] - "FieldInfo", - BaseFieldInfo._fields + ("extra", "is_unsigned", "has_json_constraint"), -) -if DJANGO_VERSION < (3, 2, 0): - InfoLine = namedtuple( - "InfoLine", - "col_name data_type max_len num_prec num_scale extra column_default " - "is_unsigned", - ) -else: - InfoLine = namedtuple( # type: ignore[no-redef] - "InfoLine", - "col_name data_type max_len num_prec num_scale extra column_default " - "collation is_unsigned", - ) - - -class DatabaseIntrospection(BaseDatabaseIntrospection): - """Encapsulate backend-specific introspection utilities.""" - - data_types_reverse = { - FieldType.BLOB: "TextField", - FieldType.DECIMAL: "DecimalField", - FieldType.NEWDECIMAL: "DecimalField", - FieldType.DATE: "DateField", - FieldType.DATETIME: "DateTimeField", - FieldType.DOUBLE: "FloatField", - FieldType.FLOAT: "FloatField", - FieldType.INT24: "IntegerField", - FieldType.LONG: "IntegerField", - FieldType.LONGLONG: "BigIntegerField", - FieldType.SHORT: "SmallIntegerField", - FieldType.STRING: "CharField", - FieldType.TIME: "TimeField", - FieldType.TIMESTAMP: "DateTimeField", - FieldType.TINY: "IntegerField", - FieldType.TINY_BLOB: "TextField", - FieldType.MEDIUM_BLOB: "TextField", - FieldType.LONG_BLOB: "TextField", - FieldType.VAR_STRING: "CharField", - } - - def get_field_type(self, data_type: str, description: FieldInfo) -> str: - field_type = super().get_field_type(data_type, description) # type: ignore[arg-type] - if "auto_increment" in description.extra: - if field_type == "IntegerField": - return "AutoField" - if field_type == "BigIntegerField": - return "BigAutoField" - if field_type == "SmallIntegerField": - return "SmallAutoField" - if description.is_unsigned: - if field_type == "BigIntegerField": - return "PositiveBigIntegerField" - if field_type == "IntegerField": - return "PositiveIntegerField" - if field_type == "SmallIntegerField": - return "PositiveSmallIntegerField" - # JSON data type is an alias for LONGTEXT in MariaDB, use check - # constraints clauses to introspect JSONField. - if description.has_json_constraint: - return "JSONField" - return field_type - - def get_table_list(self, cursor: "CursorWrapper") -> List[TableInfo]: - """Return a list of table and view names in the current database.""" - cursor.execute("SHOW FULL TABLES") - return [ - TableInfo(row[0], {"BASE TABLE": "t", "VIEW": "v"}.get(row[1])) - for row in cursor.fetchall() - ] - - def get_table_description( - self, cursor: "CursorWrapper", table_name: str - ) -> List[FieldInfo]: - """ - Return a description of the table with the DB-API cursor.description - interface." - """ - json_constraints: Dict[Any, Any] = {} - # A default collation for the given table. - cursor.execute( - """ - SELECT table_collation - FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name = %s - """, - [table_name], - ) - row = cursor.fetchone() - default_column_collation = row[0] if row else "" - # information_schema database gives more accurate results for some figures: - # - varchar length returned by cursor.description is an internal length, - # not visible length (#5725) - # - precision and scale (for decimal fields) (#5014) - # - auto_increment is not available in cursor.description - if DJANGO_VERSION < (3, 2, 0): - cursor.execute( - """ - SELECT - column_name, data_type, character_maximum_length, - numeric_precision, numeric_scale, extra, column_default, - CASE - WHEN column_type LIKE '%% unsigned' THEN 1 - ELSE 0 - END AS is_unsigned - FROM information_schema.columns - WHERE table_name = %s AND table_schema = DATABASE() - """, - [table_name], - ) - else: - cursor.execute( - """ - SELECT - column_name, data_type, character_maximum_length, - numeric_precision, numeric_scale, extra, column_default, - CASE - WHEN collation_name = %s THEN NULL - ELSE collation_name - END AS collation_name, - CASE - WHEN column_type LIKE '%% unsigned' THEN 1 - ELSE 0 - END AS is_unsigned - FROM information_schema.columns - WHERE table_name = %s AND table_schema = DATABASE() - """, - [default_column_collation, table_name], - ) - field_info = {line[0]: InfoLine(*line) for line in cursor.fetchall()} - - cursor.execute( - f"SELECT * FROM {self.connection.ops.quote_name(table_name)} LIMIT 1" - ) - - def to_int(i: Any) -> Optional[int]: - return int(i) if i is not None else i - - fields = [] - for line in cursor.description: - info = field_info[line[0]] - if DJANGO_VERSION < (3, 2, 0): - fields.append( - FieldInfo( - *line[:3], - to_int(info.max_len) or line[3], - to_int(info.num_prec) or line[4], - to_int(info.num_scale) or line[5], - line[6], - info.column_default, - info.extra, - info.is_unsigned, - line[0] in json_constraints, - ) - ) - else: - fields.append( - FieldInfo( - *line[:3], - to_int(info.max_len) or line[3], - to_int(info.num_prec) or line[4], - to_int(info.num_scale) or line[5], - line[6], - info.column_default, - info.collation, - info.extra, - info.is_unsigned, - line[0] in json_constraints, - ) - ) - return fields - - def get_indexes( - self, cursor: "CursorWrapper", table_name: str - ) -> Dict[int, Dict[str, bool]]: - """Return indexes from table.""" - cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}") - # Do a two-pass search for indexes: on first pass check which indexes - # are multicolumn, on second pass check which single-column indexes - # are present. - rows = list(cursor.fetchall()) - multicol_indexes = set() - for row in rows: - if row[3] > 1: - multicol_indexes.add(row[2]) - indexes: Dict[int, Dict[str, bool]] = {} - for row in rows: - if row[2] in multicol_indexes: - continue - if row[4] not in indexes: - indexes[row[4]] = {"primary_key": False, "unique": False} - # It's possible to have the unique and PK constraints in - # separate indexes. - if row[2] == "PRIMARY": - indexes[row[4]]["primary_key"] = True - if not row[1]: - indexes[row[4]]["unique"] = True - return indexes - - def get_primary_key_column( - self, cursor: "CursorWrapper", table_name: str - ) -> Optional[int]: - """ - Returns the name of the primary key column for the given table - """ - for column in self.get_indexes(cursor, table_name).items(): - if column[1]["primary_key"]: - return column[0] - return None - - def get_sequences( - self, cursor: "CursorWrapper", table_name: str, table_fields: Any = () - ) -> List[Dict[str, str]]: - for field_info in self.get_table_description(cursor, table_name): - if "auto_increment" in field_info.extra: - # MySQL allows only one auto-increment column per table. - return [{"table": table_name, "column": field_info.name}] - return [] - - def get_relations( - self, cursor: "CursorWrapper", table_name: str - ) -> Dict[str, Tuple[str, str]]: - """ - Return a dictionary of {field_name: (field_name_other_table, other_table)} - representing all relationships to the given table. - """ - constraints = self.get_key_columns(cursor, table_name) - relations = {} - for my_fieldname, other_table, other_field in constraints: - relations[my_fieldname] = (other_field, other_table) - return relations - - def get_key_columns( - self, cursor: "CursorWrapper", table_name: str - ) -> List[Tuple[str, str, str]]: - """ - Return a list of (column_name, referenced_table_name, referenced_column_name) - for all key columns in the given table. - """ - key_columns: List[Any] = [] - cursor.execute( - """ - SELECT column_name, referenced_table_name, referenced_column_name - FROM information_schema.key_column_usage - WHERE table_name = %s - AND table_schema = DATABASE() - AND referenced_table_name IS NOT NULL - AND referenced_column_name IS NOT NULL""", - [table_name], - ) - key_columns.extend(cursor.fetchall()) - return key_columns - - def get_storage_engine(self, cursor: "CursorWrapper", table_name: str) -> str: - """ - Retrieve the storage engine for a given table. Return the default - storage engine if the table doesn't exist. - """ - cursor.execute( - "SELECT engine FROM information_schema.tables WHERE table_name = %s", - [table_name], - ) - result = cursor.fetchone() - # pylint: disable=protected-access - if not result: - return self.connection.features._mysql_storage_engine - # pylint: enable=protected-access - return result[0] - - def _parse_constraint_columns( - self, check_clause: Any, columns: Set[str] - ) -> OrderedSet: - check_columns: OrderedSet = OrderedSet() - statement = sqlparse.parse(check_clause)[0] - tokens = (token for token in statement.flatten() if not token.is_whitespace) - for token in tokens: - if ( - token.ttype == sqlparse.tokens.Name - and self.connection.ops.quote_name(token.value) == token.value - and token.value[1:-1] in columns - ): - check_columns.add(token.value[1:-1]) - return check_columns - - def get_constraints( - self, cursor: "CursorWrapper", table_name: str - ) -> Dict[str, Any]: - """ - Retrieve any constraints or keys (unique, pk, fk, check, index) across - one or more columns. - """ - constraints: Dict[str, Any] = {} - # Get the actual constraint names and columns - name_query = """ - SELECT kc.`constraint_name`, kc.`column_name`, - kc.`referenced_table_name`, kc.`referenced_column_name` - FROM information_schema.key_column_usage AS kc - WHERE - kc.table_schema = DATABASE() AND - kc.table_name = %s - ORDER BY kc.`ordinal_position` - """ - cursor.execute(name_query, [table_name]) - for constraint, column, ref_table, ref_column in cursor.fetchall(): - if constraint not in constraints: - constraints[constraint] = { - "columns": OrderedSet(), - "primary_key": False, - "unique": False, - "index": False, - "check": False, - "foreign_key": (ref_table, ref_column) if ref_column else None, - } - if self.connection.features.supports_index_column_ordering: - constraints[constraint]["orders"] = [] - constraints[constraint]["columns"].add(column) - # Now get the constraint types - type_query = """ - SELECT c.constraint_name, c.constraint_type - FROM information_schema.table_constraints AS c - WHERE - c.table_schema = DATABASE() AND - c.table_name = %s - """ - cursor.execute(type_query, [table_name]) - for constraint, kind in cursor.fetchall(): - if kind.lower() == "primary key": - constraints[constraint]["primary_key"] = True - constraints[constraint]["unique"] = True - elif kind.lower() == "unique": - constraints[constraint]["unique"] = True - # Add check constraints. - if self.connection.features.can_introspect_check_constraints: - unnamed_constraints_index = 0 - columns = { - info.name for info in self.get_table_description(cursor, table_name) - } - type_query = """ - SELECT cc.constraint_name, cc.check_clause - FROM - information_schema.check_constraints AS cc, - information_schema.table_constraints AS tc - WHERE - cc.constraint_schema = DATABASE() AND - tc.table_schema = cc.constraint_schema AND - cc.constraint_name = tc.constraint_name AND - tc.constraint_type = 'CHECK' AND - tc.table_name = %s - """ - cursor.execute(type_query, [table_name]) - for constraint, check_clause in cursor.fetchall(): - constraint_columns = self._parse_constraint_columns( - check_clause, columns - ) - # Ensure uniqueness of unnamed constraints. Unnamed unique - # and check columns constraints have the same name as - # a column. - if set(constraint_columns) == {constraint}: - unnamed_constraints_index += 1 - constraint = f"__unnamed_constraint_{unnamed_constraints_index}__" - constraints[constraint] = { - "columns": constraint_columns, - "primary_key": False, - "unique": False, - "index": False, - "check": True, - "foreign_key": None, - } - # Now add in the indexes - cursor.execute(f"SHOW INDEX FROM {self.connection.ops.quote_name(table_name)}") - for _, _, index, _, column, order, type_ in [ - x[:6] + (x[10],) for x in cursor.fetchall() - ]: - if index not in constraints: - constraints[index] = { - "columns": OrderedSet(), - "primary_key": False, - "unique": False, - "check": False, - "foreign_key": None, - } - if self.connection.features.supports_index_column_ordering: - constraints[index]["orders"] = [] - constraints[index]["index"] = True - constraints[index]["type"] = ( - Index.suffix if type_ == "BTREE" else type_.lower() - ) - constraints[index]["columns"].add(column) - if self.connection.features.supports_index_column_ordering: - constraints[index]["orders"].append("DESC" if order == "D" else "ASC") - # Convert the sorted sets to lists - for constraint in constraints.values(): - constraint["columns"] = list(constraint["columns"]) - return constraints diff --git a/server/venv/Lib/site-packages/mysql/connector/django/operations.py b/server/venv/Lib/site-packages/mysql/connector/django/operations.py deleted file mode 100644 index 8491181..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/operations.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="override,attr-defined" - -"""Database Operations.""" - -from datetime import datetime, time, timezone -from typing import Optional - -from django.conf import settings -from django.db.backends.mysql.operations import ( - DatabaseOperations as MySQLDatabaseOperations, -) -from django.utils import timezone as django_timezone - -try: - from _mysql_connector import datetime_to_mysql, time_to_mysql -except ImportError: - HAVE_CEXT = False -else: - HAVE_CEXT = True - - -class DatabaseOperations(MySQLDatabaseOperations): - """Database Operations class.""" - - compiler_module = "mysql.connector.django.compiler" - - def regex_lookup(self, lookup_type: str) -> str: - """Return the string to use in a query when performing regular - expression lookup.""" - if self.connection.mysql_version < (8, 0, 0): - if lookup_type == "regex": - return "%s REGEXP BINARY %s" - return "%s REGEXP %s" - - match_option = "c" if lookup_type == "regex" else "i" - return f"REGEXP_LIKE(%s, %s, '{match_option}')" - - def adapt_datetimefield_value(self, value: Optional[datetime]) -> Optional[bytes]: - """Transform a datetime value to an object compatible with what is - expected by the backend driver for datetime columns.""" - return self.value_to_db_datetime(value) - - def value_to_db_datetime(self, value: Optional[datetime]) -> Optional[bytes]: - """Convert value to MySQL DATETIME.""" - ans: Optional[bytes] = None - if value is None: - return ans - # MySQL doesn't support tz-aware times - if django_timezone.is_aware(value): - if settings.USE_TZ: - value = value.astimezone(timezone.utc).replace(tzinfo=None) - else: - raise ValueError("MySQL backend does not support timezone-aware times") - if not self.connection.features.supports_microsecond_precision: - value = value.replace(microsecond=0) - if not self.connection.use_pure: - return datetime_to_mysql(value) - return self.connection.converter.to_mysql(value) - - def adapt_timefield_value(self, value: Optional[time]) -> Optional[bytes]: - """Transform a time value to an object compatible with what is expected - by the backend driver for time columns.""" - return self.value_to_db_time(value) - - def value_to_db_time(self, value: Optional[time]) -> Optional[bytes]: - """Convert value to MySQL TIME.""" - if value is None: - return None - - # MySQL doesn't support tz-aware times - if django_timezone.is_aware(value): - raise ValueError("MySQL backend does not support timezone-aware times") - - if not self.connection.use_pure: - return time_to_mysql(value) - return self.connection.converter.to_mysql(value) diff --git a/server/venv/Lib/site-packages/mysql/connector/django/schema.py b/server/venv/Lib/site-packages/mysql/connector/django/schema.py deleted file mode 100644 index 59808df..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/schema.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="override" - -"""Database schema editor.""" -from typing import Any - -from django.db.backends.mysql.schema import ( - DatabaseSchemaEditor as MySQLDatabaseSchemaEditor, -) - - -class DatabaseSchemaEditor(MySQLDatabaseSchemaEditor): - """This class is responsible for emitting schema-changing statements to the - databases. - """ - - def quote_value(self, value: Any) -> Any: - """Quote value.""" - self.connection.ensure_connection() - if isinstance(value, str): - value = value.replace("%", "%%") - quoted = self.connection.connection.converter.escape(value) - if isinstance(value, str) and isinstance(quoted, bytes): - quoted = quoted.decode() - return quoted - - def prepare_default(self, value: Any) -> Any: - """Implement the required abstract method. - - MySQL has requires_literal_defaults=False, therefore return the value. - """ - return value diff --git a/server/venv/Lib/site-packages/mysql/connector/django/validation.py b/server/venv/Lib/site-packages/mysql/connector/django/validation.py deleted file mode 100644 index 98e62d3..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/django/validation.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2020, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Backend specific database validation.""" - -from django.db.backends.mysql.validation import DatabaseValidation - -__all__ = ["DatabaseValidation"] diff --git a/server/venv/Lib/site-packages/mysql/connector/errorcode.py b/server/venv/Lib/site-packages/mysql/connector/errorcode.py deleted file mode 100644 index 1660ef4..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/errorcode.py +++ /dev/null @@ -1,1877 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2013, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""This module contains the MySQL Server and Client error codes.""" - -# This file was auto-generated. -_GENERATED_ON = "2021-08-11" -_MYSQL_VERSION = (8, 0, 27) - -# Start MySQL Errors -OBSOLETE_ER_HASHCHK = 1000 -OBSOLETE_ER_NISAMCHK = 1001 -ER_NO = 1002 -ER_YES = 1003 -ER_CANT_CREATE_FILE = 1004 -ER_CANT_CREATE_TABLE = 1005 -ER_CANT_CREATE_DB = 1006 -ER_DB_CREATE_EXISTS = 1007 -ER_DB_DROP_EXISTS = 1008 -OBSOLETE_ER_DB_DROP_DELETE = 1009 -ER_DB_DROP_RMDIR = 1010 -OBSOLETE_ER_CANT_DELETE_FILE = 1011 -ER_CANT_FIND_SYSTEM_REC = 1012 -ER_CANT_GET_STAT = 1013 -OBSOLETE_ER_CANT_GET_WD = 1014 -ER_CANT_LOCK = 1015 -ER_CANT_OPEN_FILE = 1016 -ER_FILE_NOT_FOUND = 1017 -ER_CANT_READ_DIR = 1018 -OBSOLETE_ER_CANT_SET_WD = 1019 -ER_CHECKREAD = 1020 -OBSOLETE_ER_DISK_FULL = 1021 -ER_DUP_KEY = 1022 -OBSOLETE_ER_ERROR_ON_CLOSE = 1023 -ER_ERROR_ON_READ = 1024 -ER_ERROR_ON_RENAME = 1025 -ER_ERROR_ON_WRITE = 1026 -ER_FILE_USED = 1027 -OBSOLETE_ER_FILSORT_ABORT = 1028 -OBSOLETE_ER_FORM_NOT_FOUND = 1029 -ER_GET_ERRNO = 1030 -ER_ILLEGAL_HA = 1031 -ER_KEY_NOT_FOUND = 1032 -ER_NOT_FORM_FILE = 1033 -ER_NOT_KEYFILE = 1034 -ER_OLD_KEYFILE = 1035 -ER_OPEN_AS_READONLY = 1036 -ER_OUTOFMEMORY = 1037 -ER_OUT_OF_SORTMEMORY = 1038 -OBSOLETE_ER_UNEXPECTED_EOF = 1039 -ER_CON_COUNT_ERROR = 1040 -ER_OUT_OF_RESOURCES = 1041 -ER_BAD_HOST_ERROR = 1042 -ER_HANDSHAKE_ERROR = 1043 -ER_DBACCESS_DENIED_ERROR = 1044 -ER_ACCESS_DENIED_ERROR = 1045 -ER_NO_DB_ERROR = 1046 -ER_UNKNOWN_COM_ERROR = 1047 -ER_BAD_NULL_ERROR = 1048 -ER_BAD_DB_ERROR = 1049 -ER_TABLE_EXISTS_ERROR = 1050 -ER_BAD_TABLE_ERROR = 1051 -ER_NON_UNIQ_ERROR = 1052 -ER_SERVER_SHUTDOWN = 1053 -ER_BAD_FIELD_ERROR = 1054 -ER_WRONG_FIELD_WITH_GROUP = 1055 -ER_WRONG_GROUP_FIELD = 1056 -ER_WRONG_SUM_SELECT = 1057 -ER_WRONG_VALUE_COUNT = 1058 -ER_TOO_LONG_IDENT = 1059 -ER_DUP_FIELDNAME = 1060 -ER_DUP_KEYNAME = 1061 -ER_DUP_ENTRY = 1062 -ER_WRONG_FIELD_SPEC = 1063 -ER_PARSE_ERROR = 1064 -ER_EMPTY_QUERY = 1065 -ER_NONUNIQ_TABLE = 1066 -ER_INVALID_DEFAULT = 1067 -ER_MULTIPLE_PRI_KEY = 1068 -ER_TOO_MANY_KEYS = 1069 -ER_TOO_MANY_KEY_PARTS = 1070 -ER_TOO_LONG_KEY = 1071 -ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 -ER_BLOB_USED_AS_KEY = 1073 -ER_TOO_BIG_FIELDLENGTH = 1074 -ER_WRONG_AUTO_KEY = 1075 -ER_READY = 1076 -OBSOLETE_ER_NORMAL_SHUTDOWN = 1077 -OBSOLETE_ER_GOT_SIGNAL = 1078 -ER_SHUTDOWN_COMPLETE = 1079 -ER_FORCING_CLOSE = 1080 -ER_IPSOCK_ERROR = 1081 -ER_NO_SUCH_INDEX = 1082 -ER_WRONG_FIELD_TERMINATORS = 1083 -ER_BLOBS_AND_NO_TERMINATED = 1084 -ER_TEXTFILE_NOT_READABLE = 1085 -ER_FILE_EXISTS_ERROR = 1086 -ER_LOAD_INFO = 1087 -ER_ALTER_INFO = 1088 -ER_WRONG_SUB_KEY = 1089 -ER_CANT_REMOVE_ALL_FIELDS = 1090 -ER_CANT_DROP_FIELD_OR_KEY = 1091 -ER_INSERT_INFO = 1092 -ER_UPDATE_TABLE_USED = 1093 -ER_NO_SUCH_THREAD = 1094 -ER_KILL_DENIED_ERROR = 1095 -ER_NO_TABLES_USED = 1096 -ER_TOO_BIG_SET = 1097 -ER_NO_UNIQUE_LOGFILE = 1098 -ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 -ER_TABLE_NOT_LOCKED = 1100 -ER_BLOB_CANT_HAVE_DEFAULT = 1101 -ER_WRONG_DB_NAME = 1102 -ER_WRONG_TABLE_NAME = 1103 -ER_TOO_BIG_SELECT = 1104 -ER_UNKNOWN_ERROR = 1105 -ER_UNKNOWN_PROCEDURE = 1106 -ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 -ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 -ER_UNKNOWN_TABLE = 1109 -ER_FIELD_SPECIFIED_TWICE = 1110 -ER_INVALID_GROUP_FUNC_USE = 1111 -ER_UNSUPPORTED_EXTENSION = 1112 -ER_TABLE_MUST_HAVE_COLUMNS = 1113 -ER_RECORD_FILE_FULL = 1114 -ER_UNKNOWN_CHARACTER_SET = 1115 -ER_TOO_MANY_TABLES = 1116 -ER_TOO_MANY_FIELDS = 1117 -ER_TOO_BIG_ROWSIZE = 1118 -ER_STACK_OVERRUN = 1119 -ER_WRONG_OUTER_JOIN_UNUSED = 1120 -ER_NULL_COLUMN_IN_INDEX = 1121 -ER_CANT_FIND_UDF = 1122 -ER_CANT_INITIALIZE_UDF = 1123 -ER_UDF_NO_PATHS = 1124 -ER_UDF_EXISTS = 1125 -ER_CANT_OPEN_LIBRARY = 1126 -ER_CANT_FIND_DL_ENTRY = 1127 -ER_FUNCTION_NOT_DEFINED = 1128 -ER_HOST_IS_BLOCKED = 1129 -ER_HOST_NOT_PRIVILEGED = 1130 -ER_PASSWORD_ANONYMOUS_USER = 1131 -ER_PASSWORD_NOT_ALLOWED = 1132 -ER_PASSWORD_NO_MATCH = 1133 -ER_UPDATE_INFO = 1134 -ER_CANT_CREATE_THREAD = 1135 -ER_WRONG_VALUE_COUNT_ON_ROW = 1136 -ER_CANT_REOPEN_TABLE = 1137 -ER_INVALID_USE_OF_NULL = 1138 -ER_REGEXP_ERROR = 1139 -ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 -ER_NONEXISTING_GRANT = 1141 -ER_TABLEACCESS_DENIED_ERROR = 1142 -ER_COLUMNACCESS_DENIED_ERROR = 1143 -ER_ILLEGAL_GRANT_FOR_TABLE = 1144 -ER_GRANT_WRONG_HOST_OR_USER = 1145 -ER_NO_SUCH_TABLE = 1146 -ER_NONEXISTING_TABLE_GRANT = 1147 -ER_NOT_ALLOWED_COMMAND = 1148 -ER_SYNTAX_ERROR = 1149 -OBSOLETE_ER_UNUSED1 = 1150 -OBSOLETE_ER_UNUSED2 = 1151 -ER_ABORTING_CONNECTION = 1152 -ER_NET_PACKET_TOO_LARGE = 1153 -ER_NET_READ_ERROR_FROM_PIPE = 1154 -ER_NET_FCNTL_ERROR = 1155 -ER_NET_PACKETS_OUT_OF_ORDER = 1156 -ER_NET_UNCOMPRESS_ERROR = 1157 -ER_NET_READ_ERROR = 1158 -ER_NET_READ_INTERRUPTED = 1159 -ER_NET_ERROR_ON_WRITE = 1160 -ER_NET_WRITE_INTERRUPTED = 1161 -ER_TOO_LONG_STRING = 1162 -ER_TABLE_CANT_HANDLE_BLOB = 1163 -ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 -OBSOLETE_ER_UNUSED3 = 1165 -ER_WRONG_COLUMN_NAME = 1166 -ER_WRONG_KEY_COLUMN = 1167 -ER_WRONG_MRG_TABLE = 1168 -ER_DUP_UNIQUE = 1169 -ER_BLOB_KEY_WITHOUT_LENGTH = 1170 -ER_PRIMARY_CANT_HAVE_NULL = 1171 -ER_TOO_MANY_ROWS = 1172 -ER_REQUIRES_PRIMARY_KEY = 1173 -OBSOLETE_ER_NO_RAID_COMPILED = 1174 -ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 -ER_KEY_DOES_NOT_EXITS = 1176 -ER_CHECK_NO_SUCH_TABLE = 1177 -ER_CHECK_NOT_IMPLEMENTED = 1178 -ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 -ER_ERROR_DURING_COMMIT = 1180 -ER_ERROR_DURING_ROLLBACK = 1181 -ER_ERROR_DURING_FLUSH_LOGS = 1182 -OBSOLETE_ER_ERROR_DURING_CHECKPOINT = 1183 -ER_NEW_ABORTING_CONNECTION = 1184 -OBSOLETE_ER_DUMP_NOT_IMPLEMENTED = 1185 -OBSOLETE_ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 -OBSOLETE_ER_INDEX_REBUILD = 1187 -ER_MASTER = 1188 -ER_MASTER_NET_READ = 1189 -ER_MASTER_NET_WRITE = 1190 -ER_FT_MATCHING_KEY_NOT_FOUND = 1191 -ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 -ER_UNKNOWN_SYSTEM_VARIABLE = 1193 -ER_CRASHED_ON_USAGE = 1194 -ER_CRASHED_ON_REPAIR = 1195 -ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 -ER_TRANS_CACHE_FULL = 1197 -OBSOLETE_ER_SLAVE_MUST_STOP = 1198 -ER_SLAVE_NOT_RUNNING = 1199 -ER_BAD_SLAVE = 1200 -ER_MASTER_INFO = 1201 -ER_SLAVE_THREAD = 1202 -ER_TOO_MANY_USER_CONNECTIONS = 1203 -ER_SET_CONSTANTS_ONLY = 1204 -ER_LOCK_WAIT_TIMEOUT = 1205 -ER_LOCK_TABLE_FULL = 1206 -ER_READ_ONLY_TRANSACTION = 1207 -OBSOLETE_ER_DROP_DB_WITH_READ_LOCK = 1208 -OBSOLETE_ER_CREATE_DB_WITH_READ_LOCK = 1209 -ER_WRONG_ARGUMENTS = 1210 -ER_NO_PERMISSION_TO_CREATE_USER = 1211 -OBSOLETE_ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 -ER_LOCK_DEADLOCK = 1213 -ER_TABLE_CANT_HANDLE_FT = 1214 -ER_CANNOT_ADD_FOREIGN = 1215 -ER_NO_REFERENCED_ROW = 1216 -ER_ROW_IS_REFERENCED = 1217 -ER_CONNECT_TO_MASTER = 1218 -OBSOLETE_ER_QUERY_ON_MASTER = 1219 -ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 -ER_WRONG_USAGE = 1221 -ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 -ER_CANT_UPDATE_WITH_READLOCK = 1223 -ER_MIXING_NOT_ALLOWED = 1224 -ER_DUP_ARGUMENT = 1225 -ER_USER_LIMIT_REACHED = 1226 -ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 -ER_LOCAL_VARIABLE = 1228 -ER_GLOBAL_VARIABLE = 1229 -ER_NO_DEFAULT = 1230 -ER_WRONG_VALUE_FOR_VAR = 1231 -ER_WRONG_TYPE_FOR_VAR = 1232 -ER_VAR_CANT_BE_READ = 1233 -ER_CANT_USE_OPTION_HERE = 1234 -ER_NOT_SUPPORTED_YET = 1235 -ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 -ER_SLAVE_IGNORED_TABLE = 1237 -ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238 -ER_WRONG_FK_DEF = 1239 -ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 -ER_OPERAND_COLUMNS = 1241 -ER_SUBQUERY_NO_1_ROW = 1242 -ER_UNKNOWN_STMT_HANDLER = 1243 -ER_CORRUPT_HELP_DB = 1244 -OBSOLETE_ER_CYCLIC_REFERENCE = 1245 -ER_AUTO_CONVERT = 1246 -ER_ILLEGAL_REFERENCE = 1247 -ER_DERIVED_MUST_HAVE_ALIAS = 1248 -ER_SELECT_REDUCED = 1249 -ER_TABLENAME_NOT_ALLOWED_HERE = 1250 -ER_NOT_SUPPORTED_AUTH_MODE = 1251 -ER_SPATIAL_CANT_HAVE_NULL = 1252 -ER_COLLATION_CHARSET_MISMATCH = 1253 -OBSOLETE_ER_SLAVE_WAS_RUNNING = 1254 -OBSOLETE_ER_SLAVE_WAS_NOT_RUNNING = 1255 -ER_TOO_BIG_FOR_UNCOMPRESS = 1256 -ER_ZLIB_Z_MEM_ERROR = 1257 -ER_ZLIB_Z_BUF_ERROR = 1258 -ER_ZLIB_Z_DATA_ERROR = 1259 -ER_CUT_VALUE_GROUP_CONCAT = 1260 -ER_WARN_TOO_FEW_RECORDS = 1261 -ER_WARN_TOO_MANY_RECORDS = 1262 -ER_WARN_NULL_TO_NOTNULL = 1263 -ER_WARN_DATA_OUT_OF_RANGE = 1264 -WARN_DATA_TRUNCATED = 1265 -ER_WARN_USING_OTHER_HANDLER = 1266 -ER_CANT_AGGREGATE_2COLLATIONS = 1267 -OBSOLETE_ER_DROP_USER = 1268 -ER_REVOKE_GRANTS = 1269 -ER_CANT_AGGREGATE_3COLLATIONS = 1270 -ER_CANT_AGGREGATE_NCOLLATIONS = 1271 -ER_VARIABLE_IS_NOT_STRUCT = 1272 -ER_UNKNOWN_COLLATION = 1273 -ER_SLAVE_IGNORED_SSL_PARAMS = 1274 -OBSOLETE_ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275 -ER_WARN_FIELD_RESOLVED = 1276 -ER_BAD_SLAVE_UNTIL_COND = 1277 -ER_MISSING_SKIP_SLAVE = 1278 -ER_UNTIL_COND_IGNORED = 1279 -ER_WRONG_NAME_FOR_INDEX = 1280 -ER_WRONG_NAME_FOR_CATALOG = 1281 -OBSOLETE_ER_WARN_QC_RESIZE = 1282 -ER_BAD_FT_COLUMN = 1283 -ER_UNKNOWN_KEY_CACHE = 1284 -ER_WARN_HOSTNAME_WONT_WORK = 1285 -ER_UNKNOWN_STORAGE_ENGINE = 1286 -ER_WARN_DEPRECATED_SYNTAX = 1287 -ER_NON_UPDATABLE_TABLE = 1288 -ER_FEATURE_DISABLED = 1289 -ER_OPTION_PREVENTS_STATEMENT = 1290 -ER_DUPLICATED_VALUE_IN_TYPE = 1291 -ER_TRUNCATED_WRONG_VALUE = 1292 -OBSOLETE_ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 -ER_INVALID_ON_UPDATE = 1294 -ER_UNSUPPORTED_PS = 1295 -ER_GET_ERRMSG = 1296 -ER_GET_TEMPORARY_ERRMSG = 1297 -ER_UNKNOWN_TIME_ZONE = 1298 -ER_WARN_INVALID_TIMESTAMP = 1299 -ER_INVALID_CHARACTER_STRING = 1300 -ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301 -ER_CONFLICTING_DECLARATIONS = 1302 -ER_SP_NO_RECURSIVE_CREATE = 1303 -ER_SP_ALREADY_EXISTS = 1304 -ER_SP_DOES_NOT_EXIST = 1305 -ER_SP_DROP_FAILED = 1306 -ER_SP_STORE_FAILED = 1307 -ER_SP_LILABEL_MISMATCH = 1308 -ER_SP_LABEL_REDEFINE = 1309 -ER_SP_LABEL_MISMATCH = 1310 -ER_SP_UNINIT_VAR = 1311 -ER_SP_BADSELECT = 1312 -ER_SP_BADRETURN = 1313 -ER_SP_BADSTATEMENT = 1314 -ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315 -ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 -ER_QUERY_INTERRUPTED = 1317 -ER_SP_WRONG_NO_OF_ARGS = 1318 -ER_SP_COND_MISMATCH = 1319 -ER_SP_NORETURN = 1320 -ER_SP_NORETURNEND = 1321 -ER_SP_BAD_CURSOR_QUERY = 1322 -ER_SP_BAD_CURSOR_SELECT = 1323 -ER_SP_CURSOR_MISMATCH = 1324 -ER_SP_CURSOR_ALREADY_OPEN = 1325 -ER_SP_CURSOR_NOT_OPEN = 1326 -ER_SP_UNDECLARED_VAR = 1327 -ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328 -ER_SP_FETCH_NO_DATA = 1329 -ER_SP_DUP_PARAM = 1330 -ER_SP_DUP_VAR = 1331 -ER_SP_DUP_COND = 1332 -ER_SP_DUP_CURS = 1333 -ER_SP_CANT_ALTER = 1334 -ER_SP_SUBSELECT_NYI = 1335 -ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 -ER_SP_VARCOND_AFTER_CURSHNDLR = 1337 -ER_SP_CURSOR_AFTER_HANDLER = 1338 -ER_SP_CASE_NOT_FOUND = 1339 -ER_FPARSER_TOO_BIG_FILE = 1340 -ER_FPARSER_BAD_HEADER = 1341 -ER_FPARSER_EOF_IN_COMMENT = 1342 -ER_FPARSER_ERROR_IN_PARAMETER = 1343 -ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 -ER_VIEW_NO_EXPLAIN = 1345 -OBSOLETE_ER_FRM_UNKNOWN_TYPE = 1346 -ER_WRONG_OBJECT = 1347 -ER_NONUPDATEABLE_COLUMN = 1348 -OBSOLETE_ER_VIEW_SELECT_DERIVED_UNUSED = 1349 -ER_VIEW_SELECT_CLAUSE = 1350 -ER_VIEW_SELECT_VARIABLE = 1351 -ER_VIEW_SELECT_TMPTABLE = 1352 -ER_VIEW_WRONG_LIST = 1353 -ER_WARN_VIEW_MERGE = 1354 -ER_WARN_VIEW_WITHOUT_KEY = 1355 -ER_VIEW_INVALID = 1356 -ER_SP_NO_DROP_SP = 1357 -OBSOLETE_ER_SP_GOTO_IN_HNDLR = 1358 -ER_TRG_ALREADY_EXISTS = 1359 -ER_TRG_DOES_NOT_EXIST = 1360 -ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361 -ER_TRG_CANT_CHANGE_ROW = 1362 -ER_TRG_NO_SUCH_ROW_IN_TRG = 1363 -ER_NO_DEFAULT_FOR_FIELD = 1364 -ER_DIVISION_BY_ZERO = 1365 -ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 -ER_ILLEGAL_VALUE_FOR_TYPE = 1367 -ER_VIEW_NONUPD_CHECK = 1368 -ER_VIEW_CHECK_FAILED = 1369 -ER_PROCACCESS_DENIED_ERROR = 1370 -ER_RELAY_LOG_FAIL = 1371 -OBSOLETE_ER_PASSWD_LENGTH = 1372 -ER_UNKNOWN_TARGET_BINLOG = 1373 -ER_IO_ERR_LOG_INDEX_READ = 1374 -ER_BINLOG_PURGE_PROHIBITED = 1375 -ER_FSEEK_FAIL = 1376 -ER_BINLOG_PURGE_FATAL_ERR = 1377 -ER_LOG_IN_USE = 1378 -ER_LOG_PURGE_UNKNOWN_ERR = 1379 -ER_RELAY_LOG_INIT = 1380 -ER_NO_BINARY_LOGGING = 1381 -ER_RESERVED_SYNTAX = 1382 -OBSOLETE_ER_WSAS_FAILED = 1383 -OBSOLETE_ER_DIFF_GROUPS_PROC = 1384 -OBSOLETE_ER_NO_GROUP_FOR_PROC = 1385 -OBSOLETE_ER_ORDER_WITH_PROC = 1386 -OBSOLETE_ER_LOGGING_PROHIBIT_CHANGING_OF = 1387 -OBSOLETE_ER_NO_FILE_MAPPING = 1388 -OBSOLETE_ER_WRONG_MAGIC = 1389 -ER_PS_MANY_PARAM = 1390 -ER_KEY_PART_0 = 1391 -ER_VIEW_CHECKSUM = 1392 -ER_VIEW_MULTIUPDATE = 1393 -ER_VIEW_NO_INSERT_FIELD_LIST = 1394 -ER_VIEW_DELETE_MERGE_VIEW = 1395 -ER_CANNOT_USER = 1396 -ER_XAER_NOTA = 1397 -ER_XAER_INVAL = 1398 -ER_XAER_RMFAIL = 1399 -ER_XAER_OUTSIDE = 1400 -ER_XAER_RMERR = 1401 -ER_XA_RBROLLBACK = 1402 -ER_NONEXISTING_PROC_GRANT = 1403 -ER_PROC_AUTO_GRANT_FAIL = 1404 -ER_PROC_AUTO_REVOKE_FAIL = 1405 -ER_DATA_TOO_LONG = 1406 -ER_SP_BAD_SQLSTATE = 1407 -ER_STARTUP = 1408 -ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 -ER_CANT_CREATE_USER_WITH_GRANT = 1410 -ER_WRONG_VALUE_FOR_TYPE = 1411 -ER_TABLE_DEF_CHANGED = 1412 -ER_SP_DUP_HANDLER = 1413 -ER_SP_NOT_VAR_ARG = 1414 -ER_SP_NO_RETSET = 1415 -ER_CANT_CREATE_GEOMETRY_OBJECT = 1416 -OBSOLETE_ER_FAILED_ROUTINE_BREAK_BINLOG = 1417 -ER_BINLOG_UNSAFE_ROUTINE = 1418 -ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 -OBSOLETE_ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420 -ER_STMT_HAS_NO_OPEN_CURSOR = 1421 -ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 -ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423 -ER_SP_NO_RECURSION = 1424 -ER_TOO_BIG_SCALE = 1425 -ER_TOO_BIG_PRECISION = 1426 -ER_M_BIGGER_THAN_D = 1427 -ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428 -ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 -ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430 -ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 -ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 -ER_FOREIGN_DATA_STRING_INVALID = 1433 -OBSOLETE_ER_CANT_CREATE_FEDERATED_TABLE = 1434 -ER_TRG_IN_WRONG_SCHEMA = 1435 -ER_STACK_OVERRUN_NEED_MORE = 1436 -ER_TOO_LONG_BODY = 1437 -ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 -ER_TOO_BIG_DISPLAYWIDTH = 1439 -ER_XAER_DUPID = 1440 -ER_DATETIME_FUNCTION_OVERFLOW = 1441 -ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 -ER_VIEW_PREVENT_UPDATE = 1443 -ER_PS_NO_RECURSION = 1444 -ER_SP_CANT_SET_AUTOCOMMIT = 1445 -OBSOLETE_ER_MALFORMED_DEFINER = 1446 -ER_VIEW_FRM_NO_USER = 1447 -ER_VIEW_OTHER_USER = 1448 -ER_NO_SUCH_USER = 1449 -ER_FORBID_SCHEMA_CHANGE = 1450 -ER_ROW_IS_REFERENCED_2 = 1451 -ER_NO_REFERENCED_ROW_2 = 1452 -ER_SP_BAD_VAR_SHADOW = 1453 -ER_TRG_NO_DEFINER = 1454 -ER_OLD_FILE_FORMAT = 1455 -ER_SP_RECURSION_LIMIT = 1456 -OBSOLETE_ER_SP_PROC_TABLE_CORRUPT = 1457 -ER_SP_WRONG_NAME = 1458 -ER_TABLE_NEEDS_UPGRADE = 1459 -ER_SP_NO_AGGREGATE = 1460 -ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461 -ER_VIEW_RECURSIVE = 1462 -ER_NON_GROUPING_FIELD_USED = 1463 -ER_TABLE_CANT_HANDLE_SPKEYS = 1464 -ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 -ER_REMOVED_SPACES = 1466 -ER_AUTOINC_READ_FAILED = 1467 -ER_USERNAME = 1468 -ER_HOSTNAME = 1469 -ER_WRONG_STRING_LENGTH = 1470 -ER_NON_INSERTABLE_TABLE = 1471 -ER_ADMIN_WRONG_MRG_TABLE = 1472 -ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 -ER_NAME_BECOMES_EMPTY = 1474 -ER_AMBIGUOUS_FIELD_TERM = 1475 -ER_FOREIGN_SERVER_EXISTS = 1476 -ER_FOREIGN_SERVER_DOESNT_EXIST = 1477 -ER_ILLEGAL_HA_CREATE_OPTION = 1478 -ER_PARTITION_REQUIRES_VALUES_ERROR = 1479 -ER_PARTITION_WRONG_VALUES_ERROR = 1480 -ER_PARTITION_MAXVALUE_ERROR = 1481 -OBSOLETE_ER_PARTITION_SUBPARTITION_ERROR = 1482 -OBSOLETE_ER_PARTITION_SUBPART_MIX_ERROR = 1483 -ER_PARTITION_WRONG_NO_PART_ERROR = 1484 -ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485 -ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 -OBSOLETE_ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 -ER_FIELD_NOT_FOUND_PART_ERROR = 1488 -OBSOLETE_ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 -ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490 -ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 -ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 -ER_RANGE_NOT_INCREASING_ERROR = 1493 -ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 -ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 -ER_PARTITION_ENTRY_ERROR = 1496 -ER_MIX_HANDLER_ERROR = 1497 -ER_PARTITION_NOT_DEFINED_ERROR = 1498 -ER_TOO_MANY_PARTITIONS_ERROR = 1499 -ER_SUBPARTITION_ERROR = 1500 -ER_CANT_CREATE_HANDLER_FILE = 1501 -ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 -ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 -ER_NO_PARTS_ERROR = 1504 -ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505 -ER_FOREIGN_KEY_ON_PARTITIONED = 1506 -ER_DROP_PARTITION_NON_EXISTENT = 1507 -ER_DROP_LAST_PARTITION = 1508 -ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509 -ER_REORG_HASH_ONLY_ON_SAME_NO = 1510 -ER_REORG_NO_PARAM_ERROR = 1511 -ER_ONLY_ON_RANGE_LIST_PARTITION = 1512 -ER_ADD_PARTITION_SUBPART_ERROR = 1513 -ER_ADD_PARTITION_NO_NEW_PARTITION = 1514 -ER_COALESCE_PARTITION_NO_PARTITION = 1515 -ER_REORG_PARTITION_NOT_EXIST = 1516 -ER_SAME_NAME_PARTITION = 1517 -ER_NO_BINLOG_ERROR = 1518 -ER_CONSECUTIVE_REORG_PARTITIONS = 1519 -ER_REORG_OUTSIDE_RANGE = 1520 -ER_PARTITION_FUNCTION_FAILURE = 1521 -OBSOLETE_ER_PART_STATE_ERROR = 1522 -ER_LIMITED_PART_RANGE = 1523 -ER_PLUGIN_IS_NOT_LOADED = 1524 -ER_WRONG_VALUE = 1525 -ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526 -ER_FILEGROUP_OPTION_ONLY_ONCE = 1527 -ER_CREATE_FILEGROUP_FAILED = 1528 -ER_DROP_FILEGROUP_FAILED = 1529 -ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530 -ER_WRONG_SIZE_NUMBER = 1531 -ER_SIZE_OVERFLOW_ERROR = 1532 -ER_ALTER_FILEGROUP_FAILED = 1533 -ER_BINLOG_ROW_LOGGING_FAILED = 1534 -OBSOLETE_ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535 -OBSOLETE_ER_BINLOG_ROW_RBR_TO_SBR = 1536 -ER_EVENT_ALREADY_EXISTS = 1537 -OBSOLETE_ER_EVENT_STORE_FAILED = 1538 -ER_EVENT_DOES_NOT_EXIST = 1539 -OBSOLETE_ER_EVENT_CANT_ALTER = 1540 -OBSOLETE_ER_EVENT_DROP_FAILED = 1541 -ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 -ER_EVENT_ENDS_BEFORE_STARTS = 1543 -ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544 -OBSOLETE_ER_EVENT_OPEN_TABLE_FAILED = 1545 -OBSOLETE_ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 -OBSOLETE_ER_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 -OBSOLETE_ER_CANNOT_LOAD_FROM_TABLE = 1548 -OBSOLETE_ER_EVENT_CANNOT_DELETE = 1549 -OBSOLETE_ER_EVENT_COMPILE_ERROR = 1550 -ER_EVENT_SAME_NAME = 1551 -OBSOLETE_ER_EVENT_DATA_TOO_LONG = 1552 -ER_DROP_INDEX_FK = 1553 -ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 -OBSOLETE_ER_CANT_WRITE_LOCK_LOG_TABLE = 1555 -ER_CANT_LOCK_LOG_TABLE = 1556 -ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 -ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 -OBSOLETE_ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 -ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 -OBSOLETE_ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 -ER_PARTITION_NO_TEMPORARY = 1562 -ER_PARTITION_CONST_DOMAIN_ERROR = 1563 -ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 -OBSOLETE_ER_DDL_LOG_ERROR_UNUSED = 1565 -ER_NULL_IN_VALUES_LESS_THAN = 1566 -ER_WRONG_PARTITION_NAME = 1567 -ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568 -ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569 -OBSOLETE_ER_EVENT_MODIFY_QUEUE_ERROR = 1570 -ER_EVENT_SET_VAR_ERROR = 1571 -ER_PARTITION_MERGE_ERROR = 1572 -OBSOLETE_ER_CANT_ACTIVATE_LOG = 1573 -OBSOLETE_ER_RBR_NOT_AVAILABLE = 1574 -ER_BASE64_DECODE_ERROR = 1575 -ER_EVENT_RECURSION_FORBIDDEN = 1576 -OBSOLETE_ER_EVENTS_DB_ERROR = 1577 -ER_ONLY_INTEGERS_ALLOWED = 1578 -ER_UNSUPORTED_LOG_ENGINE = 1579 -ER_BAD_LOG_STATEMENT = 1580 -ER_CANT_RENAME_LOG_TABLE = 1581 -ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 -ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 -ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584 -ER_NATIVE_FCT_NAME_COLLISION = 1585 -ER_DUP_ENTRY_WITH_KEY_NAME = 1586 -ER_BINLOG_PURGE_EMFILE = 1587 -ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 -ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 -OBSOLETE_ER_SLAVE_INCIDENT = 1590 -ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 -ER_BINLOG_UNSAFE_STATEMENT = 1592 -ER_BINLOG_FATAL_ERROR = 1593 -OBSOLETE_ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594 -OBSOLETE_ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 -OBSOLETE_ER_SLAVE_CREATE_EVENT_FAILURE = 1596 -OBSOLETE_ER_SLAVE_MASTER_COM_FAILURE = 1597 -ER_BINLOG_LOGGING_IMPOSSIBLE = 1598 -ER_VIEW_NO_CREATION_CTX = 1599 -ER_VIEW_INVALID_CREATION_CTX = 1600 -OBSOLETE_ER_SR_INVALID_CREATION_CTX = 1601 -ER_TRG_CORRUPTED_FILE = 1602 -ER_TRG_NO_CREATION_CTX = 1603 -ER_TRG_INVALID_CREATION_CTX = 1604 -ER_EVENT_INVALID_CREATION_CTX = 1605 -ER_TRG_CANT_OPEN_TABLE = 1606 -OBSOLETE_ER_CANT_CREATE_SROUTINE = 1607 -OBSOLETE_ER_NEVER_USED = 1608 -ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 -ER_SLAVE_CORRUPT_EVENT = 1610 -OBSOLETE_ER_LOAD_DATA_INVALID_COLUMN_UNUSED = 1611 -ER_LOG_PURGE_NO_FILE = 1612 -ER_XA_RBTIMEOUT = 1613 -ER_XA_RBDEADLOCK = 1614 -ER_NEED_REPREPARE = 1615 -OBSOLETE_ER_DELAYED_NOT_SUPPORTED = 1616 -WARN_NO_MASTER_INFO = 1617 -WARN_OPTION_IGNORED = 1618 -ER_PLUGIN_DELETE_BUILTIN = 1619 -WARN_PLUGIN_BUSY = 1620 -ER_VARIABLE_IS_READONLY = 1621 -ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 -OBSOLETE_ER_SLAVE_HEARTBEAT_FAILURE = 1623 -ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 -ER_NDB_REPLICATION_SCHEMA_ERROR = 1625 -ER_CONFLICT_FN_PARSE_ERROR = 1626 -ER_EXCEPTIONS_WRITE_ERROR = 1627 -ER_TOO_LONG_TABLE_COMMENT = 1628 -ER_TOO_LONG_FIELD_COMMENT = 1629 -ER_FUNC_INEXISTENT_NAME_COLLISION = 1630 -ER_DATABASE_NAME = 1631 -ER_TABLE_NAME = 1632 -ER_PARTITION_NAME = 1633 -ER_SUBPARTITION_NAME = 1634 -ER_TEMPORARY_NAME = 1635 -ER_RENAMED_NAME = 1636 -ER_TOO_MANY_CONCURRENT_TRXS = 1637 -WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 -ER_DEBUG_SYNC_TIMEOUT = 1639 -ER_DEBUG_SYNC_HIT_LIMIT = 1640 -ER_DUP_SIGNAL_SET = 1641 -ER_SIGNAL_WARN = 1642 -ER_SIGNAL_NOT_FOUND = 1643 -ER_SIGNAL_EXCEPTION = 1644 -ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 -ER_SIGNAL_BAD_CONDITION_TYPE = 1646 -WARN_COND_ITEM_TRUNCATED = 1647 -ER_COND_ITEM_TOO_LONG = 1648 -ER_UNKNOWN_LOCALE = 1649 -ER_SLAVE_IGNORE_SERVER_IDS = 1650 -OBSOLETE_ER_QUERY_CACHE_DISABLED = 1651 -ER_SAME_NAME_PARTITION_FIELD = 1652 -ER_PARTITION_COLUMN_LIST_ERROR = 1653 -ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 -ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 -ER_MAXVALUE_IN_VALUES_IN = 1656 -ER_TOO_MANY_VALUES_ERROR = 1657 -ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 -ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 -ER_PARTITION_FIELDS_TOO_LONG = 1660 -ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 -ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 -ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 -ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 -ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 -ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 -ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 -ER_BINLOG_UNSAFE_LIMIT = 1668 -OBSOLETE_ER_UNUSED4 = 1669 -ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670 -ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 -ER_BINLOG_UNSAFE_UDF = 1672 -ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 -ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 -ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 -ER_MESSAGE_AND_STATEMENT = 1676 -OBSOLETE_ER_SLAVE_CONVERSION_FAILED = 1677 -ER_SLAVE_CANT_CREATE_CONVERSION = 1678 -ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 -ER_PATH_LENGTH = 1680 -ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 -ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682 -ER_WRONG_PERFSCHEMA_USAGE = 1683 -ER_WARN_I_S_SKIPPED_TABLE = 1684 -ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 -ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 -ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687 -ER_TOO_LONG_INDEX_COMMENT = 1688 -ER_LOCK_ABORTED = 1689 -ER_DATA_OUT_OF_RANGE = 1690 -OBSOLETE_ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691 -ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 -ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693 -ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 -ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 -ER_FAILED_READ_FROM_PAR_FILE = 1696 -ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697 -ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 -ER_SET_PASSWORD_AUTH_PLUGIN = 1699 -OBSOLETE_ER_GRANT_PLUGIN_USER_EXISTS = 1700 -ER_TRUNCATE_ILLEGAL_FK = 1701 -ER_PLUGIN_IS_PERMANENT = 1702 -ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 -ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 -ER_STMT_CACHE_FULL = 1705 -ER_MULTI_UPDATE_KEY_CONFLICT = 1706 -ER_TABLE_NEEDS_REBUILD = 1707 -WARN_OPTION_BELOW_LIMIT = 1708 -ER_INDEX_COLUMN_TOO_LONG = 1709 -ER_ERROR_IN_TRIGGER_BODY = 1710 -ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 -ER_INDEX_CORRUPT = 1712 -ER_UNDO_RECORD_TOO_BIG = 1713 -ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 -ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 -ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716 -ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 -ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 -ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719 -ER_PLUGIN_NO_UNINSTALL = 1720 -ER_PLUGIN_NO_INSTALL = 1721 -ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 -ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 -ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 -ER_TABLE_IN_FK_CHECK = 1725 -ER_UNSUPPORTED_ENGINE = 1726 -ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 -ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728 -ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 -ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 -ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 -ER_PARTITION_EXCHANGE_PART_TABLE = 1732 -ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733 -ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734 -ER_UNKNOWN_PARTITION = 1735 -ER_TABLES_DIFFERENT_METADATA = 1736 -ER_ROW_DOES_NOT_MATCH_PARTITION = 1737 -ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 -ER_WARN_INDEX_NOT_APPLICABLE = 1739 -ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740 -OBSOLETE_ER_NO_SUCH_KEY_VALUE = 1741 -ER_RPL_INFO_DATA_TOO_LONG = 1742 -OBSOLETE_ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 -OBSOLETE_ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 -ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 -ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 -ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 -ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 -OBSOLETE_ER_NO_SUCH_PARTITION__UNUSED = 1749 -ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 -ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 -ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 -ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753 -ER_MTS_UPDATED_DBS_GREATER_MAX = 1754 -ER_MTS_CANT_PARALLEL = 1755 -ER_MTS_INCONSISTENT_DATA = 1756 -ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 -ER_DA_INVALID_CONDITION_NUMBER = 1758 -ER_INSECURE_PLAIN_TEXT = 1759 -ER_INSECURE_CHANGE_MASTER = 1760 -ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 -ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 -ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763 -ER_TABLE_HAS_NO_FT = 1764 -ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 -ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 -OBSOLETE_ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 -OBSOLETE_ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION = 1768 -ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 -ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 -OBSOLETE_ER_SKIPPING_LOGGED_TRANSACTION = 1771 -ER_MALFORMED_GTID_SET_SPECIFICATION = 1772 -ER_MALFORMED_GTID_SET_ENCODING = 1773 -ER_MALFORMED_GTID_SPECIFICATION = 1774 -ER_GNO_EXHAUSTED = 1775 -ER_BAD_SLAVE_AUTO_POSITION = 1776 -ER_AUTO_POSITION_REQUIRES_GTID_MODE_NOT_OFF = 1777 -ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 -ER_GTID_MODE_ON_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 -OBSOLETE_ER_GTID_MODE_REQUIRES_BINLOG = 1780 -ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 -ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 -ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 -OBSOLETE_ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF__UNUSED = 1784 -ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 -ER_GTID_UNSAFE_CREATE_SELECT = 1786 -OBSOLETE_ER_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRANSACTION = 1787 -ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 -ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 -ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 -ER_UNKNOWN_EXPLAIN_FORMAT = 1791 -ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 -ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793 -ER_SLAVE_CONFIGURATION = 1794 -ER_INNODB_FT_LIMIT = 1795 -ER_INNODB_NO_FT_TEMP_TABLE = 1796 -ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797 -ER_INNODB_FT_WRONG_DOCID_INDEX = 1798 -ER_INNODB_ONLINE_LOG_TOO_BIG = 1799 -ER_UNKNOWN_ALTER_ALGORITHM = 1800 -ER_UNKNOWN_ALTER_LOCK = 1801 -ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 -ER_MTS_RECOVERY_FAILURE = 1803 -ER_MTS_RESET_WORKERS = 1804 -ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 -ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806 -ER_DISCARD_FK_CHECKS_RUNNING = 1807 -ER_TABLE_SCHEMA_MISMATCH = 1808 -ER_TABLE_IN_SYSTEM_TABLESPACE = 1809 -ER_IO_READ_ERROR = 1810 -ER_IO_WRITE_ERROR = 1811 -ER_TABLESPACE_MISSING = 1812 -ER_TABLESPACE_EXISTS = 1813 -ER_TABLESPACE_DISCARDED = 1814 -ER_INTERNAL_ERROR = 1815 -ER_INNODB_IMPORT_ERROR = 1816 -ER_INNODB_INDEX_CORRUPT = 1817 -ER_INVALID_YEAR_COLUMN_LENGTH = 1818 -ER_NOT_VALID_PASSWORD = 1819 -ER_MUST_CHANGE_PASSWORD = 1820 -ER_FK_NO_INDEX_CHILD = 1821 -ER_FK_NO_INDEX_PARENT = 1822 -ER_FK_FAIL_ADD_SYSTEM = 1823 -ER_FK_CANNOT_OPEN_PARENT = 1824 -ER_FK_INCORRECT_OPTION = 1825 -ER_FK_DUP_NAME = 1826 -ER_PASSWORD_FORMAT = 1827 -ER_FK_COLUMN_CANNOT_DROP = 1828 -ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829 -ER_FK_COLUMN_NOT_NULL = 1830 -ER_DUP_INDEX = 1831 -ER_FK_COLUMN_CANNOT_CHANGE = 1832 -ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 -OBSOLETE_ER_UNUSED5 = 1834 -ER_MALFORMED_PACKET = 1835 -ER_READ_ONLY_MODE = 1836 -ER_GTID_NEXT_TYPE_UNDEFINED_GTID = 1837 -ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838 -OBSOLETE_ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 -ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 -ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 -ER_GTID_PURGED_WAS_CHANGED = 1842 -ER_GTID_EXECUTED_WAS_CHANGED = 1843 -ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 -ER_ALTER_OPERATION_NOT_SUPPORTED = 1845 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 -OBSOLETE_ER_UNUSED6 = 1852 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 -OBSOLETE_ER_SQL_REPLICA_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 -ER_DUP_UNKNOWN_IN_INDEX = 1859 -ER_IDENT_CAUSES_TOO_LONG_PATH = 1860 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 -ER_MUST_CHANGE_PASSWORD_LOGIN = 1862 -ER_ROW_IN_WRONG_PARTITION = 1863 -ER_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 -OBSOLETE_ER_INNODB_NO_FT_USES_PARSER = 1865 -ER_BINLOG_LOGICAL_CORRUPTION = 1866 -ER_WARN_PURGE_LOG_IN_USE = 1867 -ER_WARN_PURGE_LOG_IS_ACTIVE = 1868 -ER_AUTO_INCREMENT_CONFLICT = 1869 -WARN_ON_BLOCKHOLE_IN_RBR = 1870 -ER_SLAVE_MI_INIT_REPOSITORY = 1871 -ER_SLAVE_RLI_INIT_REPOSITORY = 1872 -ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873 -ER_INNODB_READ_ONLY = 1874 -ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875 -ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876 -ER_TABLE_CORRUPT = 1877 -ER_TEMP_FILE_WRITE_FAILURE = 1878 -ER_INNODB_FT_AUX_NOT_HEX_ID = 1879 -ER_OLD_TEMPORALS_UPGRADED = 1880 -ER_INNODB_FORCED_RECOVERY = 1881 -ER_AES_INVALID_IV = 1882 -ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883 -ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_ASSIGNED_GTID = 1884 -ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER = 1885 -ER_MISSING_KEY = 1886 -WARN_NAMED_PIPE_ACCESS_EVERYONE = 1887 -ER_FILE_CORRUPT = 3000 -ER_ERROR_ON_MASTER = 3001 -OBSOLETE_ER_INCONSISTENT_ERROR = 3002 -ER_STORAGE_ENGINE_NOT_LOADED = 3003 -ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 3004 -ER_WARN_LEGACY_SYNTAX_CONVERTED = 3005 -ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 3006 -ER_CANNOT_DISCARD_TEMPORARY_TABLE = 3007 -ER_FK_DEPTH_EXCEEDED = 3008 -ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 3009 -ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 3010 -ER_REFERENCED_TRG_DOES_NOT_EXIST = 3011 -ER_EXPLAIN_NOT_SUPPORTED = 3012 -ER_INVALID_FIELD_SIZE = 3013 -ER_MISSING_HA_CREATE_OPTION = 3014 -ER_ENGINE_OUT_OF_MEMORY = 3015 -ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 3016 -ER_SLAVE_SQL_THREAD_MUST_STOP = 3017 -ER_NO_FT_MATERIALIZED_SUBQUERY = 3018 -ER_INNODB_UNDO_LOG_FULL = 3019 -ER_INVALID_ARGUMENT_FOR_LOGARITHM = 3020 -ER_SLAVE_CHANNEL_IO_THREAD_MUST_STOP = 3021 -ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 3022 -ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 3023 -ER_QUERY_TIMEOUT = 3024 -ER_NON_RO_SELECT_DISABLE_TIMER = 3025 -ER_DUP_LIST_ENTRY = 3026 -OBSOLETE_ER_SQL_MODE_NO_EFFECT = 3027 -ER_AGGREGATE_ORDER_FOR_UNION = 3028 -ER_AGGREGATE_ORDER_NON_AGG_QUERY = 3029 -ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 3030 -ER_DONT_SUPPORT_REPLICA_PRESERVE_COMMIT_ORDER = 3031 -ER_SERVER_OFFLINE_MODE = 3032 -ER_GIS_DIFFERENT_SRIDS = 3033 -ER_GIS_UNSUPPORTED_ARGUMENT = 3034 -ER_GIS_UNKNOWN_ERROR = 3035 -ER_GIS_UNKNOWN_EXCEPTION = 3036 -ER_GIS_INVALID_DATA = 3037 -ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 3038 -ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 3039 -ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 3040 -ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 3041 -ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 3042 -ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 3043 -ER_STD_BAD_ALLOC_ERROR = 3044 -ER_STD_DOMAIN_ERROR = 3045 -ER_STD_LENGTH_ERROR = 3046 -ER_STD_INVALID_ARGUMENT = 3047 -ER_STD_OUT_OF_RANGE_ERROR = 3048 -ER_STD_OVERFLOW_ERROR = 3049 -ER_STD_RANGE_ERROR = 3050 -ER_STD_UNDERFLOW_ERROR = 3051 -ER_STD_LOGIC_ERROR = 3052 -ER_STD_RUNTIME_ERROR = 3053 -ER_STD_UNKNOWN_EXCEPTION = 3054 -ER_GIS_DATA_WRONG_ENDIANESS = 3055 -ER_CHANGE_MASTER_PASSWORD_LENGTH = 3056 -ER_USER_LOCK_WRONG_NAME = 3057 -ER_USER_LOCK_DEADLOCK = 3058 -ER_REPLACE_INACCESSIBLE_ROWS = 3059 -ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 3060 -ER_ILLEGAL_USER_VAR = 3061 -ER_GTID_MODE_OFF = 3062 -OBSOLETE_ER_UNSUPPORTED_BY_REPLICATION_THREAD = 3063 -ER_INCORRECT_TYPE = 3064 -ER_FIELD_IN_ORDER_NOT_SELECT = 3065 -ER_AGGREGATE_IN_ORDER_NOT_SELECT = 3066 -ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 3067 -ER_NET_OK_PACKET_TOO_LARGE = 3068 -ER_INVALID_JSON_DATA = 3069 -ER_INVALID_GEOJSON_MISSING_MEMBER = 3070 -ER_INVALID_GEOJSON_WRONG_TYPE = 3071 -ER_INVALID_GEOJSON_UNSPECIFIED = 3072 -ER_DIMENSION_UNSUPPORTED = 3073 -ER_SLAVE_CHANNEL_DOES_NOT_EXIST = 3074 -OBSOLETE_ER_SLAVE_MULTIPLE_CHANNELS_HOST_PORT = 3075 -ER_SLAVE_CHANNEL_NAME_INVALID_OR_TOO_LONG = 3076 -ER_SLAVE_NEW_CHANNEL_WRONG_REPOSITORY = 3077 -OBSOLETE_ER_SLAVE_CHANNEL_DELETE = 3078 -ER_SLAVE_MULTIPLE_CHANNELS_CMD = 3079 -ER_SLAVE_MAX_CHANNELS_EXCEEDED = 3080 -ER_SLAVE_CHANNEL_MUST_STOP = 3081 -ER_SLAVE_CHANNEL_NOT_RUNNING = 3082 -ER_SLAVE_CHANNEL_WAS_RUNNING = 3083 -ER_SLAVE_CHANNEL_WAS_NOT_RUNNING = 3084 -ER_SLAVE_CHANNEL_SQL_THREAD_MUST_STOP = 3085 -ER_SLAVE_CHANNEL_SQL_SKIP_COUNTER = 3086 -ER_WRONG_FIELD_WITH_GROUP_V2 = 3087 -ER_MIX_OF_GROUP_FUNC_AND_FIELDS_V2 = 3088 -ER_WARN_DEPRECATED_SYSVAR_UPDATE = 3089 -ER_WARN_DEPRECATED_SQLMODE = 3090 -ER_CANNOT_LOG_PARTIAL_DROP_DATABASE_WITH_GTID = 3091 -ER_GROUP_REPLICATION_CONFIGURATION = 3092 -ER_GROUP_REPLICATION_RUNNING = 3093 -ER_GROUP_REPLICATION_APPLIER_INIT_ERROR = 3094 -ER_GROUP_REPLICATION_STOP_APPLIER_THREAD_TIMEOUT = 3095 -ER_GROUP_REPLICATION_COMMUNICATION_LAYER_SESSION_ERROR = 3096 -ER_GROUP_REPLICATION_COMMUNICATION_LAYER_JOIN_ERROR = 3097 -ER_BEFORE_DML_VALIDATION_ERROR = 3098 -ER_PREVENTS_VARIABLE_WITHOUT_RBR = 3099 -ER_RUN_HOOK_ERROR = 3100 -ER_TRANSACTION_ROLLBACK_DURING_COMMIT = 3101 -ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED = 3102 -ER_UNSUPPORTED_ALTER_INPLACE_ON_VIRTUAL_COLUMN = 3103 -ER_WRONG_FK_OPTION_FOR_GENERATED_COLUMN = 3104 -ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN = 3105 -ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN = 3106 -ER_GENERATED_COLUMN_NON_PRIOR = 3107 -ER_DEPENDENT_BY_GENERATED_COLUMN = 3108 -ER_GENERATED_COLUMN_REF_AUTO_INC = 3109 -ER_FEATURE_NOT_AVAILABLE = 3110 -ER_CANT_SET_GTID_MODE = 3111 -ER_CANT_USE_AUTO_POSITION_WITH_GTID_MODE_OFF = 3112 -OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_AUTO_POSITION = 3113 -OBSOLETE_ER_CANT_REPLICATE_ANONYMOUS_WITH_GTID_MODE_ON = 3114 -OBSOLETE_ER_CANT_REPLICATE_GTID_WITH_GTID_MODE_OFF = 3115 -ER_CANT_ENFORCE_GTID_CONSISTENCY_WITH_ONGOING_GTID_VIOLATING_TX = 3116 -ER_ENFORCE_GTID_CONSISTENCY_WARN_WITH_ONGOING_GTID_VIOLATING_TX = 3117 -ER_ACCOUNT_HAS_BEEN_LOCKED = 3118 -ER_WRONG_TABLESPACE_NAME = 3119 -ER_TABLESPACE_IS_NOT_EMPTY = 3120 -ER_WRONG_FILE_NAME = 3121 -ER_BOOST_GEOMETRY_INCONSISTENT_TURNS_EXCEPTION = 3122 -ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR = 3123 -ER_WARN_BAD_MAX_EXECUTION_TIME = 3124 -ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME = 3125 -ER_WARN_CONFLICTING_HINT = 3126 -ER_WARN_UNKNOWN_QB_NAME = 3127 -ER_UNRESOLVED_HINT_NAME = 3128 -ER_WARN_ON_MODIFYING_GTID_EXECUTED_TABLE = 3129 -ER_PLUGGABLE_PROTOCOL_COMMAND_NOT_SUPPORTED = 3130 -ER_LOCKING_SERVICE_WRONG_NAME = 3131 -ER_LOCKING_SERVICE_DEADLOCK = 3132 -ER_LOCKING_SERVICE_TIMEOUT = 3133 -ER_GIS_MAX_POINTS_IN_GEOMETRY_OVERFLOWED = 3134 -ER_SQL_MODE_MERGED = 3135 -ER_VTOKEN_PLUGIN_TOKEN_MISMATCH = 3136 -ER_VTOKEN_PLUGIN_TOKEN_NOT_FOUND = 3137 -ER_CANT_SET_VARIABLE_WHEN_OWNING_GTID = 3138 -ER_SLAVE_CHANNEL_OPERATION_NOT_ALLOWED = 3139 -ER_INVALID_JSON_TEXT = 3140 -ER_INVALID_JSON_TEXT_IN_PARAM = 3141 -ER_INVALID_JSON_BINARY_DATA = 3142 -ER_INVALID_JSON_PATH = 3143 -ER_INVALID_JSON_CHARSET = 3144 -ER_INVALID_JSON_CHARSET_IN_FUNCTION = 3145 -ER_INVALID_TYPE_FOR_JSON = 3146 -ER_INVALID_CAST_TO_JSON = 3147 -ER_INVALID_JSON_PATH_CHARSET = 3148 -ER_INVALID_JSON_PATH_WILDCARD = 3149 -ER_JSON_VALUE_TOO_BIG = 3150 -ER_JSON_KEY_TOO_BIG = 3151 -ER_JSON_USED_AS_KEY = 3152 -ER_JSON_VACUOUS_PATH = 3153 -ER_JSON_BAD_ONE_OR_ALL_ARG = 3154 -ER_NUMERIC_JSON_VALUE_OUT_OF_RANGE = 3155 -ER_INVALID_JSON_VALUE_FOR_CAST = 3156 -ER_JSON_DOCUMENT_TOO_DEEP = 3157 -ER_JSON_DOCUMENT_NULL_KEY = 3158 -ER_SECURE_TRANSPORT_REQUIRED = 3159 -ER_NO_SECURE_TRANSPORTS_CONFIGURED = 3160 -ER_DISABLED_STORAGE_ENGINE = 3161 -ER_USER_DOES_NOT_EXIST = 3162 -ER_USER_ALREADY_EXISTS = 3163 -ER_AUDIT_API_ABORT = 3164 -ER_INVALID_JSON_PATH_ARRAY_CELL = 3165 -ER_BUFPOOL_RESIZE_INPROGRESS = 3166 -ER_FEATURE_DISABLED_SEE_DOC = 3167 -ER_SERVER_ISNT_AVAILABLE = 3168 -ER_SESSION_WAS_KILLED = 3169 -ER_CAPACITY_EXCEEDED = 3170 -ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER = 3171 -OBSOLETE_ER_TABLE_NEEDS_UPG_PART = 3172 -ER_CANT_WAIT_FOR_EXECUTED_GTID_SET_WHILE_OWNING_A_GTID = 3173 -ER_CANNOT_ADD_FOREIGN_BASE_COL_VIRTUAL = 3174 -ER_CANNOT_CREATE_VIRTUAL_INDEX_CONSTRAINT = 3175 -ER_ERROR_ON_MODIFYING_GTID_EXECUTED_TABLE = 3176 -ER_LOCK_REFUSED_BY_ENGINE = 3177 -ER_UNSUPPORTED_ALTER_ONLINE_ON_VIRTUAL_COLUMN = 3178 -ER_MASTER_KEY_ROTATION_NOT_SUPPORTED_BY_SE = 3179 -OBSOLETE_ER_MASTER_KEY_ROTATION_ERROR_BY_SE = 3180 -ER_MASTER_KEY_ROTATION_BINLOG_FAILED = 3181 -ER_MASTER_KEY_ROTATION_SE_UNAVAILABLE = 3182 -ER_TABLESPACE_CANNOT_ENCRYPT = 3183 -ER_INVALID_ENCRYPTION_OPTION = 3184 -ER_CANNOT_FIND_KEY_IN_KEYRING = 3185 -ER_CAPACITY_EXCEEDED_IN_PARSER = 3186 -ER_UNSUPPORTED_ALTER_ENCRYPTION_INPLACE = 3187 -ER_KEYRING_UDF_KEYRING_SERVICE_ERROR = 3188 -ER_USER_COLUMN_OLD_LENGTH = 3189 -ER_CANT_RESET_MASTER = 3190 -ER_GROUP_REPLICATION_MAX_GROUP_SIZE = 3191 -ER_CANNOT_ADD_FOREIGN_BASE_COL_STORED = 3192 -ER_TABLE_REFERENCED = 3193 -OBSOLETE_ER_PARTITION_ENGINE_DEPRECATED_FOR_TABLE = 3194 -OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID_ZERO = 3195 -OBSOLETE_ER_WARN_USING_GEOMFROMWKB_TO_SET_SRID = 3196 -ER_XA_RETRY = 3197 -ER_KEYRING_AWS_UDF_AWS_KMS_ERROR = 3198 -ER_BINLOG_UNSAFE_XA = 3199 -ER_UDF_ERROR = 3200 -ER_KEYRING_MIGRATION_FAILURE = 3201 -ER_KEYRING_ACCESS_DENIED_ERROR = 3202 -ER_KEYRING_MIGRATION_STATUS = 3203 -OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLES = 3204 -OBSOLETE_ER_PLUGIN_FAILED_TO_OPEN_TABLE = 3205 -OBSOLETE_ER_AUDIT_LOG_NO_KEYRING_PLUGIN_INSTALLED = 3206 -OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_HAS_NOT_BEEN_SET = 3207 -OBSOLETE_ER_AUDIT_LOG_COULD_NOT_CREATE_AES_KEY = 3208 -OBSOLETE_ER_AUDIT_LOG_ENCRYPTION_PASSWORD_CANNOT_BE_FETCHED = 3209 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTERING_NOT_ENABLED = 3210 -OBSOLETE_ER_AUDIT_LOG_UDF_INSUFFICIENT_PRIVILEGE = 3211 -OBSOLETE_ER_AUDIT_LOG_SUPER_PRIVILEGE_REQUIRED = 3212 -OBSOLETE_ER_COULD_NOT_REINITIALIZE_AUDIT_LOG_FILTERS = 3213 -OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_TYPE = 3214 -OBSOLETE_ER_AUDIT_LOG_UDF_INVALID_ARGUMENT_COUNT = 3215 -OBSOLETE_ER_AUDIT_LOG_HAS_NOT_BEEN_INSTALLED = 3216 -OBSOLETE_ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_TYPE = 3217 -ER_AUDIT_LOG_UDF_READ_INVALID_MAX_ARRAY_LENGTH_ARG_VALUE = 3218 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_PARSING_ERROR = 3219 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_NAME_CANNOT_BE_EMPTY = 3220 -OBSOLETE_ER_AUDIT_LOG_JSON_USER_NAME_CANNOT_BE_EMPTY = 3221 -OBSOLETE_ER_AUDIT_LOG_JSON_FILTER_DOES_NOT_EXISTS = 3222 -OBSOLETE_ER_AUDIT_LOG_USER_FIRST_CHARACTER_MUST_BE_ALPHANUMERIC = 3223 -OBSOLETE_ER_AUDIT_LOG_USER_NAME_INVALID_CHARACTER = 3224 -OBSOLETE_ER_AUDIT_LOG_HOST_NAME_INVALID_CHARACTER = 3225 -OBSOLETE_ER_XA_REPLICATION_FILTERS = 3226 -OBSOLETE_ER_CANT_OPEN_ERROR_LOG = 3227 -OBSOLETE_ER_GROUPING_ON_TIMESTAMP_IN_DST = 3228 -OBSOLETE_ER_CANT_START_SERVER_NAMED_PIPE = 3229 -ER_WRITE_SET_EXCEEDS_LIMIT = 3230 -ER_UNSUPPORT_COMPRESSED_TEMPORARY_TABLE = 3500 -ER_ACL_OPERATION_FAILED = 3501 -ER_UNSUPPORTED_INDEX_ALGORITHM = 3502 -ER_NO_SUCH_DB = 3503 -ER_TOO_BIG_ENUM = 3504 -ER_TOO_LONG_SET_ENUM_VALUE = 3505 -ER_INVALID_DD_OBJECT = 3506 -ER_UPDATING_DD_TABLE = 3507 -ER_INVALID_DD_OBJECT_ID = 3508 -ER_INVALID_DD_OBJECT_NAME = 3509 -ER_TABLESPACE_MISSING_WITH_NAME = 3510 -ER_TOO_LONG_ROUTINE_COMMENT = 3511 -ER_SP_LOAD_FAILED = 3512 -ER_INVALID_BITWISE_OPERANDS_SIZE = 3513 -ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE = 3514 -ER_WARN_UNSUPPORTED_HINT = 3515 -ER_UNEXPECTED_GEOMETRY_TYPE = 3516 -ER_SRS_PARSE_ERROR = 3517 -ER_SRS_PROJ_PARAMETER_MISSING = 3518 -ER_WARN_SRS_NOT_FOUND = 3519 -ER_SRS_NOT_CARTESIAN = 3520 -ER_SRS_NOT_CARTESIAN_UNDEFINED = 3521 -ER_PK_INDEX_CANT_BE_INVISIBLE = 3522 -ER_UNKNOWN_AUTHID = 3523 -ER_FAILED_ROLE_GRANT = 3524 -ER_OPEN_ROLE_TABLES = 3525 -ER_FAILED_DEFAULT_ROLES = 3526 -ER_COMPONENTS_NO_SCHEME = 3527 -ER_COMPONENTS_NO_SCHEME_SERVICE = 3528 -ER_COMPONENTS_CANT_LOAD = 3529 -ER_ROLE_NOT_GRANTED = 3530 -ER_FAILED_REVOKE_ROLE = 3531 -ER_RENAME_ROLE = 3532 -ER_COMPONENTS_CANT_ACQUIRE_SERVICE_IMPLEMENTATION = 3533 -ER_COMPONENTS_CANT_SATISFY_DEPENDENCY = 3534 -ER_COMPONENTS_LOAD_CANT_REGISTER_SERVICE_IMPLEMENTATION = 3535 -ER_COMPONENTS_LOAD_CANT_INITIALIZE = 3536 -ER_COMPONENTS_UNLOAD_NOT_LOADED = 3537 -ER_COMPONENTS_UNLOAD_CANT_DEINITIALIZE = 3538 -ER_COMPONENTS_CANT_RELEASE_SERVICE = 3539 -ER_COMPONENTS_UNLOAD_CANT_UNREGISTER_SERVICE = 3540 -ER_COMPONENTS_CANT_UNLOAD = 3541 -ER_WARN_UNLOAD_THE_NOT_PERSISTED = 3542 -ER_COMPONENT_TABLE_INCORRECT = 3543 -ER_COMPONENT_MANIPULATE_ROW_FAILED = 3544 -ER_COMPONENTS_UNLOAD_DUPLICATE_IN_GROUP = 3545 -ER_CANT_SET_GTID_PURGED_DUE_SETS_CONSTRAINTS = 3546 -ER_CANNOT_LOCK_USER_MANAGEMENT_CACHES = 3547 -ER_SRS_NOT_FOUND = 3548 -ER_VARIABLE_NOT_PERSISTED = 3549 -ER_IS_QUERY_INVALID_CLAUSE = 3550 -ER_UNABLE_TO_STORE_STATISTICS = 3551 -ER_NO_SYSTEM_SCHEMA_ACCESS = 3552 -ER_NO_SYSTEM_TABLESPACE_ACCESS = 3553 -ER_NO_SYSTEM_TABLE_ACCESS = 3554 -ER_NO_SYSTEM_TABLE_ACCESS_FOR_DICTIONARY_TABLE = 3555 -ER_NO_SYSTEM_TABLE_ACCESS_FOR_SYSTEM_TABLE = 3556 -ER_NO_SYSTEM_TABLE_ACCESS_FOR_TABLE = 3557 -ER_INVALID_OPTION_KEY = 3558 -ER_INVALID_OPTION_VALUE = 3559 -ER_INVALID_OPTION_KEY_VALUE_PAIR = 3560 -ER_INVALID_OPTION_START_CHARACTER = 3561 -ER_INVALID_OPTION_END_CHARACTER = 3562 -ER_INVALID_OPTION_CHARACTERS = 3563 -ER_DUPLICATE_OPTION_KEY = 3564 -ER_WARN_SRS_NOT_FOUND_AXIS_ORDER = 3565 -ER_NO_ACCESS_TO_NATIVE_FCT = 3566 -ER_RESET_MASTER_TO_VALUE_OUT_OF_RANGE = 3567 -ER_UNRESOLVED_TABLE_LOCK = 3568 -ER_DUPLICATE_TABLE_LOCK = 3569 -ER_BINLOG_UNSAFE_SKIP_LOCKED = 3570 -ER_BINLOG_UNSAFE_NOWAIT = 3571 -ER_LOCK_NOWAIT = 3572 -ER_CTE_RECURSIVE_REQUIRES_UNION = 3573 -ER_CTE_RECURSIVE_REQUIRES_NONRECURSIVE_FIRST = 3574 -ER_CTE_RECURSIVE_FORBIDS_AGGREGATION = 3575 -ER_CTE_RECURSIVE_FORBIDDEN_JOIN_ORDER = 3576 -ER_CTE_RECURSIVE_REQUIRES_SINGLE_REFERENCE = 3577 -ER_SWITCH_TMP_ENGINE = 3578 -ER_WINDOW_NO_SUCH_WINDOW = 3579 -ER_WINDOW_CIRCULARITY_IN_WINDOW_GRAPH = 3580 -ER_WINDOW_NO_CHILD_PARTITIONING = 3581 -ER_WINDOW_NO_INHERIT_FRAME = 3582 -ER_WINDOW_NO_REDEFINE_ORDER_BY = 3583 -ER_WINDOW_FRAME_START_ILLEGAL = 3584 -ER_WINDOW_FRAME_END_ILLEGAL = 3585 -ER_WINDOW_FRAME_ILLEGAL = 3586 -ER_WINDOW_RANGE_FRAME_ORDER_TYPE = 3587 -ER_WINDOW_RANGE_FRAME_TEMPORAL_TYPE = 3588 -ER_WINDOW_RANGE_FRAME_NUMERIC_TYPE = 3589 -ER_WINDOW_RANGE_BOUND_NOT_CONSTANT = 3590 -ER_WINDOW_DUPLICATE_NAME = 3591 -ER_WINDOW_ILLEGAL_ORDER_BY = 3592 -ER_WINDOW_INVALID_WINDOW_FUNC_USE = 3593 -ER_WINDOW_INVALID_WINDOW_FUNC_ALIAS_USE = 3594 -ER_WINDOW_NESTED_WINDOW_FUNC_USE_IN_WINDOW_SPEC = 3595 -ER_WINDOW_ROWS_INTERVAL_USE = 3596 -ER_WINDOW_NO_GROUP_ORDER_UNUSED = 3597 -ER_WINDOW_EXPLAIN_JSON = 3598 -ER_WINDOW_FUNCTION_IGNORES_FRAME = 3599 -ER_WL9236_NOW_UNUSED = 3600 -ER_INVALID_NO_OF_ARGS = 3601 -ER_FIELD_IN_GROUPING_NOT_GROUP_BY = 3602 -ER_TOO_LONG_TABLESPACE_COMMENT = 3603 -ER_ENGINE_CANT_DROP_TABLE = 3604 -ER_ENGINE_CANT_DROP_MISSING_TABLE = 3605 -ER_TABLESPACE_DUP_FILENAME = 3606 -ER_DB_DROP_RMDIR2 = 3607 -ER_IMP_NO_FILES_MATCHED = 3608 -ER_IMP_SCHEMA_DOES_NOT_EXIST = 3609 -ER_IMP_TABLE_ALREADY_EXISTS = 3610 -ER_IMP_INCOMPATIBLE_MYSQLD_VERSION = 3611 -ER_IMP_INCOMPATIBLE_DD_VERSION = 3612 -ER_IMP_INCOMPATIBLE_SDI_VERSION = 3613 -ER_WARN_INVALID_HINT = 3614 -ER_VAR_DOES_NOT_EXIST = 3615 -ER_LONGITUDE_OUT_OF_RANGE = 3616 -ER_LATITUDE_OUT_OF_RANGE = 3617 -ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS = 3618 -ER_ILLEGAL_PRIVILEGE_LEVEL = 3619 -ER_NO_SYSTEM_VIEW_ACCESS = 3620 -ER_COMPONENT_FILTER_FLABBERGASTED = 3621 -ER_PART_EXPR_TOO_LONG = 3622 -ER_UDF_DROP_DYNAMICALLY_REGISTERED = 3623 -ER_UNABLE_TO_STORE_COLUMN_STATISTICS = 3624 -ER_UNABLE_TO_UPDATE_COLUMN_STATISTICS = 3625 -ER_UNABLE_TO_DROP_COLUMN_STATISTICS = 3626 -ER_UNABLE_TO_BUILD_HISTOGRAM = 3627 -ER_MANDATORY_ROLE = 3628 -ER_MISSING_TABLESPACE_FILE = 3629 -ER_PERSIST_ONLY_ACCESS_DENIED_ERROR = 3630 -ER_CMD_NEED_SUPER = 3631 -ER_PATH_IN_DATADIR = 3632 -ER_CLONE_DDL_IN_PROGRESS = 3633 -ER_CLONE_TOO_MANY_CONCURRENT_CLONES = 3634 -ER_APPLIER_LOG_EVENT_VALIDATION_ERROR = 3635 -ER_CTE_MAX_RECURSION_DEPTH = 3636 -ER_NOT_HINT_UPDATABLE_VARIABLE = 3637 -ER_CREDENTIALS_CONTRADICT_TO_HISTORY = 3638 -ER_WARNING_PASSWORD_HISTORY_CLAUSES_VOID = 3639 -ER_CLIENT_DOES_NOT_SUPPORT = 3640 -ER_I_S_SKIPPED_TABLESPACE = 3641 -ER_TABLESPACE_ENGINE_MISMATCH = 3642 -ER_WRONG_SRID_FOR_COLUMN = 3643 -ER_CANNOT_ALTER_SRID_DUE_TO_INDEX = 3644 -ER_WARN_BINLOG_PARTIAL_UPDATES_DISABLED = 3645 -ER_WARN_BINLOG_V1_ROW_EVENTS_DISABLED = 3646 -ER_WARN_BINLOG_PARTIAL_UPDATES_SUGGESTS_PARTIAL_IMAGES = 3647 -ER_COULD_NOT_APPLY_JSON_DIFF = 3648 -ER_CORRUPTED_JSON_DIFF = 3649 -ER_RESOURCE_GROUP_EXISTS = 3650 -ER_RESOURCE_GROUP_NOT_EXISTS = 3651 -ER_INVALID_VCPU_ID = 3652 -ER_INVALID_VCPU_RANGE = 3653 -ER_INVALID_THREAD_PRIORITY = 3654 -ER_DISALLOWED_OPERATION = 3655 -ER_RESOURCE_GROUP_BUSY = 3656 -ER_RESOURCE_GROUP_DISABLED = 3657 -ER_FEATURE_UNSUPPORTED = 3658 -ER_ATTRIBUTE_IGNORED = 3659 -ER_INVALID_THREAD_ID = 3660 -ER_RESOURCE_GROUP_BIND_FAILED = 3661 -ER_INVALID_USE_OF_FORCE_OPTION = 3662 -ER_GROUP_REPLICATION_COMMAND_FAILURE = 3663 -ER_SDI_OPERATION_FAILED = 3664 -ER_MISSING_JSON_TABLE_VALUE = 3665 -ER_WRONG_JSON_TABLE_VALUE = 3666 -ER_TF_MUST_HAVE_ALIAS = 3667 -ER_TF_FORBIDDEN_JOIN_TYPE = 3668 -ER_JT_VALUE_OUT_OF_RANGE = 3669 -ER_JT_MAX_NESTED_PATH = 3670 -ER_PASSWORD_EXPIRATION_NOT_SUPPORTED_BY_AUTH_METHOD = 3671 -ER_INVALID_GEOJSON_CRS_NOT_TOP_LEVEL = 3672 -ER_BAD_NULL_ERROR_NOT_IGNORED = 3673 -WARN_USELESS_SPATIAL_INDEX = 3674 -ER_DISK_FULL_NOWAIT = 3675 -ER_PARSE_ERROR_IN_DIGEST_FN = 3676 -ER_UNDISCLOSED_PARSE_ERROR_IN_DIGEST_FN = 3677 -ER_SCHEMA_DIR_EXISTS = 3678 -ER_SCHEMA_DIR_MISSING = 3679 -ER_SCHEMA_DIR_CREATE_FAILED = 3680 -ER_SCHEMA_DIR_UNKNOWN = 3681 -ER_ONLY_IMPLEMENTED_FOR_SRID_0_AND_4326 = 3682 -ER_BINLOG_EXPIRE_LOG_DAYS_AND_SECS_USED_TOGETHER = 3683 -ER_REGEXP_BUFFER_OVERFLOW = 3684 -ER_REGEXP_ILLEGAL_ARGUMENT = 3685 -ER_REGEXP_INDEX_OUTOFBOUNDS_ERROR = 3686 -ER_REGEXP_INTERNAL_ERROR = 3687 -ER_REGEXP_RULE_SYNTAX = 3688 -ER_REGEXP_BAD_ESCAPE_SEQUENCE = 3689 -ER_REGEXP_UNIMPLEMENTED = 3690 -ER_REGEXP_MISMATCHED_PAREN = 3691 -ER_REGEXP_BAD_INTERVAL = 3692 -ER_REGEXP_MAX_LT_MIN = 3693 -ER_REGEXP_INVALID_BACK_REF = 3694 -ER_REGEXP_LOOK_BEHIND_LIMIT = 3695 -ER_REGEXP_MISSING_CLOSE_BRACKET = 3696 -ER_REGEXP_INVALID_RANGE = 3697 -ER_REGEXP_STACK_OVERFLOW = 3698 -ER_REGEXP_TIME_OUT = 3699 -ER_REGEXP_PATTERN_TOO_BIG = 3700 -ER_CANT_SET_ERROR_LOG_SERVICE = 3701 -ER_EMPTY_PIPELINE_FOR_ERROR_LOG_SERVICE = 3702 -ER_COMPONENT_FILTER_DIAGNOSTICS = 3703 -ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS = 3704 -ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS = 3705 -ER_NONPOSITIVE_RADIUS = 3706 -ER_RESTART_SERVER_FAILED = 3707 -ER_SRS_MISSING_MANDATORY_ATTRIBUTE = 3708 -ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS = 3709 -ER_SRS_NAME_CANT_BE_EMPTY_OR_WHITESPACE = 3710 -ER_SRS_ORGANIZATION_CANT_BE_EMPTY_OR_WHITESPACE = 3711 -ER_SRS_ID_ALREADY_EXISTS = 3712 -ER_WARN_SRS_ID_ALREADY_EXISTS = 3713 -ER_CANT_MODIFY_SRID_0 = 3714 -ER_WARN_RESERVED_SRID_RANGE = 3715 -ER_CANT_MODIFY_SRS_USED_BY_COLUMN = 3716 -ER_SRS_INVALID_CHARACTER_IN_ATTRIBUTE = 3717 -ER_SRS_ATTRIBUTE_STRING_TOO_LONG = 3718 -ER_DEPRECATED_UTF8_ALIAS = 3719 -ER_DEPRECATED_NATIONAL = 3720 -ER_INVALID_DEFAULT_UTF8MB4_COLLATION = 3721 -ER_UNABLE_TO_COLLECT_LOG_STATUS = 3722 -ER_RESERVED_TABLESPACE_NAME = 3723 -ER_UNABLE_TO_SET_OPTION = 3724 -ER_SLAVE_POSSIBLY_DIVERGED_AFTER_DDL = 3725 -ER_SRS_NOT_GEOGRAPHIC = 3726 -ER_POLYGON_TOO_LARGE = 3727 -ER_SPATIAL_UNIQUE_INDEX = 3728 -ER_INDEX_TYPE_NOT_SUPPORTED_FOR_SPATIAL_INDEX = 3729 -ER_FK_CANNOT_DROP_PARENT = 3730 -ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE = 3731 -ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE = 3732 -ER_FK_CANNOT_USE_VIRTUAL_COLUMN = 3733 -ER_FK_NO_COLUMN_PARENT = 3734 -ER_CANT_SET_ERROR_SUPPRESSION_LIST = 3735 -ER_SRS_GEOGCS_INVALID_AXES = 3736 -ER_SRS_INVALID_SEMI_MAJOR_AXIS = 3737 -ER_SRS_INVALID_INVERSE_FLATTENING = 3738 -ER_SRS_INVALID_ANGULAR_UNIT = 3739 -ER_SRS_INVALID_PRIME_MERIDIAN = 3740 -ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED = 3741 -ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED = 3742 -ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84 = 3743 -ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84 = 3744 -ER_TEMP_TABLE_PREVENTS_SWITCH_SESSION_BINLOG_FORMAT = 3745 -ER_TEMP_TABLE_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3746 -ER_RUNNING_APPLIER_PREVENTS_SWITCH_GLOBAL_BINLOG_FORMAT = 3747 -ER_CLIENT_GTID_UNSAFE_CREATE_DROP_TEMP_TABLE_IN_TRX_IN_SBR = 3748 -OBSOLETE_ER_XA_CANT_CREATE_MDL_BACKUP = 3749 -ER_TABLE_WITHOUT_PK = 3750 -ER_WARN_DATA_TRUNCATED_FUNCTIONAL_INDEX = 3751 -ER_WARN_DATA_OUT_OF_RANGE_FUNCTIONAL_INDEX = 3752 -ER_FUNCTIONAL_INDEX_ON_JSON_OR_GEOMETRY_FUNCTION = 3753 -ER_FUNCTIONAL_INDEX_REF_AUTO_INCREMENT = 3754 -ER_CANNOT_DROP_COLUMN_FUNCTIONAL_INDEX = 3755 -ER_FUNCTIONAL_INDEX_PRIMARY_KEY = 3756 -ER_FUNCTIONAL_INDEX_ON_LOB = 3757 -ER_FUNCTIONAL_INDEX_FUNCTION_IS_NOT_ALLOWED = 3758 -ER_FULLTEXT_FUNCTIONAL_INDEX = 3759 -ER_SPATIAL_FUNCTIONAL_INDEX = 3760 -ER_WRONG_KEY_COLUMN_FUNCTIONAL_INDEX = 3761 -ER_FUNCTIONAL_INDEX_ON_FIELD = 3762 -ER_GENERATED_COLUMN_NAMED_FUNCTION_IS_NOT_ALLOWED = 3763 -ER_GENERATED_COLUMN_ROW_VALUE = 3764 -ER_GENERATED_COLUMN_VARIABLES = 3765 -ER_DEPENDENT_BY_DEFAULT_GENERATED_VALUE = 3766 -ER_DEFAULT_VAL_GENERATED_NON_PRIOR = 3767 -ER_DEFAULT_VAL_GENERATED_REF_AUTO_INC = 3768 -ER_DEFAULT_VAL_GENERATED_FUNCTION_IS_NOT_ALLOWED = 3769 -ER_DEFAULT_VAL_GENERATED_NAMED_FUNCTION_IS_NOT_ALLOWED = 3770 -ER_DEFAULT_VAL_GENERATED_ROW_VALUE = 3771 -ER_DEFAULT_VAL_GENERATED_VARIABLES = 3772 -ER_DEFAULT_AS_VAL_GENERATED = 3773 -ER_UNSUPPORTED_ACTION_ON_DEFAULT_VAL_GENERATED = 3774 -ER_GTID_UNSAFE_ALTER_ADD_COL_WITH_DEFAULT_EXPRESSION = 3775 -ER_FK_CANNOT_CHANGE_ENGINE = 3776 -ER_WARN_DEPRECATED_USER_SET_EXPR = 3777 -ER_WARN_DEPRECATED_UTF8MB3_COLLATION = 3778 -ER_WARN_DEPRECATED_NESTED_COMMENT_SYNTAX = 3779 -ER_FK_INCOMPATIBLE_COLUMNS = 3780 -ER_GR_HOLD_WAIT_TIMEOUT = 3781 -ER_GR_HOLD_KILLED = 3782 -ER_GR_HOLD_MEMBER_STATUS_ERROR = 3783 -ER_RPL_ENCRYPTION_FAILED_TO_FETCH_KEY = 3784 -ER_RPL_ENCRYPTION_KEY_NOT_FOUND = 3785 -ER_RPL_ENCRYPTION_KEYRING_INVALID_KEY = 3786 -ER_RPL_ENCRYPTION_HEADER_ERROR = 3787 -ER_RPL_ENCRYPTION_FAILED_TO_ROTATE_LOGS = 3788 -ER_RPL_ENCRYPTION_KEY_EXISTS_UNEXPECTED = 3789 -ER_RPL_ENCRYPTION_FAILED_TO_GENERATE_KEY = 3790 -ER_RPL_ENCRYPTION_FAILED_TO_STORE_KEY = 3791 -ER_RPL_ENCRYPTION_FAILED_TO_REMOVE_KEY = 3792 -ER_RPL_ENCRYPTION_UNABLE_TO_CHANGE_OPTION = 3793 -ER_RPL_ENCRYPTION_MASTER_KEY_RECOVERY_FAILED = 3794 -ER_SLOW_LOG_MODE_IGNORED_WHEN_NOT_LOGGING_TO_FILE = 3795 -ER_GRP_TRX_CONSISTENCY_NOT_ALLOWED = 3796 -ER_GRP_TRX_CONSISTENCY_BEFORE = 3797 -ER_GRP_TRX_CONSISTENCY_AFTER_ON_TRX_BEGIN = 3798 -ER_GRP_TRX_CONSISTENCY_BEGIN_NOT_ALLOWED = 3799 -ER_FUNCTIONAL_INDEX_ROW_VALUE_IS_NOT_ALLOWED = 3800 -ER_RPL_ENCRYPTION_FAILED_TO_ENCRYPT = 3801 -ER_PAGE_TRACKING_NOT_STARTED = 3802 -ER_PAGE_TRACKING_RANGE_NOT_TRACKED = 3803 -ER_PAGE_TRACKING_CANNOT_PURGE = 3804 -ER_RPL_ENCRYPTION_CANNOT_ROTATE_BINLOG_MASTER_KEY = 3805 -ER_BINLOG_MASTER_KEY_RECOVERY_OUT_OF_COMBINATION = 3806 -ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_OPERATE_KEY = 3807 -ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_ROTATE_LOGS = 3808 -ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_REENCRYPT_LOG = 3809 -ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_UNUSED_KEYS = 3810 -ER_BINLOG_MASTER_KEY_ROTATION_FAIL_TO_CLEANUP_AUX_KEY = 3811 -ER_NON_BOOLEAN_EXPR_FOR_CHECK_CONSTRAINT = 3812 -ER_COLUMN_CHECK_CONSTRAINT_REFERENCES_OTHER_COLUMN = 3813 -ER_CHECK_CONSTRAINT_NAMED_FUNCTION_IS_NOT_ALLOWED = 3814 -ER_CHECK_CONSTRAINT_FUNCTION_IS_NOT_ALLOWED = 3815 -ER_CHECK_CONSTRAINT_VARIABLES = 3816 -ER_CHECK_CONSTRAINT_ROW_VALUE = 3817 -ER_CHECK_CONSTRAINT_REFERS_AUTO_INCREMENT_COLUMN = 3818 -ER_CHECK_CONSTRAINT_VIOLATED = 3819 -ER_CHECK_CONSTRAINT_REFERS_UNKNOWN_COLUMN = 3820 -ER_CHECK_CONSTRAINT_NOT_FOUND = 3821 -ER_CHECK_CONSTRAINT_DUP_NAME = 3822 -ER_CHECK_CONSTRAINT_CLAUSE_USING_FK_REFER_ACTION_COLUMN = 3823 -WARN_UNENCRYPTED_TABLE_IN_ENCRYPTED_DB = 3824 -ER_INVALID_ENCRYPTION_REQUEST = 3825 -ER_CANNOT_SET_TABLE_ENCRYPTION = 3826 -ER_CANNOT_SET_DATABASE_ENCRYPTION = 3827 -ER_CANNOT_SET_TABLESPACE_ENCRYPTION = 3828 -ER_TABLESPACE_CANNOT_BE_ENCRYPTED = 3829 -ER_TABLESPACE_CANNOT_BE_DECRYPTED = 3830 -ER_TABLESPACE_TYPE_UNKNOWN = 3831 -ER_TARGET_TABLESPACE_UNENCRYPTED = 3832 -ER_CANNOT_USE_ENCRYPTION_CLAUSE = 3833 -ER_INVALID_MULTIPLE_CLAUSES = 3834 -ER_UNSUPPORTED_USE_OF_GRANT_AS = 3835 -ER_UKNOWN_AUTH_ID_OR_ACCESS_DENIED_FOR_GRANT_AS = 3836 -ER_DEPENDENT_BY_FUNCTIONAL_INDEX = 3837 -ER_PLUGIN_NOT_EARLY = 3838 -ER_INNODB_REDO_LOG_ARCHIVE_START_SUBDIR_PATH = 3839 -ER_INNODB_REDO_LOG_ARCHIVE_START_TIMEOUT = 3840 -ER_INNODB_REDO_LOG_ARCHIVE_DIRS_INVALID = 3841 -ER_INNODB_REDO_LOG_ARCHIVE_LABEL_NOT_FOUND = 3842 -ER_INNODB_REDO_LOG_ARCHIVE_DIR_EMPTY = 3843 -ER_INNODB_REDO_LOG_ARCHIVE_NO_SUCH_DIR = 3844 -ER_INNODB_REDO_LOG_ARCHIVE_DIR_CLASH = 3845 -ER_INNODB_REDO_LOG_ARCHIVE_DIR_PERMISSIONS = 3846 -ER_INNODB_REDO_LOG_ARCHIVE_FILE_CREATE = 3847 -ER_INNODB_REDO_LOG_ARCHIVE_ACTIVE = 3848 -ER_INNODB_REDO_LOG_ARCHIVE_INACTIVE = 3849 -ER_INNODB_REDO_LOG_ARCHIVE_FAILED = 3850 -ER_INNODB_REDO_LOG_ARCHIVE_SESSION = 3851 -ER_STD_REGEX_ERROR = 3852 -ER_INVALID_JSON_TYPE = 3853 -ER_CANNOT_CONVERT_STRING = 3854 -ER_DEPENDENT_BY_PARTITION_FUNC = 3855 -ER_WARN_DEPRECATED_FLOAT_AUTO_INCREMENT = 3856 -ER_RPL_CANT_STOP_SLAVE_WHILE_LOCKED_BACKUP = 3857 -ER_WARN_DEPRECATED_FLOAT_DIGITS = 3858 -ER_WARN_DEPRECATED_FLOAT_UNSIGNED = 3859 -ER_WARN_DEPRECATED_INTEGER_DISPLAY_WIDTH = 3860 -ER_WARN_DEPRECATED_ZEROFILL = 3861 -ER_CLONE_DONOR = 3862 -ER_CLONE_PROTOCOL = 3863 -ER_CLONE_DONOR_VERSION = 3864 -ER_CLONE_OS = 3865 -ER_CLONE_PLATFORM = 3866 -ER_CLONE_CHARSET = 3867 -ER_CLONE_CONFIG = 3868 -ER_CLONE_SYS_CONFIG = 3869 -ER_CLONE_PLUGIN_MATCH = 3870 -ER_CLONE_LOOPBACK = 3871 -ER_CLONE_ENCRYPTION = 3872 -ER_CLONE_DISK_SPACE = 3873 -ER_CLONE_IN_PROGRESS = 3874 -ER_CLONE_DISALLOWED = 3875 -ER_CANNOT_GRANT_ROLES_TO_ANONYMOUS_USER = 3876 -ER_SECONDARY_ENGINE_PLUGIN = 3877 -ER_SECOND_PASSWORD_CANNOT_BE_EMPTY = 3878 -ER_DB_ACCESS_DENIED = 3879 -ER_DA_AUTH_ID_WITH_SYSTEM_USER_PRIV_IN_MANDATORY_ROLES = 3880 -ER_DA_RPL_GTID_TABLE_CANNOT_OPEN = 3881 -ER_GEOMETRY_IN_UNKNOWN_LENGTH_UNIT = 3882 -ER_DA_PLUGIN_INSTALL_ERROR = 3883 -ER_NO_SESSION_TEMP = 3884 -ER_DA_UNKNOWN_ERROR_NUMBER = 3885 -ER_COLUMN_CHANGE_SIZE = 3886 -ER_REGEXP_INVALID_CAPTURE_GROUP_NAME = 3887 -ER_DA_SSL_LIBRARY_ERROR = 3888 -ER_SECONDARY_ENGINE = 3889 -ER_SECONDARY_ENGINE_DDL = 3890 -ER_INCORRECT_CURRENT_PASSWORD = 3891 -ER_MISSING_CURRENT_PASSWORD = 3892 -ER_CURRENT_PASSWORD_NOT_REQUIRED = 3893 -ER_PASSWORD_CANNOT_BE_RETAINED_ON_PLUGIN_CHANGE = 3894 -ER_CURRENT_PASSWORD_CANNOT_BE_RETAINED = 3895 -ER_PARTIAL_REVOKES_EXIST = 3896 -ER_CANNOT_GRANT_SYSTEM_PRIV_TO_MANDATORY_ROLE = 3897 -ER_XA_REPLICATION_FILTERS = 3898 -ER_UNSUPPORTED_SQL_MODE = 3899 -ER_REGEXP_INVALID_FLAG = 3900 -ER_PARTIAL_REVOKE_AND_DB_GRANT_BOTH_EXISTS = 3901 -ER_UNIT_NOT_FOUND = 3902 -ER_INVALID_JSON_VALUE_FOR_FUNC_INDEX = 3903 -ER_JSON_VALUE_OUT_OF_RANGE_FOR_FUNC_INDEX = 3904 -ER_EXCEEDED_MV_KEYS_NUM = 3905 -ER_EXCEEDED_MV_KEYS_SPACE = 3906 -ER_FUNCTIONAL_INDEX_DATA_IS_TOO_LONG = 3907 -ER_WRONG_MVI_VALUE = 3908 -ER_WARN_FUNC_INDEX_NOT_APPLICABLE = 3909 -ER_GRP_RPL_UDF_ERROR = 3910 -ER_UPDATE_GTID_PURGED_WITH_GR = 3911 -ER_GROUPING_ON_TIMESTAMP_IN_DST = 3912 -ER_TABLE_NAME_CAUSES_TOO_LONG_PATH = 3913 -ER_AUDIT_LOG_INSUFFICIENT_PRIVILEGE = 3914 -OBSOLETE_ER_AUDIT_LOG_PASSWORD_HAS_BEEN_COPIED = 3915 -ER_DA_GRP_RPL_STARTED_AUTO_REJOIN = 3916 -ER_SYSVAR_CHANGE_DURING_QUERY = 3917 -ER_GLOBSTAT_CHANGE_DURING_QUERY = 3918 -ER_GRP_RPL_MESSAGE_SERVICE_INIT_FAILURE = 3919 -ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3920 -ER_CHANGE_MASTER_WRONG_COMPRESSION_LEVEL_CLIENT = 3921 -ER_WRONG_COMPRESSION_ALGORITHM_CLIENT = 3922 -ER_WRONG_COMPRESSION_LEVEL_CLIENT = 3923 -ER_CHANGE_MASTER_WRONG_COMPRESSION_ALGORITHM_LIST_CLIENT = 3924 -ER_CLIENT_PRIVILEGE_CHECKS_USER_CANNOT_BE_ANONYMOUS = 3925 -ER_CLIENT_PRIVILEGE_CHECKS_USER_DOES_NOT_EXIST = 3926 -ER_CLIENT_PRIVILEGE_CHECKS_USER_CORRUPT = 3927 -ER_CLIENT_PRIVILEGE_CHECKS_USER_NEEDS_RPL_APPLIER_PRIV = 3928 -ER_WARN_DA_PRIVILEGE_NOT_REGISTERED = 3929 -ER_CLIENT_KEYRING_UDF_KEY_INVALID = 3930 -ER_CLIENT_KEYRING_UDF_KEY_TYPE_INVALID = 3931 -ER_CLIENT_KEYRING_UDF_KEY_TOO_LONG = 3932 -ER_CLIENT_KEYRING_UDF_KEY_TYPE_TOO_LONG = 3933 -ER_JSON_SCHEMA_VALIDATION_ERROR_WITH_DETAILED_REPORT = 3934 -ER_DA_UDF_INVALID_CHARSET_SPECIFIED = 3935 -ER_DA_UDF_INVALID_CHARSET = 3936 -ER_DA_UDF_INVALID_COLLATION = 3937 -ER_DA_UDF_INVALID_EXTENSION_ARGUMENT_TYPE = 3938 -ER_MULTIPLE_CONSTRAINTS_WITH_SAME_NAME = 3939 -ER_CONSTRAINT_NOT_FOUND = 3940 -ER_ALTER_CONSTRAINT_ENFORCEMENT_NOT_SUPPORTED = 3941 -ER_TABLE_VALUE_CONSTRUCTOR_MUST_HAVE_COLUMNS = 3942 -ER_TABLE_VALUE_CONSTRUCTOR_CANNOT_HAVE_DEFAULT = 3943 -ER_CLIENT_QUERY_FAILURE_INVALID_NON_ROW_FORMAT = 3944 -ER_REQUIRE_ROW_FORMAT_INVALID_VALUE = 3945 -ER_FAILED_TO_DETERMINE_IF_ROLE_IS_MANDATORY = 3946 -ER_FAILED_TO_FETCH_MANDATORY_ROLE_LIST = 3947 -ER_CLIENT_LOCAL_FILES_DISABLED = 3948 -ER_IMP_INCOMPATIBLE_CFG_VERSION = 3949 -ER_DA_OOM = 3950 -ER_DA_UDF_INVALID_ARGUMENT_TO_SET_CHARSET = 3951 -ER_DA_UDF_INVALID_RETURN_TYPE_TO_SET_CHARSET = 3952 -ER_MULTIPLE_INTO_CLAUSES = 3953 -ER_MISPLACED_INTO = 3954 -ER_USER_ACCESS_DENIED_FOR_USER_ACCOUNT_BLOCKED_BY_PASSWORD_LOCK = 3955 -ER_WARN_DEPRECATED_YEAR_UNSIGNED = 3956 -ER_CLONE_NETWORK_PACKET = 3957 -ER_SDI_OPERATION_FAILED_MISSING_RECORD = 3958 -ER_DEPENDENT_BY_CHECK_CONSTRAINT = 3959 -ER_GRP_OPERATION_NOT_ALLOWED_GR_MUST_STOP = 3960 -ER_WARN_DEPRECATED_JSON_TABLE_ON_ERROR_ON_EMPTY = 3961 -ER_WARN_DEPRECATED_INNER_INTO = 3962 -ER_WARN_DEPRECATED_VALUES_FUNCTION_ALWAYS_NULL = 3963 -ER_WARN_DEPRECATED_SQL_CALC_FOUND_ROWS = 3964 -ER_WARN_DEPRECATED_FOUND_ROWS = 3965 -ER_MISSING_JSON_VALUE = 3966 -ER_MULTIPLE_JSON_VALUES = 3967 -ER_HOSTNAME_TOO_LONG = 3968 -ER_WARN_CLIENT_DEPRECATED_PARTITION_PREFIX_KEY = 3969 -ER_GROUP_REPLICATION_USER_EMPTY_MSG = 3970 -ER_GROUP_REPLICATION_USER_MANDATORY_MSG = 3971 -ER_GROUP_REPLICATION_PASSWORD_LENGTH = 3972 -ER_SUBQUERY_TRANSFORM_REJECTED = 3973 -ER_DA_GRP_RPL_RECOVERY_ENDPOINT_FORMAT = 3974 -ER_DA_GRP_RPL_RECOVERY_ENDPOINT_INVALID = 3975 -ER_WRONG_VALUE_FOR_VAR_PLUS_ACTIONABLE_PART = 3976 -ER_STATEMENT_NOT_ALLOWED_AFTER_START_TRANSACTION = 3977 -ER_FOREIGN_KEY_WITH_ATOMIC_CREATE_SELECT = 3978 -ER_NOT_ALLOWED_WITH_START_TRANSACTION = 3979 -ER_INVALID_JSON_ATTRIBUTE = 3980 -ER_ENGINE_ATTRIBUTE_NOT_SUPPORTED = 3981 -ER_INVALID_USER_ATTRIBUTE_JSON = 3982 -ER_INNODB_REDO_DISABLED = 3983 -ER_INNODB_REDO_ARCHIVING_ENABLED = 3984 -ER_MDL_OUT_OF_RESOURCES = 3985 -ER_IMPLICIT_COMPARISON_FOR_JSON = 3986 -ER_FUNCTION_DOES_NOT_SUPPORT_CHARACTER_SET = 3987 -ER_IMPOSSIBLE_STRING_CONVERSION = 3988 -ER_SCHEMA_READ_ONLY = 3989 -ER_RPL_ASYNC_RECONNECT_GTID_MODE_OFF = 3990 -ER_RPL_ASYNC_RECONNECT_AUTO_POSITION_OFF = 3991 -ER_DISABLE_GTID_MODE_REQUIRES_ASYNC_RECONNECT_OFF = 3992 -ER_DISABLE_AUTO_POSITION_REQUIRES_ASYNC_RECONNECT_OFF = 3993 -ER_INVALID_PARAMETER_USE = 3994 -ER_CHARACTER_SET_MISMATCH = 3995 -ER_WARN_VAR_VALUE_CHANGE_NOT_SUPPORTED = 3996 -ER_INVALID_TIME_ZONE_INTERVAL = 3997 -ER_INVALID_CAST = 3998 -ER_HYPERGRAPH_NOT_SUPPORTED_YET = 3999 -ER_WARN_HYPERGRAPH_EXPERIMENTAL = 4000 -ER_DA_NO_ERROR_LOG_PARSER_CONFIGURED = 4001 -ER_DA_ERROR_LOG_TABLE_DISABLED = 4002 -ER_DA_ERROR_LOG_MULTIPLE_FILTERS = 4003 -ER_DA_CANT_OPEN_ERROR_LOG = 4004 -ER_USER_REFERENCED_AS_DEFINER = 4005 -ER_CANNOT_USER_REFERENCED_AS_DEFINER = 4006 -ER_REGEX_NUMBER_TOO_BIG = 4007 -ER_SPVAR_NONINTEGER_TYPE = 4008 -WARN_UNSUPPORTED_ACL_TABLES_READ = 4009 -ER_BINLOG_UNSAFE_ACL_TABLE_READ_IN_DML_DDL = 4010 -ER_STOP_REPLICA_MONITOR_IO_THREAD_TIMEOUT = 4011 -ER_STARTING_REPLICA_MONITOR_IO_THREAD = 4012 -ER_CANT_USE_ANONYMOUS_TO_GTID_WITH_GTID_MODE_NOT_ON = 4013 -ER_CANT_COMBINE_ANONYMOUS_TO_GTID_AND_AUTOPOSITION = 4014 -ER_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_REQUIRES_GTID_MODE_ON = 4015 -ER_SQL_REPLICA_SKIP_COUNTER_USED_WITH_GTID_MODE_ON = 4016 -ER_USING_ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS_AS_LOCAL_OR_UUID = 4017 -ER_CANT_SET_ANONYMOUS_TO_GTID_AND_WAIT_UNTIL_SQL_THD_AFTER_GTIDS = 4018 -ER_CANT_SET_SQL_AFTER_OR_BEFORE_GTIDS_WITH_ANONYMOUS_TO_GTID = 4019 -ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_GROUP_NAME = 4020 -ER_CANT_USE_SAME_UUID_AS_GROUP_NAME = 4021 -ER_GRP_RPL_RECOVERY_CHANNEL_STILL_RUNNING = 4022 -ER_INNODB_INVALID_AUTOEXTEND_SIZE_VALUE = 4023 -ER_INNODB_INCOMPATIBLE_WITH_TABLESPACE = 4024 -ER_INNODB_AUTOEXTEND_SIZE_OUT_OF_RANGE = 4025 -ER_CANNOT_USE_AUTOEXTEND_SIZE_CLAUSE = 4026 -ER_ROLE_GRANTED_TO_ITSELF = 4027 -ER_TABLE_MUST_HAVE_A_VISIBLE_COLUMN = 4028 -ER_INNODB_COMPRESSION_FAILURE = 4029 -ER_WARN_ASYNC_CONN_FAILOVER_NETWORK_NAMESPACE = 4030 -ER_CLIENT_INTERACTION_TIMEOUT = 4031 -ER_INVALID_CAST_TO_GEOMETRY = 4032 -ER_INVALID_CAST_POLYGON_RING_DIRECTION = 4033 -ER_GIS_DIFFERENT_SRIDS_AGGREGATION = 4034 -ER_RELOAD_KEYRING_FAILURE = 4035 -ER_SDI_GET_KEYS_INVALID_TABLESPACE = 4036 -ER_CHANGE_RPL_SRC_WRONG_COMPRESSION_ALGORITHM_SIZE = 4037 -ER_WARN_DEPRECATED_TLS_VERSION_FOR_CHANNEL_CLI = 4038 -ER_CANT_USE_SAME_UUID_AS_VIEW_CHANGE_UUID = 4039 -ER_ANONYMOUS_TO_GTID_UUID_SAME_AS_VIEW_CHANGE_UUID = 4040 -ER_GRP_RPL_VIEW_CHANGE_UUID_FAIL_GET_VARIABLE = 4041 -ER_WARN_ADUIT_LOG_MAX_SIZE_AND_PRUNE_SECONDS = 4042 -ER_WARN_ADUIT_LOG_MAX_SIZE_CLOSE_TO_ROTATE_ON_SIZE = 4043 -ER_KERBEROS_CREATE_USER = 4044 -ER_INSTALL_PLUGIN_CONFLICT_CLIENT = 4045 -ER_DA_ERROR_LOG_COMPONENT_FLUSH_FAILED = 4046 -ER_WARN_SQL_AFTER_MTS_GAPS_GAP_NOT_CALCULATED = 4047 -ER_INVALID_ASSIGNMENT_TARGET = 4048 -ER_OPERATION_NOT_ALLOWED_ON_GR_SECONDARY = 4049 -ER_GRP_RPL_FAILOVER_CHANNEL_STATUS_PROPAGATION = 4050 -ER_WARN_AUDIT_LOG_FORMAT_UNIX_TIMESTAMP_ONLY_WHEN_JSON = 4051 -ER_INVALID_MFA_PLUGIN_SPECIFIED = 4052 -ER_IDENTIFIED_BY_UNSUPPORTED = 4053 -ER_INVALID_PLUGIN_FOR_REGISTRATION = 4054 -ER_PLUGIN_REQUIRES_REGISTRATION = 4055 -ER_MFA_METHOD_EXISTS = 4056 -ER_MFA_METHOD_NOT_EXISTS = 4057 -ER_AUTHENTICATION_POLICY_MISMATCH = 4058 -ER_PLUGIN_REGISTRATION_DONE = 4059 -ER_INVALID_USER_FOR_REGISTRATION = 4060 -ER_USER_REGISTRATION_FAILED = 4061 -ER_MFA_METHODS_INVALID_ORDER = 4062 -ER_MFA_METHODS_IDENTICAL = 4063 -ER_INVALID_MFA_OPERATIONS_FOR_PASSWORDLESS_USER = 4064 -ER_CHANGE_REPLICATION_SOURCE_NO_OPTIONS_FOR_GTID_ONLY = 4065 -ER_CHANGE_REP_SOURCE_CANT_DISABLE_REQ_ROW_FORMAT_WITH_GTID_ONLY = 4066 -ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POSITION_WITH_GTID_ONLY = 4067 -ER_CHANGE_REP_SOURCE_CANT_DISABLE_GTID_ONLY_WITHOUT_POSITIONS = 4068 -ER_CHANGE_REP_SOURCE_CANT_DISABLE_AUTO_POS_WITHOUT_POSITIONS = 4069 -ER_CHANGE_REP_SOURCE_GR_CHANNEL_WITH_GTID_MODE_NOT_ON = 4070 -ER_CANT_USE_GTID_ONLY_WITH_GTID_MODE_NOT_ON = 4071 -ER_WARN_C_DISABLE_GTID_ONLY_WITH_SOURCE_AUTO_POS_INVALID_POS = 4072 -ER_DA_SSL_FIPS_MODE_ERROR = 4073 -CR_UNKNOWN_ERROR = 2000 -CR_SOCKET_CREATE_ERROR = 2001 -CR_CONNECTION_ERROR = 2002 -CR_CONN_HOST_ERROR = 2003 -CR_IPSOCK_ERROR = 2004 -CR_UNKNOWN_HOST = 2005 -CR_SERVER_GONE_ERROR = 2006 -CR_VERSION_ERROR = 2007 -CR_OUT_OF_MEMORY = 2008 -CR_WRONG_HOST_INFO = 2009 -CR_LOCALHOST_CONNECTION = 2010 -CR_TCP_CONNECTION = 2011 -CR_SERVER_HANDSHAKE_ERR = 2012 -CR_SERVER_LOST = 2013 -CR_COMMANDS_OUT_OF_SYNC = 2014 -CR_NAMEDPIPE_CONNECTION = 2015 -CR_NAMEDPIPEWAIT_ERROR = 2016 -CR_NAMEDPIPEOPEN_ERROR = 2017 -CR_NAMEDPIPESETSTATE_ERROR = 2018 -CR_CANT_READ_CHARSET = 2019 -CR_NET_PACKET_TOO_LARGE = 2020 -CR_EMBEDDED_CONNECTION = 2021 -CR_PROBE_SLAVE_STATUS = 2022 -CR_PROBE_SLAVE_HOSTS = 2023 -CR_PROBE_SLAVE_CONNECT = 2024 -CR_PROBE_MASTER_CONNECT = 2025 -CR_SSL_CONNECTION_ERROR = 2026 -CR_MALFORMED_PACKET = 2027 -CR_WRONG_LICENSE = 2028 -CR_NULL_POINTER = 2029 -CR_NO_PREPARE_STMT = 2030 -CR_PARAMS_NOT_BOUND = 2031 -CR_DATA_TRUNCATED = 2032 -CR_NO_PARAMETERS_EXISTS = 2033 -CR_INVALID_PARAMETER_NO = 2034 -CR_INVALID_BUFFER_USE = 2035 -CR_UNSUPPORTED_PARAM_TYPE = 2036 -CR_SHARED_MEMORY_CONNECTION = 2037 -CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038 -CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039 -CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040 -CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041 -CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042 -CR_SHARED_MEMORY_MAP_ERROR = 2043 -CR_SHARED_MEMORY_EVENT_ERROR = 2044 -CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045 -CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046 -CR_CONN_UNKNOW_PROTOCOL = 2047 -CR_INVALID_CONN_HANDLE = 2048 -CR_UNUSED_1 = 2049 -CR_FETCH_CANCELED = 2050 -CR_NO_DATA = 2051 -CR_NO_STMT_METADATA = 2052 -CR_NO_RESULT_SET = 2053 -CR_NOT_IMPLEMENTED = 2054 -CR_SERVER_LOST_EXTENDED = 2055 -CR_STMT_CLOSED = 2056 -CR_NEW_STMT_METADATA = 2057 -CR_ALREADY_CONNECTED = 2058 -CR_AUTH_PLUGIN_CANNOT_LOAD = 2059 -CR_DUPLICATE_CONNECTION_ATTR = 2060 -CR_AUTH_PLUGIN_ERR = 2061 -CR_INSECURE_API_ERR = 2062 -CR_FILE_NAME_TOO_LONG = 2063 -CR_SSL_FIPS_MODE_ERR = 2064 -CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = 2065 -CR_COMPRESSION_WRONGLY_CONFIGURED = 2066 -CR_KERBEROS_USER_NOT_FOUND = 2067 -CR_LOAD_DATA_LOCAL_INFILE_REJECTED = 2068 -CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = 2069 -CR_DNS_SRV_LOOKUP_FAILED = 2070 -CR_MANDATORY_TRACKER_NOT_FOUND = 2071 -CR_INVALID_FACTOR_NO = 2072 -# End MySQL Errors - -# Start X Plugin Errors -ER_X_BAD_MESSAGE = 5000 -ER_X_CAPABILITIES_PREPARE_FAILED = 5001 -ER_X_CAPABILITY_NOT_FOUND = 5002 -ER_X_INVALID_PROTOCOL_DATA = 5003 -ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_VALUE_LENGTH = 5004 -ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_KEY_LENGTH = 5005 -ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_EMPTY_KEY = 5006 -ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_LENGTH = 5007 -ER_X_BAD_CONNECTION_SESSION_ATTRIBUTE_TYPE = 5008 -ER_X_CAPABILITY_SET_NOT_ALLOWED = 5009 -ER_X_SERVICE_ERROR = 5010 -ER_X_SESSION = 5011 -ER_X_INVALID_ARGUMENT = 5012 -ER_X_MISSING_ARGUMENT = 5013 -ER_X_BAD_INSERT_DATA = 5014 -ER_X_CMD_NUM_ARGUMENTS = 5015 -ER_X_CMD_ARGUMENT_TYPE = 5016 -ER_X_CMD_ARGUMENT_VALUE = 5017 -ER_X_BAD_UPSERT_DATA = 5018 -ER_X_DUPLICATED_CAPABILITIES = 5019 -ER_X_CMD_ARGUMENT_OBJECT_EMPTY = 5020 -ER_X_CMD_INVALID_ARGUMENT = 5021 -ER_X_BAD_UPDATE_DATA = 5050 -ER_X_BAD_TYPE_OF_UPDATE = 5051 -ER_X_BAD_COLUMN_TO_UPDATE = 5052 -ER_X_BAD_MEMBER_TO_UPDATE = 5053 -ER_X_BAD_STATEMENT_ID = 5110 -ER_X_BAD_CURSOR_ID = 5111 -ER_X_BAD_SCHEMA = 5112 -ER_X_BAD_TABLE = 5113 -ER_X_BAD_PROJECTION = 5114 -ER_X_DOC_ID_MISSING = 5115 -ER_X_DUPLICATE_ENTRY = 5116 -ER_X_DOC_REQUIRED_FIELD_MISSING = 5117 -ER_X_PROJ_BAD_KEY_NAME = 5120 -ER_X_BAD_DOC_PATH = 5121 -ER_X_CURSOR_EXISTS = 5122 -ER_X_CURSOR_REACHED_EOF = 5123 -ER_X_PREPARED_STATMENT_CAN_HAVE_ONE_CURSOR = 5131 -ER_X_PREPARED_EXECUTE_ARGUMENT_NOT_SUPPORTED = 5133 -ER_X_PREPARED_EXECUTE_ARGUMENT_CONSISTENCY = 5134 -ER_X_EXPR_BAD_OPERATOR = 5150 -ER_X_EXPR_BAD_NUM_ARGS = 5151 -ER_X_EXPR_MISSING_ARG = 5152 -ER_X_EXPR_BAD_TYPE_VALUE = 5153 -ER_X_EXPR_BAD_VALUE = 5154 -ER_X_INVALID_COLLECTION = 5156 -ER_X_INVALID_ADMIN_COMMAND = 5157 -ER_X_EXPECT_NOT_OPEN = 5158 -ER_X_EXPECT_NO_ERROR_FAILED = 5159 -ER_X_EXPECT_BAD_CONDITION = 5160 -ER_X_EXPECT_BAD_CONDITION_VALUE = 5161 -ER_X_INVALID_NAMESPACE = 5162 -ER_X_BAD_NOTICE = 5163 -ER_X_CANNOT_DISABLE_NOTICE = 5164 -ER_X_BAD_CONFIGURATION = 5165 -ER_X_MYSQLX_ACCOUNT_MISSING_PERMISSIONS = 5167 -ER_X_EXPECT_FIELD_EXISTS_FAILED = 5168 -ER_X_BAD_LOCKING = 5169 -ER_X_FRAME_COMPRESSION_DISABLED = 5170 -ER_X_DECOMPRESSION_FAILED = 5171 -ER_X_BAD_COMPRESSED_FRAME = 5174 -ER_X_CAPABILITY_COMPRESSION_INVALID_ALGORITHM = 5175 -ER_X_CAPABILITY_COMPRESSION_INVALID_SERVER_STYLE = 5176 -ER_X_CAPABILITY_COMPRESSION_INVALID_CLIENT_STYLE = 5177 -ER_X_CAPABILITY_COMPRESSION_INVALID_OPTION = 5178 -ER_X_CAPABILITY_COMPRESSION_MISSING_REQUIRED_FIELDS = 5179 -ER_X_DOCUMENT_DOESNT_MATCH_EXPECTED_SCHEMA = 5180 -ER_X_COLLECTION_OPTION_DOESNT_EXISTS = 5181 -ER_X_INVALID_VALIDATION_SCHEMA = 5182 -# End X Plugin Errors diff --git a/server/venv/Lib/site-packages/mysql/connector/errors.py b/server/venv/Lib/site-packages/mysql/connector/errors.py deleted file mode 100644 index bbfbddd..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/errors.py +++ /dev/null @@ -1,393 +0,0 @@ -# Copyright (c) 2009, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Python exceptions.""" -from typing import Dict, Mapping, Optional, Tuple, Type, Union - -from .locales import get_client_error -from .types import StrOrBytes -from .utils import read_bytes, read_int - - -class Error(Exception): - """Exception that is base class for all other error exceptions. - - See [1] for more details. - - References: - [1]: https://dev.mysql.com/doc/connector-python/en/connector-python-api-errors-error.html - """ - - def __init__( - self, - msg: Optional[str] = None, - errno: Optional[int] = None, - values: Optional[Tuple[Union[int, str], ...]] = None, - sqlstate: Optional[str] = None, - ) -> None: - super().__init__() - self.msg = msg - self._full_msg = self.msg - self.errno = errno or -1 - self.sqlstate = sqlstate - - if not self.msg and (2000 <= self.errno < 3000): - self.msg = get_client_error(self.errno) - if values is not None: - try: - self.msg = self.msg % values - except TypeError as err: - self.msg = f"{self.msg} (Warning: {err})" - elif not self.msg: - self._full_msg = self.msg = "Unknown error" - - if self.msg and self.errno != -1: - fields = {"errno": self.errno, "msg": self.msg} - if self.sqlstate: - fmt = "{errno} ({state}): {msg}" - fields["state"] = self.sqlstate - else: - fmt = "{errno}: {msg}" - self._full_msg = fmt.format(**fields) - - self.args = (self.errno, self._full_msg, self.sqlstate) - - def __str__(self) -> str: - return self._full_msg - - -class Warning(Exception): # pylint: disable=redefined-builtin - """Exception for important warnings""" - - -class InterfaceError(Error): - """Exception for errors related to the interface""" - - -class DatabaseError(Error): - """Exception for errors related to the database""" - - -class InternalError(DatabaseError): - """Exception for errors internal database errors""" - - -class OperationalError(DatabaseError): - """Exception for errors related to the database's operation""" - - -class ProgrammingError(DatabaseError): - """Exception for errors programming errors""" - - -class IntegrityError(DatabaseError): - """Exception for errors regarding relational integrity""" - - -class DataError(DatabaseError): - """Exception for errors reporting problems with processed data""" - - -class NotSupportedError(DatabaseError): - """Exception for errors when an unsupported database feature was used""" - - -class PoolError(Error): - """Exception for errors relating to connection pooling""" - - -class ConnectionTimeoutError(Error): - """ - Exception for errors related to the socket connection timing out while connecting with - the server - """ - - -class ReadTimeoutError(Error): - """Exception for errors relating to socket timing out while receiving data from the server""" - - DEFAULT_READ_TIMEOUT_ERROR_MSG: str = """ - The Read Operation timed out. As a consequence the current connection has been closed to avoid - any unstable behaviour, consider using the reconnect() option and continue with the current - session's workflow. - """ - - def __init__( - self, - msg: Optional[str] = None, - errno: Optional[int] = None, - values: Optional[Tuple[str, int]] = None, - sqlstate: Optional[str] = None, - ) -> None: - super().__init__(self.DEFAULT_READ_TIMEOUT_ERROR_MSG, errno, values, sqlstate) - - -class WriteTimeoutError(Error): - """Exception for errors relating to socket timing out while sending data to the server""" - - DEFAULT_WRITE_TIMEOUT_ERROR_MSG: str = """ - The Write Operation timed out. As a consequence the current connection has been closed to avoid - any unstable behaviour, consider using the reconnect() option to continue with the current - session's workflow. - """ - - def __init__( - self, - msg: Optional[str] = None, - errno: Optional[int] = None, - values: Optional[Tuple[str, int]] = None, - sqlstate: Optional[str] = None, - ) -> None: - super().__init__(self.DEFAULT_WRITE_TIMEOUT_ERROR_MSG, errno, values, sqlstate) - - -ErrorClassTypes = Union[ - Type[Error], - Type[InterfaceError], - Type[DatabaseError], - Type[InternalError], - Type[OperationalError], - Type[ProgrammingError], - Type[IntegrityError], - Type[DataError], - Type[NotSupportedError], - Type[PoolError], - Type[ConnectionTimeoutError], - Type[ReadTimeoutError], - Type[WriteTimeoutError], -] -ErrorTypes = Union[ - Error, - InterfaceError, - DatabaseError, - InternalError, - OperationalError, - ProgrammingError, - IntegrityError, - DataError, - NotSupportedError, - PoolError, - Warning, - ConnectionTimeoutError, - ReadTimeoutError, - WriteTimeoutError, -] -# _CUSTOM_ERROR_EXCEPTIONS holds custom exceptions and is used by the -# function custom_error_exception. _ERROR_EXCEPTIONS (at bottom of module) -# is similar, but hardcoded exceptions. -_CUSTOM_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = {} - - -def custom_error_exception( - error: Optional[Union[int, Dict[int, Optional[ErrorClassTypes]]]] = None, - exception: Optional[ErrorClassTypes] = None, -) -> Mapping[int, Optional[ErrorClassTypes]]: - """Defines custom exceptions for MySQL server errors. - - This function defines custom exceptions for MySQL server errors and - returns the current set customizations. - - To reset the customizations, simply supply an empty dictionary. - - Args: - error: Can be a MySQL Server error number or a dictionary in which case the - key is the server error number and the value is the exception to be raised. - exception: If `error` is a MySQL Server error number then you have to pass - also the exception class, otherwise you don't. - - Returns: - dictionary: Current set customizations. - - Examples: - ``` - import mysql.connector - from mysql.connector import errorcode - - # Server error 1028 should raise a DatabaseError - mysql.connector.custom_error_exception( - 1028, mysql.connector.DatabaseError) - - # Or using a dictionary: - mysql.connector.custom_error_exception({ - 1028: mysql.connector.DatabaseError, - 1029: mysql.connector.OperationalError, - }) - - # Reset - mysql.connector.custom_error_exception({}) - ``` - """ - global _CUSTOM_ERROR_EXCEPTIONS # pylint: disable=global-statement - - if isinstance(error, dict) and not error: - _CUSTOM_ERROR_EXCEPTIONS = {} - return _CUSTOM_ERROR_EXCEPTIONS - - if not error and not exception: - return _CUSTOM_ERROR_EXCEPTIONS - - if not isinstance(error, (int, dict)): - raise ValueError("The error argument should be either an integer or dictionary") - - if isinstance(error, int): - error = {error: exception} - - for errno, _exception in error.items(): - if not isinstance(errno, int): - raise ValueError("Error number should be an integer") - try: - if _exception is None or not issubclass(_exception, Exception): - raise TypeError - except TypeError as err: - raise ValueError("Exception should be subclass of Exception") from err - _CUSTOM_ERROR_EXCEPTIONS[errno] = _exception - - return _CUSTOM_ERROR_EXCEPTIONS - - -def get_mysql_exception( - errno: int, - msg: Optional[str] = None, - sqlstate: Optional[str] = None, - warning: Optional[bool] = False, -) -> ErrorTypes: - """Get the exception matching the MySQL error - - This function will return an exception based on the SQLState. The given - message will be passed on in the returned exception. - - The exception returned can be customized using the - mysql.connector.custom_error_exception() function. - - Returns an Exception - """ - try: - return _CUSTOM_ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate) - except KeyError: - # Error was not mapped to particular exception - pass - - try: - return _ERROR_EXCEPTIONS[errno](msg=msg, errno=errno, sqlstate=sqlstate) - except KeyError: - # Error was not mapped to particular exception - pass - - if not sqlstate: - if warning: - return Warning(errno, msg) - return DatabaseError(msg=msg, errno=errno) - - try: - return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]]( - msg=msg, errno=errno, sqlstate=sqlstate - ) - except KeyError: - # Return default InterfaceError - return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate) - - -def get_exception(packet: bytes) -> ErrorTypes: - """Returns an exception object based on the MySQL error - - Returns an exception object based on the MySQL error in the given - packet. - - Returns an Error-Object. - """ - errno = errmsg = None - - try: - if packet[4] != 255: - raise ValueError("Packet is not an error packet") - except IndexError as err: - return InterfaceError(f"Failed getting Error information ({err})") - - sqlstate: Optional[StrOrBytes] = None - try: - packet = packet[5:] - packet, errno = read_int(packet, 2) - if packet[0] != 35: - # Error without SQLState - if isinstance(packet, (bytes, bytearray)): - errmsg = packet.decode("utf8") - else: - errmsg = packet - else: - packet, sqlstate = read_bytes(packet[1:], 5) - sqlstate = sqlstate.decode("utf8") - errmsg = packet.decode("utf8") - except (IndexError, UnicodeError) as err: - return InterfaceError(f"Failed getting Error information ({err})") - return get_mysql_exception(errno, errmsg, sqlstate) # type: ignore[arg-type] - - -_SQLSTATE_CLASS_EXCEPTION: Dict[str, ErrorClassTypes] = { - "02": DataError, # no data - "07": DatabaseError, # dynamic SQL error - "08": OperationalError, # connection exception - "0A": NotSupportedError, # feature not supported - "21": DataError, # cardinality violation - "22": DataError, # data exception - "23": IntegrityError, # integrity constraint violation - "24": ProgrammingError, # invalid cursor state - "25": ProgrammingError, # invalid transaction state - "26": ProgrammingError, # invalid SQL statement name - "27": ProgrammingError, # triggered data change violation - "28": ProgrammingError, # invalid authorization specification - "2A": ProgrammingError, # direct SQL syntax error or access rule violation - "2B": DatabaseError, # dependent privilege descriptors still exist - "2C": ProgrammingError, # invalid character set name - "2D": DatabaseError, # invalid transaction termination - "2E": DatabaseError, # invalid connection name - "33": DatabaseError, # invalid SQL descriptor name - "34": ProgrammingError, # invalid cursor name - "35": ProgrammingError, # invalid condition number - "37": ProgrammingError, # dynamic SQL syntax error or access rule violation - "3C": ProgrammingError, # ambiguous cursor name - "3D": ProgrammingError, # invalid catalog name - "3F": ProgrammingError, # invalid schema name - "40": InternalError, # transaction rollback - "42": ProgrammingError, # syntax error or access rule violation - "44": InternalError, # with check option violation - "HZ": OperationalError, # remote database access - "XA": IntegrityError, - "0K": OperationalError, - "HY": DatabaseError, # default when no SQLState provided by MySQL server -} - -_ERROR_EXCEPTIONS: Dict[int, ErrorClassTypes] = { - 1243: ProgrammingError, - 1210: ProgrammingError, - 2002: InterfaceError, - 2013: OperationalError, - 2049: NotSupportedError, - 2055: OperationalError, - 2061: InterfaceError, - 2026: InterfaceError, -} diff --git a/server/venv/Lib/site-packages/mysql/connector/locales/__init__.py b/server/venv/Lib/site-packages/mysql/connector/locales/__init__.py deleted file mode 100644 index b20b41b..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/locales/__init__.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2012, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Translations.""" - -from typing import List, Optional, Union - -__all__: List[str] = ["get_client_error"] - -from .. import errorcode - - -def get_client_error(error: Union[int, str], language: str = "eng") -> Optional[str]: - """Lookup client error - - This function will lookup the client error message based on the given - error and return the error message. If the error was not found, - None will be returned. - - Error can be either an integer or a string. For example: - error: 2000 - error: CR_UNKNOWN_ERROR - - The language attribute can be used to retrieve a localized message, when - available. - - Returns a string or None. - """ - try: - tmp = __import__( - f"mysql.connector.locales.{language}", - globals(), - locals(), - ["client_error"], - ) - except ImportError: - raise ImportError( - f"No localization support for language '{language}'" - ) from None - client_error = tmp.client_error - - if isinstance(error, int): - errno = error - for key, value in errorcode.__dict__.items(): - if value == errno: - error = key - break - - if isinstance(error, (str)): - try: - return getattr(client_error, error) - except AttributeError: - return None - - raise ValueError("error argument needs to be either an integer or string") diff --git a/server/venv/Lib/site-packages/mysql/connector/locales/eng/__init__.py b/server/venv/Lib/site-packages/mysql/connector/locales/eng/__init__.py deleted file mode 100644 index bb506b4..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/locales/eng/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2012, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""English Content -""" diff --git a/server/venv/Lib/site-packages/mysql/connector/locales/eng/client_error.py b/server/venv/Lib/site-packages/mysql/connector/locales/eng/client_error.py deleted file mode 100644 index 44a68ab..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/locales/eng/client_error.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2013, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""MySQL Error Messages.""" - -# This file was auto-generated. -_GENERATED_ON = "2021-08-11" -_MYSQL_VERSION = (8, 0, 27) - -# pylint: disable=line-too-long -# Start MySQL Error messages -CR_UNKNOWN_ERROR = "Unknown MySQL error" -CR_SOCKET_CREATE_ERROR = "Can't create UNIX socket (%s)" -CR_CONNECTION_ERROR = ( - "Can't connect to local MySQL server through socket '%-.100s' (%s)" -) -CR_CONN_HOST_ERROR = "Can't connect to MySQL server on '%-.100s:%u' (%s)" -CR_IPSOCK_ERROR = "Can't create TCP/IP socket (%s)" -CR_UNKNOWN_HOST = "Unknown MySQL server host '%-.100s' (%s)" -CR_SERVER_GONE_ERROR = "MySQL server has gone away" -CR_VERSION_ERROR = "Protocol mismatch; server version = %s, client version = %s" -CR_OUT_OF_MEMORY = "MySQL client ran out of memory" -CR_WRONG_HOST_INFO = "Wrong host info" -CR_LOCALHOST_CONNECTION = "Localhost via UNIX socket" -CR_TCP_CONNECTION = "%-.100s via TCP/IP" -CR_SERVER_HANDSHAKE_ERR = "Error in server handshake" -CR_SERVER_LOST = "Lost connection to MySQL server during query" -CR_COMMANDS_OUT_OF_SYNC = "Commands out of sync; you can't run this command now" -CR_NAMEDPIPE_CONNECTION = "Named pipe: %-.32s" -CR_NAMEDPIPEWAIT_ERROR = "Can't wait for named pipe to host: %-.64s pipe: %-.32s (%s)" -CR_NAMEDPIPEOPEN_ERROR = "Can't open named pipe to host: %-.64s pipe: %-.32s (%s)" -CR_NAMEDPIPESETSTATE_ERROR = ( - "Can't set state of named pipe to host: %-.64s pipe: %-.32s (%s)" -) -CR_CANT_READ_CHARSET = "Can't initialize character set %-.32s (path: %-.100s)" -CR_NET_PACKET_TOO_LARGE = "Got packet bigger than 'max_allowed_packet' bytes" -CR_EMBEDDED_CONNECTION = "Embedded server" -CR_PROBE_SLAVE_STATUS = "Error on SHOW SLAVE STATUS:" -CR_PROBE_SLAVE_HOSTS = "Error on SHOW SLAVE HOSTS:" -CR_PROBE_SLAVE_CONNECT = "Error connecting to slave:" -CR_PROBE_MASTER_CONNECT = "Error connecting to master:" -CR_SSL_CONNECTION_ERROR = "SSL connection error: %-.100s" -CR_MALFORMED_PACKET = "Malformed packet" -CR_WRONG_LICENSE = "This client library is licensed only for use with MySQL servers having '%s' license" -CR_NULL_POINTER = "Invalid use of null pointer" -CR_NO_PREPARE_STMT = "Statement not prepared" -CR_PARAMS_NOT_BOUND = "No data supplied for parameters in prepared statement" -CR_DATA_TRUNCATED = "Data truncated" -CR_NO_PARAMETERS_EXISTS = "No parameters exist in the statement" -CR_INVALID_PARAMETER_NO = "Invalid parameter number" -CR_INVALID_BUFFER_USE = ( - "Can't send long data for non-string/non-binary data types (parameter: %s)" -) -CR_UNSUPPORTED_PARAM_TYPE = "Using unsupported buffer type: %s (parameter: %s)" -CR_SHARED_MEMORY_CONNECTION = "Shared memory: %-.100s" -CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = ( - "Can't open shared memory; client could not create request event (%s)" -) -CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = ( - "Can't open shared memory; no answer event received from server (%s)" -) -CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = ( - "Can't open shared memory; server could not allocate file mapping (%s)" -) -CR_SHARED_MEMORY_CONNECT_MAP_ERROR = ( - "Can't open shared memory; server could not get pointer to file mapping (%s)" -) -CR_SHARED_MEMORY_FILE_MAP_ERROR = ( - "Can't open shared memory; client could not allocate file mapping (%s)" -) -CR_SHARED_MEMORY_MAP_ERROR = ( - "Can't open shared memory; client could not get pointer to file mapping (%s)" -) -CR_SHARED_MEMORY_EVENT_ERROR = ( - "Can't open shared memory; client could not create %s event (%s)" -) -CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = ( - "Can't open shared memory; no answer from server (%s)" -) -CR_SHARED_MEMORY_CONNECT_SET_ERROR = ( - "Can't open shared memory; cannot send request event to server (%s)" -) -CR_CONN_UNKNOW_PROTOCOL = "Wrong or unknown protocol" -CR_INVALID_CONN_HANDLE = "Invalid connection handle" -CR_UNUSED_1 = "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" -CR_FETCH_CANCELED = "Row retrieval was canceled by mysql_stmt_close() call" -CR_NO_DATA = "Attempt to read column without prior row fetch" -CR_NO_STMT_METADATA = "Prepared statement contains no metadata" -CR_NO_RESULT_SET = ( - "Attempt to read a row while there is no result set associated with the statement" -) -CR_NOT_IMPLEMENTED = "This feature is not implemented yet" -CR_SERVER_LOST_EXTENDED = "Lost connection to MySQL server at '%s', system error: %s" -CR_STMT_CLOSED = "Statement closed indirectly because of a preceding %s() call" -CR_NEW_STMT_METADATA = "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again" -CR_ALREADY_CONNECTED = ( - "This handle is already connected. Use a separate handle for each connection." -) -CR_AUTH_PLUGIN_CANNOT_LOAD = "Authentication plugin '%s' cannot be loaded: %s" -CR_DUPLICATE_CONNECTION_ATTR = "There is an attribute with the same name already" -CR_AUTH_PLUGIN_ERR = "Authentication plugin '%s' reported error: %s" -CR_INSECURE_API_ERR = "Insecure API function call: '%s' Use instead: '%s'" -CR_FILE_NAME_TOO_LONG = "File name is too long" -CR_SSL_FIPS_MODE_ERR = "Set FIPS mode ON/STRICT failed" -CR_DEPRECATED_COMPRESSION_NOT_SUPPORTED = ( - "Compression protocol not supported with asynchronous protocol" -) -CR_COMPRESSION_WRONGLY_CONFIGURED = ( - "Connection failed due to wrongly configured compression algorithm" -) -CR_KERBEROS_USER_NOT_FOUND = ( - "SSO user not found, Please perform SSO authentication using kerberos." -) -CR_LOAD_DATA_LOCAL_INFILE_REJECTED = ( - "LOAD DATA LOCAL INFILE file request rejected due to restrictions on access." -) -CR_LOAD_DATA_LOCAL_INFILE_REALPATH_FAIL = ( - "Determining the real path for '%s' failed with error (%s): %s" -) -CR_DNS_SRV_LOOKUP_FAILED = "DNS SRV lookup failed with error : %s" -CR_MANDATORY_TRACKER_NOT_FOUND = ( - "Client does not recognise tracker type %s marked as mandatory by server." -) -CR_INVALID_FACTOR_NO = "Invalid first argument for MYSQL_OPT_USER_PASSWORD option. Valid value should be between 1 and 3 inclusive." -# End MySQL Error messages diff --git a/server/venv/Lib/site-packages/mysql/connector/logger.py b/server/venv/Lib/site-packages/mysql/connector/logger.py deleted file mode 100644 index b67de47..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/logger.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) 2022, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Setup of the `mysql.connector` logger.""" - -import logging - -logger = logging.getLogger("mysql.connector") diff --git a/server/venv/Lib/site-packages/mysql/connector/network.py b/server/venv/Lib/site-packages/mysql/connector/network.py deleted file mode 100644 index 98615c1..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/network.py +++ /dev/null @@ -1,811 +0,0 @@ -# Copyright (c) 2012, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Module implementing low-level socket communication with MySQL servers.""" - -# pylint: disable=overlapping-except - -import socket -import struct -import warnings -import zlib - -from abc import ABC, abstractmethod -from collections import deque -from typing import Any, Deque, List, Optional, Tuple, Union - -try: - import ssl - - TLS_VERSIONS = { - "TLSv1": ssl.PROTOCOL_TLSv1, - "TLSv1.1": ssl.PROTOCOL_TLSv1_1, - "TLSv1.2": ssl.PROTOCOL_TLSv1_2, - "TLSv1.3": ssl.PROTOCOL_TLS, - } - TLS_V1_3_SUPPORTED = hasattr(ssl, "HAS_TLSv1_3") and ssl.HAS_TLSv1_3 -except ImportError: - # If import fails, we don't have SSL support. - TLS_V1_3_SUPPORTED = False - ssl = None - -from .errors import ( - ConnectionTimeoutError, - InterfaceError, - NotSupportedError, - OperationalError, - ProgrammingError, - ReadTimeoutError, - WriteTimeoutError, -) - -MIN_COMPRESS_LENGTH = 50 -MAX_PAYLOAD_LENGTH = 2**24 - 1 -PACKET_HEADER_LENGTH = 4 -COMPRESSED_PACKET_HEADER_LENGTH = 7 - - -def _strioerror(err: IOError) -> str: - """Reformat the IOError error message. - - This function reformats the IOError error message. - """ - return str(err) if not err.errno else f"Errno {err.errno}: {err.strerror}" - - -class NetworkBroker(ABC): - """Broker class interface. - - The network object is a broker used as a delegate by a socket object. Whenever the - socket wants to deliver or get packets to or from the MySQL server it needs to rely - on its network broker (netbroker). - - The netbroker sends `payloads` and receives `packets`. - - A packet is a bytes sequence, it has a header and body (referred to as payload). - The first `PACKET_HEADER_LENGTH` or `COMPRESSED_PACKET_HEADER_LENGTH` - (as appropriate) bytes correspond to the `header`, the remaining ones represent the - `payload`. - - The maximum payload length allowed to be sent per packet to the server is - `MAX_PAYLOAD_LENGTH`. When `send` is called with a payload whose length is greater - than `MAX_PAYLOAD_LENGTH` the netbroker breaks it down into packets, so the caller - of `send` can provide payloads of arbitrary length. - - Finally, data received by the netbroker comes directly from the server, expect to - get a packet for each call to `recv`. The received packet contains a header and - payload, the latter respecting `MAX_PAYLOAD_LENGTH`. - """ - - @abstractmethod - def send( - self, - sock: socket.socket, - address: str, - payload: bytes, - packet_number: Optional[int] = None, - compressed_packet_number: Optional[int] = None, - ) -> None: - """Send `payload` to the MySQL server. - - If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is - broken down into packets. - - Args: - sock: Object holding the socket connection. - address: Socket's location. - payload: Packet's body to send. - packet_number: Sequence id (packet ID) to attach to the header when sending - plain packets. - compressed_packet_number: Same as `packet_number` but used when sending - compressed packets. - - Raises: - :class:`OperationalError`: If something goes wrong while sending packets to - the MySQL server. - """ - - @abstractmethod - def recv(self, sock: socket.socket, address: str) -> bytearray: - """Get the next available packet from the MySQL server. - - Args: - sock: Object holding the socket connection. - address: Socket's location. - - Returns: - packet: A packet from the MySQL server. - - Raises: - :class:`OperationalError`: If something goes wrong while receiving packets - from the MySQL server. - :class:`InterfaceError`: If something goes wrong while receiving packets - from the MySQL server. - """ - - -class NetworkBrokerPlain(NetworkBroker): - """Broker class for MySQL socket communication.""" - - def __init__(self) -> None: - self._pktnr: int = -1 # packet number - - def _set_next_pktnr(self) -> None: - """Increment packet id.""" - self._pktnr = (self._pktnr + 1) % 256 - - def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None: - """Write packet to the comm channel.""" - try: - sock.sendall(pkt) - except (socket.timeout, TimeoutError) as err: - raise WriteTimeoutError(errno=3024) from err - except IOError as err: - raise OperationalError( - errno=2055, values=(address, _strioerror(err)) - ) from err - except AttributeError as err: - raise OperationalError(errno=2006) from err - - def _recv_chunk(self, sock: socket.socket, size: int = 0) -> bytearray: - """Read `size` bytes from the comm channel.""" - pkt = bytearray(size) - pkt_view = memoryview(pkt) - while size: - read = sock.recv_into(pkt_view, size) - if read == 0 and size > 0: - raise InterfaceError(errno=2013) - pkt_view = pkt_view[read:] - size -= read - return pkt - - def send( - self, - sock: socket.socket, - address: str, - payload: bytes, - packet_number: Optional[int] = None, - compressed_packet_number: Optional[int] = None, - ) -> None: - """Send payload to the MySQL server. - - If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is - broken down into packets. - """ - if packet_number is None: - self._set_next_pktnr() - else: - self._pktnr = packet_number - - # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH - # the length is set to 2^24 - 1 (ff ff ff) and additional - # packets are sent with the rest of the payload until the - # payload of a packet is less than MAX_PAYLOAD_LENGTH. - if len(payload) >= MAX_PAYLOAD_LENGTH: - offset = 0 - for _ in range(len(payload) // MAX_PAYLOAD_LENGTH): - # payload_len, sequence_id, payload - self._send_pkt( - sock, - address, - b"\xff\xff\xff" - + struct.pack(" bytearray: - """Receive `one` packet from the MySQL server.""" - try: - # Read the header of the MySQL packet - header = self._recv_chunk(sock, size=PACKET_HEADER_LENGTH) - - # Pull the payload length and sequence id - payload_len, self._pktnr = ( - struct.unpack(" None: - super().__init__() - self._compressed_pktnr = -1 - self._queue_read: Deque[bytearray] = deque() - - @staticmethod - def _prepare_packets(payload: bytes, pktnr: int) -> List[bytes]: - """Prepare a payload for sending to the MySQL server.""" - pkts = [] - - # If the payload is larger than or equal to MAX_PAYLOAD_LENGTH - # the length is set to 2^24 - 1 (ff ff ff) and additional - # packets are sent with the rest of the payload until the - # payload of a packet is less than MAX_PAYLOAD_LENGTH. - if len(payload) >= MAX_PAYLOAD_LENGTH: - offset = 0 - for _ in range(len(payload) // MAX_PAYLOAD_LENGTH): - # payload length + sequence id + payload - pkts.append( - b"\xff\xff\xff" - + struct.pack(" None: - """Increment packet id.""" - self._compressed_pktnr = (self._compressed_pktnr + 1) % 256 - - def _send_pkt(self, sock: socket.socket, address: str, pkt: bytes) -> None: - """Compress packet and write it to the comm channel.""" - compressed_pkt = zlib.compress(pkt) - pkt = ( - struct.pack(" None: - """Send `payload` as compressed packets to the MySQL server. - - If provided a payload whose length is greater than `MAX_PAYLOAD_LENGTH`, it is - broken down into packets. - """ - # get next packet numbers - if packet_number is None: - self._set_next_pktnr() - else: - self._pktnr = packet_number - if compressed_packet_number is None: - self._set_next_compressed_pktnr() - else: - self._compressed_pktnr = compressed_packet_number - - payload_prep = bytearray(b"").join(self._prepare_packets(payload, self._pktnr)) - if len(payload) >= MAX_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH: - # sending a MySQL payload of the size greater or equal to 2^24 - 5 - # via compression leads to at least one extra compressed packet - # WHY? let's say len(payload) is MAX_PAYLOAD_LENGTH - 3; when preparing - # the payload, a header of size PACKET_HEADER_LENGTH is pre-appended - # to the payload. This means that len(payload_prep) is - # MAX_PAYLOAD_LENGTH - 3 + PACKET_HEADER_LENGTH = MAX_PAYLOAD_LENGTH + 1 - # surpassing the maximum allowed payload size per packet. - offset = 0 - - # send several MySQL packets - for _ in range(len(payload_prep) // MAX_PAYLOAD_LENGTH): - self._send_pkt( - sock, address, payload_prep[offset : offset + MAX_PAYLOAD_LENGTH] - ) - self._set_next_compressed_pktnr() - offset += MAX_PAYLOAD_LENGTH - self._send_pkt(sock, address, payload_prep[offset:]) - else: - # send one MySQL packet - # For small packets it may be too costly to compress the packet. - # Usually payloads less than 50 bytes (MIN_COMPRESS_LENGTH) - # aren't compressed (see MySQL source code Documentation). - if len(payload) > MIN_COMPRESS_LENGTH: - # perform compression - self._send_pkt(sock, address, payload_prep) - else: - # skip compression - super()._send_pkt( - sock, - address, - struct.pack(" None: - """Handle reading of a compressed packet.""" - # compressed_pll stands for compressed payload length. - # Recalling that if uncompressed payload length == 0, the packet - # comes in uncompressed, so no decompression is needed. - compressed_pkt = super()._recv_chunk(sock, size=compressed_pll) - pkt = ( - compressed_pkt - if uncompressed_pll == 0 - else bytearray(zlib.decompress(compressed_pkt)) - ) - - offset = 0 - while offset < len(pkt): - # pll stands for payload length - pll = struct.unpack( - " len(pkt) - offset: - # More bytes need to be consumed - # Read the header of the next MySQL packet - header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH) - - # compressed payload length, sequence id, uncompressed payload length - ( - compressed_pll, - self._compressed_pktnr, - uncompressed_pll, - ) = ( - struct.unpack(" bytearray: - """Receive `one` or `several` packets from the MySQL server, enqueue them, and - return the packet at the head. - """ - if not self._queue_read: - try: - # Read the header of the next MySQL packet - header = super()._recv_chunk(sock, size=COMPRESSED_PACKET_HEADER_LENGTH) - - # compressed payload length, sequence id, uncompressed payload length - ( - compressed_pll, - self._compressed_pktnr, - uncompressed_pll, - ) = ( - struct.unpack(" None: - """Network layer where transactions are made with plain (uncompressed) packets - is enabled by default. - """ - # holds the socket connection - self.sock: Optional[socket.socket] = None - self._connection_timeout: Optional[int] = None - self.server_host: Optional[str] = None - self._netbroker: NetworkBroker = NetworkBrokerPlain() - - def switch_to_compressed_mode(self) -> None: - """Enable network layer where transactions are made with compressed packets.""" - self._netbroker = NetworkBrokerCompressed() - - def shutdown(self) -> None: - """Shut down the socket before closing it.""" - try: - self.sock.shutdown(socket.SHUT_RDWR) - self.sock.close() - except (AttributeError, OSError): - pass - - def close_connection(self) -> None: - """Close the socket.""" - try: - self.sock.close() - except (AttributeError, OSError): - pass - - def __del__(self) -> None: - self.shutdown() - - def set_connection_timeout(self, timeout: Optional[int]) -> None: - """Set the connection timeout.""" - self._connection_timeout = timeout - if self.sock: - self.sock.settimeout(timeout) - - def switch_to_ssl(self, ssl_context: Any, host: str) -> None: - """Upgrade an existing connection to TLS. - - Args: - ssl_context (ssl.SSLContext): The SSL Context to be used. - host (str): Server host name. - - Returns: - None. - - Raises: - ProgrammingError: If the transport does not expose the socket instance. - NotSupportedError: If Python installation has no SSL support. - """ - # Ensure that self.sock is already created - assert self.sock is not None - - if self.sock.family == 1: # socket.AF_UNIX - raise ProgrammingError("SSL is not supported when using Unix sockets") - - if ssl is None: - raise NotSupportedError("Python installation has no SSL support") - - try: - self.sock = ssl_context.wrap_socket(self.sock, server_hostname=host) - except NameError as err: - raise NotSupportedError("Python installation has no SSL support") from err - except (ssl.SSLError, IOError) as err: - raise InterfaceError( - errno=2055, values=(self.address, _strioerror(err)) - ) from err - except ssl.CertificateError as err: - raise InterfaceError(str(err)) from err - except NotImplementedError as err: - raise InterfaceError(str(err)) from err - - def build_ssl_context( - self, - ssl_ca: Optional[str] = None, - ssl_cert: Optional[str] = None, - ssl_key: Optional[str] = None, - ssl_verify_cert: Optional[bool] = False, - ssl_verify_identity: Optional[bool] = False, - tls_versions: Optional[List[str]] = None, - tls_cipher_suites: Optional[List[str]] = None, - ) -> Any: - """Build a SSLContext. - - Args: - ssl_ca: Certificate authority, opptional. - ssl_cert: SSL certificate, optional. - ssl_key: Private key, optional. - ssl_verify_cert: Verify the SSL certificate if `True`. - ssl_verify_identity: Verify host identity if `True`. - tls_versions: TLS protocol versions, optional. - tls_cipher_suites: Set of steps that helps to establish a secure connection. - - Returns: - ssl_context (ssl.SSLContext): An SSL Context ready be used. - - Raises: - NotSupportedError: Python installation has no SSL support. - InterfaceError: Socket undefined or invalid ssl data. - """ - tls_version: Optional[str] = None - - if not self.sock: - raise InterfaceError(errno=2048) - - if ssl is None: - raise NotSupportedError("Python installation has no SSL support") - - if tls_versions is None: - tls_versions = [] - - if tls_cipher_suites is None: - tls_cipher_suites = [] - - try: - if tls_versions: - tls_versions.sort(reverse=True) - tls_version = tls_versions[0] - ssl_protocol = TLS_VERSIONS[tls_version] - context = ssl.SSLContext(ssl_protocol) - - if tls_version == "TLSv1.3": - if "TLSv1.2" not in tls_versions: - context.options |= ssl.OP_NO_TLSv1_2 - if "TLSv1.1" not in tls_versions: - context.options |= ssl.OP_NO_TLSv1_1 - if "TLSv1" not in tls_versions: - context.options |= ssl.OP_NO_TLSv1 - else: - # `check_hostname` is True by default - context = ssl.create_default_context() - - context.check_hostname = ssl_verify_identity - - if ssl_verify_cert: - context.verify_mode = ssl.CERT_REQUIRED - elif ssl_verify_identity: - context.verify_mode = ssl.CERT_OPTIONAL - else: - context.verify_mode = ssl.CERT_NONE - - context.load_default_certs() - - if ssl_ca: - try: - context.load_verify_locations(ssl_ca) - except (IOError, ssl.SSLError) as err: - raise InterfaceError(f"Invalid CA Certificate: {err}") from err - if ssl_cert: - try: - context.load_cert_chain(ssl_cert, ssl_key) - except (IOError, ssl.SSLError) as err: - raise InterfaceError(f"Invalid Certificate/Key: {err}") from err - - # TLSv1.3 ciphers cannot be disabled with `SSLContext.set_ciphers(...)`, - # see https://docs.python.org/3/library/ssl.html#ssl.SSLContext.set_ciphers. - if tls_cipher_suites and tls_version == "TLSv1.2": - context.set_ciphers(":".join(tls_cipher_suites)) - - return context - except NameError as err: - raise NotSupportedError("Python installation has no SSL support") from err - except ( - IOError, - NotImplementedError, - ssl.CertificateError, - ssl.SSLError, - ) as err: - raise InterfaceError(str(err)) from err - - def send( - self, - payload: bytes, - packet_number: Optional[int] = None, - compressed_packet_number: Optional[int] = None, - write_timeout: Optional[int] = None, - ) -> None: - """Send `payload` to the MySQL server. - - NOTE: if `payload` is an instance of `bytearray`, then `payload` might be - changed by this method - `bytearray` is similar to passing a variable by - reference. - - If you're sure you won't read `payload` after invoking `send()`, - then you can use `bytearray.` Otherwise, you must use `bytes`. - """ - try: - if ( - not self._connection_timeout and self.sock is not None - ): # can't update the timeout during connection phase - self.sock.settimeout(float(write_timeout) if write_timeout else None) - except OSError as _: - # Ignore the OSError as the socket might not be setup properly - pass - self._netbroker.send( - self.sock, - self.address, - payload, - packet_number=packet_number, - compressed_packet_number=compressed_packet_number, - ) - - def recv(self, read_timeout: Optional[int] = None) -> bytearray: - """Get packet from the MySQL server comm channel.""" - try: - if ( - not self._connection_timeout and self.sock is not None - ): # can't update the timeout during connection phase - self.sock.settimeout(float(read_timeout) if read_timeout else None) - except OSError as _: - # Ignore the OSError as the socket might not be setup properly - pass - return self._netbroker.recv(self.sock, self.address) - - @abstractmethod - def open_connection(self) -> None: - """Open the socket.""" - - @property - @abstractmethod - def address(self) -> str: - """Get the location of the socket.""" - - -class MySQLUnixSocket(MySQLSocket): - """MySQL socket class using UNIX sockets. - - Opens a connection through the UNIX socket of the MySQL Server. - """ - - def __init__(self, unix_socket: str = "/tmp/mysql.sock") -> None: - super().__init__() - self.unix_socket: str = unix_socket - self._address: str = unix_socket - - @property - def address(self) -> str: - return self._address - - def open_connection(self) -> None: - try: - self.sock = socket.socket( - # pylint: disable=no-member - socket.AF_UNIX, - socket.SOCK_STREAM, - ) - self.sock.settimeout(self._connection_timeout) - self.sock.connect(self.unix_socket) - except (socket.timeout, TimeoutError) as err: - raise ConnectionTimeoutError( - errno=2002, - values=( - self.address, - _strioerror(err), - ), - ) from err - except IOError as err: - raise InterfaceError( - errno=2002, values=(self.address, _strioerror(err)) - ) from err - except Exception as err: - raise InterfaceError(str(err)) from err - - def switch_to_ssl( - self, *args: Any, **kwargs: Any # pylint: disable=unused-argument - ) -> None: - """Switch the socket to use SSL.""" - warnings.warn( - "SSL is disabled when using unix socket connections", - Warning, - ) - - -class MySQLTCPSocket(MySQLSocket): - """MySQL socket class using TCP/IP. - - Opens a TCP/IP connection to the MySQL Server. - """ - - def __init__( - self, - host: str = "127.0.0.1", - port: int = 3306, - force_ipv6: bool = False, - ) -> None: - super().__init__() - self.server_host: str = host - self.server_port: int = port - self.force_ipv6: bool = force_ipv6 - self._family: int = 0 - self._address: str = f"{host}:{port}" - - @property - def address(self) -> str: - return self._address - - def open_connection(self) -> None: - """Open the TCP/IP connection to the MySQL server.""" - # pylint: disable=no-member - # Get address information - addrinfo: Tuple[ - socket.AddressFamily, - socket.SocketKind, - int, - str, - Union[tuple[str, int], tuple[str, int, int, int], tuple[int, bytes]], - ] = (None, None, None, None, None) - try: - addrinfos = socket.getaddrinfo( - self.server_host, - self.server_port, - 0, - socket.SOCK_STREAM, - socket.SOL_TCP, - ) - # If multiple results we favor IPv4, unless IPv6 was forced. - for info in addrinfos: - if self.force_ipv6 and info[0] == socket.AF_INET6: - addrinfo = info - break - if info[0] == socket.AF_INET: - addrinfo = info - break - if self.force_ipv6 and addrinfo[0] is None: - raise InterfaceError(f"No IPv6 address found for {self.server_host}") - if addrinfo[0] is None: - addrinfo = addrinfos[0] - except IOError as err: - raise InterfaceError( - errno=2003, - values=(self.server_host, self.server_port, _strioerror(err)), - ) from err - - (self._family, socktype, proto, _, sockaddr) = addrinfo - - # Instantiate the socket and connect - try: - self.sock = socket.socket(self._family, socktype, proto) - self.sock.settimeout(self._connection_timeout) - self.sock.connect(sockaddr) - except (socket.timeout, TimeoutError) as err: - raise ConnectionTimeoutError( - errno=2003, - values=( - self.server_host, - self.server_port, - _strioerror(err), - ), - ) from err - except IOError as err: - raise InterfaceError( - errno=2003, - values=(self.server_host, self.server_port, _strioerror(err)), - ) from err - except Exception as err: - raise InterfaceError(str(err)) from err diff --git a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/__init__.py b/server/venv/Lib/site-packages/mysql/connector/opentelemetry/__init__.py deleted file mode 100644 index a1dc4ae..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA diff --git a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/constants.py b/server/venv/Lib/site-packages/mysql/connector/opentelemetry/constants.py deleted file mode 100644 index 9c5e04e..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/constants.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Constants used by the opentelemetry instrumentation implementation.""" -# mypy: disable-error-code="no-redef,assignment" - -# pylint: disable=unused-import -OTEL_ENABLED = True -try: - # try to load otel from the system - from opentelemetry import trace # check api - from opentelemetry.sdk.trace import TracerProvider # check sdk - from opentelemetry.semconv.trace import SpanAttributes # check semconv -except ImportError: - OTEL_ENABLED = False - - -OPTION_CNX_SPAN = "_span" -""" -Connection option name used to inject the connection span. -This connection option name must not be used, is reserved. -""" - -OPTION_CNX_TRACER = "_tracer" -""" -Connection option name used to inject the opentelemetry tracer. -This connection option name must not be used, is reserved. -""" - -CONNECTION_SPAN_NAME = "connection" -""" -Connection span name to be used by the instrumentor. -""" - -FIRST_SUPPORTED_VERSION = "8.1.0" -""" -First mysql-connector-python version to support opentelemetry instrumentation. -""" - -TRACEPARENT_HEADER_NAME = "traceparent" - -DB_SYSTEM = "mysql" -DEFAULT_THREAD_NAME = "main" -DEFAULT_THREAD_ID = 0 - -# Reference: https://github.com/open-telemetry/opentelemetry-specification/blob/main/ -# specification/trace/semantic_conventions/span-general.md -NET_SOCK_FAMILY = "net.sock.family" -NET_SOCK_PEER_ADDR = "net.sock.peer.addr" -NET_SOCK_PEER_PORT = "net.sock.peer.port" -NET_SOCK_HOST_ADDR = "net.sock.host.addr" -NET_SOCK_HOST_PORT = "net.sock.host.port" diff --git a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/context_propagation.py b/server/venv/Lib/site-packages/mysql/connector/opentelemetry/context_propagation.py deleted file mode 100644 index 907df7e..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/context_propagation.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""Trace context propagation utilities.""" -# mypy: disable-error-code="no-redef" -# pylint: disable=invalid-name - -from typing import TYPE_CHECKING, Any, Callable - -from .constants import OTEL_ENABLED, TRACEPARENT_HEADER_NAME - -if OTEL_ENABLED: - # pylint: disable=import-error - # load otel from the system - from opentelemetry import trace - from opentelemetry.trace.span import format_span_id, format_trace_id - - -if TYPE_CHECKING: - from ..abstracts import MySQLConnectionAbstract - - -def build_traceparent_header(span: Any) -> str: - """Build a traceparent header according to the provided span. - - The context information from the provided span is used to build the traceparent - header that will be propagated to the MySQL server. For particulars regarding - the header creation, refer to [1]. - - This method assumes version 0 of the W3C specification. - - Args: - span (opentelemetry.trace.span.Span): current span in trace. - - Returns: - traceparent_header (str): HTTP header field that identifies requests in a - tracing system. - - References: - [1]: https://www.w3.org/TR/trace-context/#traceparent-header - """ - # pylint: disable=possibly-used-before-assignment - ctx = span.get_span_context() - - version = "00" # version 0 of the W3C specification - trace_id = format_trace_id(ctx.trace_id) - span_id = format_span_id(ctx.span_id) - trace_flags = "00" # sampled flag is off - - return "-".join([version, trace_id, span_id, trace_flags]) - - -def with_context_propagation(method: Callable) -> Callable: - """Perform trace context propagation. - - The trace context is propagated via query attributes. The `traceparent` header - from W3C specification [1] is used, in this sense, the attribute name is - `traceparent` (is RESERVED, avoid using it), and its value is built as per - instructed in [1]. - - If opentelemetry API/SDK is unavailable or there is no recording span, - trace context propagation is skipped. - - References: - [1]: https://www.w3.org/TR/trace-context/#traceparent-header - """ - - def wrapper(cnx: "MySQLConnectionAbstract", *args: Any, **kwargs: Any) -> Any: - """Context propagation decorator.""" - # pylint: disable=possibly-used-before-assignment - if not OTEL_ENABLED or not cnx.otel_context_propagation: - return method(cnx, *args, **kwargs) - - current_span = trace.get_current_span() - tp_header = None - if current_span.is_recording(): - tp_header = build_traceparent_header(current_span) - cnx.query_attrs_append(value=(TRACEPARENT_HEADER_NAME, tp_header)) - - try: - result = method(cnx, *args, **kwargs) - finally: - if tp_header is not None: - cnx.query_attrs_remove(name=TRACEPARENT_HEADER_NAME) - return result - - return wrapper diff --git a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/instrumentation.py b/server/venv/Lib/site-packages/mysql/connector/opentelemetry/instrumentation.py deleted file mode 100644 index 8498574..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/opentelemetry/instrumentation.py +++ /dev/null @@ -1,687 +0,0 @@ -# Copyright (c) 2023, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -"""MySQL instrumentation supporting mysql-connector.""" -# mypy: disable-error-code="no-redef" -# pylint: disable=protected-access,global-statement,invalid-name,unused-argument - -from __future__ import annotations - -import functools -import re - -from abc import ABC, abstractmethod -from contextlib import nullcontext -from typing import TYPE_CHECKING, Any, Callable, Collection, Dict, Optional, Union - -# pylint: disable=cyclic-import -if TYPE_CHECKING: - # `TYPE_CHECKING` is always False at run time, hence circular import - # will not happen at run time (no error happens whatsoever). - # Since pylint is a static checker it happens that `TYPE_CHECKING` - # is True when analyzing the code which makes pylint believe there - # is a circular import issue when there isn't. - - from ..abstracts import MySQLConnectionAbstract, MySQLCursorAbstract - from ..pooling import PooledMySQLConnection - -from ... import connector -from ..constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION -from ..logger import logger -from ..version import VERSION_TEXT - -try: - # pylint: disable=unused-import - # try to load otel from the system - from opentelemetry import trace # check api - from opentelemetry.sdk.trace import TracerProvider # check sdk - from opentelemetry.semconv.trace import SpanAttributes # check semconv -except ImportError as missing_dependencies_err: - raise connector.errors.ProgrammingError( - "OpenTelemetry installation not found. You must install the API and SDK." - ) from missing_dependencies_err - - -from .constants import ( - CONNECTION_SPAN_NAME, - DB_SYSTEM, - DEFAULT_THREAD_ID, - DEFAULT_THREAD_NAME, - FIRST_SUPPORTED_VERSION, - NET_SOCK_FAMILY, - NET_SOCK_HOST_ADDR, - NET_SOCK_HOST_PORT, - NET_SOCK_PEER_ADDR, - NET_SOCK_PEER_PORT, - OPTION_CNX_SPAN, - OPTION_CNX_TRACER, -) - -leading_comment_remover: re.Pattern = re.compile(r"^/\*.*?\*/") - - -def record_exception_event(span: trace.Span, exc: Optional[Exception]) -> None: - """Records an exeception event.""" - if not span or not span.is_recording() or not exc: - return - - span.set_status(trace.Status(trace.StatusCode.ERROR)) - span.record_exception(exc) - - -def end_span(span: trace.Span) -> None: - """Ends span.""" - if not span or not span.is_recording(): - return - - span.end() - - -def get_operation_name(operation: str) -> str: - """Parse query to extract operation name.""" - if operation and isinstance(operation, str): - # Strip leading comments so we get the operation name. - return leading_comment_remover.sub("", operation).split()[0] - return "" - - -def set_connection_span_attrs( - cnx: Optional["MySQLConnectionAbstract"], - cnx_span: trace.Span, - cnx_kwargs: Optional[Dict[str, Any]] = None, -) -> None: - """Defines connection span attributes. If `cnx` is None then we use `cnx_kwargs` - to get basic net information. Basic net attributes are defined such as: - - * DB_SYSTEM - * NET_TRANSPORT - * NET_SOCK_FAMILY - - Socket-level attributes [*] are also defined [**]. - - [*]: Socket-level attributes identify peer and host that are directly connected to - each other. Since instrumentations may have limited knowledge on network - information, instrumentations SHOULD populate such attributes to the best of - their knowledge when populate them at all. - - [**]: `CMySQLConnection` connections have no access to socket-level - details so socket-level attributes aren't included. `MySQLConnection` - connections, on the other hand, do include socket-level attributes. - - References: - [1]: https://github.com/open-telemetry/opentelemetry-specification/blob/main/\ - specification/trace/semantic_conventions/span-general.md - """ - # pylint: disable=broad-exception-caught - if not cnx_span or not cnx_span.is_recording(): - return - - if cnx_kwargs is None: - cnx_kwargs = {} - - is_tcp = not cnx._unix_socket if cnx else "unix_socket" not in cnx_kwargs - - attrs: Dict[str, Any] = { - SpanAttributes.DB_SYSTEM: DB_SYSTEM, - SpanAttributes.NET_TRANSPORT: "ip_tcp" if is_tcp else "inproc", - NET_SOCK_FAMILY: "inet" if is_tcp else "unix", - } - - # Only socket and tcp connections are supported. - if is_tcp: - attrs[SpanAttributes.NET_PEER_NAME] = ( - cnx._host if cnx else cnx_kwargs.get("host", DEFAULT_CONFIGURATION["host"]) - ) - attrs[SpanAttributes.NET_PEER_PORT] = ( - cnx._port if cnx else cnx_kwargs.get("port", DEFAULT_CONFIGURATION["port"]) - ) - - if hasattr(cnx, "_socket") and cnx._socket: - try: - ( - attrs[NET_SOCK_PEER_ADDR], - sock_peer_port, - ) = cnx._socket.sock.getpeername() - - ( - attrs[NET_SOCK_HOST_ADDR], - attrs[NET_SOCK_HOST_PORT], - ) = cnx._socket.sock.getsockname() - except Exception as sock_err: - logger.warning("Connection socket is down %s", sock_err) - else: - if attrs[SpanAttributes.NET_PEER_PORT] != sock_peer_port: - # NET_SOCK_PEER_PORT is recommended if different than net.peer.port - # and if net.sock.peer.addr is set. - attrs[NET_SOCK_PEER_PORT] = sock_peer_port - else: - # For Unix domain socket, net.sock.peer.addr attribute represents - # destination name and net.peer.name SHOULD NOT be set. - attrs[NET_SOCK_PEER_ADDR] = ( - cnx._unix_socket if cnx else cnx_kwargs.get("unix_socket") - ) - - if hasattr(cnx, "_socket") and cnx._socket: - try: - attrs[NET_SOCK_HOST_ADDR] = cnx._socket.sock.getsockname() - except Exception as sock_err: - logger.warning("Connection socket is down %s", sock_err) - - cnx_span.set_attributes(attrs) - - -def with_cnx_span_attached(method: Callable) -> Callable: - """Attach the connection span while executing the connection method.""" - - def wrapper(cnx: "MySQLConnectionAbstract", *args: Any, **kwargs: Any) -> Any: - """Connection span attacher decorator.""" - with trace.use_span( - cnx._span, end_on_exit=False - ) if cnx._span and cnx._span.is_recording() else nullcontext(): - return method(cnx, *args, **kwargs) - - return wrapper - - -def with_cnx_query_span(method: Callable) -> Callable: - """Create a query span while executing the connection method.""" - - def wrapper(cnx: TracedMySQLConnection, *args: Any, **kwargs: Any) -> Any: - """Query span creator decorator.""" - logger.info("Creating query span for connection.%s", method.__name__) - - query_span_attributes: Dict = { - SpanAttributes.DB_SYSTEM: DB_SYSTEM, - SpanAttributes.DB_USER: cnx._user, - SpanAttributes.THREAD_ID: DEFAULT_THREAD_ID, - SpanAttributes.THREAD_NAME: DEFAULT_THREAD_NAME, - "connection_type": cnx.get_wrapped_class(), - } - - with cnx._tracer.start_as_current_span( - name=method.__name__.upper(), - kind=trace.SpanKind.CLIENT, - links=[trace.Link(cnx._span.get_span_context())], - attributes=query_span_attributes, - ) if cnx._span and cnx._span.is_recording() else nullcontext(): - return method(cnx, *args, **kwargs) - - return wrapper - - -def with_cursor_query_span(method: Callable) -> Callable: - """Create a query span while executing the cursor method.""" - - def wrapper(cur: TracedMySQLCursor, *args: Any, **kwargs: Any) -> Any: - """Query span creator decorator.""" - logger.info("Creating query span for cursor.%s", method.__name__) - - connection: "MySQLConnectionAbstract" = ( - getattr(cur._wrapped, "_connection") - if hasattr(cur._wrapped, "_connection") - else getattr(cur._wrapped, "_cnx") - ) - - query_span_attributes: Dict = { - SpanAttributes.DB_SYSTEM: DB_SYSTEM, - SpanAttributes.DB_USER: connection._user, - SpanAttributes.THREAD_ID: DEFAULT_THREAD_ID, - SpanAttributes.THREAD_NAME: DEFAULT_THREAD_NAME, - "cursor_type": cur.get_wrapped_class(), - } - - with cur._tracer.start_as_current_span( - name=get_operation_name(args[0]) or "SQL statement", - kind=trace.SpanKind.CLIENT, - links=[cur._connection_span_link], - attributes=query_span_attributes, - ): - return method(cur, *args, **kwargs) - - return wrapper - - -class BaseMySQLTracer(ABC): - """Base class that provides basic object wrapper functionality.""" - - @abstractmethod - def __init__(self) -> None: - """Must be implemented by subclasses.""" - - def __getattr__(self, attr: str) -> Any: - """Gets an attribute. - - Attributes defined in the wrapper object have higher precedence - than those wrapped object equivalent. Attributes not found in - the wrapper are then searched in the wrapped object. - """ - if attr in self.__dict__: - # this object has it - return getattr(self, attr) - # proxy to the wrapped object - return getattr(self._wrapped, attr) - - def __setattr__(self, name: str, value: Any) -> None: - if "_wrapped" not in self.__dict__: - self.__dict__["_wrapped"] = value - return - - if name in self.__dict__ or name == "autocommit": - # this object has it - super().__setattr__(name, value) - return - # proxy to the wrapped object - self._wrapped.__setattr__(name, value) - - def __enter__(self) -> Any: - """Magic method.""" - self._wrapped.__enter__() - return self - - def __exit__(self, *args: Any, **kwargs: Any) -> None: - """Magic method.""" - self._wrapped.__exit__(*args, **kwargs) - - def get_wrapped_class(self) -> str: - """Gets the wrapped class name.""" - return self._wrapped.__class__.__name__ - - -class TracedMySQLCursor(BaseMySQLTracer): - """Wrapper class for a `MySQLCursor` or `CMySQLCursor` object.""" - - def __init__( - self, - wrapped: "MySQLCursorAbstract", - tracer: trace.Tracer, - connection_span: trace.Span, - ): - """Constructor.""" - self._wrapped: "MySQLCursorAbstract" = wrapped - self._tracer: trace.Tracer = tracer - self._connection_span_link: trace.Link = trace.Link( - connection_span.get_span_context() - ) - - @with_cursor_query_span - def execute(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.execute(*args, **kwargs) - - @with_cursor_query_span - def executemany(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.executemany(*args, **kwargs) - - @with_cursor_query_span - def callproc(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.callproc(*args, **kwargs) - - -class TracedMySQLConnection(BaseMySQLTracer): - """Wrapper class for a `MySQLConnection` or `CMySQLConnection` object.""" - - def __init__(self, wrapped: "MySQLConnectionAbstract") -> None: - """Constructor.""" - self._wrapped: "MySQLConnectionAbstract" = wrapped - - # call `sql_mode` so its value is cached internally and querying it does not - # interfere when recording query span events later. - _ = self._wrapped.sql_mode - - def cursor(self, *args: Any, **kwargs: Any) -> TracedMySQLCursor: - """Wraps the object method.""" - return TracedMySQLCursor( - wrapped=self._wrapped.cursor(*args, **kwargs), - tracer=self._tracer, - connection_span=self._span, - ) - - @with_cnx_query_span - def cmd_change_user(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_change_user(*args, **kwargs) - - @with_cnx_query_span - def commit(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.commit(*args, **kwargs) - - @with_cnx_query_span - def rollback(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.rollback(*args, **kwargs) - - @with_cnx_query_span - def cmd_query(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_query(*args, **kwargs) - - @with_cnx_query_span - def cmd_init_db(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_init_db(*args, **kwargs) - - @with_cnx_query_span - def cmd_refresh(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_refresh(*args, **kwargs) - - @with_cnx_query_span - def cmd_quit(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_quit(*args, **kwargs) - - @with_cnx_query_span - def cmd_shutdown(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_shutdown(*args, **kwargs) - - @with_cnx_query_span - def cmd_statistics(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_statistics(*args, **kwargs) - - @with_cnx_query_span - def cmd_process_kill(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_process_kill(*args, **kwargs) - - @with_cnx_query_span - def cmd_debug(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_debug(*args, **kwargs) - - @with_cnx_query_span - def cmd_ping(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_ping(*args, **kwargs) - - @property - @with_cnx_query_span - def database(self) -> str: - """Instrument method.""" - return self._wrapped.database - - @with_cnx_query_span - def is_connected(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.is_connected(*args, **kwargs) - - @with_cnx_query_span - def reset_session(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.reset_session(*args, **kwargs) - - @with_cnx_query_span - def ping(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.ping(*args, **kwargs) - - @with_cnx_query_span - def info_query(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.info_query(*args, **kwargs) - - @with_cnx_query_span - def cmd_stmt_prepare(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_stmt_prepare(*args, **kwargs) - - @with_cnx_query_span - def cmd_stmt_execute(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_stmt_execute(*args, **kwargs) - - @with_cnx_query_span - def cmd_stmt_close(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_stmt_close(*args, **kwargs) - - @with_cnx_query_span - def cmd_stmt_send_long_data(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_stmt_send_long_data(*args, **kwargs) - - @with_cnx_query_span - def cmd_stmt_reset(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_stmt_reset(*args, **kwargs) - - @with_cnx_query_span - def cmd_reset_connection(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.cmd_reset_connection(*args, **kwargs) - - @property - @with_cnx_query_span - def time_zone(self) -> str: - """Instrument method.""" - return self._wrapped.time_zone - - @property - @with_cnx_query_span - def sql_mode(self) -> str: - """Instrument method.""" - return self._wrapped.sql_mode - - @property - @with_cnx_query_span - def autocommit(self) -> bool: - """Instrument method.""" - return self._wrapped.autocommit - - @autocommit.setter - @with_cnx_query_span - def autocommit(self, value: bool) -> None: - """Instrument method.""" - self._wrapped.autocommit = value - - @with_cnx_query_span - def set_charset_collation(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.set_charset_collation(*args, **kwargs) - - @with_cnx_query_span - def start_transaction(self, *args: Any, **kwargs: Any) -> Any: - """Instrument method.""" - return self._wrapped.start_transaction(*args, **kwargs) - - -def _instrument_connect( - connect: Callable[..., Union["MySQLConnectionAbstract", "PooledMySQLConnection"]], - tracer_provider: Optional[trace.TracerProvider] = None, -) -> Callable[..., Union["MySQLConnectionAbstract", "PooledMySQLConnection"]]: - """Retrurn the instrumented version of `connect`.""" - - # let's preserve `connect` identity. - @functools.wraps(connect) - def wrapper( - *args: Any, **kwargs: Any - ) -> Union["MySQLConnectionAbstract", "PooledMySQLConnection"]: - """Wraps the connection object returned by the method `connect`. - - Instrumentation for PooledConnections is not supported. - """ - if any(key in kwargs for key in CNX_POOL_ARGS): - logger.warning("Instrumentation for pooled connections not supported") - return connect(*args, **kwargs) - - tracer = trace.get_tracer( - instrumenting_module_name="MySQL Connector/Python", - instrumenting_library_version=VERSION_TEXT, - tracer_provider=tracer_provider, - ) - - # The connection span is passed in as an argument so the connection object can - # keep a pointer to it. - kwargs[OPTION_CNX_SPAN] = tracer.start_span( - name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT - ) - kwargs[OPTION_CNX_TRACER] = tracer - - # attach connection span - # pylint: disable=not-context-manager - with trace.use_span(kwargs[OPTION_CNX_SPAN], end_on_exit=False) as cnx_span: - # Add basic net information. - set_connection_span_attrs(None, cnx_span, kwargs) - - # Connection may fail at this point, in case it does, basic net info is already - # included so the user can check the net configuration she/he provided. - cnx = connect(*args, **kwargs) - - # connection went ok, let's refine the net information. - set_connection_span_attrs(cnx, cnx_span, kwargs) # type: ignore[arg-type] - - return TracedMySQLConnection( - wrapped=cnx, # type: ignore[return-value, arg-type] - ) - - return wrapper - - -class MySQLInstrumentor: - """MySQL instrumentation supporting mysql-connector-python.""" - - _instance: Optional[MySQLInstrumentor] = None - - def __new__(cls, *args: Any, **kwargs: Any) -> MySQLInstrumentor: - """Singlenton. - - Restricts the instantiation to a singular instance. - """ - if cls._instance is None: - # create instance - cls._instance = object.__new__(cls, *args, **kwargs) - # keep a pointer to the uninstrumented connect method - setattr(cls._instance, "_original_connect", connector.connect) - return cls._instance - - def instrumentation_dependencies(self) -> Collection[str]: - """Return a list of python packages with versions - that the will be instrumented (e.g., versions >= 8.1.0).""" - return [f"mysql-connector-python >= {FIRST_SUPPORTED_VERSION}"] - - def instrument(self, **kwargs: Any) -> None: - """Instrument the library. - - Args: - trace_module: reference to the 'trace' module from opentelemetry. - tracer_provider (optional): TracerProvider instance. - - NOTE: Instrumentation for pooled connections not supported. - """ - if connector.connect != getattr(self, "_original_connect"): - logger.warning("MySQL Connector/Python module already instrumented.") - return - connector.connect = _instrument_connect( - connect=getattr(self, "_original_connect"), - tracer_provider=kwargs.get("tracer_provider"), - ) - - def instrument_connection( - self, - connection: "MySQLConnectionAbstract", - tracer_provider: Optional[trace.TracerProvider] = None, - ) -> "MySQLConnectionAbstract": - """Enable instrumentation in a MySQL connection. - - Args: - connection: uninstrumented connection instance. - trace_module: reference to the 'trace' module from opentelemetry. - tracer_provider (optional): TracerProvider instance. - - Returns: - connection: instrumented connection instace. - - NOTE: Instrumentation for pooled connections not supported. - """ - if isinstance(connection, TracedMySQLConnection): - logger.warning("Connection already instrumented.") - return connection - - if not hasattr(connection, "_span") or not hasattr(connection, "_tracer"): - logger.warning( - "Instrumentation for class %s not supported.", - connection.__class__.__name__, - ) - return connection - - tracer = trace.get_tracer( - instrumenting_module_name="MySQL Connector/Python", - instrumenting_library_version=VERSION_TEXT, - tracer_provider=tracer_provider, - ) - connection._span = tracer.start_span( - name=CONNECTION_SPAN_NAME, kind=trace.SpanKind.CLIENT - ) - connection._tracer = tracer - - set_connection_span_attrs(connection, connection._span) - - return TracedMySQLConnection(wrapped=connection) # type: ignore[return-value] - - def uninstrument(self, **kwargs: Any) -> None: - """Uninstrument the library.""" - # pylint: disable=unused-argument - if connector.connect == getattr(self, "_original_connect"): - logger.warning("MySQL Connector/Python module already uninstrumented.") - return - connector.connect = getattr(self, "_original_connect") - - def uninstrument_connection( - self, connection: "MySQLConnectionAbstract" - ) -> "MySQLConnectionAbstract": - """Disable instrumentation in a MySQL connection. - - Args: - connection: instrumented connection instance. - - Returns: - connection: uninstrumented connection instace. - - NOTE: Instrumentation for pooled connections not supported. - """ - if not hasattr(connection, "_span"): - logger.warning( - "Uninstrumentation for class %s not supported.", - connection.__class__.__name__, - ) - return connection - - if not isinstance(connection, TracedMySQLConnection): - logger.warning("Connection already uninstrumented.") - return connection - - # stop connection span recording - if connection._span and connection._span.is_recording(): - connection._span.end() - connection._span = None - - return connection._wrapped diff --git a/server/venv/Lib/site-packages/mysql/connector/optionfiles.py b/server/venv/Lib/site-packages/mysql/connector/optionfiles.py deleted file mode 100644 index 849686f..0000000 --- a/server/venv/Lib/site-packages/mysql/connector/optionfiles.py +++ /dev/null @@ -1,367 +0,0 @@ -# Copyright (c) 2014, 2026, Oracle and/or its affiliates. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License, version 2.0, as -# published by the Free Software Foundation. -# -# This program is designed to work with certain software (including -# but not limited to OpenSSL) that is licensed under separate terms, -# as designated in a particular file or component or in included license -# documentation. The authors of MySQL hereby grant you an -# additional permission to link the program and your derivative works -# with the separately licensed software that they have either included with -# the program or referenced in the documentation. -# -# Without limiting anything contained in the foregoing, this file, -# which is part of MySQL Connector/Python, is also subject to the -# Universal FOSS Exception, version 1.0, a copy of which can be found at -# http://oss.oracle.com/licenses/universal-foss-exception. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License, version 2.0, for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, Inc., -# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -# mypy: disable-error-code="attr-defined" - -"""Implements parser to parse MySQL option files.""" - -import ast -import codecs -import io -import os -import re - -from configparser import ConfigParser as SafeConfigParser, MissingSectionHeaderError -from typing import Any, Dict, List, Optional, Tuple, Union - -from .constants import CNX_POOL_ARGS, DEFAULT_CONFIGURATION - -DEFAULT_EXTENSIONS: Dict[str, Tuple[str, ...]] = { - "nt": ("ini", "cnf"), - "posix": ("cnf",), -} - - -def read_option_files(**config: Union[str, List[str]]) -> Dict[str, Any]: - """ - Read option files for connection parameters. - - Checks if connection arguments contain option file arguments, and then - reads option files accordingly. - """ - if "option_files" in config: - try: - if isinstance(config["option_groups"], str): - config["option_groups"] = [config["option_groups"]] - groups = config["option_groups"] - del config["option_groups"] - except KeyError: - groups = ["client", "connector_python"] - - if isinstance(config["option_files"], str): - config["option_files"] = [config["option_files"]] - option_parser = MySQLOptionsParser( - list(config["option_files"]), keep_dashes=False - ) - del config["option_files"] - - config_from_file: Dict[str, Any] = ( - option_parser.get_groups_as_dict_with_priority(*groups) - ) - config_options: Dict[str, Tuple[str, int, str]] = {} - for group in groups: - try: - for option, value in config_from_file[group].items(): - value += (group,) - try: - if option == "socket": - option = "unix_socket" - option_parser.set(group, "unix_socket", value[0]) - - if option not in CNX_POOL_ARGS and option != "failover": - _ = DEFAULT_CONFIGURATION[option] - - if ( - option not in config_options - or config_options[option][1] <= value[1] - ): - config_options[option] = value - except KeyError: - if group == "connector_python": - raise AttributeError( - f"Unsupported argument '{option}'" - ) from None - except KeyError: - continue - - for option, values in config_options.items(): - value, _, section = values - if ( - option not in config - and option_parser.has_section(section) - and option_parser.has_option(section, option) - ): - if option in ("password", "passwd"): # keep the value as string - config[option] = str(value) - else: - try: - config[option] = ast.literal_eval(value) - except (ValueError, TypeError, SyntaxError): - config[option] = value - if "socket" in config: - config["unix_socket"] = config.pop("socket") - return config - - -class MySQLOptionsParser(SafeConfigParser): - """This class implements methods to parse MySQL option files""" - - def __init__( - self, files: Optional[Union[List[str], str]] = None, keep_dashes: bool = True - ) -> None: - """Initialize - - If defaults is True, default option files are read first - - Raises ValueError if defaults is set to True but defaults files - cannot be found. - """ - - # Regular expression to allow options with no value(For Python v2.6) - self.optcre: re.Pattern = re.compile( - r"(?P