From 2a15b1cda6b6023d916704740d2b98b70cf88672 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 7 Feb 2023 11:31:12 +0100 Subject: [PATCH 01/53] docs: add contributing md Signed-off-by: Johannes Messner --- CONTRIBUTING.md | 327 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..d2e7dcd13f7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,327 @@ +# Contributing to DocArray + +Thanks for your interest in contributing to DocArray. We're grateful for your initiative! ❤️ + +In this guide, we're going to go through the steps for each kind of contribution, and good and bad examples of what to do. We look forward to your contributions! + + + + +- [🐞 Bugs and Issues](#-bugs-and-issues) +- [🥇 Making Your First Submission](#-making-your-first-submission) +- [📝 Code style conventions](#-code-style-conventions) +- [☑️ Naming Conventions](#-naming-conventions) +- [💥 Testing DocArray Locally and on CI](#-testing-docarray-locally-and-on-ci) +- [📖 Contributing Documentation](#-contributing-documentation) +- [🙏 Thank You](#-thank-you) + + + + +## 🐞 Bugs and Issues + +### Submitting Issues + +We love to get issue reports. But we love it even more if they're in the right format. For any bugs you encounter, we need you to: + +* **Describe your problem**: What exactly is the bug. Be as clear and concise as possible +* **Why do you think it's happening?** If you have any insight, here's where to share it + +There are also a couple of nice to haves: + +* **Environment:** Operating system, DocArray version, python version,... +* **Screenshots:** If they're relevant + + +## 🥇 Making Your First Submission + +0. Associate your local git config with your GitHub account. If this is your first time using git you can follow [the steps](#associate-with-github-account). +1. Fork the DocArray repo and clone onto your computer. +1. Configure git pre-commit hooks. Please follow [the steps](#install-pre-commit-hooks) +1. Create a [new branch](#naming-your-branch), for example `fix-docarray-typo-1`. +1. Work on this branch to do the fix/improvement. +1. Commit the changes with the [correct commit style](#writing-your-commit-message). +1. Make a pull request. +1. Submit your pull request and wait for all checks to pass. +1. Request reviews from one of [the code owners](.github/CODEOWNERS). +1. Get a LGTM 👍 and PR gets merged. + +**Note:** If you're just fixing a typo or grammatical issue, you can go straight to a pull request. + +### Associate with GitHub Account + +- Confirm username and email on [your profile page](https://github.com/settings/profile). +- Set git config on your computer. + +```shell +git config user.name "YOUR GITHUB NAME" +git config user.email "YOUR GITHUB EMAIL" +``` + +- (Optional) Reset the commit author if you made commits before you set the git config. + +```shell +git checkout YOUR-WORKED-BRANCH +git commit --amend --author="YOUR-GITHUB-NAME " --no-edit +git log # to confirm the change is effective +git push --force +``` + +### Install pre-commit hooks + +In DocArray we use git's pre-commit hooks in order to make sure the code matches our standards of quality and documentation. +It's easy to configure it: + +1. `pip install pre-commit` +1. `pre-commit install` + +Now you will be automatically reminded to add docstrings to your code. `black` will take care that your code will match our style. Note that `black` will fail your commit but reformat your code, so you just need to add the files again and commit **again**. + +#### Restoring correct git blame + +Run `git config blame.ignoreRevsFile .github/.git-blame-ignore-revs` + +## 📝 Code style conventions: + +Most of our codebase is written in Python. + +### PEP compliance + +We comply to the official PEP: E9, F63, F7, F82 code style and required every contribution to follow it. This is enforced by using [flake8](https://github.com/PyCQA/flake8) in our CI and in our [pre-commit hooks](https://github.com/jina-ai/docarray/blob/main/CONTRIBUTING.md#install-pre-commit-hooks). + +### Python version +DocArray is compatible with Python 3.7 and above, therefore we can't accept contribution that used features from the newest Python versions without ensuring compatibility with python 3.7 + +### Code formatting + +All of our Python codebase follows formatting standard. We are following the [PEP8](https://peps.python.org/pep-0008/) standard, and we require that every code contribution is formatted using [black](https://github.com/psf/black) with the default configurations. +If you have installed the [pre-commit hooks](https://github.com/jina-ai/docarray/blob/main/CONTRIBUTING.md#install-pre-commit-hooks) the formatting should be automatic on every commit. Moreover, our CI will block contributions that do not respect these conventions. + +### Type Hint + +Python is not a strongly typed programming language, nevertheless the use of [type hints](https://docs.python.org/3/library/typing.html) +contribute to a better codebase especially when reading, reviewing and refactoring. Therefore, we **require every contribution +to utilise type hints**, unless there are strong reasons for not using them. + +Further, DocArray is type checked using **Mypy**, and all contributions will have to pass this type check. + +Note: Example code in the documentation should also follow our code style conventions + + + +## ☑️ Naming Conventions + +For branches, commits, and PRs we follow some basic naming conventions: + +* Be descriptive +* Use all lower-case +* Limit punctuation +* Include one of our specified [types](#specify-the-correct-types) +* Short (under 70 characters is best) +* In general, follow the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/#summary) guidelines + +Note: If you don't follow naming conventions, your commit will be automatically flagged to be fixed. + +### Specify the correct types + +Type is an important prefix in PR, commit message. For each branch, commit, or PR, we need you to specify the type to help us keep things organized. For example, + +``` +feat: add hat wobble +^--^ ^------------^ +| | +| +-> Summary in present tense. +| ++-------> Type: build, ci, chore, docs, feat, fix, refactor, style, or test. +``` + +- build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) +- ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) +- docs: Documentation only changes +- feat: A new feature +- fix: A bug fix +- perf: A code change that improves performance +- refactor: A code change that neither fixes a bug nor adds a feature +- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semicolons, etc.) +- test: Adding missing tests or correcting existing tests +- chore: updating grunt tasks etc.; no production code change + +### Naming your Branch + +Your branch name should follow the format `type-scope(-issue_id)`: + +* `type` is one of the [types above](#specify-the-correct-types) +* `scope` is optional, and represents the module your branch is working on. +* `issue_id` is [the GitHub issue](https://github.com/jina-ai/docarray/issues) number. Having the correct issue number will automatically link the Pull Request on this branch to that issue. + +> Good examples: +> +```text +fix-executor-loader-113 +chore-update-version +docs-add-cloud-section-33 +``` + +> Bad examples: +> + +| Branch name | Feedback | +|-----------------|-------------------------------------------------------| +| `FIXAWESOME123` | Not descriptive enough, all caps, doesn't follow spec | +| `NEW-test-1` | Should be lower case, not descriptive | +| `mybranch-1` | No type, not descriptive | + +### Writing your Commit Message + +A good commit message helps us track DocArray's development. A Pull Request with a bad commit message will be *rejected* automatically in the CI pipeline. + +Commit messages should stick to our [naming conventions](#-naming-conventions) outlined above, and use the format `type(scope?): subject`: + +* `type` is one of the [types above](#specify-the-correct-types). +* `scope` is optional, and represents the module your commit is working on. +* `subject` explains the commit, without an ending period`.` + +For example, a commit that fixes a bug in the executor module should be phrased as: `fix(executor): fix the bad naming in init function` + +> Good examples: +> +```text +fix(indexer): fix wrong sharding number in indexer +feat: add remote api +``` + +> Bad examples: +> + +| Commit message | Feedback | +|-------------------------------------------------------------------------------------------------|------------------------------------| +| `doc(101): improved 101 document` | Should be `docs(101)` | +| `tests(flow): add unit test to document array` | Should be `test(array)` | +| `DOC(101): Improved 101 Documentation` | All letters should be in lowercase | +| `fix(pea): i fix this issue and this looks really awesome and everything should be working now` | Too long | +| `fix(array):fix array serialization` | Missing space after `:` | +| `hello: add hello-world` | Type `hello` is not allowed | + +#### DCO and Signed commit + +Commit need to be signed. Indeed the DocArray repo enforce the [Developer Certificate of Origin](https://developercertificate.org/) via the [DCO](https://github.com/apps/dco) github app + +To do so you need to use the `-s` argument when commiting: + +``` +git commit -m -s 'feat: add a new feature' +``` + +#### What if I Mess Up? + +We all make mistakes. GitHub has a guide on [rewriting commit messages](https://docs.github.com/en/free-pro-team@latest/github/committing-changes-to-your-project/changing-a-commit-message) so they can adhere to our standards. + +You can also install [commitlint](https://commitlint.js.org/#/) onto your own machine and check your commit message by running: + +```bash +echo "" | commitlint +``` + +### Naming your Pull Request + +We don't enforce naming of PRs and branches, but we recommend you follow the same style. It can simply be one of your commit messages, just copy/paste it, e.g. `fix(readme): improve the readability and move sections`. + + +## 💥 Testing DocArray Locally and on CI +Locally you can do unittest via: + +```bash +pip install ".[test]" +pytest -v -s tests +``` + +### Test policy + +Every contribution that adds or modifies the behavior of a feature must include a suite of tests that validates that the feature works as expected. + +This allows: +* the reviewer to be very confident that the feature does what it is supposed to do before merging it into the code base +* the contributors to be sure that they don't break already-merged features when refactoring or modifying the code base. + + +## Compiling protobuf + +Some changes to the code base require also changing the `.proto` files that describe how DocArray serializes to and from +protobuf messages. + +Changes to the `.proto` definitions **should be kept to a minimum**, in order to avoid breaking changes. + +If you _do_ make modification in a `.proto` file, you need to **recompile the protobuf definitions.** +In order to maintain compatibility with most of the Python ecosystem, in DocArray we compile to two different protobuf +versions. Therefore, compilation is a two-step process: + +1. **Step 1:** Compile using `protoc` version 3.19 + 1. Download protoc v3.19 as appropriate for your system, e.g. from [here](https://github.com/protocolbuffers/protobuf/releases/tag/v3.19.5) + 2. Unzip the file and make `protoc` executable: `chmod +x bin/protoc` + 3. Compile the protobuf definitions in the `pb2` directory. From `docarray/proto/` run `path/to/v-3-19/bin/protoc -I . --python_out="pb2" docarray.proto`. +2. **Step 2:** Compile using `protoc` version 3.21 + 1. Download protoc v3.21 as appropriate for your system, e.g. from [here](https://github.com/protocolbuffers/protobuf/releases/tag/v21.12) + 2. Same as above + 3. Compile the protobuf definitions in the `pb` directory. From `docarray/proto/` run `path/to/v-3-21/bin/protoc -I . --python_out="pb" docarray.proto`. + + +## 📖 Contributing Documentation + +Good docs make developers happy, and we love happy developers! We've got a few different types of docs: + +* General documentation +* Tutorials/examples +* Docstrings in Python functions in RST format - generated by Sphinx + +### Documentation guidelines + +1. Decide if your page is a **guide or a tutorial**. Make sure it fits its section. +2. Use “**you**” instead of “we” or “I”. It **engages** the reader more. +3. **Sentence case** for headers. (Use [https://convertcase.net/](https://convertcase.net/) to check) +4. Keep sentences short. If possible, **fewer than 13 words**. +5. Only use `backticks` for direct references to code elements. +6. All **acronyms** should be UPPERCASE (Ex. YAML, JSON, HTTP, SSL). +7. Think about the **structure** of the page beforehand. Split it into headers before writing the content. +8. If relevant, include a “**See also**” section at the end. +9. Link to any existing explanations of the concepts you are using. + +Bonus: **Know when to break the rules**. Documentation writing is as much art as it is science. Sometimes you will have to deviate from these rules in order to write good documentation. + +[MyST](https://myst-parser.readthedocs.io/en/latest/) Elements Usage + +1. Use the `{tab}` element to show multiple ways of doing one thing. [Example](https://docarray.jina.ai/fundamentals/document/#document) +2. Use the `{admonition}` boxes with care. +3. Use `{dropdown}` to hide optional content, such as long code snippets or console output. + + +Note: Example code in the documentation should also follow our code style conventions that you can find above + +### Building documentation on your local machine + +#### Requirements + +* Python 3 +* [jq](https://stedolan.github.io/jq/download/) + +#### Steps to build locally + +```bash +cd docs +pip install -r requirements.txt +export NUM_RELEASES=10 +bash makedoc.sh local-only +``` + +Docs website will be generated in `_build/dirhtml` +To serve it, run + +```bash +cd _build/dirhtml +python -m http.server +``` + +You can now see docs website on [http://localhost:8000](http://localhost:8000) on your browser. + +## 🙏 Thank You + +Once again, thanks so much for your interest in contributing to DocArray. We're excited to see your contributions! From a643c2e05d61916266b676191a38343cd3b38611 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 23 Jan 2023 18:14:11 +0100 Subject: [PATCH 02/53] feat: draft document store Signed-off-by: Johannes Messner --- docarray/storage/__init__.py | 0 docarray/storage/abstract_doc_store.py | 110 ++++++++++++++++++++ docarray/storage/backends/MemoryDocStore.py | 57 ++++++++++ docarray/storage/backends/__init__.py | 0 4 files changed, 167 insertions(+) create mode 100644 docarray/storage/__init__.py create mode 100644 docarray/storage/abstract_doc_store.py create mode 100644 docarray/storage/backends/MemoryDocStore.py create mode 100644 docarray/storage/backends/__init__.py diff --git a/docarray/storage/__init__.py b/docarray/storage/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py new file mode 100644 index 00000000000..b98feaf54f1 --- /dev/null +++ b/docarray/storage/abstract_doc_store.py @@ -0,0 +1,110 @@ +from abc import ABC, abstractmethod +from typing import Any, Dict, Generic, List, Sequence, Type, TypeVar, Union + +from typing_inspect import is_union_type + +from docarray import BaseDocument, DocumentArray +from docarray.array.abstract_array import AnyDocumentArray +from docarray.typing import AnyTensor +from docarray.utils.find import FindResult + +TSchema = TypeVar('TSchema', bound=BaseDocument) + + +class BaseDocumentStore(ABC, Generic[TSchema]): + """Abstract class for all Document Stores""" + + _schema: Type = type(None) + _columns: Dict[str, Type] = {} + + def __init__(self): + if issubclass(self._schema, type(None)): + raise ValueError( + 'A DocumentStore must be typed with a Document type.' + 'To do so, use the syntax: DocumentStore[DocumentType]' + ) + self._db_columns = { + name: self.python_type_to_db_type(type_) + for name, type_ in self._columns.items() + } + + @abstractmethod + def python_type_to_db_type(self, python_type: Type) -> Any: + """Map python type to database type.""" + ... + + @abstractmethod + def index(self, docs: Union[TSchema, Sequence[TSchema]]): + """Index a document into the store""" + ... + + @abstractmethod + def find( + self, + query: Union[AnyTensor, BaseDocument], + embedding_field: str = 'embedding', + metric: str = 'cosine_sim', + limit: int = 10, + **kwargs, + ) -> FindResult: + """Find documents in the store""" + # TODO(johannes) refine method signature + ... + + @abstractmethod + def find_batched( + self, + query: Union[AnyTensor, DocumentArray], + embedding_field: str = 'embedding', + metric: str = 'cosine_sim', + limit: int = 10, + **kwargs, + ) -> List[FindResult]: + """Find documents in the store""" + # TODO(johannes) refine method signature + ... + + def __class_getitem__(cls, item: Type[TSchema]): + if not issubclass(item, BaseDocument): + raise ValueError( + f'{cls.__name__}[item] item should be a Document not a {item} ' + ) + + class _DocumentStoreTyped(cls): # type: ignore + _schema: Type[TSchema] = item + _columns: Dict[str, Type] = cls._unwrap_columns(_schema) + + _DocumentStoreTyped.__name__ = f'{cls.__name__}[{item.__name__}]' + _DocumentStoreTyped.__qualname__ = f'{cls.__qualname__}[{item.__name__}]' + + return _DocumentStoreTyped + + @classmethod + def _unwrap_columns(cls, schema: Type[BaseDocument]) -> Dict[str, Type]: + columns: Dict[str, Type] = dict() + for field_name, field in schema.__fields__.items(): + t_ = field.type_ + if is_union_type(t_): + # TODO(johannes): this restriction has to + # go othws we can't even index built in docs + raise ValueError( + 'Indexing field of Union type is not' + f'supported. Instead of using type' + f'{t_} use a single specific type.' + ) + elif issubclass(t_, AnyDocumentArray): + raise ValueError( + 'Indexing field of DocumentArray type (=subindex)' + 'is not yet supported.' + ) + elif issubclass(t_, BaseDocument): + columns = dict( + columns, + **{ + f'{field_name}__{nested_name}': t + for nested_name, t in cls._unwrap_columns(t_).items() + }, + ) + else: + columns[field_name] = t_ + return columns diff --git a/docarray/storage/backends/MemoryDocStore.py b/docarray/storage/backends/MemoryDocStore.py new file mode 100644 index 00000000000..e339e28c378 --- /dev/null +++ b/docarray/storage/backends/MemoryDocStore.py @@ -0,0 +1,57 @@ +from typing import Generic, List, Sequence, Type, TypeVar, Union + +from docarray import BaseDocument, DocumentArray +from docarray.storage.abstract_doc_store import BaseDocumentStore, FindResult +from docarray.typing import AnyTensor +from docarray.utils.find import find, find_batched + +TSchema = TypeVar('TSchema', bound=BaseDocument) + + +class MemoryDocumentStore(BaseDocumentStore, Generic[TSchema]): + """In-memory document store""" + + def __init__(self): + super().__init__() + # TODO(johannes) not sure why the line below doesn't type check + self._docs = DocumentArray[self._schema]([]) # type: ignore + + def python_type_to_db_type(self, python_type: Type) -> Type: + return python_type + + def index(self, docs: Union[TSchema, Sequence[TSchema]]): + self._docs.extend(docs) + + def find( + self, + query: Union[AnyTensor, BaseDocument], + embedding_field: str = 'embedding', + metric: str = 'cosine_sim', + limit: int = 10, + **kwargs, + ) -> FindResult: + return find( + index=self._docs, + query=query, + embedding_field=embedding_field, + metric=metric, + limit=limit, + **kwargs, + ) + + def find_batched( + self, + query: Union[AnyTensor, DocumentArray], + embedding_field: str = 'embedding', + metric: str = 'cosine_sim', + limit: int = 10, + **kwargs, + ) -> List[FindResult]: + return find_batched( + index=self._docs, + query=query, + embedding_field=embedding_field, + metric=metric, + limit=limit, + **kwargs, + ) diff --git a/docarray/storage/backends/__init__.py b/docarray/storage/backends/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From c21c94adc6bd91c9cae224a579dd9b5a59c1088a Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Fri, 10 Feb 2023 15:07:39 +0100 Subject: [PATCH 03/53] wip: initial implementation, incomplete Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 100 +++++++++++++++++++-- docarray/storage/backends/HnswDocStore.py | 102 ++++++++++++++++++++++ docarray/utils/protocols.py | 7 ++ 3 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 docarray/storage/backends/HnswDocStore.py create mode 100644 docarray/utils/protocols.py diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index b98feaf54f1..a7702df42e0 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -1,5 +1,16 @@ from abc import ABC, abstractmethod -from typing import Any, Dict, Generic, List, Sequence, Type, TypeVar, Union +from typing import ( + Any, + Dict, + Generator, + Generic, + List, + Optional, + Sequence, + Type, + TypeVar, + Union, +) from typing_inspect import is_union_type @@ -7,6 +18,7 @@ from docarray.array.abstract_array import AnyDocumentArray from docarray.typing import AnyTensor from docarray.utils.find import FindResult +from docarray.utils.protocols import IsDataclass TSchema = TypeVar('TSchema', bound=BaseDocument) @@ -14,19 +26,31 @@ class BaseDocumentStore(ABC, Generic[TSchema]): """Abstract class for all Document Stores""" - _schema: Type = type(None) - _columns: Dict[str, Type] = {} - - def __init__(self): + # the BaseDocument that defines the schema of the store + _schema: Type = type(None) # this is filled automatically + # columns with types according to the Document schema + _columns_schema: Dict[str, Type] = {} # this is filled automatically + # default configurations for every column type + # a dictionary from a column type (DB specific) to a dictionary + # of default configurations for that type + # These configs are used if no configs are specified in the `Field(...)` + # of a field in the Document schema (`cls._schema`) + # Example: `_default_column_config['VARCHAR'] = {'length': 255}` + _default_column_config: Dict[Any, Dict[str, Any]] = {} + + def __init__(self, config: Optional[IsDataclass] = None): if issubclass(self._schema, type(None)): raise ValueError( 'A DocumentStore must be typed with a Document type.' 'To do so, use the syntax: DocumentStore[DocumentType]' ) - self._db_columns = { + self._config = config + # columns with DB specific types + self._columns_db = { name: self.python_type_to_db_type(type_) - for name, type_ in self._columns.items() + for name, type_ in self._columns_schema.items() } + self._column_configs: Dict[Any, Dict[str, Any]] = self._get_column_configs() @abstractmethod def python_type_to_db_type(self, python_type: Type) -> Any: @@ -72,7 +96,7 @@ def __class_getitem__(cls, item: Type[TSchema]): class _DocumentStoreTyped(cls): # type: ignore _schema: Type[TSchema] = item - _columns: Dict[str, Type] = cls._unwrap_columns(_schema) + _columns_schema: Dict[str, Type] = cls._unwrap_columns(_schema) _DocumentStoreTyped.__name__ = f'{cls.__name__}[{item.__name__}]' _DocumentStoreTyped.__qualname__ = f'{cls.__qualname__}[{item.__name__}]' @@ -108,3 +132,63 @@ def _unwrap_columns(cls, schema: Type[BaseDocument]) -> Dict[str, Type]: else: columns[field_name] = t_ return columns + + def _is_schema_compatible(self, docs: Sequence[BaseDocument]) -> bool: + """Flatten a DocumentArray into a DocumentArray of the schema type.""" + if isinstance(docs, AnyDocumentArray): + docs_columns = self._unwrap_columns(docs.document_type) + # this could be relaxed in the future, + # see schema translation ideas in the design doc + return docs_columns == self._columns_schema + else: + for d in docs: + doc_columns = self._unwrap_columns(type(d)) + # this could be relaxed in the future, + # see schema translation ideas in the design doc + if doc_columns != self._columns_schema: + return False + return True + + @staticmethod + def get_value(doc: BaseDocument, col_name: str) -> Any: + """Get the value of a column of a document.""" + if '__' in col_name: + fields = col_name.split('__') + leaf_doc: BaseDocument = doc + for f in fields[:-1]: + leaf_doc = getattr(leaf_doc, f) + return getattr(leaf_doc, fields[-1]) + else: + return getattr(doc, col_name) + + def get_data_by_columns( + self, docs: Union[BaseDocument, Sequence[BaseDocument]] + ) -> Dict[str, Generator[Any, None, None]]: + """Get the payload of a document.""" + if isinstance(docs, BaseDocument): + docs = [docs] + if not self._is_schema_compatible(docs): + raise ValueError( + 'The schema of the documents to be indexed is not compatible' + ' with the schema of the store.' + ) + return { + col_name: (self.get_value(doc, col_name) for doc in docs) + for col_name in self._columns_schema + } + + def _get_column_configs(self) -> Dict[str, Dict[str, Any]]: + col_configs: Dict[str, Dict[str, Any]] = {} + for col_name, db_type in self._columns_db.items(): + col_configs[col_name] = self._default_column_config[db_type].copy() + + nestings = col_name.split('__') + nested_fields, leaf_field = nestings[:-1], nestings[-1] + leaf_doc: Type[BaseDocument] = self._schema + for nested_field in nested_fields: + # traverse nested document fields + leaf_doc = leaf_doc.__fields__[nested_field].type_ + custom_config = leaf_doc.__fields__[leaf_field].field_info.extra + col_configs[col_name].update(custom_config) + + return col_configs diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py new file mode 100644 index 00000000000..f2cdfb68460 --- /dev/null +++ b/docarray/storage/backends/HnswDocStore.py @@ -0,0 +1,102 @@ +from typing import Any, Generic, Optional, Sequence, Type, TypeVar, Union + +import hnswlib +import numpy as np + +import docarray.typing +from docarray import BaseDocument +from docarray.storage.abstract_doc_store import BaseDocumentStore +from docarray.typing import AnyTensor +from docarray.utils.find import FindResult +from docarray.utils.misc import torch_imported +from docarray.utils.protocols import IsDataclass + +TSchema = TypeVar('TSchema', bound=BaseDocument) + +HNSWLIB_PY_VEC_TYPES = [list, tuple, np.ndarray] +if torch_imported: + import torch + + HNSWLIB_PY_VEC_TYPES.append(torch.Tensor) + + +class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): + _default_column_config = { + np.ndarray: { + 'dim': 128, + 'space': 'l2', + 'max_elements': 1024, + 'ef_construction': 200, + 'M': 16, + }, + None: {}, + } + + def __init__(self, config: Optional[IsDataclass] = None): + super().__init__(config) + self._index_construct_params = ('space', 'dim') + self._index_init_params = ('max_elements', 'ef_construction', 'M') + + self._indices = {} + for col_name, col_config in self._column_configs.items(): + if not col_config: + continue # do not create column index if no config is given + construct_params = dict( + (k, col_config[k]) for k in self._index_construct_params + ) + index = hnswlib.Index(**construct_params) + init_params = dict((k, col_config[k]) for k in self._index_init_params) + index.init_index(**init_params) + self._indices[col_name] = index + + def python_type_to_db_type(self, python_type: Type) -> Any: + """Map python type to database type.""" + for allowed_type in HNSWLIB_PY_VEC_TYPES: + if issubclass(python_type, allowed_type): + return np.ndarray + + if python_type == docarray.typing.ID: + return None # TODO(johannes): handle this + + raise ValueError(f'Unsupported column type for {type(self)}: {python_type}') + + def _to_numpy(self, val: Any) -> Any: + if isinstance(val, np.ndarray): + return val + elif isinstance(val, (list, tuple)): + return np.array(val) + elif torch_imported and isinstance(val, torch.Tensor): + return val.numpy() + else: + raise ValueError(f'Unsupported input type for {type(self)}: {type(val)}') + + def index(self, docs: Union[TSchema, Sequence[TSchema]]): + """Index a document into the store""" + data_by_columns = self.get_data_by_columns(docs) + for col_name, index in self._indices.items(): + data = data_by_columns[col_name] + data_np = [self._to_numpy(arr) for arr in data] + data_stacked = np.stack(data_np) + index.add_items(data_stacked, ids=range(len(docs))) + + def find( + self, + query: Union[AnyTensor, BaseDocument], + embedding_field: str = 'embedding', + metric: str = 'cosine_sim', + limit: int = 10, + **kwargs, + ) -> FindResult: + # the below should be done in the abstract class + if isinstance(query, BaseDocument): + query_vec = self.get_value(query, embedding_field) + else: + query_vec = query + query_vec_np = self._to_numpy(query_vec) + + index = self._indices[embedding_field] + labels, distances = index.knn_query(query_vec_np, k=limit) + return labels, distances + + def find_batched(self, *args, **kwargs): + ... diff --git a/docarray/utils/protocols.py b/docarray/utils/protocols.py new file mode 100644 index 00000000000..aa735300b72 --- /dev/null +++ b/docarray/utils/protocols.py @@ -0,0 +1,7 @@ +from typing import ClassVar, Dict + +from typing_extensions import Protocol + + +class IsDataclass(Protocol): + __dataclass_fields__: ClassVar[Dict] From 9e51f707bbac899cff1da23e0f7ee4e4d2431c97 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Fri, 10 Feb 2023 17:58:16 +0100 Subject: [PATCH 04/53] feat: return documents on find Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 86 +++++++++++++---------- docarray/storage/backends/HnswDocStore.py | 30 +++++--- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index a7702df42e0..a981060d4b9 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -1,10 +1,12 @@ from abc import ABC, abstractmethod +from dataclasses import dataclass from typing import ( Any, Dict, Generator, Generic, List, + NamedTuple, Optional, Sequence, Type, @@ -12,6 +14,7 @@ Union, ) +import numpy as np from typing_inspect import is_union_type from docarray import BaseDocument, DocumentArray @@ -23,13 +26,25 @@ TSchema = TypeVar('TSchema', bound=BaseDocument) +class FindResultBatched(NamedTuple): + documents: List[DocumentArray] + scores: np.ndarray + + +@dataclass +class _Column: + docarray_type: Type + db_type: Any + config: Dict[str, Any] + + class BaseDocumentStore(ABC, Generic[TSchema]): """Abstract class for all Document Stores""" # the BaseDocument that defines the schema of the store - _schema: Type = type(None) # this is filled automatically - # columns with types according to the Document schema - _columns_schema: Dict[str, Type] = {} # this is filled automatically + # for subclasses this is filled automatically + _schema: Optional[Type[BaseDocument]] = None + # default configurations for every column type # a dictionary from a column type (DB specific) to a dictionary # of default configurations for that type @@ -39,18 +54,14 @@ class BaseDocumentStore(ABC, Generic[TSchema]): _default_column_config: Dict[Any, Dict[str, Any]] = {} def __init__(self, config: Optional[IsDataclass] = None): - if issubclass(self._schema, type(None)): + if self._schema is None: raise ValueError( 'A DocumentStore must be typed with a Document type.' 'To do so, use the syntax: DocumentStore[DocumentType]' ) self._config = config - # columns with DB specific types - self._columns_db = { - name: self.python_type_to_db_type(type_) - for name, type_ in self._columns_schema.items() - } - self._column_configs: Dict[Any, Dict[str, Any]] = self._get_column_configs() + + self._columns: Dict[str, _Column] = self._create_columns(self._schema) @abstractmethod def python_type_to_db_type(self, python_type: Type) -> Any: @@ -83,7 +94,7 @@ def find_batched( metric: str = 'cosine_sim', limit: int = 10, **kwargs, - ) -> List[FindResult]: + ) -> FindResultBatched: """Find documents in the store""" # TODO(johannes) refine method signature ... @@ -96,16 +107,14 @@ def __class_getitem__(cls, item: Type[TSchema]): class _DocumentStoreTyped(cls): # type: ignore _schema: Type[TSchema] = item - _columns_schema: Dict[str, Type] = cls._unwrap_columns(_schema) _DocumentStoreTyped.__name__ = f'{cls.__name__}[{item.__name__}]' _DocumentStoreTyped.__qualname__ = f'{cls.__qualname__}[{item.__name__}]' return _DocumentStoreTyped - @classmethod - def _unwrap_columns(cls, schema: Type[BaseDocument]) -> Dict[str, Type]: - columns: Dict[str, Type] = dict() + def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: + columns: Dict[str, _Column] = dict() for field_name, field in schema.__fields__.items(): t_ = field.type_ if is_union_type(t_): @@ -126,26 +135,43 @@ def _unwrap_columns(cls, schema: Type[BaseDocument]) -> Dict[str, Type]: columns, **{ f'{field_name}__{nested_name}': t - for nested_name, t in cls._unwrap_columns(t_).items() + for nested_name, t in self._create_columns(t_).items() }, ) else: - columns[field_name] = t_ + columns[field_name] = self._create_single_column(field) return columns + def _create_single_column(self, field): + type_ = field.type_ + db_type = self.python_type_to_db_type(type_) + config = self._default_column_config[db_type].copy() + custom_config = field.field_info.extra + config.update(custom_config) + return _Column(docarray_type=type_, db_type=db_type, config=config) + def _is_schema_compatible(self, docs: Sequence[BaseDocument]) -> bool: """Flatten a DocumentArray into a DocumentArray of the schema type.""" + reference_col_db_types = [ + (name, col.db_type) for name, col in self._columns.items() + ] if isinstance(docs, AnyDocumentArray): - docs_columns = self._unwrap_columns(docs.document_type) + input_columns = self._create_columns(docs.document_type) + input_col_db_types = [ + (name, col.db_type) for name, col in input_columns.items() + ] # this could be relaxed in the future, # see schema translation ideas in the design doc - return docs_columns == self._columns_schema + return reference_col_db_types == input_col_db_types else: for d in docs: - doc_columns = self._unwrap_columns(type(d)) + input_columns = self._create_columns(type(d)) + input_col_db_types = [ + (name, col.db_type) for name, col in input_columns.items() + ] # this could be relaxed in the future, # see schema translation ideas in the design doc - if doc_columns != self._columns_schema: + if reference_col_db_types != input_col_db_types: return False return True @@ -174,21 +200,5 @@ def get_data_by_columns( ) return { col_name: (self.get_value(doc, col_name) for doc in docs) - for col_name in self._columns_schema + for col_name in self._columns } - - def _get_column_configs(self) -> Dict[str, Dict[str, Any]]: - col_configs: Dict[str, Dict[str, Any]] = {} - for col_name, db_type in self._columns_db.items(): - col_configs[col_name] = self._default_column_config[db_type].copy() - - nestings = col_name.split('__') - nested_fields, leaf_field = nestings[:-1], nestings[-1] - leaf_doc: Type[BaseDocument] = self._schema - for nested_field in nested_fields: - # traverse nested document fields - leaf_doc = leaf_doc.__fields__[nested_field].type_ - custom_config = leaf_doc.__fields__[leaf_field].field_info.extra - col_configs[col_name].update(custom_config) - - return col_configs diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index f2cdfb68460..7b64184a169 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -4,10 +4,9 @@ import numpy as np import docarray.typing -from docarray import BaseDocument -from docarray.storage.abstract_doc_store import BaseDocumentStore +from docarray import BaseDocument, DocumentArray +from docarray.storage.abstract_doc_store import BaseDocumentStore, FindResultBatched from docarray.typing import AnyTensor -from docarray.utils.find import FindResult from docarray.utils.misc import torch_imported from docarray.utils.protocols import IsDataclass @@ -37,8 +36,11 @@ def __init__(self, config: Optional[IsDataclass] = None): self._index_construct_params = ('space', 'dim') self._index_init_params = ('max_elements', 'ef_construction', 'M') + self._docs_buffer = DocumentArray[self._schema]() + self._indices = {} - for col_name, col_config in self._column_configs.items(): + for col_name, col in self._columns.items(): + col_config = col.config if not col_config: continue # do not create column index if no config is given construct_params = dict( @@ -73,20 +75,24 @@ def _to_numpy(self, val: Any) -> Any: def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" data_by_columns = self.get_data_by_columns(docs) + doc_buf_len_before = len(self._docs_buffer) + self._docs_buffer.extend(docs) for col_name, index in self._indices.items(): data = data_by_columns[col_name] data_np = [self._to_numpy(arr) for arr in data] data_stacked = np.stack(data_np) - index.add_items(data_stacked, ids=range(len(docs))) + index.add_items( + data_stacked, ids=range(doc_buf_len_before, len(self._docs_buffer)) + ) - def find( + def find_batched( self, - query: Union[AnyTensor, BaseDocument], + query: Union[AnyTensor, DocumentArray], embedding_field: str = 'embedding', metric: str = 'cosine_sim', limit: int = 10, **kwargs, - ) -> FindResult: + ) -> FindResultBatched: # the below should be done in the abstract class if isinstance(query, BaseDocument): query_vec = self.get_value(query, embedding_field) @@ -96,7 +102,11 @@ def find( index = self._indices[embedding_field] labels, distances = index.knn_query(query_vec_np, k=limit) - return labels, distances + result_das = [ + DocumentArray[self._schema]([self._docs_buffer[i] for i in ids_per_query]) + for ids_per_query in labels + ] + return FindResultBatched(documents=result_das, scores=distances) - def find_batched(self, *args, **kwargs): + def find(self, *args, **kwargs): ... From 5910578e534e4a12041c6037bea25a84a19d84c4 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 13 Feb 2023 14:44:09 +0100 Subject: [PATCH 05/53] feat: delitem Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 8 +++++ docarray/storage/backends/HnswDocStore.py | 42 ++++++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index a981060d4b9..0125aa14d77 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -73,6 +73,14 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" ... + @abstractmethod + def __delitem__(self, key: Union[str, Sequence[str]]): + """Delete a document from the store, by `id`. + + :param key: id or ids to delete from the Document Store + """ + ... + @abstractmethod def find( self, diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 7b64184a169..b28314053ff 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -7,6 +7,7 @@ from docarray import BaseDocument, DocumentArray from docarray.storage.abstract_doc_store import BaseDocumentStore, FindResultBatched from docarray.typing import AnyTensor +from docarray.utils.find import FindResult from docarray.utils.misc import torch_imported from docarray.utils.protocols import IsDataclass @@ -37,8 +38,9 @@ def __init__(self, config: Optional[IsDataclass] = None): self._index_init_params = ('max_elements', 'ef_construction', 'M') self._docs_buffer = DocumentArray[self._schema]() + self._doc_id_to_hnsw_id = {} - self._indices = {} + self._hnsw_indices = {} for col_name, col in self._columns.items(): col_config = col.config if not col_config: @@ -49,7 +51,7 @@ def __init__(self, config: Optional[IsDataclass] = None): index = hnswlib.Index(**construct_params) init_params = dict((k, col_config[k]) for k in self._index_init_params) index.init_index(**init_params) - self._indices[col_name] = index + self._hnsw_indices[col_name] = index def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" @@ -77,13 +79,17 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]]): data_by_columns = self.get_data_by_columns(docs) doc_buf_len_before = len(self._docs_buffer) self._docs_buffer.extend(docs) - for col_name, index in self._indices.items(): + + hnsw_ids = range(doc_buf_len_before, len(self._docs_buffer)) + self._doc_id_to_hnsw_id.update( + (doc_id, hnsw_id) for doc_id, hnsw_id in zip(docs.id, hnsw_ids) + ) + + for col_name, index in self._hnsw_indices.items(): data = data_by_columns[col_name] data_np = [self._to_numpy(arr) for arr in data] data_stacked = np.stack(data_np) - index.add_items( - data_stacked, ids=range(doc_buf_len_before, len(self._docs_buffer)) - ) + index.add_items(data_stacked, ids=hnsw_ids) def find_batched( self, @@ -100,7 +106,7 @@ def find_batched( query_vec = query query_vec_np = self._to_numpy(query_vec) - index = self._indices[embedding_field] + index = self._hnsw_indices[embedding_field] labels, distances = index.knn_query(query_vec_np, k=limit) result_das = [ DocumentArray[self._schema]([self._docs_buffer[i] for i in ids_per_query]) @@ -109,4 +115,24 @@ def find_batched( return FindResultBatched(documents=result_das, scores=distances) def find(self, *args, **kwargs): - ... + batched_result = self.find_batched(*args, **kwargs) + return FindResult( + documents=batched_result.documents[0], scores=batched_result.scores[0] + ) + + def __delitem__(self, key: Union[str, Sequence[str]]): + # delete from the indices + if isinstance(key, str): + key = [key] + for k in key: + id_ = self._doc_id_to_hnsw_id[k] + for col_name, index in self._hnsw_indices.items(): + index.mark_deleted(id_) + del self._doc_id_to_hnsw_id[k] + + # delete from the buffer + # TODO(johannes): this will become delete from SQLite + # this is inefficient, but temporary anyways + for doc in self._docs_buffer: + if doc.id in key: + self._docs_buffer.remove(doc) From ddd379e8ed4c27503ea6ae3b79e5b2dec385fb5d Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 13 Feb 2023 15:19:39 +0100 Subject: [PATCH 06/53] feat: get tensor dim from tensor parametrization Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 14 +++++++++++++- docarray/storage/backends/HnswDocStore.py | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 0125aa14d77..88dd343dab2 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -35,6 +35,7 @@ class FindResultBatched(NamedTuple): class _Column: docarray_type: Type db_type: Any + n_dim: Optional[int] config: Dict[str, Any] @@ -156,7 +157,18 @@ def _create_single_column(self, field): config = self._default_column_config[db_type].copy() custom_config = field.field_info.extra config.update(custom_config) - return _Column(docarray_type=type_, db_type=db_type, config=config) + # parse n_dim from parametrized tensor type + if ( + hasattr(type_, '__docarray_target_shape__') + and type_.__docarray_target_shape__ + ): + if len(type_.__docarray_target_shape__) == 1: + n_dim = type_.__docarray_target_shape__[0] + else: + n_dim = type_.__docarray_target_shape__ + else: + n_dim = None + return _Column(docarray_type=type_, db_type=db_type, config=config, n_dim=n_dim) def _is_schema_compatible(self, docs: Sequence[BaseDocument]) -> bool: """Flatten a DocumentArray into a DocumentArray of the schema type.""" diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index b28314053ff..e7c1de1251c 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -48,6 +48,8 @@ def __init__(self, config: Optional[IsDataclass] = None): construct_params = dict( (k, col_config[k]) for k in self._index_construct_params ) + if col.n_dim: + construct_params['dim'] = col.n_dim index = hnswlib.Index(**construct_params) init_params = dict((k, col_config[k]) for k in self._index_init_params) index.init_index(**init_params) From 831d2cea64e62ddfbfd50f205ebe1aad4f1441e3 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 13 Feb 2023 17:37:35 +0100 Subject: [PATCH 07/53] docs: add sqlite integration Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 112 +++++++++++++++++++--- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index e7c1de1251c..a0291c8bc3f 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -1,3 +1,7 @@ +import os +import sqlite3 +from dataclasses import dataclass +from pathlib import Path from typing import Any, Generic, Optional, Sequence, Type, TypeVar, Union import hnswlib @@ -5,6 +9,7 @@ import docarray.typing from docarray import BaseDocument, DocumentArray +from docarray.proto import DocumentProto from docarray.storage.abstract_doc_store import BaseDocumentStore, FindResultBatched from docarray.typing import AnyTensor from docarray.utils.find import FindResult @@ -20,6 +25,11 @@ HNSWLIB_PY_VEC_TYPES.append(torch.Tensor) +@dataclass +class HNSWConfig: + work_dir: str = '.' + + class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): _default_column_config = { np.ndarray: { @@ -34,11 +44,15 @@ class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): def __init__(self, config: Optional[IsDataclass] = None): super().__init__(config) - self._index_construct_params = ('space', 'dim') - self._index_init_params = ('max_elements', 'ef_construction', 'M') + self._work_dir = config.work_dir + Path(self._work_dir).mkdir(parents=True, exist_ok=True) - self._docs_buffer = DocumentArray[self._schema]() self._doc_id_to_hnsw_id = {} + self._hnsw_id_to_doc_id = {} + + # HNSWLib setup + self._index_construct_params = ('space', 'dim') + self._index_init_params = ('max_elements', 'ef_construction', 'M') self._hnsw_indices = {} for col_name, col in self._columns.items(): @@ -54,6 +68,17 @@ def __init__(self, config: Optional[IsDataclass] = None): init_params = dict((k, col_config[k]) for k in self._index_init_params) index.init_index(**init_params) self._hnsw_indices[col_name] = index + self._hnsw_locations = { + col_name: os.path.join(self._work_dir, f'{col_name}.bin') + for col_name in self._hnsw_indices.keys() + } + + # SQLite setup + self._sqlite_db_path = os.path.join(self._work_dir, 'docs_sqlite.db') + self._sqlite_conn = sqlite3.connect(self._sqlite_db_path) + self._sqlite_cursor = self._sqlite_conn.cursor() + _create_docs_table(self._sqlite_cursor) + self._sqlite_conn.commit() def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" @@ -79,20 +104,28 @@ def _to_numpy(self, val: Any) -> Any: def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" data_by_columns = self.get_data_by_columns(docs) - doc_buf_len_before = len(self._docs_buffer) - self._docs_buffer.extend(docs) - hnsw_ids = range(doc_buf_len_before, len(self._docs_buffer)) + num_docs_before = _get_num_docs_sqlite(self._sqlite_cursor) + hnsw_ids = range(num_docs_before, num_docs_before + len(docs)) + # the dict updates below could maybe be optimized? self._doc_id_to_hnsw_id.update( (doc_id, hnsw_id) for doc_id, hnsw_id in zip(docs.id, hnsw_ids) ) + self._hnsw_id_to_doc_id.update( + (hnsw_id, doc_id) for doc_id, hnsw_id in zip(docs.id, hnsw_ids) + ) + # indexing into HNSWLib and SQLite sequentially + # could be improved by processing in parallel for col_name, index in self._hnsw_indices.items(): data = data_by_columns[col_name] data_np = [self._to_numpy(arr) for arr in data] data_stacked = np.stack(data_np) index.add_items(data_stacked, ids=hnsw_ids) + _send_docs_to_sqlite(self._sqlite_cursor, docs) + self._sqlite_conn.commit() + def find_batched( self, query: Union[AnyTensor, DocumentArray], @@ -111,7 +144,11 @@ def find_batched( index = self._hnsw_indices[embedding_field] labels, distances = index.knn_query(query_vec_np, k=limit) result_das = [ - DocumentArray[self._schema]([self._docs_buffer[i] for i in ids_per_query]) + _get_docs_from_sqlite( + self._sqlite_cursor, + [self._hnsw_id_to_doc_id[id_] for id_ in ids_per_query], + self._schema, + ) for ids_per_query in labels ] return FindResultBatched(documents=result_das, scores=distances) @@ -132,9 +169,58 @@ def __delitem__(self, key: Union[str, Sequence[str]]): index.mark_deleted(id_) del self._doc_id_to_hnsw_id[k] - # delete from the buffer - # TODO(johannes): this will become delete from SQLite - # this is inefficient, but temporary anyways - for doc in self._docs_buffer: - if doc.id in key: - self._docs_buffer.remove(doc) + _delete_docs_from_sqlite(self._sqlite_cursor, key) + self._sqlite_conn.commit() + + +# serialization helpers +def _doc_to_bytes(doc: BaseDocument) -> bytes: + return doc.to_protobuf().SerializeToString() + + +TSerialize = TypeVar('TSerialize', bound=BaseDocument) + + +def _doc_from_bytes(data: bytes, doc_class: Type[TSerialize]) -> TSerialize: + return doc_class.from_protobuf(DocumentProto.FromString(data)) + + +# SQLite helpers + + +def _create_docs_table(cursor: sqlite3.Cursor): + cursor.execute( + 'CREATE TABLE IF NOT EXISTS docs (doc_id TEXT PRIMARY KEY, data BLOB)' + ) + + +def _send_docs_to_sqlite(cursor: sqlite3.Cursor, docs: Sequence[BaseDocument]): + cursor.executemany( + 'INSERT INTO docs VALUES (?, ?)', + ((doc.id, _doc_to_bytes(doc)) for doc in docs), + ) + + +def _get_docs_from_sqlite( + cursor: sqlite3.Cursor, doc_ids: Sequence[str], doc_class: Type[TSerialize] +) -> DocumentArray: + cursor.execute( + 'SELECT data FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(doc_ids)), + doc_ids, + ) + rows = cursor.fetchall() + return DocumentArray[doc_class]( + (_doc_from_bytes(row[0], doc_class) for row in rows) + ) + + +def _delete_docs_from_sqlite(cursor: sqlite3.Cursor, doc_ids: Sequence[str]): + cursor.execute( + 'DELETE FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(doc_ids)), + doc_ids, + ) + + +def _get_num_docs_sqlite(cursor: sqlite3.Cursor) -> int: + cursor.execute('SELECT COUNT(*) FROM docs') + return cursor.fetchone()[0] From bfd2bb5d5048a79d499b2b93c8f2449b858f4b75 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 13 Feb 2023 18:07:24 +0100 Subject: [PATCH 08/53] feat: store hnsw indices Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 55 ++++++++++++++++------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index a0291c8bc3f..567fe5388f6 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -10,7 +10,11 @@ import docarray.typing from docarray import BaseDocument, DocumentArray from docarray.proto import DocumentProto -from docarray.storage.abstract_doc_store import BaseDocumentStore, FindResultBatched +from docarray.storage.abstract_doc_store import ( + BaseDocumentStore, + FindResultBatched, + _Column, +) from docarray.typing import AnyTensor from docarray.utils.find import FindResult from docarray.utils.misc import torch_imported @@ -45,6 +49,7 @@ class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): def __init__(self, config: Optional[IsDataclass] = None): super().__init__(config) self._work_dir = config.work_dir + load_existing = os.path.exists(self._work_dir) Path(self._work_dir).mkdir(parents=True, exist_ok=True) self._doc_id_to_hnsw_id = {} @@ -54,24 +59,19 @@ def __init__(self, config: Optional[IsDataclass] = None): self._index_construct_params = ('space', 'dim') self._index_init_params = ('max_elements', 'ef_construction', 'M') - self._hnsw_indices = {} - for col_name, col in self._columns.items(): - col_config = col.config - if not col_config: - continue # do not create column index if no config is given - construct_params = dict( - (k, col_config[k]) for k in self._index_construct_params - ) - if col.n_dim: - construct_params['dim'] = col.n_dim - index = hnswlib.Index(**construct_params) - init_params = dict((k, col_config[k]) for k in self._index_init_params) - index.init_index(**init_params) - self._hnsw_indices[col_name] = index self._hnsw_locations = { col_name: os.path.join(self._work_dir, f'{col_name}.bin') - for col_name in self._hnsw_indices.keys() + for col_name, col in self._columns.items() + if col.config } + self._hnsw_indices = {} + for col_name, col in self._columns.items(): + if not col.config: + continue # do not create column index if no config is given + if load_existing: + self._hnsw_indices[col_name] = self._load_index(col_name, col) + else: + self._hnsw_indices[col_name] = self._create_index(col) # SQLite setup self._sqlite_db_path = os.path.join(self._work_dir, 'docs_sqlite.db') @@ -80,6 +80,28 @@ def __init__(self, config: Optional[IsDataclass] = None): _create_docs_table(self._sqlite_cursor) self._sqlite_conn.commit() + def _create_index_class(self, col: '_Column') -> hnswlib.Index: + '''Create an instance of hnswlib.Index without initializing it.''' + construct_params = dict( + (k, col.config[k]) for k in self._index_construct_params + ) + if col.n_dim: + construct_params['dim'] = col.n_dim + return hnswlib.Index(**construct_params) + + def _create_index(self, col: '_Column') -> hnswlib.Index: + '''Create a new HNSW index for a column, and initialize it.''' + index = self._create_index_class(col) + init_params = dict((k, col.config[k]) for k in self._index_init_params) + index.init_index(**init_params) + return index + + def _load_index(self, col_name: str, col: '_Column') -> hnswlib.Index: + '''Load an existing HNSW index from disk.''' + index = self._create_index_class(col) + index.load_index(self._hnsw_locations[col_name]) + return index + def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" for allowed_type in HNSWLIB_PY_VEC_TYPES: @@ -122,6 +144,7 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]]): data_np = [self._to_numpy(arr) for arr in data] data_stacked = np.stack(data_np) index.add_items(data_stacked, ids=hnsw_ids) + index.save_index(self._hnsw_locations[col_name]) _send_docs_to_sqlite(self._sqlite_cursor, docs) self._sqlite_conn.commit() From d59eae8b9e15d6eae6f5cfb81fe331f925b2fbb1 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 14 Feb 2023 15:12:16 +0100 Subject: [PATCH 09/53] reafctor: use universal id for sqlite and hnswlib Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 127 ++++++++++------------ 1 file changed, 58 insertions(+), 69 deletions(-) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 567fe5388f6..6e1b854ea24 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -1,3 +1,4 @@ +import hashlib import os import sqlite3 from dataclasses import dataclass @@ -52,9 +53,6 @@ def __init__(self, config: Optional[IsDataclass] = None): load_existing = os.path.exists(self._work_dir) Path(self._work_dir).mkdir(parents=True, exist_ok=True) - self._doc_id_to_hnsw_id = {} - self._hnsw_id_to_doc_id = {} - # HNSWLib setup self._index_construct_params = ('space', 'dim') self._index_init_params = ('max_elements', 'ef_construction', 'M') @@ -77,9 +75,15 @@ def __init__(self, config: Optional[IsDataclass] = None): self._sqlite_db_path = os.path.join(self._work_dir, 'docs_sqlite.db') self._sqlite_conn = sqlite3.connect(self._sqlite_db_path) self._sqlite_cursor = self._sqlite_conn.cursor() - _create_docs_table(self._sqlite_cursor) + self._create_docs_table() self._sqlite_conn.commit() + @staticmethod + def _to_universal_id(doc_id: str) -> int: + # https://stackoverflow.com/questions/16008670/how-to-hash-a-string-into-8-digits + # hashing to 18 digits avoids overflow of sqlite INTEGER + return int(hashlib.sha256(doc_id.encode('utf-8')).hexdigest(), 16) % 10**18 + def _create_index_class(self, col: '_Column') -> hnswlib.Index: '''Create an instance of hnswlib.Index without initializing it.''' construct_params = dict( @@ -126,16 +130,7 @@ def _to_numpy(self, val: Any) -> Any: def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" data_by_columns = self.get_data_by_columns(docs) - - num_docs_before = _get_num_docs_sqlite(self._sqlite_cursor) - hnsw_ids = range(num_docs_before, num_docs_before + len(docs)) - # the dict updates below could maybe be optimized? - self._doc_id_to_hnsw_id.update( - (doc_id, hnsw_id) for doc_id, hnsw_id in zip(docs.id, hnsw_ids) - ) - self._hnsw_id_to_doc_id.update( - (hnsw_id, doc_id) for doc_id, hnsw_id in zip(docs.id, hnsw_ids) - ) + hnsw_ids = tuple(self._to_universal_id(doc.id) for doc in docs) # indexing into HNSWLib and SQLite sequentially # could be improved by processing in parallel @@ -146,7 +141,7 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]]): index.add_items(data_stacked, ids=hnsw_ids) index.save_index(self._hnsw_locations[col_name]) - _send_docs_to_sqlite(self._sqlite_cursor, docs) + self._send_docs_to_sqlite(docs) self._sqlite_conn.commit() def find_batched( @@ -167,10 +162,8 @@ def find_batched( index = self._hnsw_indices[embedding_field] labels, distances = index.knn_query(query_vec_np, k=limit) result_das = [ - _get_docs_from_sqlite( - self._sqlite_cursor, - [self._hnsw_id_to_doc_id[id_] for id_ in ids_per_query], - self._schema, + self._get_docs_from_sqlite( + ids_per_query, ) for ids_per_query in labels ] @@ -186,64 +179,60 @@ def __delitem__(self, key: Union[str, Sequence[str]]): # delete from the indices if isinstance(key, str): key = [key] - for k in key: - id_ = self._doc_id_to_hnsw_id[k] + for doc_id in key: + id_ = self._to_universal_id(doc_id) for col_name, index in self._hnsw_indices.items(): index.mark_deleted(id_) - del self._doc_id_to_hnsw_id[k] - _delete_docs_from_sqlite(self._sqlite_cursor, key) + self._delete_docs_from_sqlite(key) self._sqlite_conn.commit() + # SQLite helpers + def _create_docs_table(self): + self._sqlite_cursor.execute( + 'CREATE TABLE IF NOT EXISTS docs (doc_id INTEGER PRIMARY KEY, data BLOB)' + ) -# serialization helpers -def _doc_to_bytes(doc: BaseDocument) -> bytes: - return doc.to_protobuf().SerializeToString() - - -TSerialize = TypeVar('TSerialize', bound=BaseDocument) - - -def _doc_from_bytes(data: bytes, doc_class: Type[TSerialize]) -> TSerialize: - return doc_class.from_protobuf(DocumentProto.FromString(data)) - - -# SQLite helpers - - -def _create_docs_table(cursor: sqlite3.Cursor): - cursor.execute( - 'CREATE TABLE IF NOT EXISTS docs (doc_id TEXT PRIMARY KEY, data BLOB)' - ) - - -def _send_docs_to_sqlite(cursor: sqlite3.Cursor, docs: Sequence[BaseDocument]): - cursor.executemany( - 'INSERT INTO docs VALUES (?, ?)', - ((doc.id, _doc_to_bytes(doc)) for doc in docs), - ) - + def _send_docs_to_sqlite(self, docs: Sequence[BaseDocument]): + ids = (self._to_universal_id(doc.id) for doc in docs) + self._sqlite_cursor.executemany( + 'INSERT INTO docs VALUES (?, ?)', + ((id_, self._doc_to_bytes(doc)) for id_, doc in zip(ids, docs)), + ) -def _get_docs_from_sqlite( - cursor: sqlite3.Cursor, doc_ids: Sequence[str], doc_class: Type[TSerialize] -) -> DocumentArray: - cursor.execute( - 'SELECT data FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(doc_ids)), - doc_ids, - ) - rows = cursor.fetchall() - return DocumentArray[doc_class]( - (_doc_from_bytes(row[0], doc_class) for row in rows) - ) + def _get_docs_from_sqlite( + self, doc_ids: Sequence[Union[str, int]] + ) -> DocumentArray: + ids = tuple( + self._to_universal_id(id_) if isinstance(id_, str) else id_ + for id_ in doc_ids + ) + self._sqlite_cursor.execute( + 'SELECT data FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(ids)), + ids, + ) + rows = self._sqlite_cursor.fetchall() + return DocumentArray[self._schema]( + (self._doc_from_bytes(row[0]) for row in rows) + ) + def _delete_docs_from_sqlite(self, doc_ids: Sequence[Union[str, int]]): + ids = tuple( + self._to_universal_id(id_) if isinstance(id_, str) else id_ + for id_ in doc_ids + ) + self._sqlite_cursor.execute( + 'DELETE FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(ids)), + ids, + ) -def _delete_docs_from_sqlite(cursor: sqlite3.Cursor, doc_ids: Sequence[str]): - cursor.execute( - 'DELETE FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(doc_ids)), - doc_ids, - ) + def _get_num_docs_sqlite(self) -> int: + self._sqlite_cursor.execute('SELECT COUNT(*) FROM docs') + return self._sqlite_cursor.fetchone()[0] + # serialization helpers + def _doc_to_bytes(self, doc: BaseDocument) -> bytes: + return doc.to_protobuf().SerializeToString() -def _get_num_docs_sqlite(cursor: sqlite3.Cursor) -> int: - cursor.execute('SELECT COUNT(*) FROM docs') - return cursor.fetchone()[0] + def _doc_from_bytes(self, data: bytes) -> BaseDocument: + return self._schema.from_protobuf(DocumentProto.FromString(data)) From df2e766bf6761095c7d03802a693d395e59da49b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 14 Feb 2023 15:30:17 +0100 Subject: [PATCH 10/53] feat: implement num docs method Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 9 +++++++++ docarray/storage/backends/HnswDocStore.py | 9 ++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 88dd343dab2..f5bc8242ff3 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -64,6 +64,8 @@ def __init__(self, config: Optional[IsDataclass] = None): self._columns: Dict[str, _Column] = self._create_columns(self._schema) + # Abstract methods + # Subclasses must implement these @abstractmethod def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" @@ -74,6 +76,11 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" ... + @abstractmethod + def num_docs(self) -> int: + """Return the number of indexed documents""" + ... + @abstractmethod def __delitem__(self, key: Union[str, Sequence[str]]): """Delete a document from the store, by `id`. @@ -108,6 +115,8 @@ def find_batched( # TODO(johannes) refine method signature ... + # Behind-the-scenes magic + # Subclasses should not need to implement these def __class_getitem__(cls, item: Type[TSchema]): if not issubclass(item, BaseDocument): raise ValueError( diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 6e1b854ea24..334ef3d464d 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -85,7 +85,7 @@ def _to_universal_id(doc_id: str) -> int: return int(hashlib.sha256(doc_id.encode('utf-8')).hexdigest(), 16) % 10**18 def _create_index_class(self, col: '_Column') -> hnswlib.Index: - '''Create an instance of hnswlib.Index without initializing it.''' + """Create an instance of hnswlib.Index without initializing it.""" construct_params = dict( (k, col.config[k]) for k in self._index_construct_params ) @@ -94,14 +94,14 @@ def _create_index_class(self, col: '_Column') -> hnswlib.Index: return hnswlib.Index(**construct_params) def _create_index(self, col: '_Column') -> hnswlib.Index: - '''Create a new HNSW index for a column, and initialize it.''' + """Create a new HNSW index for a column, and initialize it.""" index = self._create_index_class(col) init_params = dict((k, col.config[k]) for k in self._index_init_params) index.init_index(**init_params) return index def _load_index(self, col_name: str, col: '_Column') -> hnswlib.Index: - '''Load an existing HNSW index from disk.''' + """Load an existing HNSW index from disk.""" index = self._create_index_class(col) index.load_index(self._hnsw_locations[col_name]) return index @@ -187,6 +187,9 @@ def __delitem__(self, key: Union[str, Sequence[str]]): self._delete_docs_from_sqlite(key) self._sqlite_conn.commit() + def num_docs(self) -> int: + return self._get_num_docs_sqlite() + # SQLite helpers def _create_docs_table(self): self._sqlite_cursor.execute( From fd051cde7dc156032f5782eeb06cab870d7ce11b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Thu, 16 Feb 2023 10:55:31 +0100 Subject: [PATCH 11/53] feat: query builder Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 142 +++++++++++++++++++++- docarray/storage/backends/HnswDocStore.py | 118 ++++++++++++++++-- 2 files changed, 247 insertions(+), 13 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index f5bc8242ff3..58f0c03f272 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -9,6 +9,7 @@ NamedTuple, Optional, Sequence, + Tuple, Type, TypeVar, Union, @@ -39,6 +40,40 @@ class _Column: config: Dict[str, Any] +def _delegate_to_query(method_name: str): + def inner(self, *args, **kwargs): + if args: + raise ValueError( + f'Positional arguments are not supported for ' + f'{type(self)}.`{method_name}`.' + f' Use keyword arguments instead.' + ) + self._queries.append((method_name, kwargs)) + + return inner + + +class BaseQueryBuilder(ABC): + def __init__(self): + # list of tuples (method name, kwargs) + self._queries: List[Tuple[str, Dict]] = [] + + find = _delegate_to_query('find') + find_batched = _delegate_to_query('find_batched') + filter = _delegate_to_query('filter') + filter_batched = _delegate_to_query('filter_batched') + text_search = _delegate_to_query('text_search') + text_search_batched = _delegate_to_query('text_search_batched') + + @abstractmethod + def build(self, *args, **kwargs) -> Any: + """Build the DB specific query object. + The DB specific implementation can leverage self._queries to do so. + The output of this should be able to be passed to execute_query(). + """ + ... + + class BaseDocumentStore(ABC, Generic[TSchema]): """Abstract class for all Document Stores""" @@ -54,6 +89,9 @@ class BaseDocumentStore(ABC, Generic[TSchema]): # Example: `_default_column_config['VARCHAR'] = {'length': 255}` _default_column_config: Dict[Any, Dict[str, Any]] = {} + # register your query builder here + _query_builder_cls: Type[BaseQueryBuilder] = BaseQueryBuilder + def __init__(self, config: Optional[IsDataclass] = None): if self._schema is None: raise ValueError( @@ -64,8 +102,10 @@ def __init__(self, config: Optional[IsDataclass] = None): self._columns: Dict[str, _Column] = self._create_columns(self._schema) - # Abstract methods - # Subclasses must implement these + ##################################### + # Abstract methods # + # Subclasses must implement these # + ##################################### @abstractmethod def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" @@ -83,12 +123,36 @@ def num_docs(self) -> int: @abstractmethod def __delitem__(self, key: Union[str, Sequence[str]]): - """Delete a document from the store, by `id`. + """Delete one or multiple Documents from the store, by `id`. :param key: id or ids to delete from the Document Store """ ... + @abstractmethod + def __getitem__(self, key: Union[str, Sequence[str]]): + """Get one or multiple Documents into the store, by `id`. + + :param key: id or ids to get from the Document Store + """ + ... + + @abstractmethod + def execute_query(self, query: Any, *args, **kwargs) -> Any: + """ + Execute a query on the database. + This is intended as a pass-through to the underlying database, so that users + can enjoy anything that is not available through our API. + + Also, this is the method that the output of the query builder is passed to. + + :param query: the query to execute + :param args: positional arguments to pass to the query + :param kwargs: keyword arguments to pass to the query + :return: the result of the query + """ + ... + @abstractmethod def find( self, @@ -115,8 +179,68 @@ def find_batched( # TODO(johannes) refine method signature ... - # Behind-the-scenes magic - # Subclasses should not need to implement these + @abstractmethod + def filter( + self, + filter_query: Any, + limit: int = 10, + **kwargs, + ) -> DocumentArray: + """Find documents in the store based on a filter query + + :param filter_query: the DB specific filter query to execute + """ + # TODO(johannes) refine method signature + ... + + @abstractmethod + def filter_batched( + self, + filter_queries: Any, + limit: int = 10, + **kwargs, + ) -> List[DocumentArray]: + """Find documents in the store based on multiple filter queries + + :param filter_queries: the DB specific filter queries to execute + """ + # TODO(johannes) refine method signature + ... + + @abstractmethod + def text_search( + self, + query: str, + embedding_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResult: + """Find documents in the store based on a text search query + + :param query: The text to search for + """ + # TODO(johannes) refine method signature + ... + + @abstractmethod + def text_search_batched( + self, + queries: List[str], + embedding_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResultBatched: + """Find documents in the store based on a text search query + + :param query: The text to search for + """ + # TODO(johannes) refine method signature + ... + + ################################################## + # Behind-the-scenes magic # + # Subclasses should not need to implement these # + ################################################## def __class_getitem__(cls, item: Type[TSchema]): if not issubclass(item, BaseDocument): raise ValueError( @@ -131,6 +255,14 @@ class _DocumentStoreTyped(cls): # type: ignore return _DocumentStoreTyped + def build_query(self) -> BaseQueryBuilder: + """ + Build a query for this DocumentStore. + + :return: a new `QueryBuilder` object for this DocumentStore + """ + return self._query_builder_cls() + def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: columns: Dict[str, _Column] = dict() for field_name, field in schema.__fields__.items(): diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 334ef3d464d..fb3a3ad795f 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -3,7 +3,18 @@ import sqlite3 from dataclasses import dataclass from pathlib import Path -from typing import Any, Generic, Optional, Sequence, Type, TypeVar, Union +from typing import ( + Any, + Dict, + Generic, + List, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, +) import hnswlib import numpy as np @@ -13,10 +24,12 @@ from docarray.proto import DocumentProto from docarray.storage.abstract_doc_store import ( BaseDocumentStore, + BaseQueryBuilder, FindResultBatched, _Column, ) from docarray.typing import AnyTensor +from docarray.utils.filter import filter as da_filter from docarray.utils.find import FindResult from docarray.utils.misc import torch_imported from docarray.utils.protocols import IsDataclass @@ -35,6 +48,15 @@ class HNSWConfig: work_dir: str = '.' +class HnswQueryBuilder(BaseQueryBuilder): + def build(self, *args, **kwargs) -> Any: + supported_ops = ('find', 'filter') + for op, _ in self._queries: + if op not in supported_ops: + raise ValueError(f'Unsupported query operation: {op}') + return self._queries + + class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): _default_column_config = { np.ndarray: { @@ -47,6 +69,8 @@ class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): None: {}, } + _query_builder_cls = HnswQueryBuilder + def __init__(self, config: Optional[IsDataclass] = None): super().__init__(config) self._work_dir = config.work_dir @@ -144,6 +168,34 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]]): self._send_docs_to_sqlite(docs) self._sqlite_conn.commit() + def execute_query(self, query: List[Tuple[str, Dict]], *args, **kwargs) -> Any: + if args or kwargs: + raise ValueError( + f'args and kwargs not supported for `execute_query` on {type(self)}' + ) + + ann_docs = DocumentArray[self._schema]([]) + filter_conditions = [] + doc_to_score = {} + for op, op_kwargs in query: + if op == 'find': + docs, scores = self.find(**op_kwargs) + ann_docs.extend(docs) + doc_to_score.update(zip(docs.id, scores)) + elif op == 'filter': + filter_conditions.append(op_kwargs['filter_query']) + + docs_filtered = ann_docs + for cond in filter_conditions: + docs_filtered = da_filter(docs_filtered, cond) + + docs_and_scores = zip( + docs_filtered, (doc_to_score[doc.id] for doc in docs_filtered) + ) + docs_sorted = sorted(docs_and_scores, key=lambda x: x[1]) + out_docs, out_scores = zip(*docs_sorted) + return FindResult(documents=out_docs, scores=out_scores) + def find_batched( self, query: Union[AnyTensor, DocumentArray], @@ -169,12 +221,52 @@ def find_batched( ] return FindResultBatched(documents=result_das, scores=distances) - def find(self, *args, **kwargs): - batched_result = self.find_batched(*args, **kwargs) - return FindResult( - documents=batched_result.documents[0], scores=batched_result.scores[0] + def find(self, *args, **kwargs) -> FindResult: + docs, scores = self.find_batched(*args, **kwargs) + return FindResult(documents=docs[0], scores=scores[0]) + + def filter( + self, + *args, + **kwargs, + ) -> DocumentArray: + + raise NotImplementedError( + f'{type(self)} does not support filter-only queries.' + f' To perform post-filtering on a query, use' + f' `build_query()` and `execute_query()`.' + ) + + def filter_batched( + self, + filter_queries: Any, + limit: int = 10, + **kwargs, + ) -> List[DocumentArray]: + raise NotImplementedError( + f'{type(self)} does not support filter-only queries.' + f' To perform post-filtering on a query, use' + f' `build_query()` and `execute_query()`.' ) + def text_search( + self, + query: str, + embedding_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResult: + raise NotImplementedError(f'{type(self)} does not support text search.') + + def text_search_batched( + self, + queries: List[str], + embedding_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResultBatched: + raise NotImplementedError(f'{type(self)} does not support text search.') + def __delitem__(self, key: Union[str, Sequence[str]]): # delete from the indices if isinstance(key, str): @@ -187,6 +279,12 @@ def __delitem__(self, key: Union[str, Sequence[str]]): self._delete_docs_from_sqlite(key) self._sqlite_conn.commit() + def __getitem__(self, key: Union[str, Sequence[str]]): + # delete from the indices + if isinstance(key, str): + key = [key] + return self._get_docs_from_sqlite(key) + def num_docs(self) -> int: return self._get_num_docs_sqlite() @@ -207,12 +305,16 @@ def _get_docs_from_sqlite( self, doc_ids: Sequence[Union[str, int]] ) -> DocumentArray: ids = tuple( - self._to_universal_id(id_) if isinstance(id_, str) else id_ + self._to_universal_id(id_) if isinstance(id_, str) else int(id_) for id_ in doc_ids ) + for id_ in ids: + # I hope this protects from injection attacks + # properly binding with '?' doesn't work for some reason + assert isinstance(id_, int) + sql_id_list = '(' + ', '.join(str(id_) for id_ in ids) + ')' self._sqlite_cursor.execute( - 'SELECT data FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(ids)), - ids, + 'SELECT data FROM docs WHERE doc_id IN %s' % sql_id_list, ) rows = self._sqlite_cursor.fetchall() return DocumentArray[self._schema]( From 7a9a7aa08edc9a773aa79b2ec7a3ac3f9eff6dbd Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Thu, 16 Feb 2023 11:07:00 +0100 Subject: [PATCH 12/53] chore: add lockfile Signed-off-by: Johannes Messner --- poetry.lock | 2346 ++++++++++++++++++++++++------------------------ pyproject.toml | 1 + 2 files changed, 1181 insertions(+), 1166 deletions(-) diff --git a/poetry.lock b/poetry.lock index 719cf8dd921..06e46065642 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,3 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. - [[package]] name = "anyio" version = "3.6.2" @@ -7,10 +5,6 @@ description = "High level compatibility layer for multiple asynchronous event lo category = "main" optional = false python-versions = ">=3.6.2" -files = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, -] [package.dependencies] idna = ">=2.8" @@ -29,10 +23,6 @@ description = "Disable App Nap on macOS >= 10.9" category = "dev" optional = false python-versions = "*" -files = [ - {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, - {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, -] [[package]] name = "argon2-cffi" @@ -41,10 +31,6 @@ description = "The secure Argon2 password hashing algorithm." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, - {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, -] [package.dependencies] argon2-cffi-bindings = "*" @@ -62,29 +48,6 @@ description = "Low-level CFFI bindings for Argon2" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, - {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, -] [package.dependencies] cffi = ">=1.0.1" @@ -100,9 +63,6 @@ description = "Atomic file writes." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, -] [[package]] name = "attrs" @@ -111,10 +71,6 @@ description = "Classes Without Boilerplate" category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] [package.extras] dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] @@ -129,52 +85,6 @@ description = "Pythonic bindings for FFmpeg's libraries." category = "main" optional = true python-versions = "*" -files = [ - {file = "av-10.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d19bb54197155d045a2b683d993026d4bcb06e31c2acad0327e3e8711571899c"}, - {file = "av-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dba96a85cd37315529998e6dbbe3fa05c2344eb19a431dc24996be030a904ee"}, - {file = "av-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27d6d38c7c8d46d578c008ffcb8aad1eae14d0621fff41f4ad62395589045fe4"}, - {file = "av-10.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51037f4bde03daf924236af4f444e17345792ad7f6f70760a5e5863407e14f2b"}, - {file = "av-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0577a38664e453b4ffb63d616a0d23c295827b16ae96a090e89527a753de8718"}, - {file = "av-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:07c971573035d22ce50069d3f2bbdb4d6d02d626ab13db12fda3ce519cda3f22"}, - {file = "av-10.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5085d11345484c0097898994bb3f515002e7e1deeb43dd11d30dd6f45402c49"}, - {file = "av-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:157bde3ffd1615a9006b56e4daf3b46848d3ee2bd46b0394f7568e43ed7ab5a9"}, - {file = "av-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115e144d5a1f205378a4b3a3657b7ed3e45918ebe5d2003a891e45984e8f443a"}, - {file = "av-10.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7d6e2b3fbda6464f74fe010dbcff361394bb014b0cb4aa4dc9f2bb713ce882"}, - {file = "av-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fd5a38395191a0f4b71adf31057ff177c9f0762914d73d8797742339ad67d0"}, - {file = "av-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:836d69a9543d284976b229cc8d4343ffcfc0bbaf05239e13fb7e613b13d5291d"}, - {file = "av-10.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eba192274538617bbe60097a013d83637f1a5ba9844bbbcf3ca7e43c6499b9d5"}, - {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1301e4cf1a2c899851073720cd541066c8539b64f9eb0d52216f8d0a59f20429"}, - {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eebd5aa9d8b1e33e715c5409544a712f13ec805bb0110d75f394ff28d2fb64ad"}, - {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04cd0ce13a87870fb0a0ea4673f04934af2b9ac7ae844eafe92e2c19c092ab11"}, - {file = "av-10.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10facb5b933551dd6a30d8015bc91eef5d1c864ee86aa3463ffbaff1a99f6c6a"}, - {file = "av-10.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:088636ded03724a2ab51136f6f4be0bc457bdb3c0d2ac7158792fe81150d4c1a"}, - {file = "av-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff0f7d3b1003a9ed0d06038f3f521a5ff0d3e056ec5111e2a78e303f98b815a7"}, - {file = "av-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccaf786e747b126a5b3b9a8f5ffbb6a20c5f528775cc7084c95732ca72606fba"}, - {file = "av-10.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c579d718b52beb812ea2a7bd68f812d0920b00937804d52d31d41bb71aa5557"}, - {file = "av-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cfd39baa5d82768d2a8898de7bfd450a083ef22b837d57e5dc1b6de3244218"}, - {file = "av-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:81b5264d9752f49286bc1dc4d2cc66187418c4948a326dbed837c766c9892139"}, - {file = "av-10.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:16bd82b63d0b4c1b855b3c36b13337f7cdc5925bd8284fab893bdf6c290fc3a9"}, - {file = "av-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a6c8f3f8c26d35eefe45b849c81fd0816ba4b6f589baec7357c25b4c5537d3c4"}, - {file = "av-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91ea46fea7259abdfabe00b0ed3a9ca18e7fff7ce80d2a2c66a28f797cce838a"}, - {file = "av-10.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a62edd533d330aa61902ae8cd82966affa487fa337a0c4f58ae8866ccb5d31c0"}, - {file = "av-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b67b7d028c9cf68215376662fd2e0be6ca0cc02d32d3ed8514fec67b12db9cbd"}, - {file = "av-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:0f9c88062ebfd2ce547c522b64f79e487ed2b0a6a9d6693c801b28df0d944607"}, - {file = "av-10.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:63dbafcd02415127d97509523bc285f1ab260988f87b744d7fb1baee6ffbdf96"}, - {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ea4424d0be62fe18c843420284a0907bcb38d577062d62c4b75a8e940e6057"}, - {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b6326fd0755761e3ee999e4bf90339e869fe71d548b679fee89157858b8d04a"}, - {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3fae238751ec0db6377b2106e13762ca84dbe104bd44c1ce9b424163aef4ab5"}, - {file = "av-10.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:86bb3f6e8cce62ad18cd34eb2eadd091d99f51b40be81c929b53fbd8fecf6d90"}, - {file = "av-10.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f7b508813abbc100162d305a1ac9b2dd16e5128d56f2ac69639fc6a4b5aca69e"}, - {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cc376199c0aa6e9365d03e0f4e67cfb209e40fe9c0cf566372f9daf2a0c779"}, - {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b459ca0ef25c1a0e370112556bdc5b7752f76dc9bd497acaf3e653171e4b946"}, - {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab930735112c1f788cc4d47c42c59ba0dd214d815aa906e1addf39af91d15194"}, - {file = "av-10.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13fe0b48b9211539323ecebbf84154c86c72d16723c6d0af76e29ae5c3a614b2"}, - {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2eeec7beaebfe9e2213b3c94b482381187d0afdcb632f93239b44dc668b97df"}, - {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dac2a8b0791c3373270e32f6cd27e6b60628565a188e40a5d9660d3aab05e33"}, - {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdede2325cb750b5bf79238bbf06f9c2a70b757b12726003769a43493b7233a"}, - {file = "av-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9788e6e15db0910fb8e1548ba7540799d07066177710590a5794a524c4910e05"}, - {file = "av-10.0.0.tar.gz", hash = "sha256:8afd3d5610e1086f3b2d8389d66672ea78624516912c93612de64dcaa4c67e05"}, -] [[package]] name = "babel" @@ -183,10 +93,6 @@ description = "Internationalization utilities" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, - {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, -] [package.dependencies] pytz = ">=2015.7" @@ -198,10 +104,6 @@ description = "Specifications for callback functions passed in to an API" category = "dev" optional = false python-versions = "*" -files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, -] [[package]] name = "beautifulsoup4" @@ -210,10 +112,6 @@ description = "Screen-scraping library" category = "dev" optional = false python-versions = ">=3.6.0" -files = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, -] [package.dependencies] soupsieve = ">1.2" @@ -229,29 +127,6 @@ description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] [package.dependencies] click = ">=8.0.0" @@ -275,10 +150,6 @@ description = "An easy safelist-based HTML-sanitizing tool." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, - {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, -] [package.dependencies] six = ">=1.9.0" @@ -295,10 +166,6 @@ description = "Python package for providing Mozilla's CA Bundle." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, -] [[package]] name = "cffi" @@ -307,72 +174,6 @@ description = "Foreign Function Interface for Python calling C code." category = "dev" optional = false python-versions = "*" -files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, -] [package.dependencies] pycparser = "*" @@ -384,22 +185,14 @@ description = "Validate configuration and produce human readable error messages. category = "dev" optional = false python-versions = ">=3.6.1" -files = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] [[package]] name = "charset-normalizer" -version = "2.1.1" +version = "2.0.12" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false -python-versions = ">=3.6.0" -files = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] +python-versions = ">=3.5.0" [package.extras] unicode-backport = ["unicodedata2"] @@ -411,10 +204,6 @@ description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} @@ -427,10 +216,6 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] [[package]] name = "commonmark" @@ -439,10 +224,6 @@ description = "Python parser for the CommonMark Markdown spec" category = "main" optional = false python-versions = "*" -files = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] [package.extras] test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] @@ -454,26 +235,6 @@ description = "An implementation of the Debug Adapter Protocol for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "debugpy-1.6.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c4b2bd5c245eeb49824bf7e539f95fb17f9a756186e51c3e513e32999d8846f3"}, - {file = "debugpy-1.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8deaeb779699350deeed835322730a3efec170b88927debc9ba07a1a38e2585"}, - {file = "debugpy-1.6.3-cp310-cp310-win32.whl", hash = "sha256:fc233a0160f3b117b20216f1169e7211b83235e3cd6749bcdd8dbb72177030c7"}, - {file = "debugpy-1.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:dda8652520eae3945833e061cbe2993ad94a0b545aebd62e4e6b80ee616c76b2"}, - {file = "debugpy-1.6.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5c814596a170a0a58fa6fad74947e30bfd7e192a5d2d7bd6a12156c2899e13a"}, - {file = "debugpy-1.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c4cd6f37e3c168080d61d698390dfe2cd9e74ebf80b448069822a15dadcda57d"}, - {file = "debugpy-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:3c9f985944a30cfc9ae4306ac6a27b9c31dba72ca943214dad4a0ab3840f6161"}, - {file = "debugpy-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:5ad571a36cec137ae6ed951d0ff75b5e092e9af6683da084753231150cbc5b25"}, - {file = "debugpy-1.6.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:adcfea5ea06d55d505375995e150c06445e2b20cd12885bcae566148c076636b"}, - {file = "debugpy-1.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:daadab4403427abd090eccb38d8901afd8b393e01fd243048fab3f1d7132abb4"}, - {file = "debugpy-1.6.3-cp38-cp38-win32.whl", hash = "sha256:6efc30325b68e451118b795eff6fe8488253ca3958251d5158106d9c87581bc6"}, - {file = "debugpy-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:86d784b72c5411c833af1cd45b83d80c252b77c3bfdb43db17c441d772f4c734"}, - {file = "debugpy-1.6.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4e255982552b0edfe3a6264438dbd62d404baa6556a81a88f9420d3ed79b06ae"}, - {file = "debugpy-1.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cca23cb6161ac89698d629d892520327dd1be9321c0960e610bbcb807232b45d"}, - {file = "debugpy-1.6.3-cp39-cp39-win32.whl", hash = "sha256:7c302095a81be0d5c19f6529b600bac971440db3e226dce85347cc27e6a61908"}, - {file = "debugpy-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:34d2cdd3a7c87302ba5322b86e79c32c2115be396f3f09ca13306d8a04fe0f16"}, - {file = "debugpy-1.6.3-py2.py3-none-any.whl", hash = "sha256:84c39940a0cac410bf6aa4db00ba174f973eef521fbe9dd058e26bcabad89c4f"}, - {file = "debugpy-1.6.3.zip", hash = "sha256:e8922090514a890eec99cfb991bab872dd2e353ebb793164d5f01c362b9a40bf"}, -] [[package]] name = "decorator" @@ -482,10 +243,6 @@ description = "Decorators for Humans" category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] [[package]] name = "defusedxml" @@ -494,10 +251,6 @@ description = "XML bomb protection for Python stdlib modules" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, - {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, -] [[package]] name = "distlib" @@ -506,10 +259,6 @@ description = "Distribution utilities" category = "dev" optional = false python-versions = "*" -files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] [[package]] name = "entrypoints" @@ -518,10 +267,6 @@ description = "Discover and load entry points from installed packages." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, - {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, -] [[package]] name = "fastapi" @@ -530,10 +275,6 @@ description = "FastAPI framework, high performance, easy to learn, fast to code, category = "main" optional = true python-versions = ">=3.7" -files = [ - {file = "fastapi-0.87.0-py3-none-any.whl", hash = "sha256:254453a2e22f64e2a1b4e1d8baf67d239e55b6c8165c079d25746a5220c81bb4"}, - {file = "fastapi-0.87.0.tar.gz", hash = "sha256:07032e53df9a57165047b4f38731c38bdcc3be5493220471015e2b4b51b486a4"}, -] [package.dependencies] pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" @@ -552,10 +293,6 @@ description = "Fastest Python implementation of JSON schema" category = "dev" optional = false python-versions = "*" -files = [ - {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"}, - {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"}, -] [package.extras] devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] @@ -567,10 +304,6 @@ description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, -] [package.extras] docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] @@ -583,14 +316,21 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} +[[package]] +name = "hnswlib" +version = "0.7.0" +description = "hnswlib" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = "*" + [[package]] name = "httpcore" version = "0.16.1" @@ -598,10 +338,6 @@ description = "A minimal low-level HTTP client." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "httpcore-0.16.1-py3-none-any.whl", hash = "sha256:8d393db683cc8e35cc6ecb02577c5e1abfedde52b38316d038932a84b4875ecb"}, - {file = "httpcore-0.16.1.tar.gz", hash = "sha256:3d3143ff5e1656a5740ea2f0c167e8e9d48c5a9bbd7f00ad1f8cff5711b08543"}, -] [package.dependencies] anyio = ">=3.0,<5.0" @@ -620,10 +356,6 @@ description = "The next generation HTTP client." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "httpx-0.23.1-py3-none-any.whl", hash = "sha256:0b9b1f0ee18b9978d637b0776bfd7f54e2ca278e063e3586d8f01cda89e042a8"}, - {file = "httpx-0.23.1.tar.gz", hash = "sha256:202ae15319be24efe9a8bd4ed4360e68fde7b38bcc2ce87088d416f026667d19"}, -] [package.dependencies] certifi = "*" @@ -644,10 +376,6 @@ description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"}, - {file = "identify-2.5.8.tar.gz", hash = "sha256:7a214a10313b9489a0d61467db2856ae8d0b8306fc923e03a9effa53d8aedc58"}, -] [package.extras] license = ["ukkonen"] @@ -659,10 +387,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" -files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] [[package]] name = "importlib-metadata" @@ -671,10 +395,6 @@ description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, - {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, -] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -692,10 +412,6 @@ description = "Read resources from Python packages" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"}, - {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"}, -] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} @@ -711,10 +427,6 @@ description = "iniconfig: brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = "*" -files = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] [[package]] name = "ipykernel" @@ -723,10 +435,6 @@ description = "IPython Kernel for Jupyter" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "ipykernel-6.16.2-py3-none-any.whl", hash = "sha256:67daf93e5b52456cd8eea87a8b59405d2bb80ae411864a1ea206c3631d8179af"}, - {file = "ipykernel-6.16.2.tar.gz", hash = "sha256:463f3d87a92e99969b1605cb7a5b4d7b36b7145a0e72d06e65918a6ddefbe630"}, -] [package.dependencies] appnope = {version = "*", markers = "platform_system == \"Darwin\""} @@ -752,10 +460,6 @@ description = "IPython: Productive Interactive Computing" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, - {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, -] [package.dependencies] appnope = {version = "*", markers = "sys_platform == \"darwin\""} @@ -789,26 +493,18 @@ description = "Vestigial utilities from IPython" category = "dev" optional = false python-versions = "*" -files = [ - {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, - {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, -] [[package]] name = "isort" -version = "5.10.1" +version = "5.11.5" description = "A Python utility / library to sort Python imports." category = "dev" optional = false -python-versions = ">=3.6.1,<4.0" -files = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] +python-versions = ">=3.7.0" [package.extras] colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] @@ -819,10 +515,6 @@ description = "An autocompletion tool for Python that can be used for text edito category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, - {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, -] [package.dependencies] parso = ">=0.8.0,<0.9.0" @@ -838,10 +530,6 @@ description = "A very fast and expressive template engine." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] [package.dependencies] MarkupSafe = ">=2.0" @@ -856,10 +544,6 @@ description = "A Python implementation of the JSON5 data format." category = "dev" optional = false python-versions = "*" -files = [ - {file = "json5-0.9.10-py2.py3-none-any.whl", hash = "sha256:993189671e7412e9cdd8be8dc61cf402e8e579b35f1d1bb20ae6b09baa78bbce"}, - {file = "json5-0.9.10.tar.gz", hash = "sha256:ad9f048c5b5a4c3802524474ce40a622fae789860a86f10cc4f7e5f9cf9b46ab"}, -] [package.extras] dev = ["hypothesis"] @@ -871,10 +555,6 @@ description = "An implementation of JSON Schema validation for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jsonschema-4.17.0-py3-none-any.whl", hash = "sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248"}, - {file = "jsonschema-4.17.0.tar.gz", hash = "sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d"}, -] [package.dependencies] attrs = ">=17.4.0" @@ -895,10 +575,6 @@ description = "Jupyter protocol implementation and client libraries" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jupyter_client-7.4.6-py3-none-any.whl", hash = "sha256:540b6a5c9c2dc481c5dd54fd5acb260f03dfaaa7c5325b2ffb1f676710f8c7c4"}, - {file = "jupyter_client-7.4.6.tar.gz", hash = "sha256:f7f9a9dc3a0ecd223ed6a5a00cf4140a5c252ec72e52d6de370748ed0aa083dd"}, -] [package.dependencies] entrypoints = "*" @@ -920,10 +596,6 @@ description = "Jupyter core package. A base package on which Jupyter projects re category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jupyter_core-4.12.0-py3-none-any.whl", hash = "sha256:a54672c539333258495579f6964144924e0aa7b07f7069947bef76d7ea5cb4c1"}, - {file = "jupyter_core-4.12.0.tar.gz", hash = "sha256:87f39d7642412ae8a52291cc68e71ac01dfa2c735df2701f8108251d51b4f460"}, -] [package.dependencies] pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} @@ -939,10 +611,6 @@ description = "The backend—i.e. core services, APIs, and REST endpoints—to J category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jupyter_server-1.23.2-py3-none-any.whl", hash = "sha256:c01d0e84c22a14dd6b0e7d8ce4105b08a3426b46582668e28046a64c07311a4f"}, - {file = "jupyter_server-1.23.2.tar.gz", hash = "sha256:69cb954ef02c0ba1837787e34e4a1240c93c8eb590662fae1840778861957660"}, -] [package.dependencies] anyio = ">=3.1.0,<4" @@ -972,10 +640,6 @@ description = "JupyterLab computational environment" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jupyterlab-3.5.0-py3-none-any.whl", hash = "sha256:f433059fe0e12d75ea90a81a0b6721113bb132857e3ec2197780b6fe84cbcbde"}, - {file = "jupyterlab-3.5.0.tar.gz", hash = "sha256:e02556c8ea1b386963c4b464e4618aee153c5416b07ab481425c817a033323a2"}, -] [package.dependencies] ipython = "*" @@ -1000,10 +664,6 @@ description = "Pygments theme using JupyterLab CSS variables" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, - {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, -] [[package]] name = "jupyterlab-server" @@ -1012,10 +672,6 @@ description = "A set of server components for JupyterLab and JupyterLab like app category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "jupyterlab_server-2.16.3-py3-none-any.whl", hash = "sha256:d18eb623428b4ee732c2258afaa365eedd70f38b609981ea040027914df32bc6"}, - {file = "jupyterlab_server-2.16.3.tar.gz", hash = "sha256:635a0b176a901f19351c02221a124e59317c476f511200409b7d867e8b2905c3"}, -] [package.dependencies] babel = "*" @@ -1039,48 +695,6 @@ description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] [[package]] name = "matplotlib-inline" @@ -1089,10 +703,6 @@ description = "Inline Matplotlib backend for Jupyter" category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, - {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, -] [package.dependencies] traitlets = "*" @@ -1104,10 +714,6 @@ description = "A sane Markdown parser with useful plugins and renderers" category = "dev" optional = false python-versions = "*" -files = [ - {file = "mistune-2.0.4-py2.py3-none-any.whl", hash = "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d"}, - {file = "mistune-2.0.4.tar.gz", hash = "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"}, -] [[package]] name = "mypy" @@ -1116,38 +722,6 @@ description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, - {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, - {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, - {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, - {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, - {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, - {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, - {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, - {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, - {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, - {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, - {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, - {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, - {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, - {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, - {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, - {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, - {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, - {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, - {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, - {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, - {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, - {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, - {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, - {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, - {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, - {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, -] [package.dependencies] mypy-extensions = ">=0.4.3" @@ -1168,10 +742,6 @@ description = "Experimental type system extensions for programs checked with the category = "main" optional = false python-versions = "*" -files = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] [[package]] name = "nbclassic" @@ -1180,10 +750,6 @@ description = "A web-based notebook environment for interactive computing" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "nbclassic-0.4.8-py3-none-any.whl", hash = "sha256:cbf05df5842b420d5cece0143462380ea9d308ff57c2dc0eb4d6e035b18fbfb3"}, - {file = "nbclassic-0.4.8.tar.gz", hash = "sha256:c74d8a500f8e058d46b576a41e5bc640711e1032cf7541dde5f73ea49497e283"}, -] [package.dependencies] argon2-cffi = "*" @@ -1216,10 +782,6 @@ description = "A client library for executing notebooks. Formerly nbconvert's Ex category = "dev" optional = false python-versions = ">=3.7.0" -files = [ - {file = "nbclient-0.7.0-py3-none-any.whl", hash = "sha256:434c91385cf3e53084185334d675a0d33c615108b391e260915d1aa8e86661b8"}, - {file = "nbclient-0.7.0.tar.gz", hash = "sha256:a1d844efd6da9bc39d2209bf996dbd8e07bf0f36b796edfabaa8f8a9ab77c3aa"}, -] [package.dependencies] jupyter-client = ">=6.1.5" @@ -1238,10 +800,6 @@ description = "Converting Jupyter Notebooks" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "nbconvert-7.2.5-py3-none-any.whl", hash = "sha256:3e90e108bb5637b5b8a1422af1156af1368b39dd25369ff7faa7dfdcdef18f81"}, - {file = "nbconvert-7.2.5.tar.gz", hash = "sha256:8fdc44fd7d9424db7fdc6e1e834a02f6b8620ffb653767388be2f9eb16f84184"}, -] [package.dependencies] beautifulsoup4 = "*" @@ -1277,10 +835,6 @@ description = "The Jupyter Notebook format" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "nbformat-5.7.0-py3-none-any.whl", hash = "sha256:1b05ec2c552c2f1adc745f4eddce1eac8ca9ffd59bb9fd859e827eaa031319f9"}, - {file = "nbformat-5.7.0.tar.gz", hash = "sha256:1d4760c15c1a04269ef5caf375be8b98dd2f696e5eb9e603ec2bf091f9b0d3f3"}, -] [package.dependencies] fastjsonschema = "*" @@ -1299,10 +853,6 @@ description = "Patch asyncio to allow nested event loops" category = "dev" optional = false python-versions = ">=3.5" -files = [ - {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, - {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, -] [[package]] name = "nodeenv" @@ -1311,10 +861,6 @@ description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, -] [package.dependencies] setuptools = "*" @@ -1326,10 +872,6 @@ description = "A web-based notebook environment for interactive computing" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "notebook-6.5.2-py3-none-any.whl", hash = "sha256:e04f9018ceb86e4fa841e92ea8fb214f8d23c1cedfde530cc96f92446924f0e4"}, - {file = "notebook-6.5.2.tar.gz", hash = "sha256:c1897e5317e225fc78b45549a6ab4b668e4c996fd03a04e938fe5e7af2bfffd0"}, -] [package.dependencies] argon2-cffi = "*" @@ -1361,10 +903,6 @@ description = "A shim layer for notebook traits and config" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "notebook_shim-0.2.2-py3-none-any.whl", hash = "sha256:9c6c30f74c4fbea6fce55c1be58e7fd0409b1c681b075dcedceb005db5026949"}, - {file = "notebook_shim-0.2.2.tar.gz", hash = "sha256:090e0baf9a5582ff59b607af523ca2db68ff216da0c69956b62cab2ef4fc9c3f"}, -] [package.dependencies] jupyter-server = ">=1.8,<3" @@ -1379,36 +917,6 @@ description = "NumPy is the fundamental package for array computing with Python. category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, - {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, - {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, - {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, - {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, - {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, - {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, - {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, - {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, - {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, - {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, - {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, - {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, - {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, - {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, - {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, - {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, - {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, - {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, - {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, -] [[package]] name = "nvidia-cublas-cu11" @@ -1417,10 +925,6 @@ description = "CUBLAS native runtime libraries" category = "main" optional = true python-versions = ">=3" -files = [ - {file = "nvidia_cublas_cu11-11.10.3.66-py3-none-manylinux1_x86_64.whl", hash = "sha256:d32e4d75f94ddfb93ea0a5dda08389bcc65d8916a25cb9f37ac89edaeed3bded"}, - {file = "nvidia_cublas_cu11-11.10.3.66-py3-none-win_amd64.whl", hash = "sha256:8ac17ba6ade3ed56ab898a036f9ae0756f1e81052a317bf98f8c6d18dc3ae49e"}, -] [package.dependencies] setuptools = "*" @@ -1433,11 +937,6 @@ description = "NVRTC native runtime libraries" category = "main" optional = true python-versions = ">=3" -files = [ - {file = "nvidia_cuda_nvrtc_cu11-11.7.99-2-py3-none-manylinux1_x86_64.whl", hash = "sha256:9f1562822ea264b7e34ed5930567e89242d266448e936b85bc97a3370feabb03"}, - {file = "nvidia_cuda_nvrtc_cu11-11.7.99-py3-none-manylinux1_x86_64.whl", hash = "sha256:f7d9610d9b7c331fa0da2d1b2858a4a8315e6d49765091d28711c8946e7425e7"}, - {file = "nvidia_cuda_nvrtc_cu11-11.7.99-py3-none-win_amd64.whl", hash = "sha256:f2effeb1309bdd1b3854fc9b17eaf997808f8b25968ce0c7070945c4265d64a3"}, -] [package.dependencies] setuptools = "*" @@ -1450,10 +949,6 @@ description = "CUDA Runtime native Libraries" category = "main" optional = true python-versions = ">=3" -files = [ - {file = "nvidia_cuda_runtime_cu11-11.7.99-py3-none-manylinux1_x86_64.whl", hash = "sha256:cc768314ae58d2641f07eac350f40f99dcb35719c4faff4bc458a7cd2b119e31"}, - {file = "nvidia_cuda_runtime_cu11-11.7.99-py3-none-win_amd64.whl", hash = "sha256:bc77fa59a7679310df9d5c70ab13c4e34c64ae2124dd1efd7e5474b71be125c7"}, -] [package.dependencies] setuptools = "*" @@ -1466,10 +961,6 @@ description = "cuDNN runtime libraries" category = "main" optional = true python-versions = ">=3" -files = [ - {file = "nvidia_cudnn_cu11-8.5.0.96-2-py3-none-manylinux1_x86_64.whl", hash = "sha256:402f40adfc6f418f9dae9ab402e773cfed9beae52333f6d86ae3107a1b9527e7"}, - {file = "nvidia_cudnn_cu11-8.5.0.96-py3-none-manylinux1_x86_64.whl", hash = "sha256:71f8111eb830879ff2836db3cccf03bbd735df9b0d17cd93761732ac50a8a108"}, -] [package.dependencies] setuptools = "*" @@ -1482,57 +973,6 @@ description = "Fast, correct Python JSON library supporting dataclasses, datetim category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "orjson-3.8.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:43e69b360c2851b45c7dbab3b95f7fa8469df73fab325a683f7389c4db63aa71"}, - {file = "orjson-3.8.2-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:64c5da5c9679ef3d85e9bbcbb62f4ccdc1f1975780caa20f2ec1e37b4da6bd36"}, - {file = "orjson-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c632a2157fa9ec098d655287e9e44809615af99837c49f53d96bfbca453c5bd"}, - {file = "orjson-3.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f63da6309c282a2b58d4a846f0717f6440356b4872838b9871dc843ed1fe2b38"}, - {file = "orjson-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9be25c313ba2d5478829d949165445c3bd36c62e07092b4ba8dbe5426574d1"}, - {file = "orjson-3.8.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4bcce53e9e088f82633f784f79551fcd7637943ab56c51654aaf9d4c1d5cfa54"}, - {file = "orjson-3.8.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:33edb5379c6e6337f9383c85fe4080ce3aa1057cc2ce29345b7239461f50cbd6"}, - {file = "orjson-3.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:da35d347115758bbc8bfaf39bb213c42000f2a54e3f504c84374041d20835cd6"}, - {file = "orjson-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d755d94a90a941b91b4d39a6b02e289d8ba358af2d1a911edf266be7942609dc"}, - {file = "orjson-3.8.2-cp310-none-win_amd64.whl", hash = "sha256:7ea96923e26390b2142602ebb030e2a4db9351134696e0b219e5106bddf9b48e"}, - {file = "orjson-3.8.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:a0d89de876e6f1cef917a2338378a60a98584e1c2e1c67781e20b6ed1c512478"}, - {file = "orjson-3.8.2-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8d47e7592fe938aec898eb22ea4946298c018133df084bc78442ff18e2c6347c"}, - {file = "orjson-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3d9f1043f618d0c64228aab9711e5bd822253c50b6c56223951e32b51f81d62"}, - {file = "orjson-3.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed10600e8b08f1e87b656ad38ab316191ce94f2c9adec57035680c0dc9e93c81"}, - {file = "orjson-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99c49e49a04bf61fee7aaea6d92ac2b1fcf6507aea894bbdf3fbb25fe792168c"}, - {file = "orjson-3.8.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1463674f8efe6984902473d7b5ce3edf444c1fcd09dc8aa4779638a28fb9ca01"}, - {file = "orjson-3.8.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c1ef75f1d021d817e5c60a42da0b4b7e3123b1b37415260b8415666ddacc7cd7"}, - {file = "orjson-3.8.2-cp311-none-win_amd64.whl", hash = "sha256:b6007e1ac8564b13b2521720929e8bb3ccd3293d9fdf38f28728dcc06db6248f"}, - {file = "orjson-3.8.2-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a02c13ae523221576b001071354380e277346722cc6b7fdaacb0fd6db5154b3e"}, - {file = "orjson-3.8.2-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:fa2e565cf8ffdb37ce1887bd1592709ada7f701e61aa4b1e710be94b0aecbab4"}, - {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1d8864288f7c5fccc07b43394f83b721ddc999f25dccfb5d0651671a76023f5"}, - {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1874c05d0bb994601fa2d51605cb910d09343c6ebd36e84a573293523fab772a"}, - {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:349387ed6989e5db22e08c9af8d7ca14240803edc50de451d48d41a0e7be30f6"}, - {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:4e42b19619d6e97e201053b865ca4e62a48da71165f4081508ada8e1b91c6a30"}, - {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:bc112c17e607c59d1501e72afb44226fa53d947d364aed053f0c82d153e29616"}, - {file = "orjson-3.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6fda669211f2ed1fc2c8130187ec90c96b4f77b6a250004e666d2ef8ed524e5f"}, - {file = "orjson-3.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:aebd4e80fea0f20578fd0452908b9206a6a0d5ae9f5c99b6e665bbcd989e56cd"}, - {file = "orjson-3.8.2-cp37-none-win_amd64.whl", hash = "sha256:9f3cd0394eb6d265beb2a1572b5663bc910883ddbb5cdfbcb660f5a0444e7fd8"}, - {file = "orjson-3.8.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:74e7d54d11b3da42558d69a23bf92c2c48fabf69b38432d5eee2c5b09cd4c433"}, - {file = "orjson-3.8.2-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8cbadc9be748a823f9c743c7631b1ee95d3925a9c0b21de4e862a1d57daa10ec"}, - {file = "orjson-3.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07d5a8c69a2947d9554a00302734fe3d8516415c8b280963c92bc1033477890"}, - {file = "orjson-3.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b364ea01d1b71b9f97bf97af9eb79ebee892df302e127a9e2e4f8eaa74d6b98"}, - {file = "orjson-3.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b98a8c825a59db94fbe8e0cce48618624c5a6fb1436467322d90667c08a0bf80"}, - {file = "orjson-3.8.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:ab63103f60b516c0fce9b62cb4773f689a82ab56e19ef2387b5a3182f80c0d78"}, - {file = "orjson-3.8.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:73ab3f4288389381ae33ab99f914423b69570c88d626d686764634d5e0eeb909"}, - {file = "orjson-3.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2ab3fd8728e12c36e20c6d9d70c9e15033374682ce5acb6ed6a08a80dacd254d"}, - {file = "orjson-3.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cde11822cf71a7f0daaa84223249b2696a2b6cda7fa587e9fd762dff1a8848e4"}, - {file = "orjson-3.8.2-cp38-none-win_amd64.whl", hash = "sha256:b14765ea5aabfeab1a194abfaa0be62c9fee6480a75ac8c6974b4eeede3340b4"}, - {file = "orjson-3.8.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:6068a27d59d989d4f2864c2fc3440eb7126a0cfdfaf8a4ad136b0ffd932026ae"}, - {file = "orjson-3.8.2-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6bf36fa759a1b941fc552ad76b2d7fb10c1d2a20c056be291ea45eb6ae1da09b"}, - {file = "orjson-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f436132e62e647880ca6988974c8e3165a091cb75cbed6c6fd93e931630c22fa"}, - {file = "orjson-3.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ecd8936259a5920b52a99faf62d4efeb9f5e25a0aacf0cce1e9fa7c37af154f"}, - {file = "orjson-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c13114b345cda33644f64e92fe5d8737828766cf02fbbc7d28271a95ea546832"}, - {file = "orjson-3.8.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6e43cdc3ddf96bdb751b748b1984b701125abacca8fc2226b808d203916e8cba"}, - {file = "orjson-3.8.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ee39071da2026b11e4352d6fc3608a7b27ee14bc699fd240f4e604770bc7a255"}, - {file = "orjson-3.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1c3833976ebbeb3b5b6298cb22e23bf18453f6b80802103b7d08f7dd8a61611d"}, - {file = "orjson-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b9a34519d3d70935e1cd3797fbed8fbb6f61025182bea0140ca84d95b6f8fbe5"}, - {file = "orjson-3.8.2-cp39-none-win_amd64.whl", hash = "sha256:2734086d9a3dd9591c4be7d05aff9beccc086796d3f243685e56b7973ebac5bc"}, - {file = "orjson-3.8.2.tar.gz", hash = "sha256:a2fb95a45031ccf278e44341027b3035ab99caa32aa173279b1f0a06324f434b"}, -] [[package]] name = "packaging" @@ -1541,10 +981,6 @@ description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] [package.dependencies] pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" @@ -1556,10 +992,6 @@ description = "Utilities for writing pandoc filters in python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, - {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, -] [[package]] name = "parso" @@ -1568,10 +1000,6 @@ description = "A Python Parser" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, - {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, -] [package.extras] qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] @@ -1584,10 +1012,6 @@ description = "Utility library for gitignore style pattern matching of file path category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, -] [[package]] name = "pexpect" @@ -1596,10 +1020,6 @@ description = "Pexpect allows easy control of interactive console applications." category = "dev" optional = false python-versions = "*" -files = [ - {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, - {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, -] [package.dependencies] ptyprocess = ">=0.5" @@ -1611,10 +1031,6 @@ description = "Tiny 'shelve'-like database with concurrency support" category = "dev" optional = false python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] [[package]] name = "pillow" @@ -1623,69 +1039,6 @@ description = "Python Imaging Library (Fork)" category = "main" optional = true python-versions = ">=3.7" -files = [ - {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, - {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, - {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, - {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, - {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, - {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, - {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, - {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, - {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, - {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, - {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, - {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, - {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, - {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, - {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, - {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, - {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, - {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, - {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, - {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, - {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, - {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, - {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, - {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, - {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, - {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, - {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, - {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, - {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, -] [package.extras] docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] @@ -1698,10 +1051,6 @@ description = "Resolve a name to an object." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, - {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, -] [[package]] name = "platformdirs" @@ -1710,10 +1059,6 @@ description = "A small Python package for determining appropriate platform-speci category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, -] [package.extras] docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] @@ -1726,10 +1071,6 @@ description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, - {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, -] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -1744,10 +1085,6 @@ description = "A framework for managing and maintaining multi-language pre-commi category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, -] [package.dependencies] cfgv = ">=2.0.0" @@ -1765,10 +1102,6 @@ description = "Python client for the Prometheus monitoring system." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"}, - {file = "prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"}, -] [package.extras] twisted = ["twisted"] @@ -1780,10 +1113,6 @@ description = "Library for building powerful interactive command lines in Python category = "dev" optional = false python-versions = ">=3.6.2" -files = [ - {file = "prompt_toolkit-3.0.32-py3-none-any.whl", hash = "sha256:24becda58d49ceac4dc26232eb179ef2b21f133fecda7eed6018d341766ed76e"}, - {file = "prompt_toolkit-3.0.32.tar.gz", hash = "sha256:e7f2129cba4ff3b3656bbdda0e74ee00d2f874a8bcdb9dd16f5fec7b3e173cae"}, -] [package.dependencies] wcwidth = "*" @@ -1795,22 +1124,6 @@ description = "" category = "main" optional = true python-versions = ">=3.7" -files = [ - {file = "protobuf-4.21.9-cp310-abi3-win32.whl", hash = "sha256:6e0be9f09bf9b6cf497b27425487706fa48c6d1632ddd94dab1a5fe11a422392"}, - {file = "protobuf-4.21.9-cp310-abi3-win_amd64.whl", hash = "sha256:a7d0ea43949d45b836234f4ebb5ba0b22e7432d065394b532cdca8f98415e3cf"}, - {file = "protobuf-4.21.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ab0b8918c136345ff045d4b3d5f719b505b7c8af45092d7f45e304f55e50a1"}, - {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:2c9c2ed7466ad565f18668aa4731c535511c5d9a40c6da39524bccf43e441719"}, - {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:e575c57dc8b5b2b2caa436c16d44ef6981f2235eb7179bfc847557886376d740"}, - {file = "protobuf-4.21.9-cp37-cp37m-win32.whl", hash = "sha256:9227c14010acd9ae7702d6467b4625b6fe853175a6b150e539b21d2b2f2b409c"}, - {file = "protobuf-4.21.9-cp37-cp37m-win_amd64.whl", hash = "sha256:a419cc95fca8694804709b8c4f2326266d29659b126a93befe210f5bbc772536"}, - {file = "protobuf-4.21.9-cp38-cp38-win32.whl", hash = "sha256:5b0834e61fb38f34ba8840d7dcb2e5a2f03de0c714e0293b3963b79db26de8ce"}, - {file = "protobuf-4.21.9-cp38-cp38-win_amd64.whl", hash = "sha256:84ea107016244dfc1eecae7684f7ce13c788b9a644cd3fca5b77871366556444"}, - {file = "protobuf-4.21.9-cp39-cp39-win32.whl", hash = "sha256:f9eae277dd240ae19bb06ff4e2346e771252b0e619421965504bd1b1bba7c5fa"}, - {file = "protobuf-4.21.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e312e280fbe3c74ea9e080d9e6080b636798b5e3939242298b591064470b06b"}, - {file = "protobuf-4.21.9-py2.py3-none-any.whl", hash = "sha256:7eb8f2cc41a34e9c956c256e3ac766cf4e1a4c9c925dc757a41a01be3e852965"}, - {file = "protobuf-4.21.9-py3-none-any.whl", hash = "sha256:48e2cd6b88c6ed3d5877a3ea40df79d08374088e89bedc32557348848dff250b"}, - {file = "protobuf-4.21.9.tar.gz", hash = "sha256:61f21493d96d2a77f9ca84fefa105872550ab5ef71d21c458eb80edcf4885a99"}, -] [[package]] name = "psutil" @@ -1819,22 +1132,6 @@ description = "Cross-platform lib for process and system monitoring in Python." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, - {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, - {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, - {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, - {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, - {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, - {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, - {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, - {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, - {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, - {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, -] [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] @@ -1846,10 +1143,6 @@ description = "Run a subprocess in a pseudo terminal" category = "dev" optional = false python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] [[package]] name = "py" @@ -1858,10 +1151,6 @@ description = "library with cross-python path, ini-parsing, io, code, log facili category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] [[package]] name = "pycparser" @@ -1870,10 +1159,6 @@ description = "C parser in Python" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] [[package]] name = "pydantic" @@ -1882,44 +1167,6 @@ description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, -] [package.dependencies] typing-extensions = ">=4.1.0" @@ -1935,10 +1182,6 @@ description = "Pygments is a syntax highlighting package written in Python." category = "main" optional = false python-versions = ">=3.6" -files = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, -] [package.extras] plugins = ["importlib-metadata"] @@ -1950,10 +1193,6 @@ description = "pyparsing module - Classes and methods to define and execute pars category = "dev" optional = false python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] @@ -1965,30 +1204,6 @@ description = "Persistent/Functional/Immutable data structures" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"}, - {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"}, - {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed"}, - {file = "pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41"}, - {file = "pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2"}, - {file = "pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73"}, - {file = "pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308"}, - {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584"}, - {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb"}, - {file = "pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a"}, - {file = "pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab"}, - {file = "pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"}, - {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95"}, - {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e"}, - {file = "pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b"}, - {file = "pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291"}, - {file = "pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"}, - {file = "pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"}, -] [[package]] name = "pytest" @@ -1997,10 +1212,6 @@ description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] [package.dependencies] atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} @@ -2023,10 +1234,6 @@ description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"}, - {file = "pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"}, -] [package.dependencies] pytest = ">=6.1.0" @@ -2042,10 +1249,6 @@ description = "Extensions to the standard Python datetime module" category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] [package.dependencies] six = ">=1.5" @@ -2057,10 +1260,6 @@ description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" -files = [ - {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, - {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, -] [[package]] name = "pywin32" @@ -2069,22 +1268,6 @@ description = "Python for Window Extensions" category = "dev" optional = false python-versions = "*" -files = [ - {file = "pywin32-305-cp310-cp310-win32.whl", hash = "sha256:421f6cd86e84bbb696d54563c48014b12a23ef95a14e0bdba526be756d89f116"}, - {file = "pywin32-305-cp310-cp310-win_amd64.whl", hash = "sha256:73e819c6bed89f44ff1d690498c0a811948f73777e5f97c494c152b850fad478"}, - {file = "pywin32-305-cp310-cp310-win_arm64.whl", hash = "sha256:742eb905ce2187133a29365b428e6c3b9001d79accdc30aa8969afba1d8470f4"}, - {file = "pywin32-305-cp311-cp311-win32.whl", hash = "sha256:19ca459cd2e66c0e2cc9a09d589f71d827f26d47fe4a9d09175f6aa0256b51c2"}, - {file = "pywin32-305-cp311-cp311-win_amd64.whl", hash = "sha256:326f42ab4cfff56e77e3e595aeaf6c216712bbdd91e464d167c6434b28d65990"}, - {file = "pywin32-305-cp311-cp311-win_arm64.whl", hash = "sha256:4ecd404b2c6eceaca52f8b2e3e91b2187850a1ad3f8b746d0796a98b4cea04db"}, - {file = "pywin32-305-cp36-cp36m-win32.whl", hash = "sha256:48d8b1659284f3c17b68587af047d110d8c44837736b8932c034091683e05863"}, - {file = "pywin32-305-cp36-cp36m-win_amd64.whl", hash = "sha256:13362cc5aa93c2beaf489c9c9017c793722aeb56d3e5166dadd5ef82da021fe1"}, - {file = "pywin32-305-cp37-cp37m-win32.whl", hash = "sha256:a55db448124d1c1484df22fa8bbcbc45c64da5e6eae74ab095b9ea62e6d00496"}, - {file = "pywin32-305-cp37-cp37m-win_amd64.whl", hash = "sha256:109f98980bfb27e78f4df8a51a8198e10b0f347257d1e265bb1a32993d0c973d"}, - {file = "pywin32-305-cp38-cp38-win32.whl", hash = "sha256:9dd98384da775afa009bc04863426cb30596fd78c6f8e4e2e5bbf4edf8029504"}, - {file = "pywin32-305-cp38-cp38-win_amd64.whl", hash = "sha256:56d7a9c6e1a6835f521788f53b5af7912090674bb84ef5611663ee1595860fc7"}, - {file = "pywin32-305-cp39-cp39-win32.whl", hash = "sha256:9d968c677ac4d5cbdaa62fd3014ab241718e619d8e36ef8e11fb930515a1e918"}, - {file = "pywin32-305-cp39-cp39-win_amd64.whl", hash = "sha256:50768c6b7c3f0b38b7fb14dd4104da93ebced5f1a50dc0e834594bff6fbe1271"}, -] [[package]] name = "pywinpty" @@ -2093,14 +1276,6 @@ description = "Pseudo terminal support for Windows from Python." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "pywinpty-2.0.9-cp310-none-win_amd64.whl", hash = "sha256:30a7b371446a694a6ce5ef906d70ac04e569de5308c42a2bdc9c3bc9275ec51f"}, - {file = "pywinpty-2.0.9-cp311-none-win_amd64.whl", hash = "sha256:d78ef6f4bd7a6c6f94dc1a39ba8fb028540cc39f5cb593e756506db17843125f"}, - {file = "pywinpty-2.0.9-cp37-none-win_amd64.whl", hash = "sha256:5ed36aa087e35a3a183f833631b3e4c1ae92fe2faabfce0fa91b77ed3f0f1382"}, - {file = "pywinpty-2.0.9-cp38-none-win_amd64.whl", hash = "sha256:2352f44ee913faaec0a02d3c112595e56b8af7feeb8100efc6dc1a8685044199"}, - {file = "pywinpty-2.0.9-cp39-none-win_amd64.whl", hash = "sha256:ba75ec55f46c9e17db961d26485b033deb20758b1731e8e208e1e8a387fcf70c"}, - {file = "pywinpty-2.0.9.tar.gz", hash = "sha256:01b6400dd79212f50a2f01af1c65b781290ff39610853db99bf03962eb9a615f"}, -] [[package]] name = "pyyaml" @@ -2109,48 +1284,6 @@ description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] [[package]] name = "pyzmq" @@ -2159,82 +1292,6 @@ description = "Python bindings for 0MQ" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066"}, - {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6"}, - {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef"}, - {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4"}, - {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913"}, - {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3"}, - {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246"}, - {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1"}, - {file = "pyzmq-24.0.1-cp310-cp310-win32.whl", hash = "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd"}, - {file = "pyzmq-24.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340"}, - {file = "pyzmq-24.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00"}, - {file = "pyzmq-24.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc"}, - {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef"}, - {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3"}, - {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace"}, - {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff"}, - {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8"}, - {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1"}, - {file = "pyzmq-24.0.1-cp311-cp311-win32.whl", hash = "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2"}, - {file = "pyzmq-24.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8"}, - {file = "pyzmq-24.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4"}, - {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d"}, - {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc"}, - {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d"}, - {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83"}, - {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639"}, - {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e"}, - {file = "pyzmq-24.0.1-cp36-cp36m-win32.whl", hash = "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc"}, - {file = "pyzmq-24.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be"}, - {file = "pyzmq-24.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33"}, - {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1"}, - {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6"}, - {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c"}, - {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107"}, - {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99"}, - {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2"}, - {file = "pyzmq-24.0.1-cp37-cp37m-win32.whl", hash = "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b"}, - {file = "pyzmq-24.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227"}, - {file = "pyzmq-24.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e"}, - {file = "pyzmq-24.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc"}, - {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff"}, - {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7"}, - {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66"}, - {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287"}, - {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330"}, - {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f"}, - {file = "pyzmq-24.0.1-cp38-cp38-win32.whl", hash = "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58"}, - {file = "pyzmq-24.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a"}, - {file = "pyzmq-24.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62"}, - {file = "pyzmq-24.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4"}, - {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b"}, - {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b"}, - {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d"}, - {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea"}, - {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209"}, - {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58"}, - {file = "pyzmq-24.0.1-cp39-cp39-win32.whl", hash = "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333"}, - {file = "pyzmq-24.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b"}, - {file = "pyzmq-24.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0"}, - {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5"}, - {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409"}, - {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d"}, - {file = "pyzmq-24.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944"}, - {file = "pyzmq-24.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804"}, - {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f"}, - {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43"}, - {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8"}, - {file = "pyzmq-24.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879"}, - {file = "pyzmq-24.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a"}, - {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f"}, - {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75"}, - {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4"}, - {file = "pyzmq-24.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066"}, - {file = "pyzmq-24.0.1.tar.gz", hash = "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77"}, -] [package.dependencies] cffi = {version = "*", markers = "implementation_name == \"pypy\""} @@ -2242,25 +1299,21 @@ py = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "requests" -version = "2.28.1" +version = "2.27.1" description = "Python HTTP for Humans." category = "dev" optional = false -python-versions = ">=3.7, <4" -files = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} urllib3 = ">=1.21.1,<1.27" [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] [[package]] name = "rfc3986" @@ -2269,10 +1322,6 @@ description = "Validating URI References per RFC 3986" category = "dev" optional = false python-versions = "*" -files = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] [package.dependencies] idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} @@ -2287,10 +1336,6 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar category = "main" optional = false python-versions = ">=3.7.0" -files = [ - {file = "rich-13.1.0-py3-none-any.whl", hash = "sha256:f846bff22a43e8508aebf3f0f2410ce1c6f4cde429098bd58d91fde038c57299"}, - {file = "rich-13.1.0.tar.gz", hash = "sha256:81c73a30b144bbcdedc13f4ea0b6ffd7fdc3b0d3cc259a9402309c8e4aee1964"}, -] [package.dependencies] commonmark = ">=0.9.0,<0.10.0" @@ -2307,24 +1352,6 @@ description = "An extremely fast Python linter, written in Rust." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "ruff-0.0.165-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b13d433c38966c5fe7c044de55037c9715495a2941df457a27c691f519e4a94d"}, - {file = "ruff-0.0.165-py3-none-macosx_10_9_x86_64.macosx_10_9_arm64.macosx_10_9_universal2.whl", hash = "sha256:4c69d221ceb75a9a464f9a3d000e795806dedb1d010da874859809cbe38e3d30"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3baef2179dd2067db1287c2dcb66b5ab1b1a124746d0f65485cc1129717d6554"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d70502afbefac54f85a1754869f9cd3477dc33c9ae6ca2338a11ac2b780ed06"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:133f076ceabc25ff5aec017fe8084b3eedd82ece28f287fbd2e1685bb14a2554"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c92cc05cceee332ed221702f7a63c19dca2cb87c33bf06b9a085630070c33192"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eadca0b7116d49ad6faed505ad181bca39bca111478a4b2f1f8c39a632955c2f"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85135ffc825edfcf6fa17ec2e5569aaba3fa7cd096d45a4d5fc896285b266a5b"}, - {file = "ruff-0.0.165-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1a9f6d0139571d05392a1f7f94a4e217768a9f8595910ab2dfe745a0ca1fab7"}, - {file = "ruff-0.0.165-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4109826311fabc68633073c408048448ab870456adf1c40252795131de2624a5"}, - {file = "ruff-0.0.165-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5cac57e0a80f593aebe3975cf9f8c776e13c6236608d2fef2893f7980a2a0510"}, - {file = "ruff-0.0.165-py3-none-musllinux_1_2_i686.whl", hash = "sha256:32f16721360b3e973f1e3fe013a1aa33522b24532925e622417080beda5d7478"}, - {file = "ruff-0.0.165-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0be5acdd86269963f1fa1c4dd3c3ec37f14c847d889591ff5bc1fd934c0cfa3"}, - {file = "ruff-0.0.165-py3-none-win32.whl", hash = "sha256:dacd94f66c6d42c23c22776d9cc6c726bf42987a38358953bec0e4eec0b72574"}, - {file = "ruff-0.0.165-py3-none-win_amd64.whl", hash = "sha256:c20ba25907d52fae33ea363a741e3ba03fc5e9712cbc3b12572897768f24bcf6"}, - {file = "ruff-0.0.165.tar.gz", hash = "sha256:5468b30e0c5888fd436568a47da31f8c827affbacaba06c1ca8ad1f7f0df9e4e"}, -] [[package]] name = "send2trash" @@ -2333,10 +1360,6 @@ description = "Send file to trash natively under Mac OS X, Windows and Linux." category = "dev" optional = false python-versions = "*" -files = [ - {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, - {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, -] [package.extras] nativelib = ["pyobjc-framework-Cocoa", "pywin32"] @@ -2350,10 +1373,6 @@ description = "Easily download, build, install, upgrade, and uninstall Python pa category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, - {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, -] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] @@ -2367,10 +1386,6 @@ description = "Python 2 and 3 compatibility utilities" category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] [[package]] name = "sniffio" @@ -2379,10 +1394,6 @@ description = "Sniff out which async library your code is running under" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] [[package]] name = "soupsieve" @@ -2391,10 +1402,6 @@ description = "A modern CSS selector implementation for Beautiful Soup." category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, - {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, -] [[package]] name = "starlette" @@ -2403,10 +1410,6 @@ description = "The little ASGI library that shines." category = "main" optional = true python-versions = ">=3.7" -files = [ - {file = "starlette-0.21.0-py3-none-any.whl", hash = "sha256:0efc058261bbcddeca93cad577efd36d0c8a317e44376bcfc0e097a2b3dc24a7"}, - {file = "starlette-0.21.0.tar.gz", hash = "sha256:b1b52305ee8f7cfc48cde383496f7c11ab897cd7112b33d998b1317dc8ef9027"}, -] [package.dependencies] anyio = ">=3.4.0,<5" @@ -2422,10 +1425,6 @@ description = "Tornado websocket backend for the Xterm.js Javascript terminal em category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "terminado-0.17.0-py3-none-any.whl", hash = "sha256:bf6fe52accd06d0661d7611cc73202121ec6ee51e46d8185d489ac074ca457c2"}, - {file = "terminado-0.17.0.tar.gz", hash = "sha256:520feaa3aeab8ad64a69ca779be54be9234edb2d0d6567e76c93c2c9a4e6e43f"}, -] [package.dependencies] ptyprocess = {version = "*", markers = "os_name != \"nt\""} @@ -2443,10 +1442,6 @@ description = "A tiny CSS parser" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, - {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, -] [package.dependencies] webencodings = ">=0.4" @@ -2462,10 +1457,6 @@ description = "Python Library for Tom's Obvious, Minimal Language" category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] [[package]] name = "tomli" @@ -2474,10 +1465,6 @@ description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] [[package]] name = "torch" @@ -2486,29 +1473,6 @@ description = "Tensors and Dynamic neural networks in Python with strong GPU acc category = "main" optional = true python-versions = ">=3.7.0" -files = [ - {file = "torch-1.13.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:f68edfea71ade3862039ba66bcedf954190a2db03b0c41a9b79afd72210abd97"}, - {file = "torch-1.13.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d2d2753519415d154de4d3e64d2eaaeefdba6b6fd7d69d5ffaef595988117700"}, - {file = "torch-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c227c16626e4ce766cca5351cc62a2358a11e8e466410a298487b9dff159eb1"}, - {file = "torch-1.13.0-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:49a949b8136b32b2ec0724cbf4c6678b54e974b7d68f19f1231eea21cde5c23b"}, - {file = "torch-1.13.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:0fdd38c96230947b1ed870fed4a560252f8d23c3a2bf4dab9d2d42b18f2e67c8"}, - {file = "torch-1.13.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:43db0723fc66ad6486f86dc4890c497937f7cd27429f28f73fb7e4d74b7482e2"}, - {file = "torch-1.13.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e643ac8d086706e82f77b5d4dfcf145a9dd37b69e03e64177fc23821754d2ed7"}, - {file = "torch-1.13.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bb33a911460475d1594a8c8cb73f58c08293211760796d99cae8c2509b86d7f1"}, - {file = "torch-1.13.0-cp37-cp37m-win_amd64.whl", hash = "sha256:220325d0f4e69ee9edf00c04208244ef7cf22ebce083815ce272c7491f0603f5"}, - {file = "torch-1.13.0-cp37-none-macosx_10_9_x86_64.whl", hash = "sha256:cd1e67db6575e1b173a626077a54e4911133178557aac50683db03a34e2b636a"}, - {file = "torch-1.13.0-cp37-none-macosx_11_0_arm64.whl", hash = "sha256:9197ec216833b836b67e4d68e513d31fb38d9789d7cd998a08fba5b499c38454"}, - {file = "torch-1.13.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fa768432ce4b8ffa29184c79a3376ab3de4a57b302cdf3c026a6be4c5a8ab75b"}, - {file = "torch-1.13.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:635dbb99d981a6483ca533b3dc7be18ef08dd9e1e96fb0bb0e6a99d79e85a130"}, - {file = "torch-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:857c7d5b1624c5fd979f66d2b074765733dba3f5e1cc97b7d6909155a2aae3ce"}, - {file = "torch-1.13.0-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:ef934a21da6f6a516d0a9c712a80d09c56128abdc6af8dc151bee5199b4c3b4e"}, - {file = "torch-1.13.0-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:f01a9ae0d4b69d2fc4145e8beab45b7877342dddbd4838a7d3c11ca7f6680745"}, - {file = "torch-1.13.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9ac382cedaf2f70afea41380ad8e7c06acef6b5b7e2aef3971cdad666ca6e185"}, - {file = "torch-1.13.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e20df14d874b024851c58e8bb3846249cb120e677f7463f60c986e3661f88680"}, - {file = "torch-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a378f5091307381abfb30eb821174e12986f39b1cf7c4522bf99155256819eb"}, - {file = "torch-1.13.0-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:922a4910613b310fbeb87707f00cb76fec328eb60cc1349ed2173e7c9b6edcd8"}, - {file = "torch-1.13.0-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:47fe6228386bff6d74319a2ffe9d4ed943e6e85473d78e80502518c607d644d2"}, -] [package.dependencies] nvidia-cublas-cu11 = "11.10.3.66" @@ -2527,19 +1491,6 @@ description = "Tornado is a Python web framework and asynchronous networking lib category = "dev" optional = false python-versions = ">= 3.7" -files = [ - {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, - {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, - {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, - {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, - {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, - {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, - {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, -] [[package]] name = "traitlets" @@ -2548,10 +1499,6 @@ description = "" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, - {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, -] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] @@ -2564,10 +1511,6 @@ description = "Import, export, process, analyze and view triangular meshes." category = "main" optional = true python-versions = "*" -files = [ - {file = "trimesh-3.17.1-py3-none-any.whl", hash = "sha256:a09460ee4e11c32bf9f0643b86241b3e3e2aa86296c4912a0738b76da3034c00"}, - {file = "trimesh-3.17.1.tar.gz", hash = "sha256:025bb2fa3a2e87bdd6873f11db45a7ca19216f2f8b6aed29140fca57e32c298e"}, -] [package.dependencies] numpy = "*" @@ -2584,32 +1527,6 @@ description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] [[package]] name = "types-pillow" @@ -2618,10 +1535,6 @@ description = "Typing stubs for Pillow" category = "main" optional = true python-versions = "*" -files = [ - {file = "types-Pillow-9.3.0.1.tar.gz", hash = "sha256:f3b7cada3fa496c78d75253c6b1f07a843d625f42e5639b320a72acaff6f7cfb"}, - {file = "types_Pillow-9.3.0.1-py3-none-any.whl", hash = "sha256:79837755fe9659f29efd1016e9903ac4a500e0c73260483f07296bd6ca47668b"}, -] [[package]] name = "types-protobuf" @@ -2630,10 +1543,6 @@ description = "Typing stubs for protobuf" category = "dev" optional = false python-versions = "*" -files = [ - {file = "types-protobuf-3.20.4.5.tar.gz", hash = "sha256:e9b45008d106e1d10cc77a29d2d344b85c0f01e2e643aaccf32f69e9e81b0cdd"}, - {file = "types_protobuf-3.20.4.5-py3-none-any.whl", hash = "sha256:97af5ce70d890fdb94cb0c906f5a6624ca2fef58bc04e27990a25509e992a950"}, -] [[package]] name = "types-requests" @@ -2642,10 +1551,6 @@ description = "Typing stubs for requests" category = "main" optional = false python-versions = "*" -files = [ - {file = "types-requests-2.28.11.7.tar.gz", hash = "sha256:0ae38633734990d019b80f5463dfa164ebd3581998ac8435f526da6fe4d598c3"}, - {file = "types_requests-2.28.11.7-py3-none-any.whl", hash = "sha256:b6a2fca8109f4fdba33052f11ed86102bddb2338519e1827387137fefc66a98b"}, -] [package.dependencies] types-urllib3 = "<1.27" @@ -2657,10 +1562,6 @@ description = "Typing stubs for urllib3" category = "main" optional = false python-versions = "*" -files = [ - {file = "types-urllib3-1.26.25.4.tar.gz", hash = "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"}, - {file = "types_urllib3-1.26.25.4-py3-none-any.whl", hash = "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49"}, -] [[package]] name = "typing-extensions" @@ -2669,10 +1570,6 @@ description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] [[package]] name = "typing-inspect" @@ -2681,10 +1578,6 @@ description = "Runtime inspection utilities for typing module." category = "main" optional = false python-versions = "*" -files = [ - {file = "typing_inspect-0.8.0-py3-none-any.whl", hash = "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188"}, - {file = "typing_inspect-0.8.0.tar.gz", hash = "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d"}, -] [package.dependencies] mypy-extensions = ">=0.3.0" @@ -2692,15 +1585,11 @@ typing-extensions = ">=3.7.4" [[package]] name = "urllib3" -version = "1.26.12" +version = "1.26.14" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" -files = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, -] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] @@ -2714,10 +1603,6 @@ description = "The lightning-fast ASGI server." category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "uvicorn-0.19.0-py3-none-any.whl", hash = "sha256:cc277f7e73435748e69e075a721841f7c4a95dba06d12a72fe9874acced16f6f"}, - {file = "uvicorn-0.19.0.tar.gz", hash = "sha256:cf538f3018536edb1f4a826311137ab4944ed741d52aeb98846f52215de57f25"}, -] [package.dependencies] click = ">=7.0" @@ -2734,10 +1619,6 @@ description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = ">=3.6" -files = [ - {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, - {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, -] [package.dependencies] distlib = ">=0.3.6,<1" @@ -2756,10 +1637,6 @@ description = "Measures the displayed width of unicode strings in a terminal" category = "dev" optional = false python-versions = "*" -files = [ - {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, - {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, -] [[package]] name = "webencodings" @@ -2768,10 +1645,6 @@ description = "Character encoding aliases for legacy web content" category = "dev" optional = false python-versions = "*" -files = [ - {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, - {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, -] [[package]] name = "websocket-client" @@ -2780,10 +1653,6 @@ description = "WebSocket client for Python with low level API options" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "websocket-client-1.4.2.tar.gz", hash = "sha256:d6e8f90ca8e2dd4e8027c4561adeb9456b54044312dba655e7cae652ceb9ae59"}, - {file = "websocket_client-1.4.2-py3-none-any.whl", hash = "sha256:d6b06432f184438d99ac1f456eaf22fe1ade524c3dd16e661142dc54e9cba574"}, -] [package.extras] docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] @@ -2797,10 +1666,6 @@ description = "A built-package format for Python" category = "main" optional = true python-versions = ">=3.7" -files = [ - {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, - {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, -] [package.extras] test = ["pytest (>=3.0.0)"] @@ -2812,10 +1677,6 @@ description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" -files = [ - {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, - {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, -] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] @@ -2830,6 +1691,1159 @@ video = ["av"] web = ["fastapi"] [metadata] -lock-version = "2.0" -python-versions = "^3.7" -content-hash = "6cf47b3bf40d1cfbae2f1e80f12dedf5eabef6bee6d3151d37a05997e4d965f7" +lock-version = "1.1" +python-versions = ">=3.7" +content-hash = "8607c6935a978649a45cf33d71a6c34294f66f346cc1bac713e37ca382dee671" + +[metadata.files] +anyio = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] +appnope = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] +argon2-cffi = [ + {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, + {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, +] +argon2-cffi-bindings = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +av = [ + {file = "av-10.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d19bb54197155d045a2b683d993026d4bcb06e31c2acad0327e3e8711571899c"}, + {file = "av-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dba96a85cd37315529998e6dbbe3fa05c2344eb19a431dc24996be030a904ee"}, + {file = "av-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27d6d38c7c8d46d578c008ffcb8aad1eae14d0621fff41f4ad62395589045fe4"}, + {file = "av-10.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51037f4bde03daf924236af4f444e17345792ad7f6f70760a5e5863407e14f2b"}, + {file = "av-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0577a38664e453b4ffb63d616a0d23c295827b16ae96a090e89527a753de8718"}, + {file = "av-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:07c971573035d22ce50069d3f2bbdb4d6d02d626ab13db12fda3ce519cda3f22"}, + {file = "av-10.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e5085d11345484c0097898994bb3f515002e7e1deeb43dd11d30dd6f45402c49"}, + {file = "av-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:157bde3ffd1615a9006b56e4daf3b46848d3ee2bd46b0394f7568e43ed7ab5a9"}, + {file = "av-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:115e144d5a1f205378a4b3a3657b7ed3e45918ebe5d2003a891e45984e8f443a"}, + {file = "av-10.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7d6e2b3fbda6464f74fe010dbcff361394bb014b0cb4aa4dc9f2bb713ce882"}, + {file = "av-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fd5a38395191a0f4b71adf31057ff177c9f0762914d73d8797742339ad67d0"}, + {file = "av-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:836d69a9543d284976b229cc8d4343ffcfc0bbaf05239e13fb7e613b13d5291d"}, + {file = "av-10.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:eba192274538617bbe60097a013d83637f1a5ba9844bbbcf3ca7e43c6499b9d5"}, + {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1301e4cf1a2c899851073720cd541066c8539b64f9eb0d52216f8d0a59f20429"}, + {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eebd5aa9d8b1e33e715c5409544a712f13ec805bb0110d75f394ff28d2fb64ad"}, + {file = "av-10.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04cd0ce13a87870fb0a0ea4673f04934af2b9ac7ae844eafe92e2c19c092ab11"}, + {file = "av-10.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10facb5b933551dd6a30d8015bc91eef5d1c864ee86aa3463ffbaff1a99f6c6a"}, + {file = "av-10.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:088636ded03724a2ab51136f6f4be0bc457bdb3c0d2ac7158792fe81150d4c1a"}, + {file = "av-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff0f7d3b1003a9ed0d06038f3f521a5ff0d3e056ec5111e2a78e303f98b815a7"}, + {file = "av-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccaf786e747b126a5b3b9a8f5ffbb6a20c5f528775cc7084c95732ca72606fba"}, + {file = "av-10.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c579d718b52beb812ea2a7bd68f812d0920b00937804d52d31d41bb71aa5557"}, + {file = "av-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2cfd39baa5d82768d2a8898de7bfd450a083ef22b837d57e5dc1b6de3244218"}, + {file = "av-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:81b5264d9752f49286bc1dc4d2cc66187418c4948a326dbed837c766c9892139"}, + {file = "av-10.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:16bd82b63d0b4c1b855b3c36b13337f7cdc5925bd8284fab893bdf6c290fc3a9"}, + {file = "av-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a6c8f3f8c26d35eefe45b849c81fd0816ba4b6f589baec7357c25b4c5537d3c4"}, + {file = "av-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91ea46fea7259abdfabe00b0ed3a9ca18e7fff7ce80d2a2c66a28f797cce838a"}, + {file = "av-10.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a62edd533d330aa61902ae8cd82966affa487fa337a0c4f58ae8866ccb5d31c0"}, + {file = "av-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b67b7d028c9cf68215376662fd2e0be6ca0cc02d32d3ed8514fec67b12db9cbd"}, + {file = "av-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:0f9c88062ebfd2ce547c522b64f79e487ed2b0a6a9d6693c801b28df0d944607"}, + {file = "av-10.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:63dbafcd02415127d97509523bc285f1ab260988f87b744d7fb1baee6ffbdf96"}, + {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2ea4424d0be62fe18c843420284a0907bcb38d577062d62c4b75a8e940e6057"}, + {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b6326fd0755761e3ee999e4bf90339e869fe71d548b679fee89157858b8d04a"}, + {file = "av-10.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3fae238751ec0db6377b2106e13762ca84dbe104bd44c1ce9b424163aef4ab5"}, + {file = "av-10.0.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:86bb3f6e8cce62ad18cd34eb2eadd091d99f51b40be81c929b53fbd8fecf6d90"}, + {file = "av-10.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f7b508813abbc100162d305a1ac9b2dd16e5128d56f2ac69639fc6a4b5aca69e"}, + {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cc376199c0aa6e9365d03e0f4e67cfb209e40fe9c0cf566372f9daf2a0c779"}, + {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b459ca0ef25c1a0e370112556bdc5b7752f76dc9bd497acaf3e653171e4b946"}, + {file = "av-10.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab930735112c1f788cc4d47c42c59ba0dd214d815aa906e1addf39af91d15194"}, + {file = "av-10.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13fe0b48b9211539323ecebbf84154c86c72d16723c6d0af76e29ae5c3a614b2"}, + {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2eeec7beaebfe9e2213b3c94b482381187d0afdcb632f93239b44dc668b97df"}, + {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dac2a8b0791c3373270e32f6cd27e6b60628565a188e40a5d9660d3aab05e33"}, + {file = "av-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cdede2325cb750b5bf79238bbf06f9c2a70b757b12726003769a43493b7233a"}, + {file = "av-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9788e6e15db0910fb8e1548ba7540799d07066177710590a5794a524c4910e05"}, + {file = "av-10.0.0.tar.gz", hash = "sha256:8afd3d5610e1086f3b2d8389d66672ea78624516912c93612de64dcaa4c67e05"}, +] +babel = [ + {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, + {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] +black = [ + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, +] +bleach = [ + {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, + {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +commonmark = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] +debugpy = [ + {file = "debugpy-1.6.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c4b2bd5c245eeb49824bf7e539f95fb17f9a756186e51c3e513e32999d8846f3"}, + {file = "debugpy-1.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8deaeb779699350deeed835322730a3efec170b88927debc9ba07a1a38e2585"}, + {file = "debugpy-1.6.3-cp310-cp310-win32.whl", hash = "sha256:fc233a0160f3b117b20216f1169e7211b83235e3cd6749bcdd8dbb72177030c7"}, + {file = "debugpy-1.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:dda8652520eae3945833e061cbe2993ad94a0b545aebd62e4e6b80ee616c76b2"}, + {file = "debugpy-1.6.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5c814596a170a0a58fa6fad74947e30bfd7e192a5d2d7bd6a12156c2899e13a"}, + {file = "debugpy-1.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c4cd6f37e3c168080d61d698390dfe2cd9e74ebf80b448069822a15dadcda57d"}, + {file = "debugpy-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:3c9f985944a30cfc9ae4306ac6a27b9c31dba72ca943214dad4a0ab3840f6161"}, + {file = "debugpy-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:5ad571a36cec137ae6ed951d0ff75b5e092e9af6683da084753231150cbc5b25"}, + {file = "debugpy-1.6.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:adcfea5ea06d55d505375995e150c06445e2b20cd12885bcae566148c076636b"}, + {file = "debugpy-1.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:daadab4403427abd090eccb38d8901afd8b393e01fd243048fab3f1d7132abb4"}, + {file = "debugpy-1.6.3-cp38-cp38-win32.whl", hash = "sha256:6efc30325b68e451118b795eff6fe8488253ca3958251d5158106d9c87581bc6"}, + {file = "debugpy-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:86d784b72c5411c833af1cd45b83d80c252b77c3bfdb43db17c441d772f4c734"}, + {file = "debugpy-1.6.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4e255982552b0edfe3a6264438dbd62d404baa6556a81a88f9420d3ed79b06ae"}, + {file = "debugpy-1.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cca23cb6161ac89698d629d892520327dd1be9321c0960e610bbcb807232b45d"}, + {file = "debugpy-1.6.3-cp39-cp39-win32.whl", hash = "sha256:7c302095a81be0d5c19f6529b600bac971440db3e226dce85347cc27e6a61908"}, + {file = "debugpy-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:34d2cdd3a7c87302ba5322b86e79c32c2115be396f3f09ca13306d8a04fe0f16"}, + {file = "debugpy-1.6.3-py2.py3-none-any.whl", hash = "sha256:84c39940a0cac410bf6aa4db00ba174f973eef521fbe9dd058e26bcabad89c4f"}, + {file = "debugpy-1.6.3.zip", hash = "sha256:e8922090514a890eec99cfb991bab872dd2e353ebb793164d5f01c362b9a40bf"}, +] +decorator = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +defusedxml = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] +distlib = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] +entrypoints = [ + {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, + {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, +] +fastapi = [ + {file = "fastapi-0.87.0-py3-none-any.whl", hash = "sha256:254453a2e22f64e2a1b4e1d8baf67d239e55b6c8165c079d25746a5220c81bb4"}, + {file = "fastapi-0.87.0.tar.gz", hash = "sha256:07032e53df9a57165047b4f38731c38bdcc3be5493220471015e2b4b51b486a4"}, +] +fastjsonschema = [ + {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"}, + {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"}, +] +filelock = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] +h11 = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +hnswlib = [ + {file = "hnswlib-0.7.0.tar.gz", hash = "sha256:bc459668e7e44bb7454b256b90c98c5af750653919d9a91698dafcf416cf64c4"}, +] +httpcore = [ + {file = "httpcore-0.16.1-py3-none-any.whl", hash = "sha256:8d393db683cc8e35cc6ecb02577c5e1abfedde52b38316d038932a84b4875ecb"}, + {file = "httpcore-0.16.1.tar.gz", hash = "sha256:3d3143ff5e1656a5740ea2f0c167e8e9d48c5a9bbd7f00ad1f8cff5711b08543"}, +] +httpx = [ + {file = "httpx-0.23.1-py3-none-any.whl", hash = "sha256:0b9b1f0ee18b9978d637b0776bfd7f54e2ca278e063e3586d8f01cda89e042a8"}, + {file = "httpx-0.23.1.tar.gz", hash = "sha256:202ae15319be24efe9a8bd4ed4360e68fde7b38bcc2ce87088d416f026667d19"}, +] +identify = [ + {file = "identify-2.5.8-py2.py3-none-any.whl", hash = "sha256:48b7925fe122720088aeb7a6c34f17b27e706b72c61070f27fe3789094233440"}, + {file = "identify-2.5.8.tar.gz", hash = "sha256:7a214a10313b9489a0d61467db2856ae8d0b8306fc923e03a9effa53d8aedc58"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +importlib-metadata = [ + {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, + {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, +] +importlib-resources = [ + {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"}, + {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +ipykernel = [ + {file = "ipykernel-6.16.2-py3-none-any.whl", hash = "sha256:67daf93e5b52456cd8eea87a8b59405d2bb80ae411864a1ea206c3631d8179af"}, + {file = "ipykernel-6.16.2.tar.gz", hash = "sha256:463f3d87a92e99969b1605cb7a5b4d7b36b7145a0e72d06e65918a6ddefbe630"}, +] +ipython = [ + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +isort = [ + {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, + {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, +] +jedi = [ + {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, + {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, +] +jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +json5 = [ + {file = "json5-0.9.10-py2.py3-none-any.whl", hash = "sha256:993189671e7412e9cdd8be8dc61cf402e8e579b35f1d1bb20ae6b09baa78bbce"}, + {file = "json5-0.9.10.tar.gz", hash = "sha256:ad9f048c5b5a4c3802524474ce40a622fae789860a86f10cc4f7e5f9cf9b46ab"}, +] +jsonschema = [ + {file = "jsonschema-4.17.0-py3-none-any.whl", hash = "sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248"}, + {file = "jsonschema-4.17.0.tar.gz", hash = "sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d"}, +] +jupyter-client = [ + {file = "jupyter_client-7.4.6-py3-none-any.whl", hash = "sha256:540b6a5c9c2dc481c5dd54fd5acb260f03dfaaa7c5325b2ffb1f676710f8c7c4"}, + {file = "jupyter_client-7.4.6.tar.gz", hash = "sha256:f7f9a9dc3a0ecd223ed6a5a00cf4140a5c252ec72e52d6de370748ed0aa083dd"}, +] +jupyter-core = [ + {file = "jupyter_core-4.12.0-py3-none-any.whl", hash = "sha256:a54672c539333258495579f6964144924e0aa7b07f7069947bef76d7ea5cb4c1"}, + {file = "jupyter_core-4.12.0.tar.gz", hash = "sha256:87f39d7642412ae8a52291cc68e71ac01dfa2c735df2701f8108251d51b4f460"}, +] +jupyter-server = [ + {file = "jupyter_server-1.23.2-py3-none-any.whl", hash = "sha256:c01d0e84c22a14dd6b0e7d8ce4105b08a3426b46582668e28046a64c07311a4f"}, + {file = "jupyter_server-1.23.2.tar.gz", hash = "sha256:69cb954ef02c0ba1837787e34e4a1240c93c8eb590662fae1840778861957660"}, +] +jupyterlab = [ + {file = "jupyterlab-3.5.0-py3-none-any.whl", hash = "sha256:f433059fe0e12d75ea90a81a0b6721113bb132857e3ec2197780b6fe84cbcbde"}, + {file = "jupyterlab-3.5.0.tar.gz", hash = "sha256:e02556c8ea1b386963c4b464e4618aee153c5416b07ab481425c817a033323a2"}, +] +jupyterlab-pygments = [ + {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, + {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, +] +jupyterlab-server = [ + {file = "jupyterlab_server-2.16.3-py3-none-any.whl", hash = "sha256:d18eb623428b4ee732c2258afaa365eedd70f38b609981ea040027914df32bc6"}, + {file = "jupyterlab_server-2.16.3.tar.gz", hash = "sha256:635a0b176a901f19351c02221a124e59317c476f511200409b7d867e8b2905c3"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +matplotlib-inline = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] +mistune = [ + {file = "mistune-2.0.4-py2.py3-none-any.whl", hash = "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d"}, + {file = "mistune-2.0.4.tar.gz", hash = "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"}, +] +mypy = [ + {file = "mypy-0.990-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa"}, + {file = "mypy-0.990-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4"}, + {file = "mypy-0.990-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9"}, + {file = "mypy-0.990-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd"}, + {file = "mypy-0.990-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47"}, + {file = "mypy-0.990-cp310-cp310-win_amd64.whl", hash = "sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd"}, + {file = "mypy-0.990-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2"}, + {file = "mypy-0.990-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46"}, + {file = "mypy-0.990-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0"}, + {file = "mypy-0.990-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c"}, + {file = "mypy-0.990-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea"}, + {file = "mypy-0.990-cp311-cp311-win_amd64.whl", hash = "sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec"}, + {file = "mypy-0.990-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852"}, + {file = "mypy-0.990-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af"}, + {file = "mypy-0.990-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013"}, + {file = "mypy-0.990-cp37-cp37m-win_amd64.whl", hash = "sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5"}, + {file = "mypy-0.990-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db"}, + {file = "mypy-0.990-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb"}, + {file = "mypy-0.990-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff"}, + {file = "mypy-0.990-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466"}, + {file = "mypy-0.990-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706"}, + {file = "mypy-0.990-cp38-cp38-win_amd64.whl", hash = "sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef"}, + {file = "mypy-0.990-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37"}, + {file = "mypy-0.990-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"}, + {file = "mypy-0.990-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695"}, + {file = "mypy-0.990-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8"}, + {file = "mypy-0.990-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5"}, + {file = "mypy-0.990-cp39-cp39-win_amd64.whl", hash = "sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74"}, + {file = "mypy-0.990-py3-none-any.whl", hash = "sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6"}, + {file = "mypy-0.990.tar.gz", hash = "sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nbclassic = [ + {file = "nbclassic-0.4.8-py3-none-any.whl", hash = "sha256:cbf05df5842b420d5cece0143462380ea9d308ff57c2dc0eb4d6e035b18fbfb3"}, + {file = "nbclassic-0.4.8.tar.gz", hash = "sha256:c74d8a500f8e058d46b576a41e5bc640711e1032cf7541dde5f73ea49497e283"}, +] +nbclient = [ + {file = "nbclient-0.7.0-py3-none-any.whl", hash = "sha256:434c91385cf3e53084185334d675a0d33c615108b391e260915d1aa8e86661b8"}, + {file = "nbclient-0.7.0.tar.gz", hash = "sha256:a1d844efd6da9bc39d2209bf996dbd8e07bf0f36b796edfabaa8f8a9ab77c3aa"}, +] +nbconvert = [ + {file = "nbconvert-7.2.5-py3-none-any.whl", hash = "sha256:3e90e108bb5637b5b8a1422af1156af1368b39dd25369ff7faa7dfdcdef18f81"}, + {file = "nbconvert-7.2.5.tar.gz", hash = "sha256:8fdc44fd7d9424db7fdc6e1e834a02f6b8620ffb653767388be2f9eb16f84184"}, +] +nbformat = [ + {file = "nbformat-5.7.0-py3-none-any.whl", hash = "sha256:1b05ec2c552c2f1adc745f4eddce1eac8ca9ffd59bb9fd859e827eaa031319f9"}, + {file = "nbformat-5.7.0.tar.gz", hash = "sha256:1d4760c15c1a04269ef5caf375be8b98dd2f696e5eb9e603ec2bf091f9b0d3f3"}, +] +nest-asyncio = [ + {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, + {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, +] +nodeenv = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] +notebook = [ + {file = "notebook-6.5.2-py3-none-any.whl", hash = "sha256:e04f9018ceb86e4fa841e92ea8fb214f8d23c1cedfde530cc96f92446924f0e4"}, + {file = "notebook-6.5.2.tar.gz", hash = "sha256:c1897e5317e225fc78b45549a6ab4b668e4c996fd03a04e938fe5e7af2bfffd0"}, +] +notebook-shim = [ + {file = "notebook_shim-0.2.2-py3-none-any.whl", hash = "sha256:9c6c30f74c4fbea6fce55c1be58e7fd0409b1c681b075dcedceb005db5026949"}, + {file = "notebook_shim-0.2.2.tar.gz", hash = "sha256:090e0baf9a5582ff59b607af523ca2db68ff216da0c69956b62cab2ef4fc9c3f"}, +] +numpy = [ + {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, + {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, + {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, + {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, + {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, + {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, + {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, + {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, + {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, + {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, + {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, + {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, + {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, + {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, +] +nvidia-cublas-cu11 = [ + {file = "nvidia_cublas_cu11-11.10.3.66-py3-none-manylinux1_x86_64.whl", hash = "sha256:d32e4d75f94ddfb93ea0a5dda08389bcc65d8916a25cb9f37ac89edaeed3bded"}, + {file = "nvidia_cublas_cu11-11.10.3.66-py3-none-win_amd64.whl", hash = "sha256:8ac17ba6ade3ed56ab898a036f9ae0756f1e81052a317bf98f8c6d18dc3ae49e"}, +] +nvidia-cuda-nvrtc-cu11 = [ + {file = "nvidia_cuda_nvrtc_cu11-11.7.99-2-py3-none-manylinux1_x86_64.whl", hash = "sha256:9f1562822ea264b7e34ed5930567e89242d266448e936b85bc97a3370feabb03"}, + {file = "nvidia_cuda_nvrtc_cu11-11.7.99-py3-none-manylinux1_x86_64.whl", hash = "sha256:f7d9610d9b7c331fa0da2d1b2858a4a8315e6d49765091d28711c8946e7425e7"}, + {file = "nvidia_cuda_nvrtc_cu11-11.7.99-py3-none-win_amd64.whl", hash = "sha256:f2effeb1309bdd1b3854fc9b17eaf997808f8b25968ce0c7070945c4265d64a3"}, +] +nvidia-cuda-runtime-cu11 = [ + {file = "nvidia_cuda_runtime_cu11-11.7.99-py3-none-manylinux1_x86_64.whl", hash = "sha256:cc768314ae58d2641f07eac350f40f99dcb35719c4faff4bc458a7cd2b119e31"}, + {file = "nvidia_cuda_runtime_cu11-11.7.99-py3-none-win_amd64.whl", hash = "sha256:bc77fa59a7679310df9d5c70ab13c4e34c64ae2124dd1efd7e5474b71be125c7"}, +] +nvidia-cudnn-cu11 = [ + {file = "nvidia_cudnn_cu11-8.5.0.96-2-py3-none-manylinux1_x86_64.whl", hash = "sha256:402f40adfc6f418f9dae9ab402e773cfed9beae52333f6d86ae3107a1b9527e7"}, + {file = "nvidia_cudnn_cu11-8.5.0.96-py3-none-manylinux1_x86_64.whl", hash = "sha256:71f8111eb830879ff2836db3cccf03bbd735df9b0d17cd93761732ac50a8a108"}, +] +orjson = [ + {file = "orjson-3.8.2-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:43e69b360c2851b45c7dbab3b95f7fa8469df73fab325a683f7389c4db63aa71"}, + {file = "orjson-3.8.2-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:64c5da5c9679ef3d85e9bbcbb62f4ccdc1f1975780caa20f2ec1e37b4da6bd36"}, + {file = "orjson-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c632a2157fa9ec098d655287e9e44809615af99837c49f53d96bfbca453c5bd"}, + {file = "orjson-3.8.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f63da6309c282a2b58d4a846f0717f6440356b4872838b9871dc843ed1fe2b38"}, + {file = "orjson-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9be25c313ba2d5478829d949165445c3bd36c62e07092b4ba8dbe5426574d1"}, + {file = "orjson-3.8.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4bcce53e9e088f82633f784f79551fcd7637943ab56c51654aaf9d4c1d5cfa54"}, + {file = "orjson-3.8.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:33edb5379c6e6337f9383c85fe4080ce3aa1057cc2ce29345b7239461f50cbd6"}, + {file = "orjson-3.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:da35d347115758bbc8bfaf39bb213c42000f2a54e3f504c84374041d20835cd6"}, + {file = "orjson-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d755d94a90a941b91b4d39a6b02e289d8ba358af2d1a911edf266be7942609dc"}, + {file = "orjson-3.8.2-cp310-none-win_amd64.whl", hash = "sha256:7ea96923e26390b2142602ebb030e2a4db9351134696e0b219e5106bddf9b48e"}, + {file = "orjson-3.8.2-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:a0d89de876e6f1cef917a2338378a60a98584e1c2e1c67781e20b6ed1c512478"}, + {file = "orjson-3.8.2-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8d47e7592fe938aec898eb22ea4946298c018133df084bc78442ff18e2c6347c"}, + {file = "orjson-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3d9f1043f618d0c64228aab9711e5bd822253c50b6c56223951e32b51f81d62"}, + {file = "orjson-3.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed10600e8b08f1e87b656ad38ab316191ce94f2c9adec57035680c0dc9e93c81"}, + {file = "orjson-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99c49e49a04bf61fee7aaea6d92ac2b1fcf6507aea894bbdf3fbb25fe792168c"}, + {file = "orjson-3.8.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:1463674f8efe6984902473d7b5ce3edf444c1fcd09dc8aa4779638a28fb9ca01"}, + {file = "orjson-3.8.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c1ef75f1d021d817e5c60a42da0b4b7e3123b1b37415260b8415666ddacc7cd7"}, + {file = "orjson-3.8.2-cp311-none-win_amd64.whl", hash = "sha256:b6007e1ac8564b13b2521720929e8bb3ccd3293d9fdf38f28728dcc06db6248f"}, + {file = "orjson-3.8.2-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a02c13ae523221576b001071354380e277346722cc6b7fdaacb0fd6db5154b3e"}, + {file = "orjson-3.8.2-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:fa2e565cf8ffdb37ce1887bd1592709ada7f701e61aa4b1e710be94b0aecbab4"}, + {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1d8864288f7c5fccc07b43394f83b721ddc999f25dccfb5d0651671a76023f5"}, + {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1874c05d0bb994601fa2d51605cb910d09343c6ebd36e84a573293523fab772a"}, + {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:349387ed6989e5db22e08c9af8d7ca14240803edc50de451d48d41a0e7be30f6"}, + {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:4e42b19619d6e97e201053b865ca4e62a48da71165f4081508ada8e1b91c6a30"}, + {file = "orjson-3.8.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:bc112c17e607c59d1501e72afb44226fa53d947d364aed053f0c82d153e29616"}, + {file = "orjson-3.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6fda669211f2ed1fc2c8130187ec90c96b4f77b6a250004e666d2ef8ed524e5f"}, + {file = "orjson-3.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:aebd4e80fea0f20578fd0452908b9206a6a0d5ae9f5c99b6e665bbcd989e56cd"}, + {file = "orjson-3.8.2-cp37-none-win_amd64.whl", hash = "sha256:9f3cd0394eb6d265beb2a1572b5663bc910883ddbb5cdfbcb660f5a0444e7fd8"}, + {file = "orjson-3.8.2-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:74e7d54d11b3da42558d69a23bf92c2c48fabf69b38432d5eee2c5b09cd4c433"}, + {file = "orjson-3.8.2-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:8cbadc9be748a823f9c743c7631b1ee95d3925a9c0b21de4e862a1d57daa10ec"}, + {file = "orjson-3.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07d5a8c69a2947d9554a00302734fe3d8516415c8b280963c92bc1033477890"}, + {file = "orjson-3.8.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6b364ea01d1b71b9f97bf97af9eb79ebee892df302e127a9e2e4f8eaa74d6b98"}, + {file = "orjson-3.8.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b98a8c825a59db94fbe8e0cce48618624c5a6fb1436467322d90667c08a0bf80"}, + {file = "orjson-3.8.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:ab63103f60b516c0fce9b62cb4773f689a82ab56e19ef2387b5a3182f80c0d78"}, + {file = "orjson-3.8.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:73ab3f4288389381ae33ab99f914423b69570c88d626d686764634d5e0eeb909"}, + {file = "orjson-3.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2ab3fd8728e12c36e20c6d9d70c9e15033374682ce5acb6ed6a08a80dacd254d"}, + {file = "orjson-3.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cde11822cf71a7f0daaa84223249b2696a2b6cda7fa587e9fd762dff1a8848e4"}, + {file = "orjson-3.8.2-cp38-none-win_amd64.whl", hash = "sha256:b14765ea5aabfeab1a194abfaa0be62c9fee6480a75ac8c6974b4eeede3340b4"}, + {file = "orjson-3.8.2-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:6068a27d59d989d4f2864c2fc3440eb7126a0cfdfaf8a4ad136b0ffd932026ae"}, + {file = "orjson-3.8.2-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6bf36fa759a1b941fc552ad76b2d7fb10c1d2a20c056be291ea45eb6ae1da09b"}, + {file = "orjson-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f436132e62e647880ca6988974c8e3165a091cb75cbed6c6fd93e931630c22fa"}, + {file = "orjson-3.8.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ecd8936259a5920b52a99faf62d4efeb9f5e25a0aacf0cce1e9fa7c37af154f"}, + {file = "orjson-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c13114b345cda33644f64e92fe5d8737828766cf02fbbc7d28271a95ea546832"}, + {file = "orjson-3.8.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6e43cdc3ddf96bdb751b748b1984b701125abacca8fc2226b808d203916e8cba"}, + {file = "orjson-3.8.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ee39071da2026b11e4352d6fc3608a7b27ee14bc699fd240f4e604770bc7a255"}, + {file = "orjson-3.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1c3833976ebbeb3b5b6298cb22e23bf18453f6b80802103b7d08f7dd8a61611d"}, + {file = "orjson-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b9a34519d3d70935e1cd3797fbed8fbb6f61025182bea0140ca84d95b6f8fbe5"}, + {file = "orjson-3.8.2-cp39-none-win_amd64.whl", hash = "sha256:2734086d9a3dd9591c4be7d05aff9beccc086796d3f243685e56b7973ebac5bc"}, + {file = "orjson-3.8.2.tar.gz", hash = "sha256:a2fb95a45031ccf278e44341027b3035ab99caa32aa173279b1f0a06324f434b"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pandocfilters = [ + {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, + {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, +] +parso = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] +pathspec = [ + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +pillow = [ + {file = "Pillow-9.3.0-1-cp37-cp37m-win32.whl", hash = "sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74"}, + {file = "Pillow-9.3.0-1-cp37-cp37m-win_amd64.whl", hash = "sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2"}, + {file = "Pillow-9.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c"}, + {file = "Pillow-9.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de"}, + {file = "Pillow-9.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7"}, + {file = "Pillow-9.3.0-cp310-cp310-win32.whl", hash = "sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91"}, + {file = "Pillow-9.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20"}, + {file = "Pillow-9.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812"}, + {file = "Pillow-9.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c"}, + {file = "Pillow-9.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11"}, + {file = "Pillow-9.3.0-cp311-cp311-win32.whl", hash = "sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c"}, + {file = "Pillow-9.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef"}, + {file = "Pillow-9.3.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b"}, + {file = "Pillow-9.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee"}, + {file = "Pillow-9.3.0-cp37-cp37m-win32.whl", hash = "sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29"}, + {file = "Pillow-9.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4"}, + {file = "Pillow-9.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07"}, + {file = "Pillow-9.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636"}, + {file = "Pillow-9.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32"}, + {file = "Pillow-9.3.0-cp38-cp38-win32.whl", hash = "sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"}, + {file = "Pillow-9.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad"}, + {file = "Pillow-9.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd"}, + {file = "Pillow-9.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c"}, + {file = "Pillow-9.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448"}, + {file = "Pillow-9.3.0-cp39-cp39-win32.whl", hash = "sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48"}, + {file = "Pillow-9.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e"}, + {file = "Pillow-9.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65"}, + {file = "Pillow-9.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8"}, + {file = "Pillow-9.3.0.tar.gz", hash = "sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f"}, +] +pkgutil-resolve-name = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] +platformdirs = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] +pluggy = [ + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, +] +pre-commit = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] +prometheus-client = [ + {file = "prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"}, + {file = "prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.32-py3-none-any.whl", hash = "sha256:24becda58d49ceac4dc26232eb179ef2b21f133fecda7eed6018d341766ed76e"}, + {file = "prompt_toolkit-3.0.32.tar.gz", hash = "sha256:e7f2129cba4ff3b3656bbdda0e74ee00d2f874a8bcdb9dd16f5fec7b3e173cae"}, +] +protobuf = [ + {file = "protobuf-4.21.9-cp310-abi3-win32.whl", hash = "sha256:6e0be9f09bf9b6cf497b27425487706fa48c6d1632ddd94dab1a5fe11a422392"}, + {file = "protobuf-4.21.9-cp310-abi3-win_amd64.whl", hash = "sha256:a7d0ea43949d45b836234f4ebb5ba0b22e7432d065394b532cdca8f98415e3cf"}, + {file = "protobuf-4.21.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ab0b8918c136345ff045d4b3d5f719b505b7c8af45092d7f45e304f55e50a1"}, + {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:2c9c2ed7466ad565f18668aa4731c535511c5d9a40c6da39524bccf43e441719"}, + {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:e575c57dc8b5b2b2caa436c16d44ef6981f2235eb7179bfc847557886376d740"}, + {file = "protobuf-4.21.9-cp37-cp37m-win32.whl", hash = "sha256:9227c14010acd9ae7702d6467b4625b6fe853175a6b150e539b21d2b2f2b409c"}, + {file = "protobuf-4.21.9-cp37-cp37m-win_amd64.whl", hash = "sha256:a419cc95fca8694804709b8c4f2326266d29659b126a93befe210f5bbc772536"}, + {file = "protobuf-4.21.9-cp38-cp38-win32.whl", hash = "sha256:5b0834e61fb38f34ba8840d7dcb2e5a2f03de0c714e0293b3963b79db26de8ce"}, + {file = "protobuf-4.21.9-cp38-cp38-win_amd64.whl", hash = "sha256:84ea107016244dfc1eecae7684f7ce13c788b9a644cd3fca5b77871366556444"}, + {file = "protobuf-4.21.9-cp39-cp39-win32.whl", hash = "sha256:f9eae277dd240ae19bb06ff4e2346e771252b0e619421965504bd1b1bba7c5fa"}, + {file = "protobuf-4.21.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e312e280fbe3c74ea9e080d9e6080b636798b5e3939242298b591064470b06b"}, + {file = "protobuf-4.21.9-py2.py3-none-any.whl", hash = "sha256:7eb8f2cc41a34e9c956c256e3ac766cf4e1a4c9c925dc757a41a01be3e852965"}, + {file = "protobuf-4.21.9-py3-none-any.whl", hash = "sha256:48e2cd6b88c6ed3d5877a3ea40df79d08374088e89bedc32557348848dff250b"}, + {file = "protobuf-4.21.9.tar.gz", hash = "sha256:61f21493d96d2a77f9ca84fefa105872550ab5ef71d21c458eb80edcf4885a99"}, +] +psutil = [ + {file = "psutil-5.9.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c1ca331af862803a42677c120aff8a814a804e09832f166f226bfd22b56feee8"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:68908971daf802203f3d37e78d3f8831b6d1014864d7a85937941bb35f09aefe"}, + {file = "psutil-5.9.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ff89f9b835100a825b14c2808a106b6fdcc4b15483141482a12c725e7f78549"}, + {file = "psutil-5.9.4-cp27-cp27m-win32.whl", hash = "sha256:852dd5d9f8a47169fe62fd4a971aa07859476c2ba22c2254d4a1baa4e10b95ad"}, + {file = "psutil-5.9.4-cp27-cp27m-win_amd64.whl", hash = "sha256:9120cd39dca5c5e1c54b59a41d205023d436799b1c8c4d3ff71af18535728e94"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, + {file = "psutil-5.9.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:efeae04f9516907be44904cc7ce08defb6b665128992a56957abc9b61dca94b7"}, + {file = "psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16653106f3b59386ffe10e0bad3bb6299e169d5327d3f187614b1cb8f24cf2e1"}, + {file = "psutil-5.9.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54c0d3d8e0078b7666984e11b12b88af2db11d11249a8ac8920dd5ef68a66e08"}, + {file = "psutil-5.9.4-cp36-abi3-win32.whl", hash = "sha256:149555f59a69b33f056ba1c4eb22bb7bf24332ce631c44a319cec09f876aaeff"}, + {file = "psutil-5.9.4-cp36-abi3-win_amd64.whl", hash = "sha256:fd8522436a6ada7b4aad6638662966de0d61d241cb821239b2ae7013d41a43d4"}, + {file = "psutil-5.9.4-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:6001c809253a29599bc0dfd5179d9f8a5779f9dffea1da0f13c53ee568115e1e"}, + {file = "psutil-5.9.4.tar.gz", hash = "sha256:3d7f9739eb435d4b1338944abe23f49584bde5395f27487d2ee25ad9a8774a62"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] +pydantic = [ + {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, + {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, + {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, + {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, + {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, + {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, + {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, + {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, + {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, +] +pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pyrsistent = [ + {file = "pyrsistent-0.19.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d6982b5a0237e1b7d876b60265564648a69b14017f3b5f908c5be2de3f9abb7a"}, + {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:187d5730b0507d9285a96fca9716310d572e5464cadd19f22b63a6976254d77a"}, + {file = "pyrsistent-0.19.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:055ab45d5911d7cae397dc418808d8802fb95262751872c841c170b0dbf51eed"}, + {file = "pyrsistent-0.19.2-cp310-cp310-win32.whl", hash = "sha256:456cb30ca8bff00596519f2c53e42c245c09e1a4543945703acd4312949bfd41"}, + {file = "pyrsistent-0.19.2-cp310-cp310-win_amd64.whl", hash = "sha256:b39725209e06759217d1ac5fcdb510e98670af9e37223985f330b611f62e7425"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aede922a488861de0ad00c7630a6e2d57e8023e4be72d9d7147a9fcd2d30712"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879b4c2f4d41585c42df4d7654ddffff1239dc4065bc88b745f0341828b83e78"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43bec251bbd10e3cb58ced80609c5c1eb238da9ca78b964aea410fb820d00d6"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-win32.whl", hash = "sha256:d690b18ac4b3e3cab73b0b7aa7dbe65978a172ff94970ff98d82f2031f8971c2"}, + {file = "pyrsistent-0.19.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3ba4134a3ff0fc7ad225b6b457d1309f4698108fb6b35532d015dca8f5abed73"}, + {file = "pyrsistent-0.19.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a178209e2df710e3f142cbd05313ba0c5ebed0a55d78d9945ac7a4e09d923308"}, + {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e371b844cec09d8dc424d940e54bba8f67a03ebea20ff7b7b0d56f526c71d584"}, + {file = "pyrsistent-0.19.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111156137b2e71f3a9936baf27cb322e8024dac3dc54ec7fb9f0bcf3249e68bb"}, + {file = "pyrsistent-0.19.2-cp38-cp38-win32.whl", hash = "sha256:e5d8f84d81e3729c3b506657dddfe46e8ba9c330bf1858ee33108f8bb2adb38a"}, + {file = "pyrsistent-0.19.2-cp38-cp38-win_amd64.whl", hash = "sha256:9cd3e9978d12b5d99cbdc727a3022da0430ad007dacf33d0bf554b96427f33ab"}, + {file = "pyrsistent-0.19.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f1258f4e6c42ad0b20f9cfcc3ada5bd6b83374516cd01c0960e3cb75fdca6770"}, + {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21455e2b16000440e896ab99e8304617151981ed40c29e9507ef1c2e4314ee95"}, + {file = "pyrsistent-0.19.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd880614c6237243ff53a0539f1cb26987a6dc8ac6e66e0c5a40617296a045e"}, + {file = "pyrsistent-0.19.2-cp39-cp39-win32.whl", hash = "sha256:71d332b0320642b3261e9fee47ab9e65872c2bd90260e5d225dabeed93cbd42b"}, + {file = "pyrsistent-0.19.2-cp39-cp39-win_amd64.whl", hash = "sha256:dec3eac7549869365fe263831f576c8457f6c833937c68542d08fde73457d291"}, + {file = "pyrsistent-0.19.2-py3-none-any.whl", hash = "sha256:ea6b79a02a28550c98b6ca9c35b9f492beaa54d7c5c9e9949555893c8a9234d0"}, + {file = "pyrsistent-0.19.2.tar.gz", hash = "sha256:bfa0351be89c9fcbcb8c9879b826f4353be10f58f8a677efab0c017bf7137ec2"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +pytest-asyncio = [ + {file = "pytest-asyncio-0.20.2.tar.gz", hash = "sha256:32a87a9836298a881c0ec637ebcc952cfe23a56436bdc0d09d1511941dd8a812"}, + {file = "pytest_asyncio-0.20.2-py3-none-any.whl", hash = "sha256:07e0abf9e6e6b95894a39f688a4a875d63c2128f76c02d03d16ccbc35bcc0f8a"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +pytz = [ + {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, + {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, +] +pywin32 = [ + {file = "pywin32-305-cp310-cp310-win32.whl", hash = "sha256:421f6cd86e84bbb696d54563c48014b12a23ef95a14e0bdba526be756d89f116"}, + {file = "pywin32-305-cp310-cp310-win_amd64.whl", hash = "sha256:73e819c6bed89f44ff1d690498c0a811948f73777e5f97c494c152b850fad478"}, + {file = "pywin32-305-cp310-cp310-win_arm64.whl", hash = "sha256:742eb905ce2187133a29365b428e6c3b9001d79accdc30aa8969afba1d8470f4"}, + {file = "pywin32-305-cp311-cp311-win32.whl", hash = "sha256:19ca459cd2e66c0e2cc9a09d589f71d827f26d47fe4a9d09175f6aa0256b51c2"}, + {file = "pywin32-305-cp311-cp311-win_amd64.whl", hash = "sha256:326f42ab4cfff56e77e3e595aeaf6c216712bbdd91e464d167c6434b28d65990"}, + {file = "pywin32-305-cp311-cp311-win_arm64.whl", hash = "sha256:4ecd404b2c6eceaca52f8b2e3e91b2187850a1ad3f8b746d0796a98b4cea04db"}, + {file = "pywin32-305-cp36-cp36m-win32.whl", hash = "sha256:48d8b1659284f3c17b68587af047d110d8c44837736b8932c034091683e05863"}, + {file = "pywin32-305-cp36-cp36m-win_amd64.whl", hash = "sha256:13362cc5aa93c2beaf489c9c9017c793722aeb56d3e5166dadd5ef82da021fe1"}, + {file = "pywin32-305-cp37-cp37m-win32.whl", hash = "sha256:a55db448124d1c1484df22fa8bbcbc45c64da5e6eae74ab095b9ea62e6d00496"}, + {file = "pywin32-305-cp37-cp37m-win_amd64.whl", hash = "sha256:109f98980bfb27e78f4df8a51a8198e10b0f347257d1e265bb1a32993d0c973d"}, + {file = "pywin32-305-cp38-cp38-win32.whl", hash = "sha256:9dd98384da775afa009bc04863426cb30596fd78c6f8e4e2e5bbf4edf8029504"}, + {file = "pywin32-305-cp38-cp38-win_amd64.whl", hash = "sha256:56d7a9c6e1a6835f521788f53b5af7912090674bb84ef5611663ee1595860fc7"}, + {file = "pywin32-305-cp39-cp39-win32.whl", hash = "sha256:9d968c677ac4d5cbdaa62fd3014ab241718e619d8e36ef8e11fb930515a1e918"}, + {file = "pywin32-305-cp39-cp39-win_amd64.whl", hash = "sha256:50768c6b7c3f0b38b7fb14dd4104da93ebced5f1a50dc0e834594bff6fbe1271"}, +] +pywinpty = [ + {file = "pywinpty-2.0.9-cp310-none-win_amd64.whl", hash = "sha256:30a7b371446a694a6ce5ef906d70ac04e569de5308c42a2bdc9c3bc9275ec51f"}, + {file = "pywinpty-2.0.9-cp311-none-win_amd64.whl", hash = "sha256:d78ef6f4bd7a6c6f94dc1a39ba8fb028540cc39f5cb593e756506db17843125f"}, + {file = "pywinpty-2.0.9-cp37-none-win_amd64.whl", hash = "sha256:5ed36aa087e35a3a183f833631b3e4c1ae92fe2faabfce0fa91b77ed3f0f1382"}, + {file = "pywinpty-2.0.9-cp38-none-win_amd64.whl", hash = "sha256:2352f44ee913faaec0a02d3c112595e56b8af7feeb8100efc6dc1a8685044199"}, + {file = "pywinpty-2.0.9-cp39-none-win_amd64.whl", hash = "sha256:ba75ec55f46c9e17db961d26485b033deb20758b1731e8e208e1e8a387fcf70c"}, + {file = "pywinpty-2.0.9.tar.gz", hash = "sha256:01b6400dd79212f50a2f01af1c65b781290ff39610853db99bf03962eb9a615f"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +pyzmq = [ + {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066"}, + {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6"}, + {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef"}, + {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4"}, + {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913"}, + {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3"}, + {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246"}, + {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1"}, + {file = "pyzmq-24.0.1-cp310-cp310-win32.whl", hash = "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd"}, + {file = "pyzmq-24.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340"}, + {file = "pyzmq-24.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00"}, + {file = "pyzmq-24.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc"}, + {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef"}, + {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3"}, + {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace"}, + {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff"}, + {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8"}, + {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1"}, + {file = "pyzmq-24.0.1-cp311-cp311-win32.whl", hash = "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2"}, + {file = "pyzmq-24.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8"}, + {file = "pyzmq-24.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4"}, + {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d"}, + {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc"}, + {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d"}, + {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83"}, + {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639"}, + {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e"}, + {file = "pyzmq-24.0.1-cp36-cp36m-win32.whl", hash = "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc"}, + {file = "pyzmq-24.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be"}, + {file = "pyzmq-24.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33"}, + {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1"}, + {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6"}, + {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c"}, + {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107"}, + {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99"}, + {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2"}, + {file = "pyzmq-24.0.1-cp37-cp37m-win32.whl", hash = "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b"}, + {file = "pyzmq-24.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227"}, + {file = "pyzmq-24.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e"}, + {file = "pyzmq-24.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc"}, + {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff"}, + {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7"}, + {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66"}, + {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287"}, + {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330"}, + {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f"}, + {file = "pyzmq-24.0.1-cp38-cp38-win32.whl", hash = "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58"}, + {file = "pyzmq-24.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a"}, + {file = "pyzmq-24.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62"}, + {file = "pyzmq-24.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4"}, + {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b"}, + {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b"}, + {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d"}, + {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea"}, + {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209"}, + {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58"}, + {file = "pyzmq-24.0.1-cp39-cp39-win32.whl", hash = "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333"}, + {file = "pyzmq-24.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b"}, + {file = "pyzmq-24.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0"}, + {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5"}, + {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409"}, + {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d"}, + {file = "pyzmq-24.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944"}, + {file = "pyzmq-24.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804"}, + {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f"}, + {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43"}, + {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8"}, + {file = "pyzmq-24.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879"}, + {file = "pyzmq-24.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a"}, + {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f"}, + {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75"}, + {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4"}, + {file = "pyzmq-24.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066"}, + {file = "pyzmq-24.0.1.tar.gz", hash = "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] +rich = [ + {file = "rich-13.1.0-py3-none-any.whl", hash = "sha256:f846bff22a43e8508aebf3f0f2410ce1c6f4cde429098bd58d91fde038c57299"}, + {file = "rich-13.1.0.tar.gz", hash = "sha256:81c73a30b144bbcdedc13f4ea0b6ffd7fdc3b0d3cc259a9402309c8e4aee1964"}, +] +ruff = [ + {file = "ruff-0.0.165-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b13d433c38966c5fe7c044de55037c9715495a2941df457a27c691f519e4a94d"}, + {file = "ruff-0.0.165-py3-none-macosx_10_9_x86_64.macosx_10_9_arm64.macosx_10_9_universal2.whl", hash = "sha256:4c69d221ceb75a9a464f9a3d000e795806dedb1d010da874859809cbe38e3d30"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3baef2179dd2067db1287c2dcb66b5ab1b1a124746d0f65485cc1129717d6554"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d70502afbefac54f85a1754869f9cd3477dc33c9ae6ca2338a11ac2b780ed06"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:133f076ceabc25ff5aec017fe8084b3eedd82ece28f287fbd2e1685bb14a2554"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c92cc05cceee332ed221702f7a63c19dca2cb87c33bf06b9a085630070c33192"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eadca0b7116d49ad6faed505ad181bca39bca111478a4b2f1f8c39a632955c2f"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85135ffc825edfcf6fa17ec2e5569aaba3fa7cd096d45a4d5fc896285b266a5b"}, + {file = "ruff-0.0.165-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1a9f6d0139571d05392a1f7f94a4e217768a9f8595910ab2dfe745a0ca1fab7"}, + {file = "ruff-0.0.165-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4109826311fabc68633073c408048448ab870456adf1c40252795131de2624a5"}, + {file = "ruff-0.0.165-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5cac57e0a80f593aebe3975cf9f8c776e13c6236608d2fef2893f7980a2a0510"}, + {file = "ruff-0.0.165-py3-none-musllinux_1_2_i686.whl", hash = "sha256:32f16721360b3e973f1e3fe013a1aa33522b24532925e622417080beda5d7478"}, + {file = "ruff-0.0.165-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e0be5acdd86269963f1fa1c4dd3c3ec37f14c847d889591ff5bc1fd934c0cfa3"}, + {file = "ruff-0.0.165-py3-none-win32.whl", hash = "sha256:dacd94f66c6d42c23c22776d9cc6c726bf42987a38358953bec0e4eec0b72574"}, + {file = "ruff-0.0.165-py3-none-win_amd64.whl", hash = "sha256:c20ba25907d52fae33ea363a741e3ba03fc5e9712cbc3b12572897768f24bcf6"}, + {file = "ruff-0.0.165.tar.gz", hash = "sha256:5468b30e0c5888fd436568a47da31f8c827affbacaba06c1ca8ad1f7f0df9e4e"}, +] +send2trash = [ + {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, + {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, +] +setuptools = [ + {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, + {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sniffio = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] +starlette = [ + {file = "starlette-0.21.0-py3-none-any.whl", hash = "sha256:0efc058261bbcddeca93cad577efd36d0c8a317e44376bcfc0e097a2b3dc24a7"}, + {file = "starlette-0.21.0.tar.gz", hash = "sha256:b1b52305ee8f7cfc48cde383496f7c11ab897cd7112b33d998b1317dc8ef9027"}, +] +terminado = [ + {file = "terminado-0.17.0-py3-none-any.whl", hash = "sha256:bf6fe52accd06d0661d7611cc73202121ec6ee51e46d8185d489ac074ca457c2"}, + {file = "terminado-0.17.0.tar.gz", hash = "sha256:520feaa3aeab8ad64a69ca779be54be9234edb2d0d6567e76c93c2c9a4e6e43f"}, +] +tinycss2 = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +torch = [ + {file = "torch-1.13.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:f68edfea71ade3862039ba66bcedf954190a2db03b0c41a9b79afd72210abd97"}, + {file = "torch-1.13.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d2d2753519415d154de4d3e64d2eaaeefdba6b6fd7d69d5ffaef595988117700"}, + {file = "torch-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c227c16626e4ce766cca5351cc62a2358a11e8e466410a298487b9dff159eb1"}, + {file = "torch-1.13.0-cp310-none-macosx_10_9_x86_64.whl", hash = "sha256:49a949b8136b32b2ec0724cbf4c6678b54e974b7d68f19f1231eea21cde5c23b"}, + {file = "torch-1.13.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:0fdd38c96230947b1ed870fed4a560252f8d23c3a2bf4dab9d2d42b18f2e67c8"}, + {file = "torch-1.13.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:43db0723fc66ad6486f86dc4890c497937f7cd27429f28f73fb7e4d74b7482e2"}, + {file = "torch-1.13.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e643ac8d086706e82f77b5d4dfcf145a9dd37b69e03e64177fc23821754d2ed7"}, + {file = "torch-1.13.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:bb33a911460475d1594a8c8cb73f58c08293211760796d99cae8c2509b86d7f1"}, + {file = "torch-1.13.0-cp37-cp37m-win_amd64.whl", hash = "sha256:220325d0f4e69ee9edf00c04208244ef7cf22ebce083815ce272c7491f0603f5"}, + {file = "torch-1.13.0-cp37-none-macosx_10_9_x86_64.whl", hash = "sha256:cd1e67db6575e1b173a626077a54e4911133178557aac50683db03a34e2b636a"}, + {file = "torch-1.13.0-cp37-none-macosx_11_0_arm64.whl", hash = "sha256:9197ec216833b836b67e4d68e513d31fb38d9789d7cd998a08fba5b499c38454"}, + {file = "torch-1.13.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:fa768432ce4b8ffa29184c79a3376ab3de4a57b302cdf3c026a6be4c5a8ab75b"}, + {file = "torch-1.13.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:635dbb99d981a6483ca533b3dc7be18ef08dd9e1e96fb0bb0e6a99d79e85a130"}, + {file = "torch-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:857c7d5b1624c5fd979f66d2b074765733dba3f5e1cc97b7d6909155a2aae3ce"}, + {file = "torch-1.13.0-cp38-none-macosx_10_9_x86_64.whl", hash = "sha256:ef934a21da6f6a516d0a9c712a80d09c56128abdc6af8dc151bee5199b4c3b4e"}, + {file = "torch-1.13.0-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:f01a9ae0d4b69d2fc4145e8beab45b7877342dddbd4838a7d3c11ca7f6680745"}, + {file = "torch-1.13.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9ac382cedaf2f70afea41380ad8e7c06acef6b5b7e2aef3971cdad666ca6e185"}, + {file = "torch-1.13.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:e20df14d874b024851c58e8bb3846249cb120e677f7463f60c986e3661f88680"}, + {file = "torch-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a378f5091307381abfb30eb821174e12986f39b1cf7c4522bf99155256819eb"}, + {file = "torch-1.13.0-cp39-none-macosx_10_9_x86_64.whl", hash = "sha256:922a4910613b310fbeb87707f00cb76fec328eb60cc1349ed2173e7c9b6edcd8"}, + {file = "torch-1.13.0-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:47fe6228386bff6d74319a2ffe9d4ed943e6e85473d78e80502518c607d644d2"}, +] +tornado = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] +traitlets = [ + {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, + {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, +] +trimesh = [ + {file = "trimesh-3.17.1-py3-none-any.whl", hash = "sha256:a09460ee4e11c32bf9f0643b86241b3e3e2aa86296c4912a0738b76da3034c00"}, + {file = "trimesh-3.17.1.tar.gz", hash = "sha256:025bb2fa3a2e87bdd6873f11db45a7ca19216f2f8b6aed29140fca57e32c298e"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +types-pillow = [ + {file = "types-Pillow-9.3.0.1.tar.gz", hash = "sha256:f3b7cada3fa496c78d75253c6b1f07a843d625f42e5639b320a72acaff6f7cfb"}, + {file = "types_Pillow-9.3.0.1-py3-none-any.whl", hash = "sha256:79837755fe9659f29efd1016e9903ac4a500e0c73260483f07296bd6ca47668b"}, +] +types-protobuf = [ + {file = "types-protobuf-3.20.4.5.tar.gz", hash = "sha256:e9b45008d106e1d10cc77a29d2d344b85c0f01e2e643aaccf32f69e9e81b0cdd"}, + {file = "types_protobuf-3.20.4.5-py3-none-any.whl", hash = "sha256:97af5ce70d890fdb94cb0c906f5a6624ca2fef58bc04e27990a25509e992a950"}, +] +types-requests = [ + {file = "types-requests-2.28.11.7.tar.gz", hash = "sha256:0ae38633734990d019b80f5463dfa164ebd3581998ac8435f526da6fe4d598c3"}, + {file = "types_requests-2.28.11.7-py3-none-any.whl", hash = "sha256:b6a2fca8109f4fdba33052f11ed86102bddb2338519e1827387137fefc66a98b"}, +] +types-urllib3 = [ + {file = "types-urllib3-1.26.25.4.tar.gz", hash = "sha256:eec5556428eec862b1ac578fb69aab3877995a99ffec9e5a12cf7fbd0cc9daee"}, + {file = "types_urllib3-1.26.25.4-py3-none-any.whl", hash = "sha256:ed6b9e8a8be488796f72306889a06a3fc3cb1aa99af02ab8afb50144d7317e49"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +typing-inspect = [ + {file = "typing_inspect-0.8.0-py3-none-any.whl", hash = "sha256:5fbf9c1e65d4fa01e701fe12a5bca6c6e08a4ffd5bc60bfac028253a447c5188"}, + {file = "typing_inspect-0.8.0.tar.gz", hash = "sha256:8b1ff0c400943b6145df8119c41c244ca8207f1f10c9c057aeed1560e4806e3d"}, +] +urllib3 = [ + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, +] +uvicorn = [ + {file = "uvicorn-0.19.0-py3-none-any.whl", hash = "sha256:cc277f7e73435748e69e075a721841f7c4a95dba06d12a72fe9874acced16f6f"}, + {file = "uvicorn-0.19.0.tar.gz", hash = "sha256:cf538f3018536edb1f4a826311137ab4944ed741d52aeb98846f52215de57f25"}, +] +virtualenv = [ + {file = "virtualenv-20.16.7-py3-none-any.whl", hash = "sha256:efd66b00386fdb7dbe4822d172303f40cd05e50e01740b19ea42425cbe653e29"}, + {file = "virtualenv-20.16.7.tar.gz", hash = "sha256:8691e3ff9387f743e00f6bb20f70121f5e4f596cae754531f2b3b3a1b1ac696e"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] +websocket-client = [ + {file = "websocket-client-1.4.2.tar.gz", hash = "sha256:d6e8f90ca8e2dd4e8027c4561adeb9456b54044312dba655e7cae652ceb9ae59"}, + {file = "websocket_client-1.4.2-py3-none-any.whl", hash = "sha256:d6b06432f184438d99ac1f456eaf22fe1ade524c3dd16e661142dc54e9cba574"}, +] +wheel = [ + {file = "wheel-0.38.4-py3-none-any.whl", hash = "sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"}, + {file = "wheel-0.38.4.tar.gz", hash = "sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac"}, +] +zipp = [ + {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, + {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, +] diff --git a/pyproject.toml b/pyproject.toml index f41df6db23a..9ec32b9027a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ types-requests = ">=2.28.11.6" av = {version = ">=10.0.0", optional = true} fastapi = {version = ">=0.87.0", optional = true } rich = ">=13.1.0" +hnswlib = {version = "^0.7.0", extras = ["hnswlib"]} [tool.poetry.extras] common = ["protobuf"] From cc81088844b22f577496c57c7d318b8d9e19408d Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Thu, 16 Feb 2023 14:36:31 +0100 Subject: [PATCH 13/53] test: add tests for base doc store Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 10 +- tests/doc_stores/__init__.py | 0 tests/doc_stores/base_classes/__init__.py | 0 .../base_classes/test_base_doc_store.py | 266 ++++++++++++++++++ 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 tests/doc_stores/__init__.py create mode 100644 tests/doc_stores/base_classes/__init__.py create mode 100644 tests/doc_stores/base_classes/test_base_doc_store.py diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 58f0c03f272..07f27dbf24a 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -49,6 +49,7 @@ def inner(self, *args, **kwargs): f' Use keyword arguments instead.' ) self._queries.append((method_name, kwargs)) + return self return inner @@ -359,7 +360,8 @@ def get_data_by_columns( 'The schema of the documents to be indexed is not compatible' ' with the schema of the store.' ) - return { - col_name: (self.get_value(doc, col_name) for doc in docs) - for col_name in self._columns - } + + def _col_gen(col_name): + return (self.get_value(doc, col_name) for doc in docs) + + return {col_name: _col_gen(col_name) for col_name in self._columns} diff --git a/tests/doc_stores/__init__.py b/tests/doc_stores/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/doc_stores/base_classes/__init__.py b/tests/doc_stores/base_classes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/doc_stores/base_classes/test_base_doc_store.py b/tests/doc_stores/base_classes/test_base_doc_store.py new file mode 100644 index 00000000000..39074ee5086 --- /dev/null +++ b/tests/doc_stores/base_classes/test_base_doc_store.py @@ -0,0 +1,266 @@ +import numpy as np +import pytest +from pydantic import Field + +from docarray import BaseDocument, DocumentArray +from docarray.storage.abstract_doc_store import BaseDocumentStore +from docarray.typing import ID, NdArray + + +class SimpleDoc(BaseDocument): + tens: NdArray[10] = Field(dim=1000) + + +class FlatDoc(BaseDocument): + tens_one: NdArray = Field(dim=10) + tens_two: NdArray = Field(dim=50) + + +class NestedDoc(BaseDocument): + d: SimpleDoc + + +class DeepNestedDoc(BaseDocument): + d: NestedDoc + + +class FakeQueryBuilder: + ... + + +def _identity(*x, **y): + return x, y + + +class DummyDocStore(BaseDocumentStore): + _query_builder_cls = FakeQueryBuilder + _default_column_config = {str: {'hi': 'there'}, np.ndarray: {'you': 'good?'}} + + def python_type_to_db_type(self, x): + return str + + index = _identity + num_docs = _identity + __delitem__ = _identity + __getitem__ = _identity + execute_query = _identity + find = _identity + find_batched = _identity + filter = _identity + filter_batched = _identity + text_search = _identity + text_search_batched = _identity + + +def test_parametrization(): + with pytest.raises(ValueError): + DummyDocStore() + + store = DummyDocStore[SimpleDoc]() + assert store._schema is SimpleDoc + + +def test_build_query(): + store = DummyDocStore[SimpleDoc]() + q = store.build_query() + assert isinstance(q, store._query_builder_cls) + + +def test_default_column_config(): + store = DummyDocStore[SimpleDoc]() + assert store._default_column_config == { + str: {'hi': 'there'}, + np.ndarray: {'you': 'good?'}, + } + + +def test_create_columns(): + # Simple doc + store = DummyDocStore[SimpleDoc]() + assert list(store._columns.keys()) == ['id', 'tens'] + + assert store._columns['id'].docarray_type == ID + assert store._columns['id'].db_type == str + assert store._columns['id'].n_dim is None + assert store._columns['id'].config == {'hi': 'there'} + + assert issubclass(store._columns['tens'].docarray_type, NdArray) + assert store._columns['tens'].db_type == str + assert store._columns['tens'].n_dim == 10 + assert store._columns['tens'].config == {'dim': 1000, 'hi': 'there'} + + # Flat doc + store = DummyDocStore[FlatDoc]() + assert list(store._columns.keys()) == ['id', 'tens_one', 'tens_two'] + + assert store._columns['id'].docarray_type == ID + assert store._columns['id'].db_type == str + assert store._columns['id'].n_dim is None + assert store._columns['id'].config == {'hi': 'there'} + + assert issubclass(store._columns['tens_one'].docarray_type, NdArray) + assert store._columns['tens_one'].db_type == str + assert store._columns['tens_one'].n_dim is None + assert store._columns['tens_one'].config == {'dim': 10, 'hi': 'there'} + + assert issubclass(store._columns['tens_two'].docarray_type, NdArray) + assert store._columns['tens_two'].db_type == str + assert store._columns['tens_two'].n_dim is None + assert store._columns['tens_two'].config == {'dim': 50, 'hi': 'there'} + + # Nested doc + store = DummyDocStore[NestedDoc]() + assert list(store._columns.keys()) == ['id', 'd__id', 'd__tens'] + + assert store._columns['id'].docarray_type == ID + assert store._columns['id'].db_type == str + assert store._columns['id'].n_dim is None + assert store._columns['id'].config == {'hi': 'there'} + + assert issubclass(store._columns['d__tens'].docarray_type, NdArray) + assert store._columns['d__tens'].db_type == str + assert store._columns['d__tens'].n_dim == 10 + assert store._columns['d__tens'].config == {'dim': 1000, 'hi': 'there'} + + +def test_is_schema_compatible(): + class OtherSimpleDoc(SimpleDoc): + ... + + class OtherFlatDoc(FlatDoc): + ... + + class OtherNestedDoc(NestedDoc): + ... + + store = DummyDocStore[SimpleDoc]() + assert store._is_schema_compatible([SimpleDoc(tens=np.random.random((10,)))]) + assert store._is_schema_compatible( + DocumentArray[SimpleDoc]([SimpleDoc(tens=np.random.random((10,)))]) + ) + assert store._is_schema_compatible([OtherSimpleDoc(tens=np.random.random((10,)))]) + assert store._is_schema_compatible( + DocumentArray[OtherSimpleDoc]([OtherSimpleDoc(tens=np.random.random((10,)))]) + ) + assert not store._is_schema_compatible( + [FlatDoc(tens_one=np.random.random((10,)), tens_two=np.random.random((50,)))] + ) + assert not store._is_schema_compatible( + DocumentArray[FlatDoc]( + [ + FlatDoc( + tens_one=np.random.random((10,)), tens_two=np.random.random((50,)) + ) + ] + ) + ) + + store = DummyDocStore[FlatDoc]() + assert store._is_schema_compatible( + [FlatDoc(tens_one=np.random.random((10,)), tens_two=np.random.random((50,)))] + ) + assert store._is_schema_compatible( + DocumentArray[FlatDoc]( + [ + FlatDoc( + tens_one=np.random.random((10,)), tens_two=np.random.random((50,)) + ) + ] + ) + ) + assert store._is_schema_compatible( + [ + OtherFlatDoc( + tens_one=np.random.random((10,)), tens_two=np.random.random((50,)) + ) + ] + ) + assert store._is_schema_compatible( + DocumentArray[OtherFlatDoc]( + [ + OtherFlatDoc( + tens_one=np.random.random((10,)), tens_two=np.random.random((50,)) + ) + ] + ) + ) + assert not store._is_schema_compatible([SimpleDoc(tens=np.random.random((10,)))]) + assert not store._is_schema_compatible( + DocumentArray[SimpleDoc]([SimpleDoc(tens=np.random.random((10,)))]) + ) + + store = DummyDocStore[NestedDoc]() + assert store._is_schema_compatible( + [NestedDoc(d=SimpleDoc(tens=np.random.random((10,))))] + ) + assert store._is_schema_compatible( + DocumentArray[NestedDoc]([NestedDoc(d=SimpleDoc(tens=np.random.random((10,))))]) + ) + assert store._is_schema_compatible( + [OtherNestedDoc(d=OtherSimpleDoc(tens=np.random.random((10,))))] + ) + assert store._is_schema_compatible( + DocumentArray[OtherNestedDoc]( + [OtherNestedDoc(d=OtherSimpleDoc(tens=np.random.random((10,))))] + ) + ) + assert not store._is_schema_compatible([SimpleDoc(tens=np.random.random((10,)))]) + assert not store._is_schema_compatible( + DocumentArray[SimpleDoc]([SimpleDoc(tens=np.random.random((10,)))]) + ) + + +def test_get_value(): + t = np.random.random((10,)) + + doc = SimpleDoc(tens=t) + assert np.all(DummyDocStore.get_value(doc, 'tens') == t) + + doc = FlatDoc(tens_one=t, tens_two=np.random.random((50,))) + assert np.all(DummyDocStore.get_value(doc, 'tens_one') == t) + + doc = NestedDoc(d=SimpleDoc(tens=t)) + assert np.all(DummyDocStore.get_value(doc, 'd__tens') == t) + + doc = DeepNestedDoc(d=NestedDoc(d=SimpleDoc(tens=t))) + assert np.all(DummyDocStore.get_value(doc, 'd__d__tens') == t) + + +def test_get_data_by_columns(): + store = DummyDocStore[SimpleDoc]() + docs = [SimpleDoc(tens=np.random.random((10,))) for _ in range(10)] + data_by_columns = store.get_data_by_columns(docs) + assert list(data_by_columns.keys()) == ['id', 'tens'] + assert list(data_by_columns['id']) == [doc.id for doc in docs] + assert list(data_by_columns['tens']) == [doc.tens for doc in docs] + + store = DummyDocStore[FlatDoc]() + docs = [ + FlatDoc(tens_one=np.random.random((10,)), tens_two=np.random.random((50,))) + for _ in range(10) + ] + data_by_columns = store.get_data_by_columns(docs) + assert list(data_by_columns.keys()) == ['id', 'tens_one', 'tens_two'] + assert list(data_by_columns['id']) == [doc.id for doc in docs] + assert list(data_by_columns['tens_one']) == [doc.tens_one for doc in docs] + assert list(data_by_columns['tens_two']) == [doc.tens_two for doc in docs] + + store = DummyDocStore[NestedDoc]() + docs = [NestedDoc(d=SimpleDoc(tens=np.random.random((10,)))) for _ in range(10)] + data_by_columns = store.get_data_by_columns(docs) + assert list(data_by_columns.keys()) == ['id', 'd__id', 'd__tens'] + assert list(data_by_columns['id']) == [doc.id for doc in docs] + assert list(data_by_columns['d__id']) == [doc.d.id for doc in docs] + assert list(data_by_columns['d__tens']) == [doc.d.tens for doc in docs] + + store = DummyDocStore[DeepNestedDoc]() + docs = [ + DeepNestedDoc(d=NestedDoc(d=SimpleDoc(tens=np.random.random((10,))))) + for _ in range(10) + ] + data_by_columns = store.get_data_by_columns(docs) + assert list(data_by_columns.keys()) == ['id', 'd__id', 'd__d__id', 'd__d__tens'] + assert list(data_by_columns['id']) == [doc.id for doc in docs] + assert list(data_by_columns['d__id']) == [doc.d.id for doc in docs] + assert list(data_by_columns['d__d__id']) == [doc.d.d.id for doc in docs] + assert list(data_by_columns['d__d__tens']) == [doc.d.d.tens for doc in docs] From 8cbabc207b0ecb97010db8ac826b098746fa718d Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Fri, 17 Feb 2023 11:51:28 +0100 Subject: [PATCH 14/53] test: add test for base query builder Signed-off-by: Johannes Messner --- .../base_classes/test_base_query_builder.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/doc_stores/base_classes/test_base_query_builder.py diff --git a/tests/doc_stores/base_classes/test_base_query_builder.py b/tests/doc_stores/base_classes/test_base_query_builder.py new file mode 100644 index 00000000000..bd59f4b2030 --- /dev/null +++ b/tests/doc_stores/base_classes/test_base_query_builder.py @@ -0,0 +1,33 @@ +from docarray.storage.abstract_doc_store import BaseQueryBuilder + + +class DummyQueryBuilder(BaseQueryBuilder): + def build(self): + return self._queries + + +def test_collect_calls(): + qb = DummyQueryBuilder() + qb.find(find='find', param='param') + qb.filter(filter='filter', param='param') + qb.text_search(text='text', param='param') + qb.filter_batched(filter_batched='filter_batched', param='param') + qb.text_search_batched(text_batched='text_batched', param='param') + qb.find_batched(find_batched='find_batched', param='param') + + collected_queries = qb._queries + assert collected_queries[0] == ('find', {'find': 'find', 'param': 'param'}) + assert collected_queries[1] == ('filter', {'filter': 'filter', 'param': 'param'}) + assert collected_queries[2] == ('text_search', {'text': 'text', 'param': 'param'}) + assert collected_queries[3] == ( + 'filter_batched', + {'filter_batched': 'filter_batched', 'param': 'param'}, + ) + assert collected_queries[4] == ( + 'text_search_batched', + {'text_batched': 'text_batched', 'param': 'param'}, + ) + assert collected_queries[5] == ( + 'find_batched', + {'find_batched': 'find_batched', 'param': 'param'}, + ) From 02b86703d5ab8303bdbdd217332376a9c460de42 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 10:35:38 +0100 Subject: [PATCH 15/53] refactor: settings management Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 68 +++++++--- docarray/storage/backends/HnswDocStore.py | 59 ++++----- .../base_classes/test_base_doc_store.py | 28 ++-- tests/doc_stores/base_classes/test_configs.py | 121 ++++++++++++++++++ 4 files changed, 221 insertions(+), 55 deletions(-) create mode 100644 tests/doc_stores/base_classes/test_configs.py diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 07f27dbf24a..ac10e8ad572 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from dataclasses import dataclass +from dataclasses import dataclass, replace from typing import ( Any, Dict, @@ -22,7 +22,6 @@ from docarray.array.abstract_array import AnyDocumentArray from docarray.typing import AnyTensor from docarray.utils.find import FindResult -from docarray.utils.protocols import IsDataclass TSchema = TypeVar('TSchema', bound=BaseDocument) @@ -32,6 +31,22 @@ class FindResultBatched(NamedTuple): scores: np.ndarray +@dataclass +class BaseDBConfig(ABC): + ... + + +@dataclass +class BaseRuntimeConfig(ABC): + # default configurations for every column type + # a dictionary from a column type (DB specific) to a dictionary + # of default configurations for that type + # These configs are used if no configs are specified in the `Field(...)` + # of a field in the Document schema (`cls._schema`) + # Example: `default_column_config['VARCHAR'] = {'length': 255}` + default_column_config: Dict[Type, Dict[str, Any]] + + @dataclass class _Column: docarray_type: Type @@ -82,25 +97,21 @@ class BaseDocumentStore(ABC, Generic[TSchema]): # for subclasses this is filled automatically _schema: Optional[Type[BaseDocument]] = None - # default configurations for every column type - # a dictionary from a column type (DB specific) to a dictionary - # of default configurations for that type - # These configs are used if no configs are specified in the `Field(...)` - # of a field in the Document schema (`cls._schema`) - # Example: `_default_column_config['VARCHAR'] = {'length': 255}` - _default_column_config: Dict[Any, Dict[str, Any]] = {} - - # register your query builder here + # register helper classes here _query_builder_cls: Type[BaseQueryBuilder] = BaseQueryBuilder + _db_config_cls: Type # should be dataclass + _runtime_config_cls: Type # should be dataclass - def __init__(self, config: Optional[IsDataclass] = None): + def __init__(self, db_config=None, **kwargs): if self._schema is None: raise ValueError( 'A DocumentStore must be typed with a Document type.' 'To do so, use the syntax: DocumentStore[DocumentType]' ) - self._config = config - + self._db_config = db_config if db_config else self._db_config_cls(**kwargs) + if not isinstance(self._db_config, self._db_config_cls): + raise ValueError(f'db_config must be of type {self._db_config_cls}') + self._runtime_config = self._runtime_config_cls() self._columns: Dict[str, _Column] = self._create_columns(self._schema) ##################################### @@ -238,6 +249,31 @@ def text_search_batched( # TODO(johannes) refine method signature ... + #################################################### + # Optional overrides # + # Subclasses may more may not need to change these # + #################################################### + + def configure(self, runtime_config=None, **kwargs): + """ + Configure the DocumentStore. + You can either pass a config object to `config` or pass individual config + parameters as keyword arguments. + If a configuration object is passed, it will replace the current configuration. + If keyword arguments are passed, they will update the current configuration. + + :param runtime_config: the configuration to apply + :param kwargs: individual configuration parameters + """ + if runtime_config is None: + self._runtime_config = replace(self._runtime_config, **kwargs) + else: + if not isinstance(runtime_config, self._runtime_config_cls): + raise ValueError( + f'runtime_config must be of type {self._runtime_config_cls}' + ) + self._runtime_config = runtime_config + ################################################## # Behind-the-scenes magic # # Subclasses should not need to implement these # @@ -245,7 +281,7 @@ def text_search_batched( def __class_getitem__(cls, item: Type[TSchema]): if not issubclass(item, BaseDocument): raise ValueError( - f'{cls.__name__}[item] item should be a Document not a {item} ' + f'{cls.__name__}[item] `item` should be a Document not a {item} ' ) class _DocumentStoreTyped(cls): # type: ignore @@ -296,7 +332,7 @@ def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: def _create_single_column(self, field): type_ = field.type_ db_type = self.python_type_to_db_type(type_) - config = self._default_column_config[db_type].copy() + config = self._runtime_config.default_column_config[db_type].copy() custom_config = field.field_info.extra config.update(custom_config) # parse n_dim from parametrized tensor type diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index fb3a3ad795f..0c460388e50 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -1,20 +1,9 @@ import hashlib import os import sqlite3 -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path -from typing import ( - Any, - Dict, - Generic, - List, - Optional, - Sequence, - Tuple, - Type, - TypeVar, - Union, -) +from typing import Any, Dict, Generic, List, Sequence, Tuple, Type, TypeVar, Union import hnswlib import numpy as np @@ -23,8 +12,10 @@ from docarray import BaseDocument, DocumentArray from docarray.proto import DocumentProto from docarray.storage.abstract_doc_store import ( + BaseDBConfig, BaseDocumentStore, BaseQueryBuilder, + BaseRuntimeConfig, FindResultBatched, _Column, ) @@ -32,7 +23,6 @@ from docarray.utils.filter import filter as da_filter from docarray.utils.find import FindResult from docarray.utils.misc import torch_imported -from docarray.utils.protocols import IsDataclass TSchema = TypeVar('TSchema', bound=BaseDocument) @@ -44,10 +34,30 @@ @dataclass -class HNSWConfig: +class DBConfig(BaseDBConfig): work_dir: str = '.' +@dataclass +class RuntimeConfig(BaseRuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = field( + default_factory=lambda: { + np.ndarray: { + 'dim': 128, + 'space': 'l2', # 'l2', 'ip', 'cosine' + 'max_elements': 1024, + 'ef_construction': 200, + 'M': 16, + 'allow_replace_deleted': True, # TODO(johannes) handle below + }, + None: {}, + } + ) + default_ef = 50 + default_num_threads = 1 + max_elements = None # use the one above + + class HnswQueryBuilder(BaseQueryBuilder): def build(self, *args, **kwargs) -> Any: supported_ops = ('find', 'filter') @@ -58,22 +68,13 @@ def build(self, *args, **kwargs) -> Any: class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): - _default_column_config = { - np.ndarray: { - 'dim': 128, - 'space': 'l2', - 'max_elements': 1024, - 'ef_construction': 200, - 'M': 16, - }, - None: {}, - } - _query_builder_cls = HnswQueryBuilder + _db_config_cls = DBConfig + _runtime_config_cls = RuntimeConfig - def __init__(self, config: Optional[IsDataclass] = None): - super().__init__(config) - self._work_dir = config.work_dir + def __init__(self, db_config=None, **kwargs): + super().__init__(db_config=db_config, **kwargs) + self._work_dir = self._db_config.work_dir load_existing = os.path.exists(self._work_dir) Path(self._work_dir).mkdir(parents=True, exist_ok=True) diff --git a/tests/doc_stores/base_classes/test_base_doc_store.py b/tests/doc_stores/base_classes/test_base_doc_store.py index 39074ee5086..14537ad44e2 100644 --- a/tests/doc_stores/base_classes/test_base_doc_store.py +++ b/tests/doc_stores/base_classes/test_base_doc_store.py @@ -1,9 +1,12 @@ +from dataclasses import dataclass, field +from typing import Any, Dict, Type + import numpy as np import pytest from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.abstract_doc_store import BaseDocumentStore +from docarray.storage.abstract_doc_store import BaseDocumentStore, BaseRuntimeConfig from docarray.typing import ID, NdArray @@ -32,9 +35,22 @@ def _identity(*x, **y): return x, y +@dataclass +class DBConfig: + work_dir: str = '.' + + +@dataclass +class RuntimeConfig(BaseRuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = field( + default_factory=lambda: {str: {'hi': 'there'}, np.ndarray: {'you': 'good?'}} + ) + + class DummyDocStore(BaseDocumentStore): _query_builder_cls = FakeQueryBuilder - _default_column_config = {str: {'hi': 'there'}, np.ndarray: {'you': 'good?'}} + _db_config_cls = DBConfig + _runtime_config_cls = RuntimeConfig def python_type_to_db_type(self, x): return str @@ -66,14 +82,6 @@ def test_build_query(): assert isinstance(q, store._query_builder_cls) -def test_default_column_config(): - store = DummyDocStore[SimpleDoc]() - assert store._default_column_config == { - str: {'hi': 'there'}, - np.ndarray: {'you': 'good?'}, - } - - def test_create_columns(): # Simple doc store = DummyDocStore[SimpleDoc]() diff --git a/tests/doc_stores/base_classes/test_configs.py b/tests/doc_stores/base_classes/test_configs.py new file mode 100644 index 00000000000..8ff8157e17f --- /dev/null +++ b/tests/doc_stores/base_classes/test_configs.py @@ -0,0 +1,121 @@ +from dataclasses import dataclass, field +from typing import Any, Dict, Type + +from pydantic import Field + +from docarray import BaseDocument +from docarray.storage.abstract_doc_store import ( + BaseDBConfig, + BaseDocumentStore, + BaseRuntimeConfig, +) +from docarray.typing import NdArray + + +class SimpleDoc(BaseDocument): + tens: NdArray[10] = Field(dim=1000) + + +class FakeQueryBuilder: + ... + + +@dataclass +class DBConfig(BaseDBConfig): + work_dir: str = '.' + other: int = 5 + + +@dataclass +class RuntimeConfig(BaseRuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = field( + default_factory=lambda: { + str: { + 'dim': 128, + 'space': 'l2', + }, + } + ) + default_ef: int = 50 + + +def _identity(*x, **y): + return x, y + + +class DummyDocStore(BaseDocumentStore): + _query_builder_cls = FakeQueryBuilder + _db_config_cls = DBConfig + _runtime_config_cls = RuntimeConfig + + def python_type_to_db_type(self, x): + return str + + index = _identity + num_docs = _identity + __delitem__ = _identity + __getitem__ = _identity + execute_query = _identity + find = _identity + find_batched = _identity + filter = _identity + filter_batched = _identity + text_search = _identity + text_search_batched = _identity + + +def test_defaults(): + store = DummyDocStore[SimpleDoc]() + assert store._db_config.other == 5 + assert store._db_config.work_dir == '.' + assert store._runtime_config.default_column_config[str] == { + 'dim': 128, + 'space': 'l2', + } + assert store._runtime_config.default_ef == 50 + + +def test_set_by_class(): + # change all settings + store = DummyDocStore[SimpleDoc](DBConfig(work_dir='hi', other=10)) + assert store._db_config.other == 10 + assert store._db_config.work_dir == 'hi' + store.configure(RuntimeConfig(default_column_config={}, default_ef=10)) + assert store._runtime_config.default_column_config == {} + assert store._runtime_config.default_ef == 10 + + # change only some settings + store = DummyDocStore[SimpleDoc](DBConfig(work_dir='hi')) + assert store._db_config.other == 5 + assert store._db_config.work_dir == 'hi' + store.configure(RuntimeConfig(default_column_config={})) + assert store._runtime_config.default_column_config == {} + assert store._runtime_config.default_ef == 50 + + +def test_set_by_kwargs(): + # change all settings + store = DummyDocStore[SimpleDoc](work_dir='hi', other=10) + assert store._db_config.other == 10 + assert store._db_config.work_dir == 'hi' + store.configure(default_column_config={}, default_ef=10) + assert store._runtime_config.default_column_config == {} + assert store._runtime_config.default_ef == 10 + + # change only some settings + store = DummyDocStore[SimpleDoc](work_dir='hi') + assert store._db_config.other == 5 + assert store._db_config.work_dir == 'hi' + store.configure(default_column_config={}) + assert store._runtime_config.default_column_config == {} + assert store._runtime_config.default_ef == 50 + + +def test_default_column_config(): + store = DummyDocStore[SimpleDoc]() + assert store._runtime_config.default_column_config == { + str: { + 'dim': 128, + 'space': 'l2', + }, + } From 7aee2394e8fbf1fef07b739aa9ae2e8fded0608a Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 13:58:00 +0100 Subject: [PATCH 16/53] refactor: query builder with decorator Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 139 +++++++++++------- docarray/storage/backends/HnswDocStore.py | 69 ++++----- .../base_classes/test_base_doc_store.py | 30 ++-- .../base_classes/test_base_query_builder.py | 66 ++++++--- tests/doc_stores/base_classes/test_configs.py | 15 +- 5 files changed, 179 insertions(+), 140 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index ac10e8ad572..1f3c2f65983 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -1,7 +1,9 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, replace +from functools import wraps from typing import ( Any, + Callable, Dict, Generator, Generic, @@ -31,31 +33,8 @@ class FindResultBatched(NamedTuple): scores: np.ndarray -@dataclass -class BaseDBConfig(ABC): - ... - - -@dataclass -class BaseRuntimeConfig(ABC): - # default configurations for every column type - # a dictionary from a column type (DB specific) to a dictionary - # of default configurations for that type - # These configs are used if no configs are specified in the `Field(...)` - # of a field in the Document schema (`cls._schema`) - # Example: `default_column_config['VARCHAR'] = {'length': 255}` - default_column_config: Dict[Type, Dict[str, Any]] - - -@dataclass -class _Column: - docarray_type: Type - db_type: Any - n_dim: Optional[int] - config: Dict[str, Any] - - -def _delegate_to_query(method_name: str): +def _delegate_to_query(method_name: str, func: Callable): + @wraps(func) def inner(self, *args, **kwargs): if args: raise ValueError( @@ -69,25 +48,35 @@ def inner(self, *args, **kwargs): return inner -class BaseQueryBuilder(ABC): - def __init__(self): - # list of tuples (method name, kwargs) - self._queries: List[Tuple[str, Dict]] = [] +def _raise_not_composable(name): + def _inner(*args, **kwargs): + raise NotImplementedError( + f'`{name}` is not usable through the query builder of this Document Store. ' + f'But you can call `doc_store.{name}()` directly.' + ) - find = _delegate_to_query('find') - find_batched = _delegate_to_query('find_batched') - filter = _delegate_to_query('filter') - filter_batched = _delegate_to_query('filter_batched') - text_search = _delegate_to_query('text_search') - text_search_batched = _delegate_to_query('text_search_batched') + return _inner - @abstractmethod - def build(self, *args, **kwargs) -> Any: - """Build the DB specific query object. - The DB specific implementation can leverage self._queries to do so. - The output of this should be able to be passed to execute_query(). - """ - ... + +@dataclass +class _Column: + docarray_type: Type + db_type: Any + n_dim: Optional[int] + config: Dict[str, Any] + + +class composable: + """Decorator that marks methods in a DocumentStore as composable, + i.e. they can be used in a query builder. + """ + + def __init__(self, fn): + self.fn = fn + + def __set_name__(self, owner, name): + setattr(owner.QueryBuilder, name, _delegate_to_query(name, self.fn)) + setattr(owner, name, self.fn) class BaseDocumentStore(ABC, Generic[TSchema]): @@ -97,27 +86,65 @@ class BaseDocumentStore(ABC, Generic[TSchema]): # for subclasses this is filled automatically _schema: Optional[Type[BaseDocument]] = None - # register helper classes here - _query_builder_cls: Type[BaseQueryBuilder] = BaseQueryBuilder - _db_config_cls: Type # should be dataclass - _runtime_config_cls: Type # should be dataclass - def __init__(self, db_config=None, **kwargs): if self._schema is None: raise ValueError( 'A DocumentStore must be typed with a Document type.' 'To do so, use the syntax: DocumentStore[DocumentType]' ) - self._db_config = db_config if db_config else self._db_config_cls(**kwargs) - if not isinstance(self._db_config, self._db_config_cls): - raise ValueError(f'db_config must be of type {self._db_config_cls}') - self._runtime_config = self._runtime_config_cls() + self._db_config = db_config if db_config else self.DBConfig(**kwargs) + if not isinstance(self._db_config, self.DBConfig): + raise ValueError(f'db_config must be of type {self.DBConfig}') + self._runtime_config = self.RuntimeConfig() self._columns: Dict[str, _Column] = self._create_columns(self._schema) + ############################################### + # Inner classes for query builder and configs # + # Subclasses must subclass & implement these # + ############################################### + + class QueryBuilder(ABC): + def __init__(self): + # list of tuples (method name, kwargs) + # no need to populate this, it's done automatically + self._queries: List[Tuple[str, Dict]] = [] + + @abstractmethod + def build(self, *args, **kwargs) -> Any: + """Build the DB specific query object. + The DB specific implementation can leverage self._queries to do so. + The output of this should be able to be passed to execute_query(). + """ + ... + + # no need to implement the methods below + # they are handled automatically by the `composable` decorator + find = _raise_not_composable('find') + filter = _raise_not_composable('filter') + text_search = _raise_not_composable('text_search') + find_batched = _raise_not_composable('find_batched') + filter_batched = _raise_not_composable('filter_batched') + text_search_batched = _raise_not_composable('text_search_batched') + + @dataclass + class DBConfig(ABC): + ... + + @dataclass + class RuntimeConfig(ABC): + # default configurations for every column type + # a dictionary from a column type (DB specific) to a dictionary + # of default configurations for that type + # These configs are used if no configs are specified in the `Field(...)` + # of a field in the Document schema (`cls._schema`) + # Example: `default_column_config['VARCHAR'] = {'length': 255}` + default_column_config: Dict[Type, Dict[str, Any]] + ##################################### # Abstract methods # # Subclasses must implement these # ##################################### + @abstractmethod def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" @@ -268,10 +295,8 @@ def configure(self, runtime_config=None, **kwargs): if runtime_config is None: self._runtime_config = replace(self._runtime_config, **kwargs) else: - if not isinstance(runtime_config, self._runtime_config_cls): - raise ValueError( - f'runtime_config must be of type {self._runtime_config_cls}' - ) + if not isinstance(runtime_config, self.RuntimeConfig): + raise ValueError(f'runtime_config must be of type {self.RuntimeConfig}') self._runtime_config = runtime_config ################################################## @@ -292,13 +317,13 @@ class _DocumentStoreTyped(cls): # type: ignore return _DocumentStoreTyped - def build_query(self) -> BaseQueryBuilder: + def build_query(self) -> QueryBuilder: """ Build a query for this DocumentStore. :return: a new `QueryBuilder` object for this DocumentStore """ - return self._query_builder_cls() + return self.QueryBuilder() def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: columns: Dict[str, _Column] = dict() diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 0c460388e50..586f147d7f9 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -12,12 +12,10 @@ from docarray import BaseDocument, DocumentArray from docarray.proto import DocumentProto from docarray.storage.abstract_doc_store import ( - BaseDBConfig, BaseDocumentStore, - BaseQueryBuilder, - BaseRuntimeConfig, FindResultBatched, _Column, + composable, ) from docarray.typing import AnyTensor from docarray.utils.filter import filter as da_filter @@ -33,44 +31,33 @@ HNSWLIB_PY_VEC_TYPES.append(torch.Tensor) -@dataclass -class DBConfig(BaseDBConfig): - work_dir: str = '.' - - -@dataclass -class RuntimeConfig(BaseRuntimeConfig): - default_column_config: Dict[Type, Dict[str, Any]] = field( - default_factory=lambda: { - np.ndarray: { - 'dim': 128, - 'space': 'l2', # 'l2', 'ip', 'cosine' - 'max_elements': 1024, - 'ef_construction': 200, - 'M': 16, - 'allow_replace_deleted': True, # TODO(johannes) handle below - }, - None: {}, - } - ) - default_ef = 50 - default_num_threads = 1 - max_elements = None # use the one above - - -class HnswQueryBuilder(BaseQueryBuilder): - def build(self, *args, **kwargs) -> Any: - supported_ops = ('find', 'filter') - for op, _ in self._queries: - if op not in supported_ops: - raise ValueError(f'Unsupported query operation: {op}') - return self._queries - - class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): - _query_builder_cls = HnswQueryBuilder - _db_config_cls = DBConfig - _runtime_config_cls = RuntimeConfig + class QueryBuilder(BaseDocumentStore.QueryBuilder): + def build(self, *args, **kwargs) -> Any: + return self._queries + + @dataclass + class DBConfig(BaseDocumentStore.DBConfig): + work_dir: str = '.' + + @dataclass + class RuntimeConfig(BaseDocumentStore.RuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = field( + default_factory=lambda: { + np.ndarray: { + 'dim': 128, + 'space': 'l2', # 'l2', 'ip', 'cosine' + 'max_elements': 1024, + 'ef_construction': 200, + 'M': 16, + 'allow_replace_deleted': True, # TODO(johannes) handle below + }, + None: {}, + } + ) + default_ef = 50 + default_num_threads = 1 + max_elements = None # use the one above def __init__(self, db_config=None, **kwargs): super().__init__(db_config=db_config, **kwargs) @@ -222,10 +209,12 @@ def find_batched( ] return FindResultBatched(documents=result_das, scores=distances) + @composable def find(self, *args, **kwargs) -> FindResult: docs, scores = self.find_batched(*args, **kwargs) return FindResult(documents=docs[0], scores=scores[0]) + @composable def filter( self, *args, diff --git a/tests/doc_stores/base_classes/test_base_doc_store.py b/tests/doc_stores/base_classes/test_base_doc_store.py index 14537ad44e2..50513ad4bb4 100644 --- a/tests/doc_stores/base_classes/test_base_doc_store.py +++ b/tests/doc_stores/base_classes/test_base_doc_store.py @@ -6,7 +6,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.abstract_doc_store import BaseDocumentStore, BaseRuntimeConfig +from docarray.storage.abstract_doc_store import BaseDocumentStore from docarray.typing import ID, NdArray @@ -35,22 +35,20 @@ def _identity(*x, **y): return x, y -@dataclass -class DBConfig: - work_dir: str = '.' - - -@dataclass -class RuntimeConfig(BaseRuntimeConfig): - default_column_config: Dict[Type, Dict[str, Any]] = field( - default_factory=lambda: {str: {'hi': 'there'}, np.ndarray: {'you': 'good?'}} - ) +class DummyDocStore(BaseDocumentStore): + @dataclass + class RuntimeConfig(BaseDocumentStore.RuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = field( + default_factory=lambda: {str: {'hi': 'there'}, np.ndarray: {'you': 'good?'}} + ) + @dataclass + class DBConfig(BaseDocumentStore.DBConfig): + work_dir: str = '.' -class DummyDocStore(BaseDocumentStore): - _query_builder_cls = FakeQueryBuilder - _db_config_cls = DBConfig - _runtime_config_cls = RuntimeConfig + class QueryBuilder(BaseDocumentStore.QueryBuilder): + def build(self): + return self._queries def python_type_to_db_type(self, x): return str @@ -79,7 +77,7 @@ def test_parametrization(): def test_build_query(): store = DummyDocStore[SimpleDoc]() q = store.build_query() - assert isinstance(q, store._query_builder_cls) + assert isinstance(q, store.QueryBuilder) def test_create_columns(): diff --git a/tests/doc_stores/base_classes/test_base_query_builder.py b/tests/doc_stores/base_classes/test_base_query_builder.py index bd59f4b2030..bc896a0ef07 100644 --- a/tests/doc_stores/base_classes/test_base_query_builder.py +++ b/tests/doc_stores/base_classes/test_base_query_builder.py @@ -1,33 +1,65 @@ -from docarray.storage.abstract_doc_store import BaseQueryBuilder +import pytest +from docarray.storage.abstract_doc_store import BaseDocumentStore, composable -class DummyQueryBuilder(BaseQueryBuilder): + +class DummyQueryBuilder(BaseDocumentStore.QueryBuilder): def build(self): return self._queries +def _identity(*x, **y): + return x, y + + +class DummyDocStore(BaseDocumentStore): + _query_builder_cls = DummyQueryBuilder + _db_config_cls = None + _runtime_config_cls = None + + def python_type_to_db_type(self, x): + return str + + index = _identity + num_docs = _identity + __delitem__ = _identity + __getitem__ = _identity + execute_query = _identity + + @composable + def find(self, *args, **kwargs): + return _identity(*args, **kwargs) + + @composable + def filter(self, *args, **kwargs): + return _identity(*args, **kwargs) + + @composable + def text_search(self, *args, **kwargs): + return _identity(*args, **kwargs) + + find_batched = _identity + filter_batched = _identity + text_search_batched = _identity + + def test_collect_calls(): qb = DummyQueryBuilder() qb.find(find='find', param='param') qb.filter(filter='filter', param='param') qb.text_search(text='text', param='param') - qb.filter_batched(filter_batched='filter_batched', param='param') - qb.text_search_batched(text_batched='text_batched', param='param') - qb.find_batched(find_batched='find_batched', param='param') collected_queries = qb._queries assert collected_queries[0] == ('find', {'find': 'find', 'param': 'param'}) assert collected_queries[1] == ('filter', {'filter': 'filter', 'param': 'param'}) assert collected_queries[2] == ('text_search', {'text': 'text', 'param': 'param'}) - assert collected_queries[3] == ( - 'filter_batched', - {'filter_batched': 'filter_batched', 'param': 'param'}, - ) - assert collected_queries[4] == ( - 'text_search_batched', - {'text_batched': 'text_batched', 'param': 'param'}, - ) - assert collected_queries[5] == ( - 'find_batched', - {'find_batched': 'find_batched', 'param': 'param'}, - ) + + +def test_not_composable_raises(): + qb = DummyQueryBuilder() + with pytest.raises(NotImplementedError): + qb.filter_batched(filter_batched='filter_batched', param='param') + with pytest.raises(NotImplementedError): + qb.text_search_batched(text_batched='text_batched', param='param') + with pytest.raises(NotImplementedError): + qb.find_batched(find_batched='find_batched', param='param') diff --git a/tests/doc_stores/base_classes/test_configs.py b/tests/doc_stores/base_classes/test_configs.py index 8ff8157e17f..047ef8f19a2 100644 --- a/tests/doc_stores/base_classes/test_configs.py +++ b/tests/doc_stores/base_classes/test_configs.py @@ -4,11 +4,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.abstract_doc_store import ( - BaseDBConfig, - BaseDocumentStore, - BaseRuntimeConfig, -) +from docarray.storage.abstract_doc_store import BaseDocumentStore from docarray.typing import NdArray @@ -21,13 +17,13 @@ class FakeQueryBuilder: @dataclass -class DBConfig(BaseDBConfig): +class DBConfig(BaseDocumentStore.DBConfig): work_dir: str = '.' other: int = 5 @dataclass -class RuntimeConfig(BaseRuntimeConfig): +class RuntimeConfig(BaseDocumentStore.RuntimeConfig): default_column_config: Dict[Type, Dict[str, Any]] = field( default_factory=lambda: { str: { @@ -44,9 +40,8 @@ def _identity(*x, **y): class DummyDocStore(BaseDocumentStore): - _query_builder_cls = FakeQueryBuilder - _db_config_cls = DBConfig - _runtime_config_cls = RuntimeConfig + DBConfig = DBConfig + RuntimeConfig = RuntimeConfig def python_type_to_db_type(self, x): return str From b5cd6e2d98d11fb2438e96c71b6f4613550390e5 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 14:08:24 +0100 Subject: [PATCH 17/53] test: add one of those Signed-off-by: Johannes Messner --- .../base_classes/test_base_query_builder.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/doc_stores/base_classes/test_base_query_builder.py b/tests/doc_stores/base_classes/test_base_query_builder.py index bc896a0ef07..d44e06c77e2 100644 --- a/tests/doc_stores/base_classes/test_base_query_builder.py +++ b/tests/doc_stores/base_classes/test_base_query_builder.py @@ -55,6 +55,18 @@ def test_collect_calls(): assert collected_queries[2] == ('text_search', {'text': 'text', 'param': 'param'}) +def test_fluent_interface(): + qb = DummyQueryBuilder() + qb.find(find='find', param='param').filter( + filter='filter', param='param' + ).text_search(text='text', param='param') + + collected_queries = qb._queries + assert collected_queries[0] == ('find', {'find': 'find', 'param': 'param'}) + assert collected_queries[1] == ('filter', {'filter': 'filter', 'param': 'param'}) + assert collected_queries[2] == ('text_search', {'text': 'text', 'param': 'param'}) + + def test_not_composable_raises(): qb = DummyQueryBuilder() with pytest.raises(NotImplementedError): From 4fd9bc6c88889fb5d52cce259aa699298e22b9f9 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 14:08:44 +0100 Subject: [PATCH 18/53] refactor: add structure and comments Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 135 ++++++++++++---------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 586f147d7f9..a185b5385be 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -32,33 +32,6 @@ class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): - class QueryBuilder(BaseDocumentStore.QueryBuilder): - def build(self, *args, **kwargs) -> Any: - return self._queries - - @dataclass - class DBConfig(BaseDocumentStore.DBConfig): - work_dir: str = '.' - - @dataclass - class RuntimeConfig(BaseDocumentStore.RuntimeConfig): - default_column_config: Dict[Type, Dict[str, Any]] = field( - default_factory=lambda: { - np.ndarray: { - 'dim': 128, - 'space': 'l2', # 'l2', 'ip', 'cosine' - 'max_elements': 1024, - 'ef_construction': 200, - 'M': 16, - 'allow_replace_deleted': True, # TODO(johannes) handle below - }, - None: {}, - } - ) - default_ef = 50 - default_num_threads = 1 - max_elements = None # use the one above - def __init__(self, db_config=None, **kwargs): super().__init__(db_config=db_config, **kwargs) self._work_dir = self._db_config.work_dir @@ -90,33 +63,39 @@ def __init__(self, db_config=None, **kwargs): self._create_docs_table() self._sqlite_conn.commit() - @staticmethod - def _to_universal_id(doc_id: str) -> int: - # https://stackoverflow.com/questions/16008670/how-to-hash-a-string-into-8-digits - # hashing to 18 digits avoids overflow of sqlite INTEGER - return int(hashlib.sha256(doc_id.encode('utf-8')).hexdigest(), 16) % 10**18 + ############################################### + # Inner classes for query builder and configs # + ############################################### + class QueryBuilder(BaseDocumentStore.QueryBuilder): + def build(self, *args, **kwargs) -> Any: + return self._queries - def _create_index_class(self, col: '_Column') -> hnswlib.Index: - """Create an instance of hnswlib.Index without initializing it.""" - construct_params = dict( - (k, col.config[k]) for k in self._index_construct_params - ) - if col.n_dim: - construct_params['dim'] = col.n_dim - return hnswlib.Index(**construct_params) + @dataclass + class DBConfig(BaseDocumentStore.DBConfig): + work_dir: str = '.' - def _create_index(self, col: '_Column') -> hnswlib.Index: - """Create a new HNSW index for a column, and initialize it.""" - index = self._create_index_class(col) - init_params = dict((k, col.config[k]) for k in self._index_init_params) - index.init_index(**init_params) - return index + @dataclass + class RuntimeConfig(BaseDocumentStore.RuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = field( + default_factory=lambda: { + np.ndarray: { + 'dim': 128, + 'space': 'l2', # 'l2', 'ip', 'cosine' + 'max_elements': 1024, + 'ef_construction': 200, + 'M': 16, + 'allow_replace_deleted': True, # TODO(johannes) handle below + }, + None: {}, + } + ) + default_ef = 50 + default_num_threads = 1 + max_elements = None # use the one above - def _load_index(self, col_name: str, col: '_Column') -> hnswlib.Index: - """Load an existing HNSW index from disk.""" - index = self._create_index_class(col) - index.load_index(self._hnsw_locations[col_name]) - return index + ############################################### + # Implementation of abstract methods # + ############################################### def python_type_to_db_type(self, python_type: Type) -> Any: """Map python type to database type.""" @@ -129,16 +108,6 @@ def python_type_to_db_type(self, python_type: Type) -> Any: raise ValueError(f'Unsupported column type for {type(self)}: {python_type}') - def _to_numpy(self, val: Any) -> Any: - if isinstance(val, np.ndarray): - return val - elif isinstance(val, (list, tuple)): - return np.array(val) - elif torch_imported and isinstance(val, torch.Tensor): - return val.numpy() - else: - raise ValueError(f'Unsupported input type for {type(self)}: {type(val)}') - def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" data_by_columns = self.get_data_by_columns(docs) @@ -278,6 +247,50 @@ def __getitem__(self, key: Union[str, Sequence[str]]): def num_docs(self) -> int: return self._get_num_docs_sqlite() + ############################################### + # Helpers # + ############################################### + + # general helpers + @staticmethod + def _to_universal_id(doc_id: str) -> int: + # https://stackoverflow.com/questions/16008670/how-to-hash-a-string-into-8-digits + # hashing to 18 digits avoids overflow of sqlite INTEGER + return int(hashlib.sha256(doc_id.encode('utf-8')).hexdigest(), 16) % 10**18 + + def _load_index(self, col_name: str, col: '_Column') -> hnswlib.Index: + """Load an existing HNSW index from disk.""" + index = self._create_index_class(col) + index.load_index(self._hnsw_locations[col_name]) + return index + + def _to_numpy(self, val: Any) -> Any: + if isinstance(val, np.ndarray): + return val + elif isinstance(val, (list, tuple)): + return np.array(val) + elif torch_imported and isinstance(val, torch.Tensor): + return val.numpy() + else: + raise ValueError(f'Unsupported input type for {type(self)}: {type(val)}') + + # HNSWLib helpers + def _create_index_class(self, col: '_Column') -> hnswlib.Index: + """Create an instance of hnswlib.Index without initializing it.""" + construct_params = dict( + (k, col.config[k]) for k in self._index_construct_params + ) + if col.n_dim: + construct_params['dim'] = col.n_dim + return hnswlib.Index(**construct_params) + + def _create_index(self, col: '_Column') -> hnswlib.Index: + """Create a new HNSW index for a column, and initialize it.""" + index = self._create_index_class(col) + init_params = dict((k, col.config[k]) for k in self._index_init_params) + index.init_index(**init_params) + return index + # SQLite helpers def _create_docs_table(self): self._sqlite_cursor.execute( From 8cf81691e4c0057b95fdab39efffbb391865be23 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 16:39:22 +0100 Subject: [PATCH 19/53] refactor: remove mem doc store Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 79 ++++++++++++------- docarray/storage/backends/HnswDocStore.py | 4 +- docarray/storage/backends/MemoryDocStore.py | 57 ------------- .../base_classes/test_base_doc_store.py | 16 ++-- 4 files changed, 60 insertions(+), 96 deletions(-) delete mode 100644 docarray/storage/backends/MemoryDocStore.py diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 1f3c2f65983..a22f5bae3e2 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -2,6 +2,7 @@ from dataclasses import dataclass, replace from functools import wraps from typing import ( + TYPE_CHECKING, Any, Callable, Dict, @@ -79,6 +80,12 @@ def __set_name__(self, owner, name): setattr(owner, name, self.fn) +if TYPE_CHECKING: + # static type checkers do not like the solution above + def composable(fn): + return fn + + class BaseDocumentStore(ABC, Generic[TSchema]): """Abstract class for all Document Stores""" @@ -299,6 +306,49 @@ def configure(self, runtime_config=None, **kwargs): raise ValueError(f'runtime_config must be of type {self.RuntimeConfig}') self._runtime_config = runtime_config + ########################################################## + # Helper methods # + # These might be useful in your subclass implementation # + ########################################################## + + @staticmethod + def _get_value_by_column(doc: BaseDocument, col_name: str) -> Any: + """Get the value of a column of a document. + + :param doc: The Document to get the value from + :param col_name: The name of the column, e.g. 'text' or 'image__tensor' + :return: The value of the column of `doc` + """ + if '__' in col_name: + fields = col_name.split('__') + leaf_doc: BaseDocument = doc + for f in fields[:-1]: + leaf_doc = getattr(leaf_doc, f) + return getattr(leaf_doc, fields[-1]) + else: + return getattr(doc, col_name) + + def _get_values_by_columns( + self, docs: Union[BaseDocument, Sequence[BaseDocument]] + ) -> Dict[str, Generator[Any, None, None]]: + """ + Get all data from a (sequence of) document(s), flattened out by column. + :param docs: The document(s) to get the data from + :return: A dictionary mapping column names to a generator of values + """ + if isinstance(docs, BaseDocument): + docs = [docs] + if not self._is_schema_compatible(docs): + raise ValueError( + 'The schema of the documents to be indexed is not compatible' + ' with the schema of the store.' + ) + + def _col_gen(col_name): + return (self._get_value_by_column(doc, col_name) for doc in docs) + + return {col_name: _col_gen(col_name) for col_name in self._columns} + ################################################## # Behind-the-scenes magic # # Subclasses should not need to implement these # @@ -397,32 +447,3 @@ def _is_schema_compatible(self, docs: Sequence[BaseDocument]) -> bool: if reference_col_db_types != input_col_db_types: return False return True - - @staticmethod - def get_value(doc: BaseDocument, col_name: str) -> Any: - """Get the value of a column of a document.""" - if '__' in col_name: - fields = col_name.split('__') - leaf_doc: BaseDocument = doc - for f in fields[:-1]: - leaf_doc = getattr(leaf_doc, f) - return getattr(leaf_doc, fields[-1]) - else: - return getattr(doc, col_name) - - def get_data_by_columns( - self, docs: Union[BaseDocument, Sequence[BaseDocument]] - ) -> Dict[str, Generator[Any, None, None]]: - """Get the payload of a document.""" - if isinstance(docs, BaseDocument): - docs = [docs] - if not self._is_schema_compatible(docs): - raise ValueError( - 'The schema of the documents to be indexed is not compatible' - ' with the schema of the store.' - ) - - def _col_gen(col_name): - return (self.get_value(doc, col_name) for doc in docs) - - return {col_name: _col_gen(col_name) for col_name in self._columns} diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index a185b5385be..80215de1234 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -110,7 +110,7 @@ def python_type_to_db_type(self, python_type: Type) -> Any: def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" - data_by_columns = self.get_data_by_columns(docs) + data_by_columns = self._get_values_by_columns(docs) hnsw_ids = tuple(self._to_universal_id(doc.id) for doc in docs) # indexing into HNSWLib and SQLite sequentially @@ -163,7 +163,7 @@ def find_batched( ) -> FindResultBatched: # the below should be done in the abstract class if isinstance(query, BaseDocument): - query_vec = self.get_value(query, embedding_field) + query_vec = self._get_value_by_column(query, embedding_field) else: query_vec = query query_vec_np = self._to_numpy(query_vec) diff --git a/docarray/storage/backends/MemoryDocStore.py b/docarray/storage/backends/MemoryDocStore.py deleted file mode 100644 index e339e28c378..00000000000 --- a/docarray/storage/backends/MemoryDocStore.py +++ /dev/null @@ -1,57 +0,0 @@ -from typing import Generic, List, Sequence, Type, TypeVar, Union - -from docarray import BaseDocument, DocumentArray -from docarray.storage.abstract_doc_store import BaseDocumentStore, FindResult -from docarray.typing import AnyTensor -from docarray.utils.find import find, find_batched - -TSchema = TypeVar('TSchema', bound=BaseDocument) - - -class MemoryDocumentStore(BaseDocumentStore, Generic[TSchema]): - """In-memory document store""" - - def __init__(self): - super().__init__() - # TODO(johannes) not sure why the line below doesn't type check - self._docs = DocumentArray[self._schema]([]) # type: ignore - - def python_type_to_db_type(self, python_type: Type) -> Type: - return python_type - - def index(self, docs: Union[TSchema, Sequence[TSchema]]): - self._docs.extend(docs) - - def find( - self, - query: Union[AnyTensor, BaseDocument], - embedding_field: str = 'embedding', - metric: str = 'cosine_sim', - limit: int = 10, - **kwargs, - ) -> FindResult: - return find( - index=self._docs, - query=query, - embedding_field=embedding_field, - metric=metric, - limit=limit, - **kwargs, - ) - - def find_batched( - self, - query: Union[AnyTensor, DocumentArray], - embedding_field: str = 'embedding', - metric: str = 'cosine_sim', - limit: int = 10, - **kwargs, - ) -> List[FindResult]: - return find_batched( - index=self._docs, - query=query, - embedding_field=embedding_field, - metric=metric, - limit=limit, - **kwargs, - ) diff --git a/tests/doc_stores/base_classes/test_base_doc_store.py b/tests/doc_stores/base_classes/test_base_doc_store.py index 50513ad4bb4..eb1de132987 100644 --- a/tests/doc_stores/base_classes/test_base_doc_store.py +++ b/tests/doc_stores/base_classes/test_base_doc_store.py @@ -220,22 +220,22 @@ def test_get_value(): t = np.random.random((10,)) doc = SimpleDoc(tens=t) - assert np.all(DummyDocStore.get_value(doc, 'tens') == t) + assert np.all(DummyDocStore._get_value_by_column(doc, 'tens') == t) doc = FlatDoc(tens_one=t, tens_two=np.random.random((50,))) - assert np.all(DummyDocStore.get_value(doc, 'tens_one') == t) + assert np.all(DummyDocStore._get_value_by_column(doc, 'tens_one') == t) doc = NestedDoc(d=SimpleDoc(tens=t)) - assert np.all(DummyDocStore.get_value(doc, 'd__tens') == t) + assert np.all(DummyDocStore._get_value_by_column(doc, 'd__tens') == t) doc = DeepNestedDoc(d=NestedDoc(d=SimpleDoc(tens=t))) - assert np.all(DummyDocStore.get_value(doc, 'd__d__tens') == t) + assert np.all(DummyDocStore._get_value_by_column(doc, 'd__d__tens') == t) def test_get_data_by_columns(): store = DummyDocStore[SimpleDoc]() docs = [SimpleDoc(tens=np.random.random((10,))) for _ in range(10)] - data_by_columns = store.get_data_by_columns(docs) + data_by_columns = store._get_values_by_columns(docs) assert list(data_by_columns.keys()) == ['id', 'tens'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['tens']) == [doc.tens for doc in docs] @@ -245,7 +245,7 @@ def test_get_data_by_columns(): FlatDoc(tens_one=np.random.random((10,)), tens_two=np.random.random((50,))) for _ in range(10) ] - data_by_columns = store.get_data_by_columns(docs) + data_by_columns = store._get_values_by_columns(docs) assert list(data_by_columns.keys()) == ['id', 'tens_one', 'tens_two'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['tens_one']) == [doc.tens_one for doc in docs] @@ -253,7 +253,7 @@ def test_get_data_by_columns(): store = DummyDocStore[NestedDoc]() docs = [NestedDoc(d=SimpleDoc(tens=np.random.random((10,)))) for _ in range(10)] - data_by_columns = store.get_data_by_columns(docs) + data_by_columns = store._get_values_by_columns(docs) assert list(data_by_columns.keys()) == ['id', 'd__id', 'd__tens'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['d__id']) == [doc.d.id for doc in docs] @@ -264,7 +264,7 @@ def test_get_data_by_columns(): DeepNestedDoc(d=NestedDoc(d=SimpleDoc(tens=np.random.random((10,))))) for _ in range(10) ] - data_by_columns = store.get_data_by_columns(docs) + data_by_columns = store._get_values_by_columns(docs) assert list(data_by_columns.keys()) == ['id', 'd__id', 'd__d__id', 'd__d__tens'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['d__id']) == [doc.d.id for doc in docs] From a913054b30d52985517ff422709232ca13bb0ed7 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 16:40:00 +0100 Subject: [PATCH 20/53] refactor: remove useless code Signed-off-by: Johannes Messner --- docarray/utils/protocols.py | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 docarray/utils/protocols.py diff --git a/docarray/utils/protocols.py b/docarray/utils/protocols.py deleted file mode 100644 index aa735300b72..00000000000 --- a/docarray/utils/protocols.py +++ /dev/null @@ -1,7 +0,0 @@ -from typing import ClassVar, Dict - -from typing_extensions import Protocol - - -class IsDataclass(Protocol): - __dataclass_fields__: ClassVar[Dict] From f1b05a170258110f0c52f01927e7b922e240e063 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 17:16:03 +0100 Subject: [PATCH 21/53] feat: expose all hnswlib options Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 80215de1234..b2aa9bfa2b0 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -40,7 +40,12 @@ def __init__(self, db_config=None, **kwargs): # HNSWLib setup self._index_construct_params = ('space', 'dim') - self._index_init_params = ('max_elements', 'ef_construction', 'M') + self._index_init_params = ( + 'max_elements', + 'ef_construction', + 'M', + 'allow_replace_deleted', + ) self._hnsw_locations = { col_name: os.path.join(self._work_dir, f'{col_name}.bin') @@ -83,15 +88,14 @@ class RuntimeConfig(BaseDocumentStore.RuntimeConfig): 'space': 'l2', # 'l2', 'ip', 'cosine' 'max_elements': 1024, 'ef_construction': 200, + 'ef': 10, 'M': 16, - 'allow_replace_deleted': True, # TODO(johannes) handle below + 'allow_replace_deleted': True, + 'num_threads': 1, }, None: {}, } ) - default_ef = 50 - default_num_threads = 1 - max_elements = None # use the one above ############################################### # Implementation of abstract methods # @@ -289,6 +293,8 @@ def _create_index(self, col: '_Column') -> hnswlib.Index: index = self._create_index_class(col) init_params = dict((k, col.config[k]) for k in self._index_init_params) index.init_index(**init_params) + index.set_ef(col.config['ef']) + index.set_num_threads(col.config['num_threads']) return index # SQLite helpers From f18026aa8d377ffe752f1af87d714a4a436d862f Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 20 Feb 2023 17:25:35 +0100 Subject: [PATCH 22/53] test: adjust tests Signed-off-by: Johannes Messner --- tests/doc_stores/base_classes/test_configs.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/doc_stores/base_classes/test_configs.py b/tests/doc_stores/base_classes/test_configs.py index 047ef8f19a2..623c8845b2c 100644 --- a/tests/doc_stores/base_classes/test_configs.py +++ b/tests/doc_stores/base_classes/test_configs.py @@ -67,7 +67,6 @@ def test_defaults(): 'dim': 128, 'space': 'l2', } - assert store._runtime_config.default_ef == 50 def test_set_by_class(): @@ -77,7 +76,6 @@ def test_set_by_class(): assert store._db_config.work_dir == 'hi' store.configure(RuntimeConfig(default_column_config={}, default_ef=10)) assert store._runtime_config.default_column_config == {} - assert store._runtime_config.default_ef == 10 # change only some settings store = DummyDocStore[SimpleDoc](DBConfig(work_dir='hi')) @@ -85,7 +83,6 @@ def test_set_by_class(): assert store._db_config.work_dir == 'hi' store.configure(RuntimeConfig(default_column_config={})) assert store._runtime_config.default_column_config == {} - assert store._runtime_config.default_ef == 50 def test_set_by_kwargs(): @@ -95,7 +92,6 @@ def test_set_by_kwargs(): assert store._db_config.work_dir == 'hi' store.configure(default_column_config={}, default_ef=10) assert store._runtime_config.default_column_config == {} - assert store._runtime_config.default_ef == 10 # change only some settings store = DummyDocStore[SimpleDoc](work_dir='hi') @@ -103,7 +99,6 @@ def test_set_by_kwargs(): assert store._db_config.work_dir == 'hi' store.configure(default_column_config={}) assert store._runtime_config.default_column_config == {} - assert store._runtime_config.default_ef == 50 def test_default_column_config(): From aab20f8db56a0fb426075d7b658fdbcc6c6305c6 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 21 Feb 2023 17:37:29 +0100 Subject: [PATCH 23/53] docs: add comments Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index a22f5bae3e2..e0fa2a76103 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -176,8 +176,14 @@ def __delitem__(self, key: Union[str, Sequence[str]]): ... @abstractmethod - def __getitem__(self, key: Union[str, Sequence[str]]): + def __getitem__( + self, key: Union[str, Sequence[str]] + ) -> Union[TSchema, Sequence[TSchema]]: """Get one or multiple Documents into the store, by `id`. + Document are returned in the same order as the ids. + If the same id is requested multiple times, + the document is returned multiple times. + If no document is found, a KeyError is raised. :param key: id or ids to get from the Document Store """ From ebbeed6b3f8b7f8f57261f9f3e54b035aee4bcbc Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 21 Feb 2023 17:37:52 +0100 Subject: [PATCH 24/53] fix: sort docs when retrieving Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 41 ++++++++++++++++------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index b2aa9bfa2b0..6c5eb29b06c 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -35,7 +35,7 @@ class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): def __init__(self, db_config=None, **kwargs): super().__init__(db_config=db_config, **kwargs) self._work_dir = self._db_config.work_dir - load_existing = os.path.exists(self._work_dir) + load_existing = os.path.exists(self._work_dir) and os.listdir(self._work_dir) Path(self._work_dir).mkdir(parents=True, exist_ok=True) # HNSWLib setup @@ -175,7 +175,7 @@ def find_batched( index = self._hnsw_indices[embedding_field] labels, distances = index.knn_query(query_vec_np, k=limit) result_das = [ - self._get_docs_from_sqlite( + self._get_docs_sqlite_univ_id( ids_per_query, ) for ids_per_query in labels @@ -246,7 +246,13 @@ def __getitem__(self, key: Union[str, Sequence[str]]): # delete from the indices if isinstance(key, str): key = [key] - return self._get_docs_from_sqlite(key) + out_docs = self._get_docs_sqlite_doc_id(key) + # TODO(johannes): this could be handled for all stores + if len(out_docs) == 0: + raise KeyError(f'No document with id {key} found') + if len(out_docs) == 1: + return out_docs[0] + return out_docs def num_docs(self) -> int: return self._get_num_docs_sqlite() @@ -310,26 +316,35 @@ def _send_docs_to_sqlite(self, docs: Sequence[BaseDocument]): ((id_, self._doc_to_bytes(doc)) for id_, doc in zip(ids, docs)), ) - def _get_docs_from_sqlite( - self, doc_ids: Sequence[Union[str, int]] - ) -> DocumentArray: - ids = tuple( - self._to_universal_id(id_) if isinstance(id_, str) else int(id_) - for id_ in doc_ids - ) - for id_ in ids: + def _get_docs_sqlite_unsorted(self, univ_ids: Sequence[int]): + for id_ in univ_ids: # I hope this protects from injection attacks # properly binding with '?' doesn't work for some reason assert isinstance(id_, int) - sql_id_list = '(' + ', '.join(str(id_) for id_ in ids) + ')' + sql_id_list = '(' + ', '.join(str(id_) for id_ in univ_ids) + ')' self._sqlite_cursor.execute( 'SELECT data FROM docs WHERE doc_id IN %s' % sql_id_list, ) rows = self._sqlite_cursor.fetchall() return DocumentArray[self._schema]( - (self._doc_from_bytes(row[0]) for row in rows) + [self._doc_from_bytes(row[0]) for row in rows] + ) + + def _get_docs_sqlite_doc_id(self, doc_ids: Sequence[str]) -> DocumentArray: + univ_ids = tuple(self._to_universal_id(id_) for id_ in doc_ids) + docs_unsorted = self._get_docs_sqlite_unsorted(univ_ids) + return DocumentArray[self._schema]( + sorted(docs_unsorted, key=lambda doc: doc_ids.index(doc.id)) ) + def _get_docs_sqlite_univ_id(self, univ_ids: Sequence[int]) -> DocumentArray: + docs_unsorted = self._get_docs_sqlite_unsorted(univ_ids) + + def _in_position(doc): + return univ_ids.index(self._to_universal_id(doc.id)) + + return DocumentArray[self._schema](sorted(docs_unsorted, key=_in_position)) + def _delete_docs_from_sqlite(self, doc_ids: Sequence[Union[str, int]]): ids = tuple( self._to_universal_id(id_) if isinstance(id_, str) else id_ From 404ba2e9f14b46536d11388e3468d0dd7dc16da1 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 22 Feb 2023 13:36:06 +0100 Subject: [PATCH 25/53] refactor: rename some vars Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 23 ++- tests/doc_stores/hnswlib/__init__.py | 0 .../doc_stores/hnswlib/test_index_get_del.py | 161 ++++++++++++++++++ 3 files changed, 172 insertions(+), 12 deletions(-) create mode 100644 tests/doc_stores/hnswlib/__init__.py create mode 100644 tests/doc_stores/hnswlib/test_index_get_del.py diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 6c5eb29b06c..aa41ebc30d9 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -115,7 +115,7 @@ def python_type_to_db_type(self, python_type: Type) -> Any: def index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" data_by_columns = self._get_values_by_columns(docs) - hnsw_ids = tuple(self._to_universal_id(doc.id) for doc in docs) + hnsw_ids = tuple(self._to_hasdhed_id(doc.id) for doc in docs) # indexing into HNSWLib and SQLite sequentially # could be improved by processing in parallel @@ -175,7 +175,7 @@ def find_batched( index = self._hnsw_indices[embedding_field] labels, distances = index.knn_query(query_vec_np, k=limit) result_das = [ - self._get_docs_sqlite_univ_id( + self._get_docs_sqlite_hashed_id( ids_per_query, ) for ids_per_query in labels @@ -235,7 +235,7 @@ def __delitem__(self, key: Union[str, Sequence[str]]): if isinstance(key, str): key = [key] for doc_id in key: - id_ = self._to_universal_id(doc_id) + id_ = self._to_hasdhed_id(doc_id) for col_name, index in self._hnsw_indices.items(): index.mark_deleted(id_) @@ -263,7 +263,7 @@ def num_docs(self) -> int: # general helpers @staticmethod - def _to_universal_id(doc_id: str) -> int: + def _to_hasdhed_id(doc_id: str) -> int: # https://stackoverflow.com/questions/16008670/how-to-hash-a-string-into-8-digits # hashing to 18 digits avoids overflow of sqlite INTEGER return int(hashlib.sha256(doc_id.encode('utf-8')).hexdigest(), 16) % 10**18 @@ -310,7 +310,7 @@ def _create_docs_table(self): ) def _send_docs_to_sqlite(self, docs: Sequence[BaseDocument]): - ids = (self._to_universal_id(doc.id) for doc in docs) + ids = (self._to_hasdhed_id(doc.id) for doc in docs) self._sqlite_cursor.executemany( 'INSERT INTO docs VALUES (?, ?)', ((id_, self._doc_to_bytes(doc)) for id_, doc in zip(ids, docs)), @@ -331,24 +331,23 @@ def _get_docs_sqlite_unsorted(self, univ_ids: Sequence[int]): ) def _get_docs_sqlite_doc_id(self, doc_ids: Sequence[str]) -> DocumentArray: - univ_ids = tuple(self._to_universal_id(id_) for id_ in doc_ids) - docs_unsorted = self._get_docs_sqlite_unsorted(univ_ids) + hashed_ids = tuple(self._to_hasdhed_id(id_) for id_ in doc_ids) + docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) return DocumentArray[self._schema]( sorted(docs_unsorted, key=lambda doc: doc_ids.index(doc.id)) ) - def _get_docs_sqlite_univ_id(self, univ_ids: Sequence[int]) -> DocumentArray: - docs_unsorted = self._get_docs_sqlite_unsorted(univ_ids) + def _get_docs_sqlite_hashed_id(self, hashed_ids: Sequence[int]) -> DocumentArray: + docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) def _in_position(doc): - return univ_ids.index(self._to_universal_id(doc.id)) + return hashed_ids.index(self._to_hasdhed_id(doc.id)) return DocumentArray[self._schema](sorted(docs_unsorted, key=_in_position)) def _delete_docs_from_sqlite(self, doc_ids: Sequence[Union[str, int]]): ids = tuple( - self._to_universal_id(id_) if isinstance(id_, str) else id_ - for id_ in doc_ids + self._to_hasdhed_id(id_) if isinstance(id_, str) else id_ for id_ in doc_ids ) self._sqlite_cursor.execute( 'DELETE FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(ids)), diff --git a/tests/doc_stores/hnswlib/__init__.py b/tests/doc_stores/hnswlib/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/doc_stores/hnswlib/test_index_get_del.py b/tests/doc_stores/hnswlib/test_index_get_del.py new file mode 100644 index 00000000000..c65003721fd --- /dev/null +++ b/tests/doc_stores/hnswlib/test_index_get_del.py @@ -0,0 +1,161 @@ +import numpy as np +import pytest +from pydantic import Field + +from docarray import BaseDocument, DocumentArray +from docarray.storage.backends.HnswDocStore import HnswDocumentStore +from docarray.typing import NdArray + + +class SimpleDoc(BaseDocument): + tens: NdArray[10] = Field(dim=1000) + + +class FlatDoc(BaseDocument): + tens_one: NdArray = Field(dim=10) + tens_two: NdArray = Field(dim=50) + + +class NestedDoc(BaseDocument): + d: SimpleDoc + + +class DeepNestedDoc(BaseDocument): + d: NestedDoc + + +@pytest.fixture +def ten_simple_docs(): + return [SimpleDoc(tens=np.random.randn(10)) for _ in range(10)] + + +@pytest.fixture +def ten_flat_docs(): + return [ + FlatDoc(tens_one=np.random.randn(10), tens_two=np.random.randn(50)) + for _ in range(10) + ] + + +@pytest.fixture +def ten_nested_docs(): + return [NestedDoc(d=SimpleDoc(tens=np.random.randn(10))) for _ in range(10)] + + +@pytest.mark.parametrize('use_docarray', [True, False]) +def test_index_simple_schema(ten_simple_docs, tmp_path, use_docarray): + store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + if use_docarray: + ten_simple_docs = DocumentArray[SimpleDoc](ten_simple_docs) + + store.index(ten_simple_docs) + assert store.num_docs() == 10 + for index in store._hnsw_indices.values(): + assert index.get_current_count() == 10 + + +@pytest.mark.parametrize('use_docarray', [True, False]) +def test_index_flat_schema(ten_flat_docs, tmp_path, use_docarray): + store = HnswDocumentStore[FlatDoc](work_dir=str(tmp_path)) + if use_docarray: + ten_flat_docs = DocumentArray[FlatDoc](ten_flat_docs) + + store.index(ten_flat_docs) + assert store.num_docs() == 10 + for index in store._hnsw_indices.values(): + assert index.get_current_count() == 10 + + +@pytest.mark.parametrize('use_docarray', [True, False]) +def test_index_nested_schema(ten_nested_docs, tmp_path, use_docarray): + store = HnswDocumentStore[NestedDoc](work_dir=str(tmp_path)) + if use_docarray: + ten_nested_docs = DocumentArray[NestedDoc](ten_nested_docs) + + store.index(ten_nested_docs) + assert store.num_docs() == 10 + for index in store._hnsw_indices.values(): + assert index.get_current_count() == 10 + + +def test_get_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): + simple_path = tmp_path / 'simple' + flat_path = tmp_path / 'flat' + nested_path = tmp_path / 'nested' + + # simple + store = HnswDocumentStore[SimpleDoc](work_dir=str(simple_path)) + store.index(ten_simple_docs) + + assert store.num_docs() == 10 + for d in ten_simple_docs: + id_ = d.id + assert store[id_].id == id_ + assert np.all(store[id_].tens == d.tens) + + # flat + store = HnswDocumentStore[FlatDoc](work_dir=str(flat_path)) + store.index(ten_flat_docs) + + assert store.num_docs() == 10 + for d in ten_flat_docs: + id_ = d.id + assert store[id_].id == id_ + assert np.all(store[id_].tens_one == d.tens_one) + assert np.all(store[id_].tens_two == d.tens_two) + + # nested + store = HnswDocumentStore[NestedDoc](work_dir=str(nested_path)) + store.index(ten_nested_docs) + + assert store.num_docs() == 10 + for d in ten_nested_docs: + id_ = d.id + assert store[id_].id == id_ + assert store[id_].d.id == d.d.id + assert np.all(store[id_].d.tens == d.d.tens) + + +def test_get_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): + simple_path = tmp_path / 'simple' + flat_path = tmp_path / 'flat' + nested_path = tmp_path / 'nested' + docs_to_get_idx = [0, 2, 4, 6, 8] + + # simple + store = HnswDocumentStore[SimpleDoc](work_dir=str(simple_path)) + store.index(ten_simple_docs) + + assert store.num_docs() == 10 + docs_to_get = [ten_simple_docs[i] for i in docs_to_get_idx] + ids_to_get = [d.id for d in docs_to_get] + retrieved_docs = store[ids_to_get] + for id_, d_in, d_out in zip(ids_to_get, docs_to_get, retrieved_docs): + assert d_out.id == id_ + assert np.all(d_out.tens == d_in.tens) + + # flat + store = HnswDocumentStore[FlatDoc](work_dir=str(flat_path)) + store.index(ten_flat_docs) + + assert store.num_docs() == 10 + docs_to_get = [ten_flat_docs[i] for i in docs_to_get_idx] + ids_to_get = [d.id for d in docs_to_get] + retrieved_docs = store[ids_to_get] + for id_, d_in, d_out in zip(ids_to_get, docs_to_get, retrieved_docs): + assert d_out.id == id_ + assert np.all(d_out.tens_one == d_in.tens_one) + assert np.all(d_out.tens_two == d_in.tens_two) + + # nested + store = HnswDocumentStore[NestedDoc](work_dir=str(nested_path)) + store.index(ten_nested_docs) + + assert store.num_docs() == 10 + docs_to_get = [ten_nested_docs[i] for i in docs_to_get_idx] + ids_to_get = [d.id for d in docs_to_get] + retrieved_docs = store[ids_to_get] + for id_, d_in, d_out in zip(ids_to_get, docs_to_get, retrieved_docs): + assert d_out.id == id_ + assert d_out.d.id == d_in.d.id + assert np.all(d_out.d.tens == d_in.d.tens) From f920c5f64de03e304f387ca73bf63e23892d80b2 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 22 Feb 2023 13:53:29 +0100 Subject: [PATCH 26/53] test: delitem Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 4 +- docarray/storage/backends/HnswDocStore.py | 11 ++-- .../doc_stores/hnswlib/test_index_get_del.py | 63 +++++++++++++++++++ 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index e0fa2a76103..e8e4d92e2b2 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -170,6 +170,7 @@ def num_docs(self) -> int: @abstractmethod def __delitem__(self, key: Union[str, Sequence[str]]): """Delete one or multiple Documents from the store, by `id`. + If no document is found, a KeyError is raised. :param key: id or ids to delete from the Document Store """ @@ -180,9 +181,6 @@ def __getitem__( self, key: Union[str, Sequence[str]] ) -> Union[TSchema, Sequence[TSchema]]: """Get one or multiple Documents into the store, by `id`. - Document are returned in the same order as the ids. - If the same id is requested multiple times, - the document is returned multiple times. If no document is found, a KeyError is raised. :param key: id or ids to get from the Document Store diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index aa41ebc30d9..f348ea207ae 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -234,10 +234,13 @@ def __delitem__(self, key: Union[str, Sequence[str]]): # delete from the indices if isinstance(key, str): key = [key] - for doc_id in key: - id_ = self._to_hasdhed_id(doc_id) - for col_name, index in self._hnsw_indices.items(): - index.mark_deleted(id_) + try: + for doc_id in key: + id_ = self._to_hasdhed_id(doc_id) + for col_name, index in self._hnsw_indices.items(): + index.mark_deleted(id_) + except RuntimeError: + raise KeyError(f'No document with id {key} found') self._delete_docs_from_sqlite(key) self._sqlite_conn.commit() diff --git a/tests/doc_stores/hnswlib/test_index_get_del.py b/tests/doc_stores/hnswlib/test_index_get_del.py index c65003721fd..2cf72ad5ec9 100644 --- a/tests/doc_stores/hnswlib/test_index_get_del.py +++ b/tests/doc_stores/hnswlib/test_index_get_del.py @@ -159,3 +159,66 @@ def test_get_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path) assert d_out.id == id_ assert d_out.d.id == d_in.d.id assert np.all(d_out.d.tens == d_in.d.tens) + + +def test_get_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): + store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store.index(ten_simple_docs) + + with pytest.raises(KeyError): + store['not_a_real_id'] + + +def test_del_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): + store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store.index(ten_simple_docs) + # delete once + assert store.num_docs() == 10 + del store[ten_simple_docs[0].id] + assert store.num_docs() == 9 + for i, d in enumerate(ten_simple_docs): + id_ = d.id + if i == 0: # deleted + with pytest.raises(KeyError): + store[id_] + else: + assert store[id_].id == id_ + assert np.all(store[id_].tens == d.tens) + # delete again + del store[ten_simple_docs[3].id] + assert store.num_docs() == 8 + for i, d in enumerate(ten_simple_docs): + id_ = d.id + if i in (0, 3): # deleted + with pytest.raises(KeyError): + store[id_] + else: + assert store[id_].id == id_ + assert np.all(store[id_].tens == d.tens) + + +def test_del_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): + docs_to_del_idx = [0, 2, 4, 6, 8] + + store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store.index(ten_simple_docs) + + assert store.num_docs() == 10 + docs_to_del = [ten_simple_docs[i] for i in docs_to_del_idx] + ids_to_del = [d.id for d in docs_to_del] + del store[ids_to_del] + for i, doc in enumerate(ten_simple_docs): + if i in docs_to_del_idx: + with pytest.raises(KeyError): + store[doc.id] + else: + assert store[doc.id].id == doc.id + assert np.all(store[doc.id].tens == doc.tens) + + +def test_del_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): + store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store.index(ten_simple_docs) + + with pytest.raises(KeyError): + del store['not_a_real_id'] From c28b309cd8418c00983aae178e4149ab96255d56 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 22 Feb 2023 14:00:37 +0100 Subject: [PATCH 27/53] test: test num docs Signed-off-by: Johannes Messner --- .../doc_stores/hnswlib/test_index_get_del.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/doc_stores/hnswlib/test_index_get_del.py b/tests/doc_stores/hnswlib/test_index_get_del.py index 2cf72ad5ec9..aae03144869 100644 --- a/tests/doc_stores/hnswlib/test_index_get_del.py +++ b/tests/doc_stores/hnswlib/test_index_get_del.py @@ -222,3 +222,23 @@ def test_del_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path with pytest.raises(KeyError): del store['not_a_real_id'] + + +def test_num_docs(ten_simple_docs, tmp_path): + store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store.index(ten_simple_docs) + + assert store.num_docs() == 10 + + del store[ten_simple_docs[0].id] + assert store.num_docs() == 9 + + del store[ten_simple_docs[3].id, ten_simple_docs[5].id] + assert store.num_docs() == 7 + + more_docs = [SimpleDoc(tens=np.random.rand(10)) for _ in range(5)] + store.index(more_docs) + assert store.num_docs() == 12 + + del store[more_docs[2].id, ten_simple_docs[7].id] + assert store.num_docs() == 10 From 9b8362bc3a0bcf37f41cffd34c30606200899990 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Thu, 23 Feb 2023 11:57:20 +0100 Subject: [PATCH 28/53] test: test find Signed-off-by: Johannes Messner --- docarray/array/array.py | 15 +-- docarray/storage/backends/HnswDocStore.py | 6 +- docarray/utils/misc.py | 15 +++ tests/doc_stores/hnswlib/test_find.py | 149 ++++++++++++++++++++++ 4 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 tests/doc_stores/hnswlib/test_find.py diff --git a/docarray/array/array.py b/docarray/array/array.py index e65ca435787..0034c60a247 100644 --- a/docarray/array/array.py +++ b/docarray/array/array.py @@ -22,7 +22,7 @@ from docarray.array.abstract_array import AnyDocumentArray from docarray.base_document import AnyDocument, BaseDocument from docarray.typing import NdArray -from docarray.utils.misc import is_torch_available +from docarray.utils.misc import is_np_int, is_torch_available if TYPE_CHECKING: from pydantic import BaseConfig @@ -56,17 +56,6 @@ def _delegate_meth(self, *args, **kwargs): return _delegate_meth -def _is_np_int(item: Any) -> bool: - dtype = getattr(item, 'dtype', None) - ndim = getattr(item, 'ndim', None) - if dtype is not None and ndim is not None: - try: - return ndim == 0 and np.issubdtype(dtype, np.integer) - except TypeError: - return False - return False # this is unreachable, but mypy wants it - - class DocumentArray(AnyDocumentArray, Generic[T_doc]): """ DocumentArray is a container of Documents. @@ -207,7 +196,7 @@ def _normalize_index_item( return item # numpy index types - if _is_np_int(item): + if is_np_int(item): return item.item() index_has_getitem = hasattr(item, '__getitem__') diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index f348ea207ae..136e5748221 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -20,7 +20,7 @@ from docarray.typing import AnyTensor from docarray.utils.filter import filter as da_filter from docarray.utils.find import FindResult -from docarray.utils.misc import torch_imported +from docarray.utils.misc import is_np_int, torch_imported TSchema = TypeVar('TSchema', bound=BaseDocument) @@ -176,7 +176,7 @@ def find_batched( labels, distances = index.knn_query(query_vec_np, k=limit) result_das = [ self._get_docs_sqlite_hashed_id( - ids_per_query, + ids_per_query.tolist(), ) for ids_per_query in labels ] @@ -323,7 +323,7 @@ def _get_docs_sqlite_unsorted(self, univ_ids: Sequence[int]): for id_ in univ_ids: # I hope this protects from injection attacks # properly binding with '?' doesn't work for some reason - assert isinstance(id_, int) + assert isinstance(id_, int) or is_np_int(id_) sql_id_list = '(' + ', '.join(str(id_) for id_ in univ_ids) + ')' self._sqlite_cursor.execute( 'SELECT data FROM docs WHERE doc_id IN %s' % sql_id_list, diff --git a/docarray/utils/misc.py b/docarray/utils/misc.py index 939d2fe08c9..4dc07405bd2 100644 --- a/docarray/utils/misc.py +++ b/docarray/utils/misc.py @@ -1,3 +1,7 @@ +from typing import Any + +import numpy as np + try: import torch # noqa: F401 except ImportError: @@ -8,3 +12,14 @@ def is_torch_available(): return torch_imported + + +def is_np_int(item: Any) -> bool: + dtype = getattr(item, 'dtype', None) + ndim = getattr(item, 'ndim', None) + if dtype is not None and ndim is not None: + try: + return ndim == 0 and np.issubdtype(dtype, np.integer) + except TypeError: + return False + return False # this is unreachable, but mypy wants it diff --git a/tests/doc_stores/hnswlib/test_find.py b/tests/doc_stores/hnswlib/test_find.py new file mode 100644 index 00000000000..a7365c1cbfc --- /dev/null +++ b/tests/doc_stores/hnswlib/test_find.py @@ -0,0 +1,149 @@ +import numpy as np +import pytest +from pydantic import Field + +from docarray import BaseDocument +from docarray.storage.backends.HnswDocStore import HnswDocumentStore +from docarray.typing import NdArray + + +class SimpleDoc(BaseDocument): + tens: NdArray[10] = Field(dim=1000) + + +class FlatDoc(BaseDocument): + tens_one: NdArray = Field(dim=10) + tens_two: NdArray = Field(dim=50) + + +class NestedDoc(BaseDocument): + d: SimpleDoc + + +class DeepNestedDoc(BaseDocument): + d: NestedDoc + + +@pytest.mark.parametrize('space', ['cosine', 'l2', 'ip']) +def test_find_simple_schema(tmp_path, space): + class SimpleSchema(BaseDocument): + tens: NdArray[10] = Field(space=space) + + store = HnswDocumentStore[SimpleSchema](work_dir=str(tmp_path)) + + index_docs = [SimpleDoc(tens=np.zeros(10)) for _ in range(10)] + index_docs.append(SimpleDoc(tens=np.ones(10))) + store.index(index_docs) + + query = SimpleDoc(tens=np.ones(10)) + + docs, scores = store.find(query, embedding_field='tens', limit=5) + + assert len(docs) == 5 + assert len(scores) == 5 + assert docs[0].id == index_docs[-1].id + assert np.allclose(docs[0].tens, index_docs[-1].tens) + for result in docs[1:]: + assert np.allclose(result.tens, np.zeros(10)) + + +@pytest.mark.parametrize('space', ['cosine', 'l2', 'ip']) +def test_find_flat_schema(tmp_path, space): + class FlatSchema(BaseDocument): + tens_one: NdArray = Field(dim=10, space=space) + tens_two: NdArray = Field(dim=50, space=space) + + store = HnswDocumentStore[FlatSchema](work_dir=str(tmp_path)) + + index_docs = [ + FlatDoc(tens_one=np.zeros(10), tens_two=np.zeros(50)) for _ in range(10) + ] + index_docs.append(FlatDoc(tens_one=np.zeros(10), tens_two=np.ones(50))) + index_docs.append(FlatDoc(tens_one=np.ones(10), tens_two=np.zeros(50))) + store.index(index_docs) + + query = FlatDoc(tens_one=np.ones(10), tens_two=np.ones(50)) + + # find on tens_one + docs, scores = store.find(query, embedding_field='tens_one', limit=5) + assert len(docs) == 5 + assert len(scores) == 5 + assert docs[0].id == index_docs[-1].id + assert np.allclose(docs[0].tens_one, index_docs[-1].tens_one) + assert np.allclose(docs[0].tens_two, index_docs[-1].tens_two) + + # find on tens_two + docs, scores = store.find(query, embedding_field='tens_two', limit=5) + assert len(docs) == 5 + assert len(scores) == 5 + assert docs[0].id == index_docs[-2].id + assert np.allclose(docs[0].tens_one, index_docs[-2].tens_one) + assert np.allclose(docs[0].tens_two, index_docs[-2].tens_two) + + +@pytest.mark.parametrize('space', ['cosine', 'l2', 'ip']) +def test_find_nested_schema(tmp_path, space): + class SimpleDoc(BaseDocument): + tens: NdArray[10] = Field(space=space) + + class NestedDoc(BaseDocument): + d: SimpleDoc + tens: NdArray[10] = Field(space=space) + + class DeepNestedDoc(BaseDocument): + d: NestedDoc + tens: NdArray = Field(space=space, dim=10) + + store = HnswDocumentStore[DeepNestedDoc](work_dir=str(tmp_path)) + + index_docs = [ + DeepNestedDoc( + d=NestedDoc(d=SimpleDoc(tens=np.zeros(10)), tens=np.zeros(10)), + tens=np.zeros(10), + ) + for _ in range(10) + ] + index_docs.append( + DeepNestedDoc( + d=NestedDoc(d=SimpleDoc(tens=np.ones(10)), tens=np.zeros(10)), + tens=np.zeros(10), + ) + ) + index_docs.append( + DeepNestedDoc( + d=NestedDoc(d=SimpleDoc(tens=np.zeros(10)), tens=np.ones(10)), + tens=np.zeros(10), + ) + ) + index_docs.append( + DeepNestedDoc( + d=NestedDoc(d=SimpleDoc(tens=np.zeros(10)), tens=np.zeros(10)), + tens=np.ones(10), + ) + ) + store.index(index_docs) + + query = DeepNestedDoc( + d=NestedDoc(d=SimpleDoc(tens=np.ones(10)), tens=np.ones(10)), tens=np.ones(10) + ) + + # find on root level + docs, scores = store.find(query, embedding_field='tens', limit=5) + assert len(docs) == 5 + assert len(scores) == 5 + assert docs[0].id == index_docs[-1].id + assert np.allclose(docs[0].tens, index_docs[-1].tens) + + # find on first nesting level + docs, scores = store.find(query, embedding_field='d__tens', limit=5) + assert len(docs) == 5 + assert len(scores) == 5 + assert docs[0].id == index_docs[-2].id + assert np.allclose(docs[0].d.tens, index_docs[-2].d.tens) + + # find on second nesting level + docs, scores = store.find(query, embedding_field='d__d__tens', limit=5) + assert len(docs) == 5 + assert len(scores) == 5 + assert docs[0].id == index_docs[-3].id + assert np.allclose(docs[0].d.d.tens, index_docs[-3].d.d.tens) From 451db41cdb4a10c7b239e1a155939acf5d2f3de2 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Thu, 23 Feb 2023 17:03:12 +0100 Subject: [PATCH 29/53] refactor: pull more logic into the abstract class Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 204 ++++++++++++++---- docarray/storage/backends/HnswDocStore.py | 52 +++-- .../base_classes/test_base_doc_store.py | 70 +++--- .../base_classes/test_base_query_builder.py | 6 +- tests/doc_stores/base_classes/test_configs.py | 20 +- tests/doc_stores/hnswlib/test_find.py | 20 +- .../doc_stores/hnswlib/test_index_get_del.py | 30 +-- 7 files changed, 261 insertions(+), 141 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index e8e4d92e2b2..873350546d5 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -25,6 +25,10 @@ from docarray.array.abstract_array import AnyDocumentArray from docarray.typing import AnyTensor from docarray.utils.find import FindResult +from docarray.utils.misc import torch_imported + +if torch_imported: + import torch TSchema = TypeVar('TSchema', bound=BaseDocument) @@ -76,7 +80,13 @@ def __init__(self, fn): self.fn = fn def __set_name__(self, owner, name): - setattr(owner.QueryBuilder, name, _delegate_to_query(name, self.fn)) + if name.startswith('_') and not name.startswith('__'): + public_name = name[1:] + else: + public_name = name + setattr( + owner.QueryBuilder, public_name, _delegate_to_query(public_name, self.fn) + ) setattr(owner, name, self.fn) @@ -86,7 +96,7 @@ def composable(fn): return fn -class BaseDocumentStore(ABC, Generic[TSchema]): +class BaseDocumentIndex(ABC, Generic[TSchema]): """Abstract class for all Document Stores""" # the BaseDocument that defines the schema of the store @@ -158,7 +168,7 @@ def python_type_to_db_type(self, python_type: Type) -> Any: ... @abstractmethod - def index(self, docs: Union[TSchema, Sequence[TSchema]]): + def _index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" ... @@ -204,87 +214,79 @@ def execute_query(self, query: Any, *args, **kwargs) -> Any: ... @abstractmethod - def find( + def _find( self, - query: Union[AnyTensor, BaseDocument], - embedding_field: str = 'embedding', - metric: str = 'cosine_sim', - limit: int = 10, + query: np.ndarray, + search_field: str, + limit: int, **kwargs, ) -> FindResult: """Find documents in the store""" - # TODO(johannes) refine method signature ... @abstractmethod - def find_batched( + def _find_batched( self, - query: Union[AnyTensor, DocumentArray], - embedding_field: str = 'embedding', - metric: str = 'cosine_sim', - limit: int = 10, + query: np.ndarray, + search_field: str, + limit: int, **kwargs, ) -> FindResultBatched: """Find documents in the store""" - # TODO(johannes) refine method signature ... @abstractmethod - def filter( + def _filter( self, filter_query: Any, - limit: int = 10, + limit: int, **kwargs, ) -> DocumentArray: """Find documents in the store based on a filter query :param filter_query: the DB specific filter query to execute """ - # TODO(johannes) refine method signature ... @abstractmethod - def filter_batched( + def _filter_batched( self, filter_queries: Any, - limit: int = 10, + limit: int, **kwargs, ) -> List[DocumentArray]: """Find documents in the store based on multiple filter queries :param filter_queries: the DB specific filter queries to execute """ - # TODO(johannes) refine method signature ... @abstractmethod - def text_search( + def _text_search( self, query: str, - embedding_field: str = 'embedding', - limit: int = 10, + search_field: str, + limit: int, **kwargs, ) -> FindResult: """Find documents in the store based on a text search query :param query: The text to search for """ - # TODO(johannes) refine method signature ... @abstractmethod - def text_search_batched( + def _text_search_batched( self, - queries: List[str], - embedding_field: str = 'embedding', - limit: int = 10, + queries: Sequence[str], + search_field: str, + limit: int, **kwargs, ) -> FindResultBatched: """Find documents in the store based on a text search query :param query: The text to search for """ - # TODO(johannes) refine method signature ... #################################################### @@ -310,29 +312,137 @@ def configure(self, runtime_config=None, **kwargs): raise ValueError(f'runtime_config must be of type {self.RuntimeConfig}') self._runtime_config = runtime_config + def index(self, docs: Union[TSchema, Sequence[TSchema]]): + """Index a document into the store""" + if not isinstance(docs, Sequence): + docs = [docs] + self._index(docs) + + def find( + self, + query: Union[AnyTensor, BaseDocument], + search_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResult: + """Find documents in the store""" + if isinstance(query, BaseDocument): + query_vec = self._get_values_by_column([query], search_field)[0] + else: + query_vec = query + query_vec_np = self._to_numpy(query_vec) + return self._find( + query_vec_np, search_field=search_field, limit=limit, **kwargs + ) + + def find_batched( + self, + queries: Union[AnyTensor, DocumentArray], + search_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResultBatched: + """Find documents in the store""" + if isinstance(queries, Sequence): + query_vec_list = self._get_values_by_column(queries, search_field) + query_vec_np = np.stack( + self._to_numpy(query_vec) for query_vec in query_vec_list + ) + else: + query_vec_np = self._to_numpy(queries) + + return self._find_batched( + query_vec_np, search_field=search_field, limit=limit, **kwargs + ) + + def filter( + self, + filter_query: Any, + limit: int = 10, + **kwargs, + ) -> DocumentArray: + """Find documents in the store based on a filter query + + :param filter_query: the DB specific filter query to execute + """ + return self._filter(filter_query, limit=limit, **kwargs) + + def filter_batched( + self, + filter_queries: Any, + limit: int = 10, + **kwargs, + ) -> List[DocumentArray]: + """Find documents in the store based on multiple filter queries + + :param filter_queries: the DB specific filter queries to execute + """ + return self._filter_batched(filter_queries, limit=limit, **kwargs) + + def text_search( + self, + query: Union[str, BaseDocument], + search_field: str = 'text', + limit: int = 10, + **kwargs, + ) -> FindResult: + """Find documents in the store based on a text search query + + :param query: The text to search for + """ + if isinstance(query, BaseDocument): + query_text = self._get_values_by_column([query], search_field)[0] + else: + query_text = query + return self._text_search( + query_text, search_field=search_field, limit=limit, **kwargs + ) + + def text_search_batched( + self, + queries: Union[Sequence[str], Sequence[BaseDocument]], + search_field: str = 'embedding', + limit: int = 10, + **kwargs, + ) -> FindResultBatched: + """Find documents in the store based on a text search query + + :param query: The text to search for + """ + if isinstance(queries, Sequence): + query_texts = self._get_values_by_column(queries, search_field) + else: + query_texts = queries + return self._text_search_batched( + query_texts, search_field=search_field, limit=limit, **kwargs + ) + ########################################################## # Helper methods # # These might be useful in your subclass implementation # ########################################################## @staticmethod - def _get_value_by_column(doc: BaseDocument, col_name: str) -> Any: + def _get_values_by_column(docs: Sequence[BaseDocument], col_name: str) -> List[Any]: """Get the value of a column of a document. - :param doc: The Document to get the value from + :param docs: The DocumentArray to get the values from :param col_name: The name of the column, e.g. 'text' or 'image__tensor' :return: The value of the column of `doc` """ - if '__' in col_name: - fields = col_name.split('__') - leaf_doc: BaseDocument = doc - for f in fields[:-1]: - leaf_doc = getattr(leaf_doc, f) - return getattr(leaf_doc, fields[-1]) - else: - return getattr(doc, col_name) + leaf_vals = [] + for doc in docs: + if '__' in col_name: + fields = col_name.split('__') + leaf_doc: BaseDocument = doc + for f in fields[:-1]: + leaf_doc = getattr(leaf_doc, f) + leaf_vals.append(getattr(leaf_doc, fields[-1])) + else: + leaf_vals.append(getattr(doc, col_name)) + return leaf_vals - def _get_values_by_columns( + def _get_col_value_dict( self, docs: Union[BaseDocument, Sequence[BaseDocument]] ) -> Dict[str, Generator[Any, None, None]]: """ @@ -349,7 +459,7 @@ def _get_values_by_columns( ) def _col_gen(col_name): - return (self._get_value_by_column(doc, col_name) for doc in docs) + return (self._get_values_by_column([doc], col_name)[0] for doc in docs) return {col_name: _col_gen(col_name) for col_name in self._columns} @@ -451,3 +561,13 @@ def _is_schema_compatible(self, docs: Sequence[BaseDocument]) -> bool: if reference_col_db_types != input_col_db_types: return False return True + + def _to_numpy(self, val: Any) -> Any: + if isinstance(val, np.ndarray): + return val + elif isinstance(val, (list, tuple)): + return np.array(val) + elif torch_imported and isinstance(val, torch.Tensor): + return val.numpy() + else: + raise ValueError(f'Unsupported input type for {type(self)}: {type(val)}') diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 136e5748221..2e5faa9141d 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -12,12 +12,11 @@ from docarray import BaseDocument, DocumentArray from docarray.proto import DocumentProto from docarray.storage.abstract_doc_store import ( - BaseDocumentStore, + BaseDocumentIndex, FindResultBatched, _Column, composable, ) -from docarray.typing import AnyTensor from docarray.utils.filter import filter as da_filter from docarray.utils.find import FindResult from docarray.utils.misc import is_np_int, torch_imported @@ -31,7 +30,7 @@ HNSWLIB_PY_VEC_TYPES.append(torch.Tensor) -class HnswDocumentStore(BaseDocumentStore, Generic[TSchema]): +class HnswDocumentIndex(BaseDocumentIndex, Generic[TSchema]): def __init__(self, db_config=None, **kwargs): super().__init__(db_config=db_config, **kwargs) self._work_dir = self._db_config.work_dir @@ -71,16 +70,16 @@ def __init__(self, db_config=None, **kwargs): ############################################### # Inner classes for query builder and configs # ############################################### - class QueryBuilder(BaseDocumentStore.QueryBuilder): + class QueryBuilder(BaseDocumentIndex.QueryBuilder): def build(self, *args, **kwargs) -> Any: return self._queries @dataclass - class DBConfig(BaseDocumentStore.DBConfig): + class DBConfig(BaseDocumentIndex.DBConfig): work_dir: str = '.' @dataclass - class RuntimeConfig(BaseDocumentStore.RuntimeConfig): + class RuntimeConfig(BaseDocumentIndex.RuntimeConfig): default_column_config: Dict[Type, Dict[str, Any]] = field( default_factory=lambda: { np.ndarray: { @@ -112,9 +111,9 @@ def python_type_to_db_type(self, python_type: Type) -> Any: raise ValueError(f'Unsupported column type for {type(self)}: {python_type}') - def index(self, docs: Union[TSchema, Sequence[TSchema]]): + def _index(self, docs: Union[TSchema, Sequence[TSchema]]): """Index a document into the store""" - data_by_columns = self._get_values_by_columns(docs) + data_by_columns = self._get_col_value_dict(docs) hnsw_ids = tuple(self._to_hasdhed_id(doc.id) for doc in docs) # indexing into HNSWLib and SQLite sequentially @@ -157,23 +156,17 @@ def execute_query(self, query: List[Tuple[str, Dict]], *args, **kwargs) -> Any: out_docs, out_scores = zip(*docs_sorted) return FindResult(documents=out_docs, scores=out_scores) - def find_batched( + def _find_batched( self, - query: Union[AnyTensor, DocumentArray], - embedding_field: str = 'embedding', - metric: str = 'cosine_sim', - limit: int = 10, + query: np.ndarray, + search_field: str, + limit: int, **kwargs, ) -> FindResultBatched: - # the below should be done in the abstract class - if isinstance(query, BaseDocument): - query_vec = self._get_value_by_column(query, embedding_field) - else: - query_vec = query - query_vec_np = self._to_numpy(query_vec) - - index = self._hnsw_indices[embedding_field] - labels, distances = index.knn_query(query_vec_np, k=limit) + if kwargs: + raise ValueError(f'{list(kwargs.keys())} are not valid keyword arguments') + index = self._hnsw_indices[search_field] + labels, distances = index.knn_query(query, k=limit) result_das = [ self._get_docs_sqlite_hashed_id( ids_per_query.tolist(), @@ -183,12 +176,15 @@ def find_batched( return FindResultBatched(documents=result_das, scores=distances) @composable - def find(self, *args, **kwargs) -> FindResult: - docs, scores = self.find_batched(*args, **kwargs) + def _find( + self, query: np.ndarray, search_field: str, limit: int, **kwargs + ) -> FindResult: + query_batched = np.expand_dims(query, axis=0) + docs, scores = self._find_batched(query_batched, search_field, limit, **kwargs) return FindResult(documents=docs[0], scores=scores[0]) @composable - def filter( + def _filter( self, *args, **kwargs, @@ -200,7 +196,7 @@ def filter( f' `build_query()` and `execute_query()`.' ) - def filter_batched( + def _filter_batched( self, filter_queries: Any, limit: int = 10, @@ -212,7 +208,7 @@ def filter_batched( f' `build_query()` and `execute_query()`.' ) - def text_search( + def _text_search( self, query: str, embedding_field: str = 'embedding', @@ -221,7 +217,7 @@ def text_search( ) -> FindResult: raise NotImplementedError(f'{type(self)} does not support text search.') - def text_search_batched( + def _text_search_batched( self, queries: List[str], embedding_field: str = 'embedding', diff --git a/tests/doc_stores/base_classes/test_base_doc_store.py b/tests/doc_stores/base_classes/test_base_doc_store.py index eb1de132987..b087f285949 100644 --- a/tests/doc_stores/base_classes/test_base_doc_store.py +++ b/tests/doc_stores/base_classes/test_base_doc_store.py @@ -6,7 +6,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.abstract_doc_store import BaseDocumentStore +from docarray.storage.abstract_doc_store import BaseDocumentIndex from docarray.typing import ID, NdArray @@ -35,54 +35,54 @@ def _identity(*x, **y): return x, y -class DummyDocStore(BaseDocumentStore): +class DummyDocIndex(BaseDocumentIndex): @dataclass - class RuntimeConfig(BaseDocumentStore.RuntimeConfig): + class RuntimeConfig(BaseDocumentIndex.RuntimeConfig): default_column_config: Dict[Type, Dict[str, Any]] = field( default_factory=lambda: {str: {'hi': 'there'}, np.ndarray: {'you': 'good?'}} ) @dataclass - class DBConfig(BaseDocumentStore.DBConfig): + class DBConfig(BaseDocumentIndex.DBConfig): work_dir: str = '.' - class QueryBuilder(BaseDocumentStore.QueryBuilder): + class QueryBuilder(BaseDocumentIndex.QueryBuilder): def build(self): return self._queries def python_type_to_db_type(self, x): return str - index = _identity + _index = _identity num_docs = _identity __delitem__ = _identity __getitem__ = _identity execute_query = _identity - find = _identity - find_batched = _identity - filter = _identity - filter_batched = _identity - text_search = _identity - text_search_batched = _identity + _find = _identity + _find_batched = _identity + _filter = _identity + _filter_batched = _identity + _text_search = _identity + _text_search_batched = _identity def test_parametrization(): with pytest.raises(ValueError): - DummyDocStore() + DummyDocIndex() - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() assert store._schema is SimpleDoc def test_build_query(): - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() q = store.build_query() assert isinstance(q, store.QueryBuilder) def test_create_columns(): # Simple doc - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() assert list(store._columns.keys()) == ['id', 'tens'] assert store._columns['id'].docarray_type == ID @@ -96,7 +96,7 @@ def test_create_columns(): assert store._columns['tens'].config == {'dim': 1000, 'hi': 'there'} # Flat doc - store = DummyDocStore[FlatDoc]() + store = DummyDocIndex[FlatDoc]() assert list(store._columns.keys()) == ['id', 'tens_one', 'tens_two'] assert store._columns['id'].docarray_type == ID @@ -115,7 +115,7 @@ def test_create_columns(): assert store._columns['tens_two'].config == {'dim': 50, 'hi': 'there'} # Nested doc - store = DummyDocStore[NestedDoc]() + store = DummyDocIndex[NestedDoc]() assert list(store._columns.keys()) == ['id', 'd__id', 'd__tens'] assert store._columns['id'].docarray_type == ID @@ -139,7 +139,7 @@ class OtherFlatDoc(FlatDoc): class OtherNestedDoc(NestedDoc): ... - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() assert store._is_schema_compatible([SimpleDoc(tens=np.random.random((10,)))]) assert store._is_schema_compatible( DocumentArray[SimpleDoc]([SimpleDoc(tens=np.random.random((10,)))]) @@ -161,7 +161,7 @@ class OtherNestedDoc(NestedDoc): ) ) - store = DummyDocStore[FlatDoc]() + store = DummyDocIndex[FlatDoc]() assert store._is_schema_compatible( [FlatDoc(tens_one=np.random.random((10,)), tens_two=np.random.random((50,)))] ) @@ -195,7 +195,7 @@ class OtherNestedDoc(NestedDoc): DocumentArray[SimpleDoc]([SimpleDoc(tens=np.random.random((10,)))]) ) - store = DummyDocStore[NestedDoc]() + store = DummyDocIndex[NestedDoc]() assert store._is_schema_compatible( [NestedDoc(d=SimpleDoc(tens=np.random.random((10,))))] ) @@ -220,51 +220,55 @@ def test_get_value(): t = np.random.random((10,)) doc = SimpleDoc(tens=t) - assert np.all(DummyDocStore._get_value_by_column(doc, 'tens') == t) + assert np.all(DummyDocIndex._get_values_by_column([doc], 'tens')[0] == t) doc = FlatDoc(tens_one=t, tens_two=np.random.random((50,))) - assert np.all(DummyDocStore._get_value_by_column(doc, 'tens_one') == t) + assert np.all(DummyDocIndex._get_values_by_column([doc], 'tens_one')[0] == t) doc = NestedDoc(d=SimpleDoc(tens=t)) - assert np.all(DummyDocStore._get_value_by_column(doc, 'd__tens') == t) + assert np.all(DummyDocIndex._get_values_by_column([doc], 'd__tens')[0] == t) doc = DeepNestedDoc(d=NestedDoc(d=SimpleDoc(tens=t))) - assert np.all(DummyDocStore._get_value_by_column(doc, 'd__d__tens') == t) + assert np.all(DummyDocIndex._get_values_by_column([doc], 'd__d__tens')[0] == t) + + vals = DummyDocIndex._get_values_by_column([doc, doc], 'd__d__tens') + assert np.all(vals[0] == t) + assert np.all(vals[1] == t) def test_get_data_by_columns(): - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() docs = [SimpleDoc(tens=np.random.random((10,))) for _ in range(10)] - data_by_columns = store._get_values_by_columns(docs) + data_by_columns = store._get_col_value_dict(docs) assert list(data_by_columns.keys()) == ['id', 'tens'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['tens']) == [doc.tens for doc in docs] - store = DummyDocStore[FlatDoc]() + store = DummyDocIndex[FlatDoc]() docs = [ FlatDoc(tens_one=np.random.random((10,)), tens_two=np.random.random((50,))) for _ in range(10) ] - data_by_columns = store._get_values_by_columns(docs) + data_by_columns = store._get_col_value_dict(docs) assert list(data_by_columns.keys()) == ['id', 'tens_one', 'tens_two'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['tens_one']) == [doc.tens_one for doc in docs] assert list(data_by_columns['tens_two']) == [doc.tens_two for doc in docs] - store = DummyDocStore[NestedDoc]() + store = DummyDocIndex[NestedDoc]() docs = [NestedDoc(d=SimpleDoc(tens=np.random.random((10,)))) for _ in range(10)] - data_by_columns = store._get_values_by_columns(docs) + data_by_columns = store._get_col_value_dict(docs) assert list(data_by_columns.keys()) == ['id', 'd__id', 'd__tens'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['d__id']) == [doc.d.id for doc in docs] assert list(data_by_columns['d__tens']) == [doc.d.tens for doc in docs] - store = DummyDocStore[DeepNestedDoc]() + store = DummyDocIndex[DeepNestedDoc]() docs = [ DeepNestedDoc(d=NestedDoc(d=SimpleDoc(tens=np.random.random((10,))))) for _ in range(10) ] - data_by_columns = store._get_values_by_columns(docs) + data_by_columns = store._get_col_value_dict(docs) assert list(data_by_columns.keys()) == ['id', 'd__id', 'd__d__id', 'd__d__tens'] assert list(data_by_columns['id']) == [doc.id for doc in docs] assert list(data_by_columns['d__id']) == [doc.d.id for doc in docs] diff --git a/tests/doc_stores/base_classes/test_base_query_builder.py b/tests/doc_stores/base_classes/test_base_query_builder.py index d44e06c77e2..012b48bfab5 100644 --- a/tests/doc_stores/base_classes/test_base_query_builder.py +++ b/tests/doc_stores/base_classes/test_base_query_builder.py @@ -1,9 +1,9 @@ import pytest -from docarray.storage.abstract_doc_store import BaseDocumentStore, composable +from docarray.storage.abstract_doc_store import BaseDocumentIndex, composable -class DummyQueryBuilder(BaseDocumentStore.QueryBuilder): +class DummyQueryBuilder(BaseDocumentIndex.QueryBuilder): def build(self): return self._queries @@ -12,7 +12,7 @@ def _identity(*x, **y): return x, y -class DummyDocStore(BaseDocumentStore): +class DummyDocIndex(BaseDocumentIndex): _query_builder_cls = DummyQueryBuilder _db_config_cls = None _runtime_config_cls = None diff --git a/tests/doc_stores/base_classes/test_configs.py b/tests/doc_stores/base_classes/test_configs.py index 623c8845b2c..389177e08d4 100644 --- a/tests/doc_stores/base_classes/test_configs.py +++ b/tests/doc_stores/base_classes/test_configs.py @@ -4,7 +4,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.abstract_doc_store import BaseDocumentStore +from docarray.storage.abstract_doc_store import BaseDocumentIndex from docarray.typing import NdArray @@ -17,13 +17,13 @@ class FakeQueryBuilder: @dataclass -class DBConfig(BaseDocumentStore.DBConfig): +class DBConfig(BaseDocumentIndex.DBConfig): work_dir: str = '.' other: int = 5 @dataclass -class RuntimeConfig(BaseDocumentStore.RuntimeConfig): +class RuntimeConfig(BaseDocumentIndex.RuntimeConfig): default_column_config: Dict[Type, Dict[str, Any]] = field( default_factory=lambda: { str: { @@ -39,7 +39,7 @@ def _identity(*x, **y): return x, y -class DummyDocStore(BaseDocumentStore): +class DummyDocIndex(BaseDocumentIndex): DBConfig = DBConfig RuntimeConfig = RuntimeConfig @@ -60,7 +60,7 @@ def python_type_to_db_type(self, x): def test_defaults(): - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() assert store._db_config.other == 5 assert store._db_config.work_dir == '.' assert store._runtime_config.default_column_config[str] == { @@ -71,14 +71,14 @@ def test_defaults(): def test_set_by_class(): # change all settings - store = DummyDocStore[SimpleDoc](DBConfig(work_dir='hi', other=10)) + store = DummyDocIndex[SimpleDoc](DBConfig(work_dir='hi', other=10)) assert store._db_config.other == 10 assert store._db_config.work_dir == 'hi' store.configure(RuntimeConfig(default_column_config={}, default_ef=10)) assert store._runtime_config.default_column_config == {} # change only some settings - store = DummyDocStore[SimpleDoc](DBConfig(work_dir='hi')) + store = DummyDocIndex[SimpleDoc](DBConfig(work_dir='hi')) assert store._db_config.other == 5 assert store._db_config.work_dir == 'hi' store.configure(RuntimeConfig(default_column_config={})) @@ -87,14 +87,14 @@ def test_set_by_class(): def test_set_by_kwargs(): # change all settings - store = DummyDocStore[SimpleDoc](work_dir='hi', other=10) + store = DummyDocIndex[SimpleDoc](work_dir='hi', other=10) assert store._db_config.other == 10 assert store._db_config.work_dir == 'hi' store.configure(default_column_config={}, default_ef=10) assert store._runtime_config.default_column_config == {} # change only some settings - store = DummyDocStore[SimpleDoc](work_dir='hi') + store = DummyDocIndex[SimpleDoc](work_dir='hi') assert store._db_config.other == 5 assert store._db_config.work_dir == 'hi' store.configure(default_column_config={}) @@ -102,7 +102,7 @@ def test_set_by_kwargs(): def test_default_column_config(): - store = DummyDocStore[SimpleDoc]() + store = DummyDocIndex[SimpleDoc]() assert store._runtime_config.default_column_config == { str: { 'dim': 128, diff --git a/tests/doc_stores/hnswlib/test_find.py b/tests/doc_stores/hnswlib/test_find.py index a7365c1cbfc..ab713903418 100644 --- a/tests/doc_stores/hnswlib/test_find.py +++ b/tests/doc_stores/hnswlib/test_find.py @@ -3,7 +3,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.backends.HnswDocStore import HnswDocumentStore +from docarray.storage.backends.HnswDocStore import HnswDocumentIndex from docarray.typing import NdArray @@ -29,7 +29,7 @@ def test_find_simple_schema(tmp_path, space): class SimpleSchema(BaseDocument): tens: NdArray[10] = Field(space=space) - store = HnswDocumentStore[SimpleSchema](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleSchema](work_dir=str(tmp_path)) index_docs = [SimpleDoc(tens=np.zeros(10)) for _ in range(10)] index_docs.append(SimpleDoc(tens=np.ones(10))) @@ -37,7 +37,7 @@ class SimpleSchema(BaseDocument): query = SimpleDoc(tens=np.ones(10)) - docs, scores = store.find(query, embedding_field='tens', limit=5) + docs, scores = store.find(query, search_field='tens', limit=5) assert len(docs) == 5 assert len(scores) == 5 @@ -53,7 +53,7 @@ class FlatSchema(BaseDocument): tens_one: NdArray = Field(dim=10, space=space) tens_two: NdArray = Field(dim=50, space=space) - store = HnswDocumentStore[FlatSchema](work_dir=str(tmp_path)) + store = HnswDocumentIndex[FlatSchema](work_dir=str(tmp_path)) index_docs = [ FlatDoc(tens_one=np.zeros(10), tens_two=np.zeros(50)) for _ in range(10) @@ -65,7 +65,7 @@ class FlatSchema(BaseDocument): query = FlatDoc(tens_one=np.ones(10), tens_two=np.ones(50)) # find on tens_one - docs, scores = store.find(query, embedding_field='tens_one', limit=5) + docs, scores = store.find(query, search_field='tens_one', limit=5) assert len(docs) == 5 assert len(scores) == 5 assert docs[0].id == index_docs[-1].id @@ -73,7 +73,7 @@ class FlatSchema(BaseDocument): assert np.allclose(docs[0].tens_two, index_docs[-1].tens_two) # find on tens_two - docs, scores = store.find(query, embedding_field='tens_two', limit=5) + docs, scores = store.find(query, search_field='tens_two', limit=5) assert len(docs) == 5 assert len(scores) == 5 assert docs[0].id == index_docs[-2].id @@ -94,7 +94,7 @@ class DeepNestedDoc(BaseDocument): d: NestedDoc tens: NdArray = Field(space=space, dim=10) - store = HnswDocumentStore[DeepNestedDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[DeepNestedDoc](work_dir=str(tmp_path)) index_docs = [ DeepNestedDoc( @@ -128,21 +128,21 @@ class DeepNestedDoc(BaseDocument): ) # find on root level - docs, scores = store.find(query, embedding_field='tens', limit=5) + docs, scores = store.find(query, search_field='tens', limit=5) assert len(docs) == 5 assert len(scores) == 5 assert docs[0].id == index_docs[-1].id assert np.allclose(docs[0].tens, index_docs[-1].tens) # find on first nesting level - docs, scores = store.find(query, embedding_field='d__tens', limit=5) + docs, scores = store.find(query, search_field='d__tens', limit=5) assert len(docs) == 5 assert len(scores) == 5 assert docs[0].id == index_docs[-2].id assert np.allclose(docs[0].d.tens, index_docs[-2].d.tens) # find on second nesting level - docs, scores = store.find(query, embedding_field='d__d__tens', limit=5) + docs, scores = store.find(query, search_field='d__d__tens', limit=5) assert len(docs) == 5 assert len(scores) == 5 assert docs[0].id == index_docs[-3].id diff --git a/tests/doc_stores/hnswlib/test_index_get_del.py b/tests/doc_stores/hnswlib/test_index_get_del.py index aae03144869..97cdecdcc33 100644 --- a/tests/doc_stores/hnswlib/test_index_get_del.py +++ b/tests/doc_stores/hnswlib/test_index_get_del.py @@ -3,7 +3,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.backends.HnswDocStore import HnswDocumentStore +from docarray.storage.backends.HnswDocStore import HnswDocumentIndex from docarray.typing import NdArray @@ -44,7 +44,7 @@ def ten_nested_docs(): @pytest.mark.parametrize('use_docarray', [True, False]) def test_index_simple_schema(ten_simple_docs, tmp_path, use_docarray): - store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(tmp_path)) if use_docarray: ten_simple_docs = DocumentArray[SimpleDoc](ten_simple_docs) @@ -56,7 +56,7 @@ def test_index_simple_schema(ten_simple_docs, tmp_path, use_docarray): @pytest.mark.parametrize('use_docarray', [True, False]) def test_index_flat_schema(ten_flat_docs, tmp_path, use_docarray): - store = HnswDocumentStore[FlatDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[FlatDoc](work_dir=str(tmp_path)) if use_docarray: ten_flat_docs = DocumentArray[FlatDoc](ten_flat_docs) @@ -68,7 +68,7 @@ def test_index_flat_schema(ten_flat_docs, tmp_path, use_docarray): @pytest.mark.parametrize('use_docarray', [True, False]) def test_index_nested_schema(ten_nested_docs, tmp_path, use_docarray): - store = HnswDocumentStore[NestedDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[NestedDoc](work_dir=str(tmp_path)) if use_docarray: ten_nested_docs = DocumentArray[NestedDoc](ten_nested_docs) @@ -84,7 +84,7 @@ def test_get_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): nested_path = tmp_path / 'nested' # simple - store = HnswDocumentStore[SimpleDoc](work_dir=str(simple_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(simple_path)) store.index(ten_simple_docs) assert store.num_docs() == 10 @@ -94,7 +94,7 @@ def test_get_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): assert np.all(store[id_].tens == d.tens) # flat - store = HnswDocumentStore[FlatDoc](work_dir=str(flat_path)) + store = HnswDocumentIndex[FlatDoc](work_dir=str(flat_path)) store.index(ten_flat_docs) assert store.num_docs() == 10 @@ -105,7 +105,7 @@ def test_get_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): assert np.all(store[id_].tens_two == d.tens_two) # nested - store = HnswDocumentStore[NestedDoc](work_dir=str(nested_path)) + store = HnswDocumentIndex[NestedDoc](work_dir=str(nested_path)) store.index(ten_nested_docs) assert store.num_docs() == 10 @@ -123,7 +123,7 @@ def test_get_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path) docs_to_get_idx = [0, 2, 4, 6, 8] # simple - store = HnswDocumentStore[SimpleDoc](work_dir=str(simple_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(simple_path)) store.index(ten_simple_docs) assert store.num_docs() == 10 @@ -135,7 +135,7 @@ def test_get_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path) assert np.all(d_out.tens == d_in.tens) # flat - store = HnswDocumentStore[FlatDoc](work_dir=str(flat_path)) + store = HnswDocumentIndex[FlatDoc](work_dir=str(flat_path)) store.index(ten_flat_docs) assert store.num_docs() == 10 @@ -148,7 +148,7 @@ def test_get_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path) assert np.all(d_out.tens_two == d_in.tens_two) # nested - store = HnswDocumentStore[NestedDoc](work_dir=str(nested_path)) + store = HnswDocumentIndex[NestedDoc](work_dir=str(nested_path)) store.index(ten_nested_docs) assert store.num_docs() == 10 @@ -162,7 +162,7 @@ def test_get_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path) def test_get_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): - store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(tmp_path)) store.index(ten_simple_docs) with pytest.raises(KeyError): @@ -170,7 +170,7 @@ def test_get_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path def test_del_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): - store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(tmp_path)) store.index(ten_simple_docs) # delete once assert store.num_docs() == 10 @@ -200,7 +200,7 @@ def test_del_single(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): def test_del_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): docs_to_del_idx = [0, 2, 4, 6, 8] - store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(tmp_path)) store.index(ten_simple_docs) assert store.num_docs() == 10 @@ -217,7 +217,7 @@ def test_del_multiple(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path) def test_del_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path): - store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(tmp_path)) store.index(ten_simple_docs) with pytest.raises(KeyError): @@ -225,7 +225,7 @@ def test_del_key_error(ten_simple_docs, ten_flat_docs, ten_nested_docs, tmp_path def test_num_docs(ten_simple_docs, tmp_path): - store = HnswDocumentStore[SimpleDoc](work_dir=str(tmp_path)) + store = HnswDocumentIndex[SimpleDoc](work_dir=str(tmp_path)) store.index(ten_simple_docs) assert store.num_docs() == 10 From b83158fc4fa5a22bfbc2987597db1f57dd085666 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Thu, 23 Feb 2023 17:19:19 +0100 Subject: [PATCH 30/53] refactor: improve abstraction of index Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 9 ++++--- docarray/storage/backends/HnswDocStore.py | 24 ++++++++++++------- tests/doc_stores/base_classes/test_configs.py | 14 +++++------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 873350546d5..7bd20361cb7 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -168,8 +168,11 @@ def python_type_to_db_type(self, python_type: Type) -> Any: ... @abstractmethod - def _index(self, docs: Union[TSchema, Sequence[TSchema]]): + def _index(self, column_to_data: Dict[str, Generator[Any, None, None]], **kwargs): """Index a document into the store""" + # column_to_data is a dictionary from column name to a generator + # that yields the data for that column. + # if you want to work directly on documents, you can implement index() instead ... @abstractmethod @@ -312,11 +315,11 @@ def configure(self, runtime_config=None, **kwargs): raise ValueError(f'runtime_config must be of type {self.RuntimeConfig}') self._runtime_config = runtime_config - def index(self, docs: Union[TSchema, Sequence[TSchema]]): + def index(self, docs: Union[TSchema, Sequence[TSchema]], **kwargs): """Index a document into the store""" if not isinstance(docs, Sequence): docs = [docs] - self._index(docs) + self._index(docs, **kwargs) def find( self, diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 2e5faa9141d..379cd31e549 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -111,10 +111,16 @@ def python_type_to_db_type(self, python_type: Type) -> Any: raise ValueError(f'Unsupported column type for {type(self)}: {python_type}') - def _index(self, docs: Union[TSchema, Sequence[TSchema]]): + def _index(self, column_data_dic, **kwargs): + # not needed, we implement `index` directly + ... + + def index(self, docs: Union[TSchema, Sequence[TSchema]], **kwargs): """Index a document into the store""" + if kwargs: + raise ValueError(f'{list(kwargs.keys())} are not valid keyword arguments') data_by_columns = self._get_col_value_dict(docs) - hnsw_ids = tuple(self._to_hasdhed_id(doc.id) for doc in docs) + hashed_ids = tuple(self._to_hashed_id(doc.id) for doc in docs) # indexing into HNSWLib and SQLite sequentially # could be improved by processing in parallel @@ -122,7 +128,7 @@ def _index(self, docs: Union[TSchema, Sequence[TSchema]]): data = data_by_columns[col_name] data_np = [self._to_numpy(arr) for arr in data] data_stacked = np.stack(data_np) - index.add_items(data_stacked, ids=hnsw_ids) + index.add_items(data_stacked, ids=hashed_ids) index.save_index(self._hnsw_locations[col_name]) self._send_docs_to_sqlite(docs) @@ -232,7 +238,7 @@ def __delitem__(self, key: Union[str, Sequence[str]]): key = [key] try: for doc_id in key: - id_ = self._to_hasdhed_id(doc_id) + id_ = self._to_hashed_id(doc_id) for col_name, index in self._hnsw_indices.items(): index.mark_deleted(id_) except RuntimeError: @@ -262,7 +268,7 @@ def num_docs(self) -> int: # general helpers @staticmethod - def _to_hasdhed_id(doc_id: str) -> int: + def _to_hashed_id(doc_id: str) -> int: # https://stackoverflow.com/questions/16008670/how-to-hash-a-string-into-8-digits # hashing to 18 digits avoids overflow of sqlite INTEGER return int(hashlib.sha256(doc_id.encode('utf-8')).hexdigest(), 16) % 10**18 @@ -309,7 +315,7 @@ def _create_docs_table(self): ) def _send_docs_to_sqlite(self, docs: Sequence[BaseDocument]): - ids = (self._to_hasdhed_id(doc.id) for doc in docs) + ids = (self._to_hashed_id(doc.id) for doc in docs) self._sqlite_cursor.executemany( 'INSERT INTO docs VALUES (?, ?)', ((id_, self._doc_to_bytes(doc)) for id_, doc in zip(ids, docs)), @@ -330,7 +336,7 @@ def _get_docs_sqlite_unsorted(self, univ_ids: Sequence[int]): ) def _get_docs_sqlite_doc_id(self, doc_ids: Sequence[str]) -> DocumentArray: - hashed_ids = tuple(self._to_hasdhed_id(id_) for id_ in doc_ids) + hashed_ids = tuple(self._to_hashed_id(id_) for id_ in doc_ids) docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) return DocumentArray[self._schema]( sorted(docs_unsorted, key=lambda doc: doc_ids.index(doc.id)) @@ -340,13 +346,13 @@ def _get_docs_sqlite_hashed_id(self, hashed_ids: Sequence[int]) -> DocumentArray docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) def _in_position(doc): - return hashed_ids.index(self._to_hasdhed_id(doc.id)) + return hashed_ids.index(self._to_hashed_id(doc.id)) return DocumentArray[self._schema](sorted(docs_unsorted, key=_in_position)) def _delete_docs_from_sqlite(self, doc_ids: Sequence[Union[str, int]]): ids = tuple( - self._to_hasdhed_id(id_) if isinstance(id_, str) else id_ for id_ in doc_ids + self._to_hashed_id(id_) if isinstance(id_, str) else id_ for id_ in doc_ids ) self._sqlite_cursor.execute( 'DELETE FROM docs WHERE doc_id IN (%s)' % ','.join('?' * len(ids)), diff --git a/tests/doc_stores/base_classes/test_configs.py b/tests/doc_stores/base_classes/test_configs.py index 389177e08d4..5d00adc2095 100644 --- a/tests/doc_stores/base_classes/test_configs.py +++ b/tests/doc_stores/base_classes/test_configs.py @@ -46,17 +46,17 @@ class DummyDocIndex(BaseDocumentIndex): def python_type_to_db_type(self, x): return str - index = _identity + _index = _identity num_docs = _identity __delitem__ = _identity __getitem__ = _identity execute_query = _identity - find = _identity - find_batched = _identity - filter = _identity - filter_batched = _identity - text_search = _identity - text_search_batched = _identity + _find = _identity + _find_batched = _identity + _filter = _identity + _filter_batched = _identity + _text_search = _identity + _text_search_batched = _identity def test_defaults(): From f74ab5a1d196952cfcd71286d181dfc8a5ffeaa0 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 27 Feb 2023 12:08:40 +0100 Subject: [PATCH 31/53] refactor: use helper to get field type Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 7bd20361cb7..8371ec12801 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -27,6 +27,9 @@ from docarray.utils.find import FindResult from docarray.utils.misc import torch_imported +if TYPE_CHECKING: + from pydantic.fields import ModelField + if torch_imported: import torch @@ -495,7 +498,7 @@ def build_query(self) -> QueryBuilder: def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: columns: Dict[str, _Column] = dict() for field_name, field in schema.__fields__.items(): - t_ = field.type_ + t_ = schema._get_field_type(field_name) if is_union_type(t_): # TODO(johannes): this restriction has to # go othws we can't even index built in docs @@ -518,11 +521,10 @@ def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: }, ) else: - columns[field_name] = self._create_single_column(field) + columns[field_name] = self._create_single_column(field, t_) return columns - def _create_single_column(self, field): - type_ = field.type_ + def _create_single_column(self, field: 'ModelField', type_: Type) -> _Column: db_type = self.python_type_to_db_type(type_) config = self._runtime_config.default_column_config[db_type].copy() custom_config = field.field_info.extra From b8a553a0b4a7c16d695b55fab43558674d6062fb Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 27 Feb 2023 12:19:35 +0100 Subject: [PATCH 32/53] refactor: change error message Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 8371ec12801..eb229a73cae 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -500,12 +500,9 @@ def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: for field_name, field in schema.__fields__.items(): t_ = schema._get_field_type(field_name) if is_union_type(t_): - # TODO(johannes): this restriction has to - # go othws we can't even index built in docs raise ValueError( - 'Indexing field of Union type is not' - f'supported. Instead of using type' - f'{t_} use a single specific type.' + 'Union types are not supported in the schema of a DocumentIndex.' + f' Instead of using type {t_} use a single specific type.' ) elif issubclass(t_, AnyDocumentArray): raise ValueError( From 5c260e8518fc54420ffd80499f85b2398490a488 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 27 Feb 2023 14:02:35 +0100 Subject: [PATCH 33/53] docs: add docstrings Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 102 +++++++++++++++++++++---- 1 file changed, 87 insertions(+), 15 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index eb229a73cae..2fc55d36dbf 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -167,15 +167,22 @@ class RuntimeConfig(ABC): @abstractmethod def python_type_to_db_type(self, python_type: Type) -> Any: - """Map python type to database type.""" + """Map python type to database type. + Takes any python type and returns the corresponding database column type. + + :param python_type: a python type. + :return: the corresponding database column type, + or None if ``python_type`` is not supported. + """ ... @abstractmethod def _index(self, column_to_data: Dict[str, Generator[Any, None, None]], **kwargs): """Index a document into the store""" - # column_to_data is a dictionary from column name to a generator + # `column_to_data` is a dictionary from column name to a generator # that yields the data for that column. - # if you want to work directly on documents, you can implement index() instead + # If you want to work directly on documents, you can implement index() instead + # If you implement index(), _index() only needs a dummy implementation. ... @abstractmethod @@ -227,7 +234,15 @@ def _find( limit: int, **kwargs, ) -> FindResult: - """Find documents in the store""" + """Find documents in the store + + :param query: query vector for KNN/ANN search. Has single axis. + :param search_field: name of the field to search on + :param limit: maximum number of documents to return per query + :return: a named tuple containing `documents` and `scores` + """ + # NOTE: in standard implementations, + # `search_field` is equal to the column name to search on ... @abstractmethod @@ -238,7 +253,14 @@ def _find_batched( limit: int, **kwargs, ) -> FindResultBatched: - """Find documents in the store""" + """Find documents in the store + + :param query: query vectors for KNN/ANN search. + Has shape (batch_size, vector_dim) + :param search_field: name of the field to search on + :param limit: maximum number of documents to return + :return: a named tuple containing `documents` and `scores` + """ ... @abstractmethod @@ -251,6 +273,8 @@ def _filter( """Find documents in the store based on a filter query :param filter_query: the DB specific filter query to execute + :param limit: maximum number of documents to return + :return: a DocumentArray containing the documents that match the filter query """ ... @@ -261,9 +285,13 @@ def _filter_batched( limit: int, **kwargs, ) -> List[DocumentArray]: - """Find documents in the store based on multiple filter queries + """Find documents in the store based on multiple filter queries. + Each query is considered individually, and results are returned per query. :param filter_queries: the DB specific filter queries to execute + :param limit: maximum number of documents to return per query + :return: List of DocumentArrays containing the documents + that match the filter queries """ ... @@ -278,7 +306,12 @@ def _text_search( """Find documents in the store based on a text search query :param query: The text to search for + :param search_field: name of the field to search on + :param limit: maximum number of documents to return + :return: a named tuple containing `documents` and `scores` """ + # NOTE: in standard implementations, + # `search_field` is equal to the column name to search on ... @abstractmethod @@ -291,8 +324,13 @@ def _text_search_batched( ) -> FindResultBatched: """Find documents in the store based on a text search query - :param query: The text to search for + :param queries: The texts to search for + :param search_field: name of the field to search on + :param limit: maximum number of documents to return per query + :return: a named tuple containing `documents` and `scores` """ + # NOTE: in standard implementations, + # `search_field` is equal to the column name to search on ... #################################################### @@ -319,7 +357,10 @@ def configure(self, runtime_config=None, **kwargs): self._runtime_config = runtime_config def index(self, docs: Union[TSchema, Sequence[TSchema]], **kwargs): - """Index a document into the store""" + """Index Documents into the store. + + :param docs: Documents to index + """ if not isinstance(docs, Sequence): docs = [docs] self._index(docs, **kwargs) @@ -331,7 +372,17 @@ def find( limit: int = 10, **kwargs, ) -> FindResult: - """Find documents in the store""" + """Find documents in the store using nearest neighbor search. + + :param query: query vector for KNN/ANN search. + Can be either a tensor-like (np.array, torch.Tensor, etc.) + with a single axis, or a Document + :param search_field: name of the field to search on. + Documents in the index are retrieved based on this similarity + of this field to the query. + :param limit: maximum number of documents to return + :return: a named tuple containing `documents` and `scores` + """ if isinstance(query, BaseDocument): query_vec = self._get_values_by_column([query], search_field)[0] else: @@ -348,7 +399,18 @@ def find_batched( limit: int = 10, **kwargs, ) -> FindResultBatched: - """Find documents in the store""" + """Find documents in the store using nearest neighbor search. + + :param queries: query vector for KNN/ANN search. + Can be either a tensor-like (np.array, torch.Tensor, etc.) with a, + or a DocumentArray. + If a tensor-like is passed, it should have shape (batch_size, vector_dim) + :param search_field: name of the field to search on. + Documents in the index are retrieved based on this similarity + of this field to the query. + :param limit: maximum number of documents to return per query + :return: a named tuple containing `documents` and `scores` + """ if isinstance(queries, Sequence): query_vec_list = self._get_values_by_column(queries, search_field) query_vec_np = np.stack( @@ -370,6 +432,8 @@ def filter( """Find documents in the store based on a filter query :param filter_query: the DB specific filter query to execute + :param limit: maximum number of documents to return + :return: a DocumentArray containing the documents that match the filter query """ return self._filter(filter_query, limit=limit, **kwargs) @@ -379,9 +443,11 @@ def filter_batched( limit: int = 10, **kwargs, ) -> List[DocumentArray]: - """Find documents in the store based on multiple filter queries + """Find documents in the store based on multiple filter queries. - :param filter_queries: the DB specific filter queries to execute + :param filter_queries: the DB specific filter query to execute + :param limit: maximum number of documents to return + :return: a DocumentArray containing the documents that match the filter query """ return self._filter_batched(filter_queries, limit=limit, **kwargs) @@ -392,9 +458,12 @@ def text_search( limit: int = 10, **kwargs, ) -> FindResult: - """Find documents in the store based on a text search query + """Find documents in the store based on a text search query. :param query: The text to search for + :param search_field: name of the field to search on + :param limit: maximum number of documents to return + :return: a named tuple containing `documents` and `scores` """ if isinstance(query, BaseDocument): query_text = self._get_values_by_column([query], search_field)[0] @@ -411,9 +480,12 @@ def text_search_batched( limit: int = 10, **kwargs, ) -> FindResultBatched: - """Find documents in the store based on a text search query + """Find documents in the store based on a text search query. - :param query: The text to search for + :param queries: The texts to search for + :param search_field: name of the field to search on + :param limit: maximum number of documents to return + :return: a named tuple containing `documents` and `scores` """ if isinstance(queries, Sequence): query_texts = self._get_values_by_column(queries, search_field) From 2b9ba37d7e55729a0f4ae2bcbe08c223d2855e4b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 27 Feb 2023 16:51:20 +0100 Subject: [PATCH 34/53] fix: mypy Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 41 ++++++------ docarray/storage/backends/HnswDocStore.py | 78 +++++++++++++++-------- poetry.lock | 5 +- pyproject.toml | 7 +- 4 files changed, 82 insertions(+), 49 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 2fc55d36dbf..e08b5dbe65d 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from dataclasses import dataclass, replace +from dataclasses import dataclass, field, replace from functools import wraps from typing import ( TYPE_CHECKING, @@ -16,6 +16,7 @@ Type, TypeVar, Union, + cast, ) import numpy as np @@ -93,12 +94,6 @@ def __set_name__(self, owner, name): setattr(owner, name, self.fn) -if TYPE_CHECKING: - # static type checkers do not like the solution above - def composable(fn): - return fn - - class BaseDocumentIndex(ABC, Generic[TSchema]): """Abstract class for all Document Stores""" @@ -158,7 +153,7 @@ class RuntimeConfig(ABC): # These configs are used if no configs are specified in the `Field(...)` # of a field in the Document schema (`cls._schema`) # Example: `default_column_config['VARCHAR'] = {'length': 255}` - default_column_config: Dict[Type, Dict[str, Any]] + default_column_config: Dict[Type, Dict[str, Any]] = field(default_factory=dict) ##################################### # Abstract methods # @@ -356,14 +351,13 @@ def configure(self, runtime_config=None, **kwargs): raise ValueError(f'runtime_config must be of type {self.RuntimeConfig}') self._runtime_config = runtime_config - def index(self, docs: Union[TSchema, Sequence[TSchema]], **kwargs): + def index(self, docs: Union[BaseDocument, Sequence[BaseDocument]], **kwargs): """Index Documents into the store. :param docs: Documents to index """ - if not isinstance(docs, Sequence): - docs = [docs] - self._index(docs, **kwargs) + data_by_columns = self._get_col_value_dict(docs) + self._index(data_by_columns, **kwargs) def find( self, @@ -414,7 +408,7 @@ def find_batched( if isinstance(queries, Sequence): query_vec_list = self._get_values_by_column(queries, search_field) query_vec_np = np.stack( - self._to_numpy(query_vec) for query_vec in query_vec_list + tuple(self._to_numpy(query_vec) for query_vec in query_vec_list) ) else: query_vec_np = self._to_numpy(queries) @@ -487,10 +481,13 @@ def text_search_batched( :param limit: maximum number of documents to return :return: a named tuple containing `documents` and `scores` """ - if isinstance(queries, Sequence): - query_texts = self._get_values_by_column(queries, search_field) + if isinstance(queries[0], BaseDocument): + query_docs: Sequence[BaseDocument] = cast(Sequence[BaseDocument], queries) + query_texts: Sequence[str] = self._get_values_by_column( + query_docs, search_field + ) else: - query_texts = queries + query_texts = cast(Sequence[str], queries) return self._text_search_batched( query_texts, search_field=search_field, limit=limit, **kwargs ) @@ -529,15 +526,17 @@ def _get_col_value_dict( :return: A dictionary mapping column names to a generator of values """ if isinstance(docs, BaseDocument): - docs = [docs] - if not self._is_schema_compatible(docs): + docs_seq: Sequence[BaseDocument] = [docs] + else: + docs_seq = docs + if not self._is_schema_compatible(docs_seq): raise ValueError( 'The schema of the documents to be indexed is not compatible' ' with the schema of the store.' ) - def _col_gen(col_name): - return (self._get_values_by_column([doc], col_name)[0] for doc in docs) + def _col_gen(col_name: str): + return (self._get_values_by_column([doc], col_name)[0] for doc in docs_seq) return {col_name: _col_gen(col_name) for col_name in self._columns} @@ -565,7 +564,7 @@ def build_query(self) -> QueryBuilder: :return: a new `QueryBuilder` object for this DocumentStore """ - return self.QueryBuilder() + return self.QueryBuilder() # type: ignore def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: columns: Dict[str, _Column] = dict() diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index 379cd31e549..ec012818ce9 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -3,7 +3,19 @@ import sqlite3 from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Dict, Generic, List, Sequence, Tuple, Type, TypeVar, Union +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generic, + List, + Sequence, + Tuple, + Type, + TypeVar, + Union, + cast, +) import hnswlib import numpy as np @@ -15,13 +27,22 @@ BaseDocumentIndex, FindResultBatched, _Column, - composable, ) from docarray.utils.filter import filter as da_filter from docarray.utils.find import FindResult from docarray.utils.misc import is_np_int, torch_imported +if TYPE_CHECKING: + + def composable(fn): # type: ignore + return fn + +else: + # static type checkers do not like callable objects as decorators + from docarray.storage.abstract_doc_store import composable + TSchema = TypeVar('TSchema', bound=BaseDocument) +T = TypeVar('T', bound='HnswDocumentIndex') HNSWLIB_PY_VEC_TYPES = [list, tuple, np.ndarray] if torch_imported: @@ -92,7 +113,8 @@ class RuntimeConfig(BaseDocumentIndex.RuntimeConfig): 'allow_replace_deleted': True, 'num_threads': 1, }, - None: {}, + # `None` is not a Type, but we allow it here anyway + None: {}, # type: ignore } ) @@ -115,12 +137,13 @@ def _index(self, column_data_dic, **kwargs): # not needed, we implement `index` directly ... - def index(self, docs: Union[TSchema, Sequence[TSchema]], **kwargs): + def index(self, docs: Union[BaseDocument, Sequence[BaseDocument]], **kwargs): """Index a document into the store""" if kwargs: raise ValueError(f'{list(kwargs.keys())} are not valid keyword arguments') - data_by_columns = self._get_col_value_dict(docs) - hashed_ids = tuple(self._to_hashed_id(doc.id) for doc in docs) + doc_seq = docs if isinstance(docs, Sequence) else [docs] + data_by_columns = self._get_col_value_dict(doc_seq) + hashed_ids = tuple(self._to_hashed_id(doc.id) for doc in doc_seq) # indexing into HNSWLib and SQLite sequentially # could be improved by processing in parallel @@ -131,7 +154,7 @@ def index(self, docs: Union[TSchema, Sequence[TSchema]], **kwargs): index.add_items(data_stacked, ids=hashed_ids) index.save_index(self._hnsw_locations[col_name]) - self._send_docs_to_sqlite(docs) + self._send_docs_to_sqlite(doc_seq) self._sqlite_conn.commit() def execute_query(self, query: List[Tuple[str, Dict]], *args, **kwargs) -> Any: @@ -140,20 +163,25 @@ def execute_query(self, query: List[Tuple[str, Dict]], *args, **kwargs) -> Any: f'args and kwargs not supported for `execute_query` on {type(self)}' ) - ann_docs = DocumentArray[self._schema]([]) + ann_docs = DocumentArray.__class_getitem__( + cast(Type[BaseDocument], self._schema) + )([]) filter_conditions = [] - doc_to_score = {} + doc_to_score: Dict[BaseDocument, Any] = {} for op, op_kwargs in query: if op == 'find': docs, scores = self.find(**op_kwargs) ann_docs.extend(docs) - doc_to_score.update(zip(docs.id, scores)) + doc_to_score.update(zip(docs.__getattribute__('id'), scores)) elif op == 'filter': filter_conditions.append(op_kwargs['filter_query']) docs_filtered = ann_docs for cond in filter_conditions: - docs_filtered = da_filter(docs_filtered, cond) + da_cls = DocumentArray.__class_getitem__( + cast(Type[BaseDocument], self._schema) + ) + docs_filtered = da_cls(da_filter(docs_filtered, cond)) docs_and_scores = zip( docs_filtered, (doc_to_score[doc.id] for doc in docs_filtered) @@ -205,7 +233,7 @@ def _filter( def _filter_batched( self, filter_queries: Any, - limit: int = 10, + limit: int, **kwargs, ) -> List[DocumentArray]: raise NotImplementedError( @@ -217,17 +245,17 @@ def _filter_batched( def _text_search( self, query: str, - embedding_field: str = 'embedding', - limit: int = 10, + search_field: str, + limit: int, **kwargs, ) -> FindResult: raise NotImplementedError(f'{type(self)} does not support text search.') def _text_search_batched( self, - queries: List[str], - embedding_field: str = 'embedding', - limit: int = 10, + queries: Sequence[str], + search_field: str, + limit: int, **kwargs, ) -> FindResultBatched: raise NotImplementedError(f'{type(self)} does not support text search.') @@ -331,16 +359,14 @@ def _get_docs_sqlite_unsorted(self, univ_ids: Sequence[int]): 'SELECT data FROM docs WHERE doc_id IN %s' % sql_id_list, ) rows = self._sqlite_cursor.fetchall() - return DocumentArray[self._schema]( - [self._doc_from_bytes(row[0]) for row in rows] - ) + da_cls = DocumentArray.__class_getitem__(cast(Type[BaseDocument], self._schema)) + return da_cls([self._doc_from_bytes(row[0]) for row in rows]) def _get_docs_sqlite_doc_id(self, doc_ids: Sequence[str]) -> DocumentArray: hashed_ids = tuple(self._to_hashed_id(id_) for id_ in doc_ids) docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) - return DocumentArray[self._schema]( - sorted(docs_unsorted, key=lambda doc: doc_ids.index(doc.id)) - ) + da_cls = DocumentArray.__class_getitem__(cast(Type[BaseDocument], self._schema)) + return da_cls(sorted(docs_unsorted, key=lambda doc: doc_ids.index(doc.id))) def _get_docs_sqlite_hashed_id(self, hashed_ids: Sequence[int]) -> DocumentArray: docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) @@ -348,7 +374,8 @@ def _get_docs_sqlite_hashed_id(self, hashed_ids: Sequence[int]) -> DocumentArray def _in_position(doc): return hashed_ids.index(self._to_hashed_id(doc.id)) - return DocumentArray[self._schema](sorted(docs_unsorted, key=_in_position)) + da_cls = DocumentArray.__class_getitem__(cast(Type[BaseDocument], self._schema)) + return da_cls(sorted(docs_unsorted, key=_in_position)) def _delete_docs_from_sqlite(self, doc_ids: Sequence[Union[str, int]]): ids = tuple( @@ -368,4 +395,5 @@ def _doc_to_bytes(self, doc: BaseDocument) -> bytes: return doc.to_protobuf().SerializeToString() def _doc_from_bytes(self, data: bytes) -> BaseDocument: - return self._schema.from_protobuf(DocumentProto.FromString(data)) + schema_cls = cast(Type[BaseDocument], self._schema) + return schema_cls.from_protobuf(DocumentProto.FromString(data)) diff --git a/poetry.lock b/poetry.lock index 06e46065642..124ff62d72e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -325,7 +325,7 @@ name = "hnswlib" version = "0.7.0" description = "hnswlib" category = "main" -optional = false +optional = true python-versions = "*" [package.dependencies] @@ -1684,6 +1684,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [extras] common = ["protobuf"] +hnswlib = ["hnswlib"] image = ["pillow", "types-pillow"] mesh = ["trimesh"] torch = ["torch"] @@ -1693,7 +1694,7 @@ web = ["fastapi"] [metadata] lock-version = "1.1" python-versions = ">=3.7" -content-hash = "8607c6935a978649a45cf33d71a6c34294f66f346cc1bac713e37ca382dee671" +content-hash = "b93a00f3648453be19ccc456b883fd07eb183278e6fc3ca4667bee6100fb1e2a" [metadata.files] anyio = [ diff --git a/pyproject.toml b/pyproject.toml index 9ec32b9027a..421c1267c13 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ types-requests = ">=2.28.11.6" av = {version = ">=10.0.0", optional = true} fastapi = {version = ">=0.87.0", optional = true } rich = ">=13.1.0" -hnswlib = {version = "^0.7.0", extras = ["hnswlib"]} +hnswlib = {version = ">=0.6.2", optional = true } [tool.poetry.extras] common = ["protobuf"] @@ -29,6 +29,7 @@ image = ["pillow", "types-pillow"] video = ["av"] mesh = ["trimesh"] web = ["fastapi"] +hnswlib = ["hnswlib"] [tool.poetry.dev-dependencies] pytest = ">=6.1" @@ -62,6 +63,10 @@ ignore_missing_imports = true module = "trimesh" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "hnswlib" +ignore_missing_imports = true + [[tool.mypy.overrides]] module = "typing_inspect" ignore_missing_imports = true From 1ae379cbfc8a9331eacc968296f839e0b9f67656 Mon Sep 17 00:00:00 2001 From: Johannes Messner <44071807+JohannesMessner@users.noreply.github.com> Date: Mon, 27 Feb 2023 17:00:50 +0100 Subject: [PATCH 35/53] docs: update docarray/storage/abstract_doc_store.py Co-authored-by: Joan Fontanals Signed-off-by: Johannes Messner <44071807+JohannesMessner@users.noreply.github.com> --- docarray/storage/abstract_doc_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index e08b5dbe65d..794f366a991 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -330,7 +330,7 @@ def _text_search_batched( #################################################### # Optional overrides # - # Subclasses may more may not need to change these # + # Subclasses may or may not need to change these # #################################################### def configure(self, runtime_config=None, **kwargs): From c7076f68531fe7cef723708e1f5a156bec6649c6 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 27 Feb 2023 17:18:55 +0100 Subject: [PATCH 36/53] refactor: ruff Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 794f366a991..3d5a72d950e 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -568,7 +568,7 @@ def build_query(self) -> QueryBuilder: def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: columns: Dict[str, _Column] = dict() - for field_name, field in schema.__fields__.items(): + for field_name, field_ in schema.__fields__.items(): t_ = schema._get_field_type(field_name) if is_union_type(t_): raise ValueError( @@ -589,7 +589,7 @@ def _create_columns(self, schema: Type[BaseDocument]) -> Dict[str, _Column]: }, ) else: - columns[field_name] = self._create_single_column(field, t_) + columns[field_name] = self._create_single_column(field_, t_) return columns def _create_single_column(self, field: 'ModelField', type_: Type) -> _Column: From f00f926e4d5bafc6d0db8208fe5357312b3f6313 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Mon, 27 Feb 2023 17:26:39 +0100 Subject: [PATCH 37/53] refactor: apply suggestion Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_store.py index 3d5a72d950e..ee35edbe504 100644 --- a/docarray/storage/abstract_doc_store.py +++ b/docarray/storage/abstract_doc_store.py @@ -107,7 +107,7 @@ def __init__(self, db_config=None, **kwargs): 'A DocumentStore must be typed with a Document type.' 'To do so, use the syntax: DocumentStore[DocumentType]' ) - self._db_config = db_config if db_config else self.DBConfig(**kwargs) + self._db_config = db_config or self.DBConfig(**kwargs) if not isinstance(self._db_config, self.DBConfig): raise ValueError(f'db_config must be of type {self.DBConfig}') self._runtime_config = self.RuntimeConfig() From b8632ded4a88855ee533032dcab63500e701d356 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 11:57:25 +0100 Subject: [PATCH 38/53] fix: mypy bug Signed-off-by: Johannes Messner --- docarray/storage/backends/HnswDocStore.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/HnswDocStore.py index ec012818ce9..044abc2576e 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/HnswDocStore.py @@ -54,6 +54,7 @@ def composable(fn): # type: ignore class HnswDocumentIndex(BaseDocumentIndex, Generic[TSchema]): def __init__(self, db_config=None, **kwargs): super().__init__(db_config=db_config, **kwargs) + self._db_config = cast(HnswDocumentIndex.DBConfig, self._db_config) self._work_dir = self._db_config.work_dir load_existing = os.path.exists(self._work_dir) and os.listdir(self._work_dir) Path(self._work_dir).mkdir(parents=True, exist_ok=True) From 992df0adae97c14eb7614a8bc8b3cc343d1db669 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 12:03:22 +0100 Subject: [PATCH 39/53] refactor: rename folders Signed-off-by: Johannes Messner --- .../storage/{abstract_doc_store.py => abstract_doc_index.py} | 0 .../backends/{HnswDocStore.py => hnswlib_doc_index.py} | 4 ++-- tests/{doc_stores => doc_index}/__init__.py | 0 tests/{doc_stores => doc_index}/base_classes/__init__.py | 0 .../base_classes/test_base_doc_store.py | 2 +- .../base_classes/test_base_query_builder.py | 2 +- tests/{doc_stores => doc_index}/base_classes/test_configs.py | 2 +- tests/{doc_stores => doc_index}/hnswlib/__init__.py | 0 tests/{doc_stores => doc_index}/hnswlib/test_find.py | 2 +- tests/{doc_stores => doc_index}/hnswlib/test_index_get_del.py | 2 +- 10 files changed, 7 insertions(+), 7 deletions(-) rename docarray/storage/{abstract_doc_store.py => abstract_doc_index.py} (100%) rename docarray/storage/backends/{HnswDocStore.py => hnswlib_doc_index.py} (99%) rename tests/{doc_stores => doc_index}/__init__.py (100%) rename tests/{doc_stores => doc_index}/base_classes/__init__.py (100%) rename tests/{doc_stores => doc_index}/base_classes/test_base_doc_store.py (99%) rename tests/{doc_stores => doc_index}/base_classes/test_base_query_builder.py (97%) rename tests/{doc_stores => doc_index}/base_classes/test_configs.py (97%) rename tests/{doc_stores => doc_index}/hnswlib/__init__.py (100%) rename tests/{doc_stores => doc_index}/hnswlib/test_find.py (98%) rename tests/{doc_stores => doc_index}/hnswlib/test_index_get_del.py (99%) diff --git a/docarray/storage/abstract_doc_store.py b/docarray/storage/abstract_doc_index.py similarity index 100% rename from docarray/storage/abstract_doc_store.py rename to docarray/storage/abstract_doc_index.py diff --git a/docarray/storage/backends/HnswDocStore.py b/docarray/storage/backends/hnswlib_doc_index.py similarity index 99% rename from docarray/storage/backends/HnswDocStore.py rename to docarray/storage/backends/hnswlib_doc_index.py index 044abc2576e..c0c222cf80d 100644 --- a/docarray/storage/backends/HnswDocStore.py +++ b/docarray/storage/backends/hnswlib_doc_index.py @@ -23,7 +23,7 @@ import docarray.typing from docarray import BaseDocument, DocumentArray from docarray.proto import DocumentProto -from docarray.storage.abstract_doc_store import ( +from docarray.storage.abstract_doc_index import ( BaseDocumentIndex, FindResultBatched, _Column, @@ -39,7 +39,7 @@ def composable(fn): # type: ignore else: # static type checkers do not like callable objects as decorators - from docarray.storage.abstract_doc_store import composable + from docarray.storage.abstract_doc_index import composable TSchema = TypeVar('TSchema', bound=BaseDocument) T = TypeVar('T', bound='HnswDocumentIndex') diff --git a/tests/doc_stores/__init__.py b/tests/doc_index/__init__.py similarity index 100% rename from tests/doc_stores/__init__.py rename to tests/doc_index/__init__.py diff --git a/tests/doc_stores/base_classes/__init__.py b/tests/doc_index/base_classes/__init__.py similarity index 100% rename from tests/doc_stores/base_classes/__init__.py rename to tests/doc_index/base_classes/__init__.py diff --git a/tests/doc_stores/base_classes/test_base_doc_store.py b/tests/doc_index/base_classes/test_base_doc_store.py similarity index 99% rename from tests/doc_stores/base_classes/test_base_doc_store.py rename to tests/doc_index/base_classes/test_base_doc_store.py index b087f285949..3c81b6ad59c 100644 --- a/tests/doc_stores/base_classes/test_base_doc_store.py +++ b/tests/doc_index/base_classes/test_base_doc_store.py @@ -6,7 +6,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.abstract_doc_store import BaseDocumentIndex +from docarray.storage.abstract_doc_index import BaseDocumentIndex from docarray.typing import ID, NdArray diff --git a/tests/doc_stores/base_classes/test_base_query_builder.py b/tests/doc_index/base_classes/test_base_query_builder.py similarity index 97% rename from tests/doc_stores/base_classes/test_base_query_builder.py rename to tests/doc_index/base_classes/test_base_query_builder.py index 012b48bfab5..6c78b86bdba 100644 --- a/tests/doc_stores/base_classes/test_base_query_builder.py +++ b/tests/doc_index/base_classes/test_base_query_builder.py @@ -1,6 +1,6 @@ import pytest -from docarray.storage.abstract_doc_store import BaseDocumentIndex, composable +from docarray.storage.abstract_doc_index import BaseDocumentIndex, composable class DummyQueryBuilder(BaseDocumentIndex.QueryBuilder): diff --git a/tests/doc_stores/base_classes/test_configs.py b/tests/doc_index/base_classes/test_configs.py similarity index 97% rename from tests/doc_stores/base_classes/test_configs.py rename to tests/doc_index/base_classes/test_configs.py index 5d00adc2095..5f9f2511ec0 100644 --- a/tests/doc_stores/base_classes/test_configs.py +++ b/tests/doc_index/base_classes/test_configs.py @@ -4,7 +4,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.abstract_doc_store import BaseDocumentIndex +from docarray.storage.abstract_doc_index import BaseDocumentIndex from docarray.typing import NdArray diff --git a/tests/doc_stores/hnswlib/__init__.py b/tests/doc_index/hnswlib/__init__.py similarity index 100% rename from tests/doc_stores/hnswlib/__init__.py rename to tests/doc_index/hnswlib/__init__.py diff --git a/tests/doc_stores/hnswlib/test_find.py b/tests/doc_index/hnswlib/test_find.py similarity index 98% rename from tests/doc_stores/hnswlib/test_find.py rename to tests/doc_index/hnswlib/test_find.py index ab713903418..13343acea5e 100644 --- a/tests/doc_stores/hnswlib/test_find.py +++ b/tests/doc_index/hnswlib/test_find.py @@ -3,7 +3,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.backends.HnswDocStore import HnswDocumentIndex +from docarray.storage.backends.hnswlib_doc_index import HnswDocumentIndex from docarray.typing import NdArray diff --git a/tests/doc_stores/hnswlib/test_index_get_del.py b/tests/doc_index/hnswlib/test_index_get_del.py similarity index 99% rename from tests/doc_stores/hnswlib/test_index_get_del.py rename to tests/doc_index/hnswlib/test_index_get_del.py index 97cdecdcc33..293827bdf96 100644 --- a/tests/doc_stores/hnswlib/test_index_get_del.py +++ b/tests/doc_index/hnswlib/test_index_get_del.py @@ -3,7 +3,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.backends.HnswDocStore import HnswDocumentIndex +from docarray.storage.backends.hnswlib_doc_index import HnswDocumentIndex from docarray.typing import NdArray From e2d099a057eee5219f124c65aad58712f587a435 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 12:05:44 +0100 Subject: [PATCH 40/53] fix: handle type vars in class getitem Signed-off-by: Johannes Messner --- docarray/storage/abstract_doc_index.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docarray/storage/abstract_doc_index.py b/docarray/storage/abstract_doc_index.py index ee35edbe504..553b1e1bded 100644 --- a/docarray/storage/abstract_doc_index.py +++ b/docarray/storage/abstract_doc_index.py @@ -545,6 +545,10 @@ def _col_gen(col_name: str): # Subclasses should not need to implement these # ################################################## def __class_getitem__(cls, item: Type[TSchema]): + if not isinstance(item, type): + # do nothing + # enables use in static contexts with type vars, e.g. as type annotation + return Generic.__class_getitem__.__func__(cls, item) # type: ignore if not issubclass(item, BaseDocument): raise ValueError( f'{cls.__name__}[item] `item` should be a Document not a {item} ' From f9727fad2509f1b1bf8715739c8e1c4bbf274b04 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 14:29:45 +0100 Subject: [PATCH 41/53] refactor: rename directory Signed-off-by: Johannes Messner --- docarray/{storage => doc_index}/__init__.py | 0 docarray/{storage => doc_index}/abstract_doc_index.py | 0 docarray/{storage => doc_index}/backends/__init__.py | 0 docarray/{storage => doc_index}/backends/hnswlib_doc_index.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename docarray/{storage => doc_index}/__init__.py (100%) rename docarray/{storage => doc_index}/abstract_doc_index.py (100%) rename docarray/{storage => doc_index}/backends/__init__.py (100%) rename docarray/{storage => doc_index}/backends/hnswlib_doc_index.py (100%) diff --git a/docarray/storage/__init__.py b/docarray/doc_index/__init__.py similarity index 100% rename from docarray/storage/__init__.py rename to docarray/doc_index/__init__.py diff --git a/docarray/storage/abstract_doc_index.py b/docarray/doc_index/abstract_doc_index.py similarity index 100% rename from docarray/storage/abstract_doc_index.py rename to docarray/doc_index/abstract_doc_index.py diff --git a/docarray/storage/backends/__init__.py b/docarray/doc_index/backends/__init__.py similarity index 100% rename from docarray/storage/backends/__init__.py rename to docarray/doc_index/backends/__init__.py diff --git a/docarray/storage/backends/hnswlib_doc_index.py b/docarray/doc_index/backends/hnswlib_doc_index.py similarity index 100% rename from docarray/storage/backends/hnswlib_doc_index.py rename to docarray/doc_index/backends/hnswlib_doc_index.py From 61e7800c841139ed80fc7b34c1842bdf68611c37 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 14:30:36 +0100 Subject: [PATCH 42/53] refactor: remove unused code Signed-off-by: Johannes Messner --- docarray/array/array/array.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docarray/array/array/array.py b/docarray/array/array/array.py index fe5d626f3f4..056a962a0a9 100644 --- a/docarray/array/array/array.py +++ b/docarray/array/array/array.py @@ -56,17 +56,6 @@ def _delegate_meth(self, *args, **kwargs): return _delegate_meth -def _is_np_int(item: Any) -> bool: - dtype = getattr(item, 'dtype', None) - ndim = getattr(item, 'ndim', None) - if dtype is not None and ndim is not None: - try: - return ndim == 0 and np.issubdtype(dtype, np.integer) - except TypeError: - return False - return False # this is unreachable, but mypy wants it - - class DocumentArray(IOMixinArray, AnyDocumentArray[T_doc]): """ DocumentArray is a container of Documents. From c3734ce86eda83be81ffe2d3459301891d23665b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 14:34:47 +0100 Subject: [PATCH 43/53] fix: adjust imports to new dir name Signed-off-by: Johannes Messner --- docarray/doc_index/backends/hnswlib_doc_index.py | 6 +++--- tests/doc_index/base_classes/test_base_doc_store.py | 2 +- tests/doc_index/base_classes/test_base_query_builder.py | 2 +- tests/doc_index/base_classes/test_configs.py | 2 +- tests/doc_index/hnswlib/test_find.py | 2 +- tests/doc_index/hnswlib/test_index_get_del.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docarray/doc_index/backends/hnswlib_doc_index.py b/docarray/doc_index/backends/hnswlib_doc_index.py index c0c222cf80d..812478de419 100644 --- a/docarray/doc_index/backends/hnswlib_doc_index.py +++ b/docarray/doc_index/backends/hnswlib_doc_index.py @@ -22,12 +22,12 @@ import docarray.typing from docarray import BaseDocument, DocumentArray -from docarray.proto import DocumentProto -from docarray.storage.abstract_doc_index import ( +from docarray.doc_index.abstract_doc_index import ( BaseDocumentIndex, FindResultBatched, _Column, ) +from docarray.proto import DocumentProto from docarray.utils.filter import filter as da_filter from docarray.utils.find import FindResult from docarray.utils.misc import is_np_int, torch_imported @@ -39,7 +39,7 @@ def composable(fn): # type: ignore else: # static type checkers do not like callable objects as decorators - from docarray.storage.abstract_doc_index import composable + from docarray.doc_index.abstract_doc_index import composable TSchema = TypeVar('TSchema', bound=BaseDocument) T = TypeVar('T', bound='HnswDocumentIndex') diff --git a/tests/doc_index/base_classes/test_base_doc_store.py b/tests/doc_index/base_classes/test_base_doc_store.py index 3c81b6ad59c..c3c82f05454 100644 --- a/tests/doc_index/base_classes/test_base_doc_store.py +++ b/tests/doc_index/base_classes/test_base_doc_store.py @@ -6,7 +6,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.abstract_doc_index import BaseDocumentIndex +from docarray.doc_index.abstract_doc_index import BaseDocumentIndex from docarray.typing import ID, NdArray diff --git a/tests/doc_index/base_classes/test_base_query_builder.py b/tests/doc_index/base_classes/test_base_query_builder.py index 6c78b86bdba..6e99eefcc7b 100644 --- a/tests/doc_index/base_classes/test_base_query_builder.py +++ b/tests/doc_index/base_classes/test_base_query_builder.py @@ -1,6 +1,6 @@ import pytest -from docarray.storage.abstract_doc_index import BaseDocumentIndex, composable +from docarray.doc_index.abstract_doc_index import BaseDocumentIndex, composable class DummyQueryBuilder(BaseDocumentIndex.QueryBuilder): diff --git a/tests/doc_index/base_classes/test_configs.py b/tests/doc_index/base_classes/test_configs.py index 5f9f2511ec0..9e1af88b6f1 100644 --- a/tests/doc_index/base_classes/test_configs.py +++ b/tests/doc_index/base_classes/test_configs.py @@ -4,7 +4,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.abstract_doc_index import BaseDocumentIndex +from docarray.doc_index.abstract_doc_index import BaseDocumentIndex from docarray.typing import NdArray diff --git a/tests/doc_index/hnswlib/test_find.py b/tests/doc_index/hnswlib/test_find.py index 13343acea5e..fc2a35c3580 100644 --- a/tests/doc_index/hnswlib/test_find.py +++ b/tests/doc_index/hnswlib/test_find.py @@ -3,7 +3,7 @@ from pydantic import Field from docarray import BaseDocument -from docarray.storage.backends.hnswlib_doc_index import HnswDocumentIndex +from docarray.doc_index.backends.hnswlib_doc_index import HnswDocumentIndex from docarray.typing import NdArray diff --git a/tests/doc_index/hnswlib/test_index_get_del.py b/tests/doc_index/hnswlib/test_index_get_del.py index 293827bdf96..c59c3ff4786 100644 --- a/tests/doc_index/hnswlib/test_index_get_del.py +++ b/tests/doc_index/hnswlib/test_index_get_del.py @@ -3,7 +3,7 @@ from pydantic import Field from docarray import BaseDocument, DocumentArray -from docarray.storage.backends.hnswlib_doc_index import HnswDocumentIndex +from docarray.doc_index.backends.hnswlib_doc_index import HnswDocumentIndex from docarray.typing import NdArray From 58ad417a7727803ddad0ac53b3e7bf265d7bfc0b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Tue, 28 Feb 2023 18:29:08 +0100 Subject: [PATCH 44/53] docs: add guide for new doc stores Signed-off-by: Johannes Messner --- docs/tutorials/add_doc_index.md | 200 ++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 docs/tutorials/add_doc_index.md diff --git a/docs/tutorials/add_doc_index.md b/docs/tutorials/add_doc_index.md new file mode 100644 index 00000000000..7d85c7da560 --- /dev/null +++ b/docs/tutorials/add_doc_index.md @@ -0,0 +1,200 @@ +# Add a new Document Index + +In DocArray there exists the concept of _Document Index_, a class that takes `Document`s, persists them, +and makes them searchable. + +There are different Document Indexes leveraging different backends, such as Weaviate, Qdrant, HNSWLit etc. + +This Document shows how to add a new Document Index to DocArray. + +That process can be broken down into a number of basic steps: + +1. Create a new class that inherits from `BaseDocumentIndex` +2. Declare default configurations for your Document Index +3. Implement abstract methods for indexing, searching, and deleting +4. Implement a Query Builder for your Document Index + +For an end-to-end example of this process, you can check out the [existing HNSWLib Document Index implementation](https://github.com/docarray/docarray/pull/1124). + +## Create a new Document Index class + +To get started, create a new class that inherits from `BaseDocumentIndex` and `typing.Generic`: + + +```python +TSchema = TypeVar('TSchema', bound=BaseDocument) + + +class MyDocumentIndex(BaseDocumentIndex, Generic[TSchema]): + ... +``` + +Here, `TSchema` is a type variable representing the schema of the Document Index, which is a `Document`. +You can use it in type hints of methods and attributes. + +### Create the constructor + +You can write an `__init__` method for your Document Index where you set up all the needed bells and whistles: + +```python +def __init__(self, db_config=None, **kwargs): + super().__init__(db_config=db_config, **kwargs) + ... +``` + +Make sure that you call the `super().__init__` method, which will do some basic initialization for you. + +Overall, the following attributes will be set up automatically and be available to you (more info in the dedicated sections below): +- `self._schema` +- `self._db_config` +- `self._runtime_config` +- `self._columns` + +### The `_schema` + +When a user instantiates a Document Index, they do so in a parametric way, like so: + +```python +class Inner(BaseDocument): + embedding: NdArray[512] + + +class MyDoc(BaseDocument): + tensor: NdArray[100] + other_tensor: NdArray = Field(dim=10, space='cosine') + description: str + inner: Inner + + +store = MyDocumentIndex[MyDoc]() +``` + +In this case, `store` would have a class attribute `_schema` that is the `MyDoc` class. +This is done automatically for you, and you can use it in your implementation. + +### The `_db_config` + +The `_db_config` is a dataclass that contains all "static" configurations of your Document Index. +Users can pass these configurations to the `__init__` method of your Document Index, and `self._db_config` will be populated +for you, so that you can use it in your implementation. + +You can declare allowed fields and default values for your `_db_config`, but you will see that later. + +### The `_runtime_config` + +The `_runtime_config` is a dataclass that contains all "dynamic" configurations of your Document Index. +Users can pass these configurations to the `.configure()` method of your Document Index, and `self._runtime_config` will be populated +for you, so that you can use it in your implementation. + +You can declare allowed fields and default values for your `_db_config`, but you will see that later. + +### The `_columns` + +`self._columns` is a dictionary that contains information about all columns in your Document Index instance. + +This information is automatically extracted from `self._schema`, and populated for you. + +Concretely, `self._columns: Dict[str, _Column]` maps from a column name to a `_Column` dataclass. + +For the `MyDoc` schema above, the column names would be `tensor`, `other_tensor`, `description`, `id`, `inner__embedding`, and `inner__id`. +These are the key of `self._columns`. + +The values of `self._columns` are `_Column` dataclasses, which have the following form: + +```python +@dataclass +class _Column: + docarray_type: Type + db_type: Any + n_dim: Optional[int] + config: Dict[str, Any] +``` + +- `docarray_type` is the type of the column in DocArray, e.g. `NdArray` or `str` +- `db_type` is the type of the column in the Document Index, e.g. `np.ndarray` or `str`. You can customize the mapping from `docarray_type` to `db_type`, as we will see later. +- `n_dim` is the dimensionality of the column, e.g. `100` for a 100-dimensional vector. For columns that are not vectors, this is `None`. +- `config` is a dictionary of configurations for the column. For example, for the `other_tensor` column above, this would contain the `space` and `dim` configurations. + +Again, these are automatically populated for you, so you can just use them in your implementation. + + +## Declare default configurations + +We already made reference to the `_db_config` and `_runtime_config` attributes. + +In order to define what can be stored in them, and what the default values are, you need to create two inner classes: + +```python +@dataclass +class DBConfig(BaseDocumentIndex.DBConfig): + ... + + +@dataclass +class RuntimeConfig(BaseDocumentIndex.RuntimeConfig): + default_column_config: Dict[Type, Dict[str, Any]] = ... +``` + +Note that: +- `DBConfig` inherits from `BaseDocumentIndex.DBConfig` and `RuntimeConfig` inherits from `BaseDocumentIndex.RuntimeConfig` +- All fields in each dataclass need to have default values. Choose these sensibly, as they will be used if the user does not specify a value. + +### The `DBConfig` class + +The `DBConfig` class is used to define the static configurations of your Document Index. +These are configurations that are tied to the database (or library) running in the background, such as `host`, `port`, etc. +Here you should put everything that the user cannot or should not change after initialization. + +### The `RuntimeConfig` class + +The `RuntimeConfig` class is used to define the dynamic configurations of your Document Index. +These are configurations that can be changed at runtime, for example default behaviours such as batch sizes, consistency levels, etc. + +It is a common pattern to allow such parameters both in the `RuntimeConfig`, where they will act as global defaults, and +in specific methods (`index`, `find`, etc.), where they will act as local overrides. + +**Important**: Every `RuntimeConfig` needs to contain a `default_column_config` field. +This is a dictionary that, for each possible column type in your database, defines a default configuration for that column type. +This will automatically be passed to a `_Column` whenever a user does not manually specify a configuration for that column. + +For example, in the `MyDoc` schema above, the `tensor` `_Column` would have a default configuration specified for `np.ndarray` columns. + +What is actually contained in these type-dependant configurations is up to you (and database specific). +For example, for `np.ndarray` columns you could define the configurations `index_type` and `metric_type`, +and for `varchar` columns you could define a `max_length` configuration. + +It is probably best to see this in action, so you should check out the `HnswDocumentIndex` implementation. + +## Implement abstract methods for indexing, searching, and deleting + +After you've done the basic setup above, you can jump into the good stuff: implementing the actual indexing, searching, and deleting. + +In general, the following is true: +- For every method that you need to implement, there is a public variant (e.g. `index`) and a private variant (e.g. `_index`) +- You should usually implement the private variant, which is called by the already implemented public variant. This should make your life easier, because some preprocessing and data normalization will already be done for you. +- You can, however, also implement the public variant directly, if you want to do something special. + - **Caution**: While this is a perfectly fine thing to do, it might create more maintainance work for you in the future, because the public variant defined in the `BaseDocumentIndex` might change in the future, and you will have to update your implementation accordingly. + +Further: +- You don't absolutely have to implement everything. If a feature (e.g. `text_search`) is not supported by your backend, just raise a `NotImplementedError` in the corresponding method. +- Many methods come in a "singular" variant (e.g. `find`) and a "batched" variant (e.g. `find_batched`). + - The "singular" variant expects a single input, be it an ANN query, a text query, a filter, etc., and return matches and scores for that single input + - The "batched" variant expects a batch of inputs, and returns of matches and scores for each input +- Your implementations of, e.g., `_find()`, `_index()` etc. are allowed to take additional optional keyword arguments. + These can then be used to control DB specific behaviours, such as consistency levels, batch sizes, etc. As mentioned above, it is good practice to mirror these arguments in `self.RuntimeConfig`. + +Overall, you're asked to implement the methods that appear after the `Abstract methods; Subclasses must implement these` +comment in the `BaseDocumentIndex` class. +The details of each method should become clear from the docstrings and type hints. + +### The `python_type_to_db_type` method + +This method is slightly special, because 1) it is not exposed to the user, and 2) you absolutely have to implement it. + +It is intended to do the following: It takes a type of a field in the store's schema (e.g. `NdArray` for `tensor`), and returns the corresponding type in the database (e.g. `np.ndarray`). +The `BaseDocumentIndex` class uses this information to create and populate the `_Columns` in `self._columns`. + +## Implement a Query Builder for your Document Index + +This part of the process is still slightly WIP, so let's worry about it once we get there, shall we? ;) + From 89ac7d288ebd34969d6e4416f88725736a573afb Mon Sep 17 00:00:00 2001 From: Johannes Messner <44071807+JohannesMessner@users.noreply.github.com> Date: Wed, 1 Mar 2023 08:25:06 +0100 Subject: [PATCH 45/53] docs: update docs/tutorials/add_doc_index.md Co-authored-by: Anne Yang Signed-off-by: Johannes Messner <44071807+JohannesMessner@users.noreply.github.com> --- docs/tutorials/add_doc_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/add_doc_index.md b/docs/tutorials/add_doc_index.md index 7d85c7da560..a869db68cbf 100644 --- a/docs/tutorials/add_doc_index.md +++ b/docs/tutorials/add_doc_index.md @@ -86,7 +86,7 @@ The `_runtime_config` is a dataclass that contains all "dynamic" configurations Users can pass these configurations to the `.configure()` method of your Document Index, and `self._runtime_config` will be populated for you, so that you can use it in your implementation. -You can declare allowed fields and default values for your `_db_config`, but you will see that later. +You can declare allowed fields and default values for your `_runtime_config `, but you will see that later. ### The `_columns` From 9b9ef009453e3bf33e5bf131c2c2a51155903b02 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 10:50:43 +0100 Subject: [PATCH 46/53] docs: add doc store guidance to docs Signed-off-by: Johannes Messner --- docs/index_init.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index_init.md b/docs/index_init.md index 1183a3c8855..3a95ad802cf 100644 --- a/docs/index_init.md +++ b/docs/index_init.md @@ -7,6 +7,7 @@ api_public api/docarray tutorials/mutlimodal_training_and_serving.md +tutroials/add_doc_index.md CONTRIBUTING.md ``` From 701d55ee7b6b76b3797d814dab2f0e244cd6efb2 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 10:51:11 +0100 Subject: [PATCH 47/53] docs: more precise wording Signed-off-by: Johannes Messner --- docs/tutorials/add_doc_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/add_doc_index.md b/docs/tutorials/add_doc_index.md index 7d85c7da560..a5073ac3eb8 100644 --- a/docs/tutorials/add_doc_index.md +++ b/docs/tutorials/add_doc_index.md @@ -1,6 +1,6 @@ # Add a new Document Index -In DocArray there exists the concept of _Document Index_, a class that takes `Document`s, persists them, +In DocArray there exists the concept of _Document Index_, a class that takes `Document`s, optionally persists them, and makes them searchable. There are different Document Indexes leveraging different backends, such as Weaviate, Qdrant, HNSWLit etc. From 37f0e6f5bd1e60f2767335525bbe51feec985863 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 11:07:36 +0100 Subject: [PATCH 48/53] docs: more guidance Signed-off-by: Johannes Messner --- docs/tutorials/add_doc_index.md | 71 ++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/add_doc_index.md b/docs/tutorials/add_doc_index.md index a5073ac3eb8..79ac47c7aef 100644 --- a/docs/tutorials/add_doc_index.md +++ b/docs/tutorials/add_doc_index.md @@ -112,11 +112,80 @@ class _Column: - `docarray_type` is the type of the column in DocArray, e.g. `NdArray` or `str` - `db_type` is the type of the column in the Document Index, e.g. `np.ndarray` or `str`. You can customize the mapping from `docarray_type` to `db_type`, as we will see later. -- `n_dim` is the dimensionality of the column, e.g. `100` for a 100-dimensional vector. For columns that are not vectors, this is `None`. - `config` is a dictionary of configurations for the column. For example, for the `other_tensor` column above, this would contain the `space` and `dim` configurations. +- `n_dim` is the dimensionality of the column, e.g. `100` for a 100-dimensional vector. See further guidance on this below. Again, these are automatically populated for you, so you can just use them in your implementation. +### Properly handle `n_dim` + +`_Column.n_dim` is automatically obtained from type parametrizations of the form `NdArray[100]`; +if there isn't such a parametrization, `n_dim` of the columns will be `None`. + +You should also provide another way of defining the dimensionality of your columns, specifically by exposing a parameter in `Field(...)` (see example schema at the top). + +This leads to four possible scenarios: + +**Scenario 1: Only `n_dim` is defined** + +Imagine the user defines a schema like the following: + +```python +class MyDoc(BaseDocument): + tensor: NdArray[100] + + +index = MyDocumentIndex[MyDoc]() +``` + +In that case, the following will be true: `self._columns['tensor'].n_dim == 100` and `self._columns['tensor'].config == {}`. +The `tensor` column in your backend should be configured to have dimensionality 100. + +**Scenario 2: Only `Field(...)` is defined** + +Imagine the user defines a schema like the following: + +```python +class MyDoc(BaseDocument): + tensor: NdArray = Field(dim=50) + + +index = MyDocumentIndex[MyDoc]() +``` + +In that case, the following will be true: `self._columns['tensor'].n_dim is None` and `self._columns['tensor'].config['dim'] == 50`. +The `tensor` column in your backend should be configured to have dimensionality 50. + +**Scenario 3: Both `n_dim` and `Field(...)` are defined** + +Imagine the user defines a schema like the following: + +```python +class MyDoc(BaseDocument): + tensor: NdArray[100] = Field(dim=50) + + +index = MyDocumentIndex[MyDoc]() +``` + +In that case, the following will be true: `self._columns['tensor'].n_dim == 100` and `self._columns['tensor'].config['dim'] == 50`. +The `tensor` column in your backend should be configured to have dimensionality 100, as **`n_dim` takes precedence over `Field(...)`**. + +**Scenario 4: Neither `n_dim` nor `Field(...)` are defined** + +Imagine the user defines a schema like the following: + +```python +class MyDoc(BaseDocument): + tensor: NdArray + + +index = MyDocumentIndex[MyDoc]() +``` + +In that case, the following will be true: `self._columns['tensor'].n_dim is None` and `self._columns['tensor'].config == {}`. +If your backend can handle tensor/embedding columns without defined dimensionality, you should leverage that mechanism. +Otherwise, raise an Exception. ## Declare default configurations From 421b388276f16a9fb90b24c44aae3b4ac1189a3b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 11:16:08 +0100 Subject: [PATCH 49/53] refactor: remove kwargs stuff Signed-off-by: Johannes Messner --- docarray/doc_index/abstract_doc_index.py | 22 +++++++------------ .../doc_index/backends/hnswlib_doc_index.py | 18 +++++---------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/docarray/doc_index/abstract_doc_index.py b/docarray/doc_index/abstract_doc_index.py index 553b1e1bded..9c45a205469 100644 --- a/docarray/doc_index/abstract_doc_index.py +++ b/docarray/doc_index/abstract_doc_index.py @@ -172,7 +172,7 @@ def python_type_to_db_type(self, python_type: Type) -> Any: ... @abstractmethod - def _index(self, column_to_data: Dict[str, Generator[Any, None, None]], **kwargs): + def _index(self, column_to_data: Dict[str, Generator[Any, None, None]]): """Index a document into the store""" # `column_to_data` is a dictionary from column name to a generator # that yields the data for that column. @@ -227,7 +227,6 @@ def _find( query: np.ndarray, search_field: str, limit: int, - **kwargs, ) -> FindResult: """Find documents in the store @@ -246,7 +245,6 @@ def _find_batched( query: np.ndarray, search_field: str, limit: int, - **kwargs, ) -> FindResultBatched: """Find documents in the store @@ -263,7 +261,6 @@ def _filter( self, filter_query: Any, limit: int, - **kwargs, ) -> DocumentArray: """Find documents in the store based on a filter query @@ -278,7 +275,6 @@ def _filter_batched( self, filter_queries: Any, limit: int, - **kwargs, ) -> List[DocumentArray]: """Find documents in the store based on multiple filter queries. Each query is considered individually, and results are returned per query. @@ -296,7 +292,6 @@ def _text_search( query: str, search_field: str, limit: int, - **kwargs, ) -> FindResult: """Find documents in the store based on a text search query @@ -315,7 +310,6 @@ def _text_search_batched( queries: Sequence[str], search_field: str, limit: int, - **kwargs, ) -> FindResultBatched: """Find documents in the store based on a text search query @@ -357,7 +351,7 @@ def index(self, docs: Union[BaseDocument, Sequence[BaseDocument]], **kwargs): :param docs: Documents to index """ data_by_columns = self._get_col_value_dict(docs) - self._index(data_by_columns, **kwargs) + self._index(data_by_columns, **kwargs) # type: ignore def find( self, @@ -383,7 +377,7 @@ def find( query_vec = query query_vec_np = self._to_numpy(query_vec) return self._find( - query_vec_np, search_field=search_field, limit=limit, **kwargs + query_vec_np, search_field=search_field, limit=limit, **kwargs # type: ignore ) def find_batched( @@ -414,7 +408,7 @@ def find_batched( query_vec_np = self._to_numpy(queries) return self._find_batched( - query_vec_np, search_field=search_field, limit=limit, **kwargs + query_vec_np, search_field=search_field, limit=limit, **kwargs # type: ignore ) def filter( @@ -429,7 +423,7 @@ def filter( :param limit: maximum number of documents to return :return: a DocumentArray containing the documents that match the filter query """ - return self._filter(filter_query, limit=limit, **kwargs) + return self._filter(filter_query, limit=limit, **kwargs) # type: ignore def filter_batched( self, @@ -443,7 +437,7 @@ def filter_batched( :param limit: maximum number of documents to return :return: a DocumentArray containing the documents that match the filter query """ - return self._filter_batched(filter_queries, limit=limit, **kwargs) + return self._filter_batched(filter_queries, limit=limit, **kwargs) # type: ignore def text_search( self, @@ -464,7 +458,7 @@ def text_search( else: query_text = query return self._text_search( - query_text, search_field=search_field, limit=limit, **kwargs + query_text, search_field=search_field, limit=limit, **kwargs # type: ignore ) def text_search_batched( @@ -489,7 +483,7 @@ def text_search_batched( else: query_texts = cast(Sequence[str], queries) return self._text_search_batched( - query_texts, search_field=search_field, limit=limit, **kwargs + query_texts, search_field=search_field, limit=limit, **kwargs # type: ignore ) ########################################################## diff --git a/docarray/doc_index/backends/hnswlib_doc_index.py b/docarray/doc_index/backends/hnswlib_doc_index.py index 812478de419..b647cf2947a 100644 --- a/docarray/doc_index/backends/hnswlib_doc_index.py +++ b/docarray/doc_index/backends/hnswlib_doc_index.py @@ -130,7 +130,7 @@ def python_type_to_db_type(self, python_type: Type) -> Any: return np.ndarray if python_type == docarray.typing.ID: - return None # TODO(johannes): handle this + return None raise ValueError(f'Unsupported column type for {type(self)}: {python_type}') @@ -196,10 +196,7 @@ def _find_batched( query: np.ndarray, search_field: str, limit: int, - **kwargs, ) -> FindResultBatched: - if kwargs: - raise ValueError(f'{list(kwargs.keys())} are not valid keyword arguments') index = self._hnsw_indices[search_field] labels, distances = index.knn_query(query, k=limit) result_das = [ @@ -211,18 +208,16 @@ def _find_batched( return FindResultBatched(documents=result_das, scores=distances) @composable - def _find( - self, query: np.ndarray, search_field: str, limit: int, **kwargs - ) -> FindResult: + def _find(self, query: np.ndarray, search_field: str, limit: int) -> FindResult: query_batched = np.expand_dims(query, axis=0) - docs, scores = self._find_batched(query_batched, search_field, limit, **kwargs) + docs, scores = self._find_batched(query_batched, search_field, limit) return FindResult(documents=docs[0], scores=scores[0]) @composable def _filter( self, - *args, - **kwargs, + filter_query: Any, + limit: int, ) -> DocumentArray: raise NotImplementedError( @@ -235,7 +230,6 @@ def _filter_batched( self, filter_queries: Any, limit: int, - **kwargs, ) -> List[DocumentArray]: raise NotImplementedError( f'{type(self)} does not support filter-only queries.' @@ -248,7 +242,6 @@ def _text_search( query: str, search_field: str, limit: int, - **kwargs, ) -> FindResult: raise NotImplementedError(f'{type(self)} does not support text search.') @@ -257,7 +250,6 @@ def _text_search_batched( queries: Sequence[str], search_field: str, limit: int, - **kwargs, ) -> FindResultBatched: raise NotImplementedError(f'{type(self)} does not support text search.') From f31a0506f35f2ce204be7ed2fe6368a7654ae587 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 11:17:26 +0100 Subject: [PATCH 50/53] docs: typo Signed-off-by: Johannes Messner --- docs/tutorials/add_doc_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/add_doc_index.md b/docs/tutorials/add_doc_index.md index 79ac47c7aef..0568d814f24 100644 --- a/docs/tutorials/add_doc_index.md +++ b/docs/tutorials/add_doc_index.md @@ -242,7 +242,7 @@ In general, the following is true: - For every method that you need to implement, there is a public variant (e.g. `index`) and a private variant (e.g. `_index`) - You should usually implement the private variant, which is called by the already implemented public variant. This should make your life easier, because some preprocessing and data normalization will already be done for you. - You can, however, also implement the public variant directly, if you want to do something special. - - **Caution**: While this is a perfectly fine thing to do, it might create more maintainance work for you in the future, because the public variant defined in the `BaseDocumentIndex` might change in the future, and you will have to update your implementation accordingly. + - **Caution**: While this is a perfectly fine thing to do, it might create more maintenance work for you in the future, because the public variant defined in the `BaseDocumentIndex` might change in the future, and you will have to update your implementation accordingly. Further: - You don't absolutely have to implement everything. If a feature (e.g. `text_search`) is not supported by your backend, just raise a `NotImplementedError` in the corresponding method. From 6855e514eabc36cc3d9ce6a49fcbe3332c9c0dce Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 11:45:28 +0100 Subject: [PATCH 51/53] refactor: add abstractions for get and del Signed-off-by: Johannes Messner --- docarray/doc_index/abstract_doc_index.py | 92 +++++++++++++------ .../doc_index/backends/hnswlib_doc_index.py | 26 ++---- .../base_classes/test_base_doc_store.py | 4 +- .../base_classes/test_base_query_builder.py | 4 +- tests/doc_index/base_classes/test_configs.py | 4 +- 5 files changed, 78 insertions(+), 52 deletions(-) diff --git a/docarray/doc_index/abstract_doc_index.py b/docarray/doc_index/abstract_doc_index.py index 9c45a205469..ab997d5c7ab 100644 --- a/docarray/doc_index/abstract_doc_index.py +++ b/docarray/doc_index/abstract_doc_index.py @@ -76,7 +76,7 @@ class _Column: class composable: - """Decorator that marks methods in a DocumentStore as composable, + """Decorator that marks methods in a DocumentIndex as composable, i.e. they can be used in a query builder. """ @@ -104,8 +104,8 @@ class BaseDocumentIndex(ABC, Generic[TSchema]): def __init__(self, db_config=None, **kwargs): if self._schema is None: raise ValueError( - 'A DocumentStore must be typed with a Document type.' - 'To do so, use the syntax: DocumentStore[DocumentType]' + 'A DocumentIndex must be typed with a Document type.' + 'To do so, use the syntax: DocumentIndex[DocumentType]' ) self._db_config = db_config or self.DBConfig(**kwargs) if not isinstance(self._db_config, self.DBConfig): @@ -186,22 +186,20 @@ def num_docs(self) -> int: ... @abstractmethod - def __delitem__(self, key: Union[str, Sequence[str]]): - """Delete one or multiple Documents from the store, by `id`. - If no document is found, a KeyError is raised. + def _del_items(self, doc_ids: Sequence[str]): + """Delete Documents from the index. - :param key: id or ids to delete from the Document Store + :param doc_ids: ids to delete from the Document Store """ ... @abstractmethod - def __getitem__( - self, key: Union[str, Sequence[str]] - ) -> Union[TSchema, Sequence[TSchema]]: - """Get one or multiple Documents into the store, by `id`. + def _get_items(self, doc_ids: Sequence[str]) -> Sequence[TSchema]: + """Get Documents from the index, by `id`. If no document is found, a KeyError is raised. - :param key: id or ids to get from the Document Store + :param doc_ids: ids to get from the Document Index + :return: Sequence of Documents, sorted corresponding to the order of `doc_ids`. Duplicate `doc_ids` can be omitted in the output. """ ... @@ -228,7 +226,7 @@ def _find( search_field: str, limit: int, ) -> FindResult: - """Find documents in the store + """Find documents in the index :param query: query vector for KNN/ANN search. Has single axis. :param search_field: name of the field to search on @@ -246,7 +244,7 @@ def _find_batched( search_field: str, limit: int, ) -> FindResultBatched: - """Find documents in the store + """Find documents in the index :param query: query vectors for KNN/ANN search. Has shape (batch_size, vector_dim) @@ -262,7 +260,7 @@ def _filter( filter_query: Any, limit: int, ) -> DocumentArray: - """Find documents in the store based on a filter query + """Find documents in the index based on a filter query :param filter_query: the DB specific filter query to execute :param limit: maximum number of documents to return @@ -276,7 +274,7 @@ def _filter_batched( filter_queries: Any, limit: int, ) -> List[DocumentArray]: - """Find documents in the store based on multiple filter queries. + """Find documents in the index based on multiple filter queries. Each query is considered individually, and results are returned per query. :param filter_queries: the DB specific filter queries to execute @@ -293,7 +291,7 @@ def _text_search( search_field: str, limit: int, ) -> FindResult: - """Find documents in the store based on a text search query + """Find documents in the index based on a text search query :param query: The text to search for :param search_field: name of the field to search on @@ -311,7 +309,7 @@ def _text_search_batched( search_field: str, limit: int, ) -> FindResultBatched: - """Find documents in the store based on a text search query + """Find documents in the index based on a text search query :param queries: The texts to search for :param search_field: name of the field to search on @@ -327,9 +325,45 @@ def _text_search_batched( # Subclasses may or may not need to change these # #################################################### + def __getitem__( + self, key: Union[str, Sequence[str]] + ) -> Union[TSchema, DocumentArray[TSchema]]: + """Get one or multiple Documents into the index, by `id`. + If no document is found, a KeyError is raised. + + :param key: id or ids to get from the Document Index + """ + # normalize input + if isinstance(key, str): + return_singleton = True + key = [key] + else: + return_singleton = False + # retrieve data + doc_sequence = self._get_items(key) + # cast output + if isinstance(doc_sequence, DocumentArray): + out_da: DocumentArray[TSchema] = doc_sequence + else: + da_cls = DocumentArray.__class_getitem__( + cast(Type[BaseDocument], self._schema) + ) + out_da = da_cls(doc_sequence) + return out_da[0] if return_singleton else out_da # type: ignore + + def __delitem__(self, key: Union[str, Sequence[str]]): + """Delete one or multiple Documents from the index, by `id`. + If no document is found, a KeyError is raised. + + :param key: id or ids to delete from the Document Index + """ + if isinstance(key, str): + key = [key] + self._del_items(key) + def configure(self, runtime_config=None, **kwargs): """ - Configure the DocumentStore. + Configure the DocumentIndex. You can either pass a config object to `config` or pass individual config parameters as keyword arguments. If a configuration object is passed, it will replace the current configuration. @@ -346,7 +380,7 @@ def configure(self, runtime_config=None, **kwargs): self._runtime_config = runtime_config def index(self, docs: Union[BaseDocument, Sequence[BaseDocument]], **kwargs): - """Index Documents into the store. + """Index Documents into the index. :param docs: Documents to index """ @@ -360,7 +394,7 @@ def find( limit: int = 10, **kwargs, ) -> FindResult: - """Find documents in the store using nearest neighbor search. + """Find documents in the index using nearest neighbor search. :param query: query vector for KNN/ANN search. Can be either a tensor-like (np.array, torch.Tensor, etc.) @@ -387,7 +421,7 @@ def find_batched( limit: int = 10, **kwargs, ) -> FindResultBatched: - """Find documents in the store using nearest neighbor search. + """Find documents in the index using nearest neighbor search. :param queries: query vector for KNN/ANN search. Can be either a tensor-like (np.array, torch.Tensor, etc.) with a, @@ -417,7 +451,7 @@ def filter( limit: int = 10, **kwargs, ) -> DocumentArray: - """Find documents in the store based on a filter query + """Find documents in the index based on a filter query :param filter_query: the DB specific filter query to execute :param limit: maximum number of documents to return @@ -431,7 +465,7 @@ def filter_batched( limit: int = 10, **kwargs, ) -> List[DocumentArray]: - """Find documents in the store based on multiple filter queries. + """Find documents in the index based on multiple filter queries. :param filter_queries: the DB specific filter query to execute :param limit: maximum number of documents to return @@ -446,7 +480,7 @@ def text_search( limit: int = 10, **kwargs, ) -> FindResult: - """Find documents in the store based on a text search query. + """Find documents in the index based on a text search query. :param query: The text to search for :param search_field: name of the field to search on @@ -468,7 +502,7 @@ def text_search_batched( limit: int = 10, **kwargs, ) -> FindResultBatched: - """Find documents in the store based on a text search query. + """Find documents in the index based on a text search query. :param queries: The texts to search for :param search_field: name of the field to search on @@ -526,7 +560,7 @@ def _get_col_value_dict( if not self._is_schema_compatible(docs_seq): raise ValueError( 'The schema of the documents to be indexed is not compatible' - ' with the schema of the store.' + ' with the schema of the index.' ) def _col_gen(col_name: str): @@ -558,9 +592,9 @@ class _DocumentStoreTyped(cls): # type: ignore def build_query(self) -> QueryBuilder: """ - Build a query for this DocumentStore. + Build a query for this DocumentIndex. - :return: a new `QueryBuilder` object for this DocumentStore + :return: a new `QueryBuilder` object for this DocumentIndex """ return self.QueryBuilder() # type: ignore diff --git a/docarray/doc_index/backends/hnswlib_doc_index.py b/docarray/doc_index/backends/hnswlib_doc_index.py index b647cf2947a..45a358a032a 100644 --- a/docarray/doc_index/backends/hnswlib_doc_index.py +++ b/docarray/doc_index/backends/hnswlib_doc_index.py @@ -253,32 +253,24 @@ def _text_search_batched( ) -> FindResultBatched: raise NotImplementedError(f'{type(self)} does not support text search.') - def __delitem__(self, key: Union[str, Sequence[str]]): + def _del_items(self, doc_ids: Sequence[str]): # delete from the indices - if isinstance(key, str): - key = [key] try: - for doc_id in key: + for doc_id in doc_ids: id_ = self._to_hashed_id(doc_id) for col_name, index in self._hnsw_indices.items(): index.mark_deleted(id_) except RuntimeError: - raise KeyError(f'No document with id {key} found') + raise KeyError(f'No document with id {doc_ids} found') - self._delete_docs_from_sqlite(key) + self._delete_docs_from_sqlite(doc_ids) self._sqlite_conn.commit() - def __getitem__(self, key: Union[str, Sequence[str]]): - # delete from the indices - if isinstance(key, str): - key = [key] - out_docs = self._get_docs_sqlite_doc_id(key) - # TODO(johannes): this could be handled for all stores + def _get_items(self, doc_ids: Sequence[str]) -> Sequence[TSchema]: + out_docs = self._get_docs_sqlite_doc_id(doc_ids) if len(out_docs) == 0: - raise KeyError(f'No document with id {key} found') - if len(out_docs) == 1: - return out_docs[0] - return out_docs + raise KeyError(f'No document with id {doc_ids} found') + return out_docs # type: ignore def num_docs(self) -> int: return self._get_num_docs_sqlite() @@ -355,7 +347,7 @@ def _get_docs_sqlite_unsorted(self, univ_ids: Sequence[int]): da_cls = DocumentArray.__class_getitem__(cast(Type[BaseDocument], self._schema)) return da_cls([self._doc_from_bytes(row[0]) for row in rows]) - def _get_docs_sqlite_doc_id(self, doc_ids: Sequence[str]) -> DocumentArray: + def _get_docs_sqlite_doc_id(self, doc_ids: Sequence[str]) -> DocumentArray[TSchema]: hashed_ids = tuple(self._to_hashed_id(id_) for id_ in doc_ids) docs_unsorted = self._get_docs_sqlite_unsorted(hashed_ids) da_cls = DocumentArray.__class_getitem__(cast(Type[BaseDocument], self._schema)) diff --git a/tests/doc_index/base_classes/test_base_doc_store.py b/tests/doc_index/base_classes/test_base_doc_store.py index c3c82f05454..5779bb4e4f5 100644 --- a/tests/doc_index/base_classes/test_base_doc_store.py +++ b/tests/doc_index/base_classes/test_base_doc_store.py @@ -55,8 +55,8 @@ def python_type_to_db_type(self, x): _index = _identity num_docs = _identity - __delitem__ = _identity - __getitem__ = _identity + _del_items = _identity + _get_items = _identity execute_query = _identity _find = _identity _find_batched = _identity diff --git a/tests/doc_index/base_classes/test_base_query_builder.py b/tests/doc_index/base_classes/test_base_query_builder.py index 6e99eefcc7b..22ff04af114 100644 --- a/tests/doc_index/base_classes/test_base_query_builder.py +++ b/tests/doc_index/base_classes/test_base_query_builder.py @@ -22,8 +22,8 @@ def python_type_to_db_type(self, x): index = _identity num_docs = _identity - __delitem__ = _identity - __getitem__ = _identity + _del_items = _identity + _get_items = _identity execute_query = _identity @composable diff --git a/tests/doc_index/base_classes/test_configs.py b/tests/doc_index/base_classes/test_configs.py index 9e1af88b6f1..eb4690d2e19 100644 --- a/tests/doc_index/base_classes/test_configs.py +++ b/tests/doc_index/base_classes/test_configs.py @@ -48,8 +48,8 @@ def python_type_to_db_type(self, x): _index = _identity num_docs = _identity - __delitem__ = _identity - __getitem__ = _identity + _del_items = _identity + _get_items = _identity execute_query = _identity _find = _identity _find_batched = _identity From eb12fde02c20f1e19d84b0e4e88eff2657df6c5b Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 11:46:02 +0100 Subject: [PATCH 52/53] refactor: renaming Signed-off-by: Johannes Messner --- docarray/doc_index/abstract_doc_index.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docarray/doc_index/abstract_doc_index.py b/docarray/doc_index/abstract_doc_index.py index ab997d5c7ab..99557d78b5d 100644 --- a/docarray/doc_index/abstract_doc_index.py +++ b/docarray/doc_index/abstract_doc_index.py @@ -582,13 +582,13 @@ def __class_getitem__(cls, item: Type[TSchema]): f'{cls.__name__}[item] `item` should be a Document not a {item} ' ) - class _DocumentStoreTyped(cls): # type: ignore + class _DocumentIndexTyped(cls): # type: ignore _schema: Type[TSchema] = item - _DocumentStoreTyped.__name__ = f'{cls.__name__}[{item.__name__}]' - _DocumentStoreTyped.__qualname__ = f'{cls.__qualname__}[{item.__name__}]' + _DocumentIndexTyped.__name__ = f'{cls.__name__}[{item.__name__}]' + _DocumentIndexTyped.__qualname__ = f'{cls.__qualname__}[{item.__name__}]' - return _DocumentStoreTyped + return _DocumentIndexTyped def build_query(self) -> QueryBuilder: """ From c5bc8cffa10169f600e28a1b57417aae87e655d5 Mon Sep 17 00:00:00 2001 From: Johannes Messner Date: Wed, 1 Mar 2023 12:11:08 +0100 Subject: [PATCH 53/53] test: add markers for slow and doc index Signed-off-by: Johannes Messner --- pyproject.toml | 3 ++- tests/doc_index/base_classes/test_base_doc_store.py | 2 ++ tests/doc_index/base_classes/test_base_query_builder.py | 2 ++ tests/doc_index/base_classes/test_configs.py | 3 +++ tests/doc_index/hnswlib/test_find.py | 2 ++ tests/doc_index/hnswlib/test_index_get_del.py | 2 ++ 6 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1e513979d82..7148623672b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,5 +102,6 @@ markers = [ "internet: marks tests as requiring internet (deselect with '-m \"not internet\"')", "asyncio: marks that run async tests", "proto: mark tests that run with proto", - "tensorflow: marks test using tensorflow and proto 3" + "tensorflow: marks test using tensorflow and proto 3", + "doc_index: marks test using a document index" ] diff --git a/tests/doc_index/base_classes/test_base_doc_store.py b/tests/doc_index/base_classes/test_base_doc_store.py index 5779bb4e4f5..e244c4f491c 100644 --- a/tests/doc_index/base_classes/test_base_doc_store.py +++ b/tests/doc_index/base_classes/test_base_doc_store.py @@ -9,6 +9,8 @@ from docarray.doc_index.abstract_doc_index import BaseDocumentIndex from docarray.typing import ID, NdArray +pytestmark = pytest.mark.doc_index + class SimpleDoc(BaseDocument): tens: NdArray[10] = Field(dim=1000) diff --git a/tests/doc_index/base_classes/test_base_query_builder.py b/tests/doc_index/base_classes/test_base_query_builder.py index 22ff04af114..d44837b9075 100644 --- a/tests/doc_index/base_classes/test_base_query_builder.py +++ b/tests/doc_index/base_classes/test_base_query_builder.py @@ -2,6 +2,8 @@ from docarray.doc_index.abstract_doc_index import BaseDocumentIndex, composable +pytestmark = pytest.mark.doc_index + class DummyQueryBuilder(BaseDocumentIndex.QueryBuilder): def build(self): diff --git a/tests/doc_index/base_classes/test_configs.py b/tests/doc_index/base_classes/test_configs.py index eb4690d2e19..ef1867d140a 100644 --- a/tests/doc_index/base_classes/test_configs.py +++ b/tests/doc_index/base_classes/test_configs.py @@ -1,12 +1,15 @@ from dataclasses import dataclass, field from typing import Any, Dict, Type +import pytest from pydantic import Field from docarray import BaseDocument from docarray.doc_index.abstract_doc_index import BaseDocumentIndex from docarray.typing import NdArray +pytestmark = pytest.mark.doc_index + class SimpleDoc(BaseDocument): tens: NdArray[10] = Field(dim=1000) diff --git a/tests/doc_index/hnswlib/test_find.py b/tests/doc_index/hnswlib/test_find.py index fc2a35c3580..147d842286a 100644 --- a/tests/doc_index/hnswlib/test_find.py +++ b/tests/doc_index/hnswlib/test_find.py @@ -6,6 +6,8 @@ from docarray.doc_index.backends.hnswlib_doc_index import HnswDocumentIndex from docarray.typing import NdArray +pytestmark = [pytest.mark.slow, pytest.mark.doc_index] + class SimpleDoc(BaseDocument): tens: NdArray[10] = Field(dim=1000) diff --git a/tests/doc_index/hnswlib/test_index_get_del.py b/tests/doc_index/hnswlib/test_index_get_del.py index c59c3ff4786..72fecc66b9a 100644 --- a/tests/doc_index/hnswlib/test_index_get_del.py +++ b/tests/doc_index/hnswlib/test_index_get_del.py @@ -6,6 +6,8 @@ from docarray.doc_index.backends.hnswlib_doc_index import HnswDocumentIndex from docarray.typing import NdArray +pytestmark = [pytest.mark.slow, pytest.mark.doc_index] + class SimpleDoc(BaseDocument): tens: NdArray[10] = Field(dim=1000)