diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e1a9fdf --- /dev/null +++ b/.flake8 @@ -0,0 +1,10 @@ +[flake8] +max-line-length = 120 +# E203 (whitespace before ':') conflicts with black's slice formatting. +extend-ignore = E203 +extend-exclude = + regexsolver/_generated, + .venv, + venv, + build, + dist diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4dd3dee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + name: Lint & Type Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + + - name: Lint with flake8 + run: flake8 regexsolver tests + + - name: Type check with mypy + run: mypy regexsolver + + test: + name: Test (Python ${{ matrix.python-version }}) + needs: lint + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + + - name: Run tests with pytest + run: pytest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bc332dd..3407c30 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,49 +1,73 @@ -name: Publish to PyPi +name: Publish to PyPI on: - push: - tags: - - 'v*' - + push: + tags: + - "v*" + jobs: - build: - name: Build distribution - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install pypa/build - run: >- - python3 -m - pip install - build - --user - - name: Build a binary wheel and a source tarball - run: python3 -m build - - name: Store the distribution packages - uses: actions/upload-artifact@v4 - with: - name: python-package-distributions - path: dist/ - publish-to-pypi: - name: Publish to PyPI - needs: - - build - runs-on: ubuntu-latest - environment: - name: pypi - url: https://pypi.org/p/regexsolver - permissions: - id-token: write - steps: - - name: Download all the dists - uses: actions/download-artifact@v4 - with: - name: python-package-distributions - path: dist/ - - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[test]" + + - name: Run tests with pytest + run: pytest + + build: + name: Build distribution + needs: test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install pypa/build + run: python3 -m pip install build --user + + - name: Build a binary wheel and a source tarball + run: python3 -m build + + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish: + name: Publish to PyPI + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/regexsolver + permissions: + id-token: write + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml deleted file mode 100644 index a9a10d5..0000000 --- a/.github/workflows/python.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Python checks - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - test: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: [3.7, 3.8, 3.9] - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install -r test-requirements.txt - pip install pytest - - - name: Run tests - run: pytest diff --git a/.gitignore b/.gitignore index efa407c..34fe970 100644 --- a/.gitignore +++ b/.gitignore @@ -1,162 +1,49 @@ +# Build output +build/ +dist/ +sdist/ +*.egg-info/ +*.egg +.eggs/ +develop-eggs/ +.installed.cfg + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class - -# C extensions *.so -# Distribution / packaging +# Virtual environments .Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ +env/ +venv/ +.venv/ lib/ lib64/ parts/ -sdist/ var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec +.python-version -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ +# Test / coverage / type checking +.pytest_cache/ +.mypy_cache/ .tox/ -.nox/ +.cache +htmlcov/ .coverage .coverage.* -.cache -nosetests.xml coverage.xml -*.cover -*.py,cover +nosetests.xml .hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/latest/usage/project/#working-with-version-control -.pdm.toml -.pdm-python -.pdm-build/ - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments +# Environment .env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ -# Cython debug symbols -cython_debug/ +# IDE +.idea/ +.vscode/ +*.iml -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +# OS +.DS_Store diff --git a/.openapi-generator-ignore b/.openapi-generator-ignore new file mode 100644 index 0000000..6289d05 --- /dev/null +++ b/.openapi-generator-ignore @@ -0,0 +1,19 @@ +setup.py +setup.cfg +tox.ini +.gitignore +requirements.txt +test-requirements.txt +git_push.sh +.travis.yml +.gitlab-ci.yml +pyproject.toml +.github/ +docs/ +test/ +README.md + +regexsolver/__init__.py +regexsolver/clients/* +regexsolver/exceptions.py +regexsolver/models/* diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES new file mode 100644 index 0000000..3163f0c --- /dev/null +++ b/.openapi-generator/FILES @@ -0,0 +1,49 @@ +regexsolver/_generated/__init__.py +regexsolver/_generated/api/__init__.py +regexsolver/_generated/api/account_api.py +regexsolver/_generated/api/analyze_api.py +regexsolver/_generated/api/compute_api.py +regexsolver/_generated/api/generate_api.py +regexsolver/_generated/api_client.py +regexsolver/_generated/api_response.py +regexsolver/_generated/configuration.py +regexsolver/_generated/exceptions.py +regexsolver/_generated/models/__init__.py +regexsolver/_generated/models/account_limits.py +regexsolver/_generated/models/boolean.py +regexsolver/_generated/models/cardinality.py +regexsolver/_generated/models/cardinality200_response.py +regexsolver/_generated/models/cardinality_big_integer.py +regexsolver/_generated/models/cardinality_infinite.py +regexsolver/_generated/models/cardinality_integer.py +regexsolver/_generated/models/concat200_response.py +regexsolver/_generated/models/dot200_response.py +regexsolver/_generated/models/empty200_response.py +regexsolver/_generated/models/error_response.py +regexsolver/_generated/models/error_response400.py +regexsolver/_generated/models/error_response401.py +regexsolver/_generated/models/error_response403.py +regexsolver/_generated/models/execution_options.py +regexsolver/_generated/models/fair_response_options.py +regexsolver/_generated/models/generate_strings_character_order.py +regexsolver/_generated/models/generate_strings_path_order.py +regexsolver/_generated/models/generate_strings_request.py +regexsolver/_generated/models/generate_strings_response.py +regexsolver/_generated/models/length.py +regexsolver/_generated/models/length200_response.py +regexsolver/_generated/models/limits200_response.py +regexsolver/_generated/models/multi_terms_request.py +regexsolver/_generated/models/repeat_request.py +regexsolver/_generated/models/request_options.py +regexsolver/_generated/models/response_options.py +regexsolver/_generated/models/string.py +regexsolver/_generated/models/strings.py +regexsolver/_generated/models/strings200_response.py +regexsolver/_generated/models/term.py +regexsolver/_generated/models/term_fair.py +regexsolver/_generated/models/term_fair_metadata.py +regexsolver/_generated/models/term_regex.py +regexsolver/_generated/models/term_request.py +regexsolver/_generated/models/two_terms_request.py +regexsolver/_generated/py.typed +regexsolver/_generated/rest.py diff --git a/.openapi-generator/VERSION b/.openapi-generator/VERSION new file mode 100644 index 0000000..a29ba3d --- /dev/null +++ b/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.21.0 diff --git a/README.md b/README.md index 7335535..fbdd91f 100644 --- a/README.md +++ b/README.md @@ -1,202 +1,166 @@ # RegexSolver Python API Client [Homepage](https://regexsolver.com) | [Online Demo](https://regexsolver.com/demo) | [Documentation](https://docs.regexsolver.com) | [Developer Console](https://console.regexsolver.com) -This repository contains the source code of the Python library for [RegexSolver](https://regexsolver.com) API. - -RegexSolver is a powerful regular expression manipulation toolkit, that gives you the power to manipulate regex as if -they were sets. +**RegexSolver** is a powerful toolkit for building, combining, and analyzing regular expressions. It is designed for constraint solvers, test generators, and other systems that need advanced regex operations. ## Installation ```sh -pip install --upgrade regexsolver +pip install regexsolver ``` -### Requirements +Requirements: **Python >= 3.10** + +## Quick Start -- Python >=3.7 +1. Create an API token in the [Developer Console](https://console.regexsolver.com/). +2. Initialize the client and start working with terms. -## Usage +### Synchronous Usage -In order to use the library you need to generate an API Token on our [Developer Console](https://console.regexsolver.com/). +The synchronous client provides a simple, blocking API. ```python -from regexsolver import RegexSolver, Term +from regexsolver import RegexSolverClient, Term -RegexSolver.initialize("YOUR TOKEN HERE") +client = RegexSolverClient("REGEXSOLVER_API_TOKEN") term1 = Term.regex(r"(abc|de|fg){2,}") term2 = Term.regex(r"de.*") -term3 = Term.regex(r".*abc") - -term4 = Term.regex(r".+(abc|de).+") - -result = term1.intersection(term2, term3)\ - .subtraction(term4) - -print(result) -``` - -## Features - -- [Intersection](#intersection) -- [Union](#union) -- [Subtraction / Difference](#subtraction--difference) -- [Equivalence](#equivalence) -- [Subset](#subset) -- [Details](#details) -- [Generate Strings](#generate-strings) - -### Intersection - -#### Request - -Compute the intersection of the provided terms and return the resulting term. - -The maximum number of terms is currently limited to 10. - -```python -term1 = Term.regex(r"(abc|de){2}") -term2 = Term.regex(r"de.*") -term3 = Term.regex(r".*abc") - -result = term1.intersection(term2, term3) -print(result) -``` - -#### Response +intersection = client.intersection(term1, term2) +pattern = client.get_pattern(intersection) +print(pattern) # de(abc|de|fg)+ ``` -regex=deabc -``` - -### Union - -Compute the union of the provided terms and return the resulting term. -The maximum number of terms is currently limited to 10. +### Asynchronous Usage -#### Request +For non-blocking applications, use the asynchronous client. ```python -term1 = Term.regex(r"abc") -term2 = Term.regex(r"de") -term3 = Term.regex(r"fghi") - -result = term1.union(term2, term3) -print(result) -``` +import asyncio +from regexsolver import AsyncRegexSolverClient, Term -#### Response +async def main(): + async with AsyncRegexSolverClient("REGEXSOLVER_API_TOKEN") as client: + term1 = Term.regex(r"(abc|de|fg){2,}") + term2 = Term.regex(r"de.*") -``` -regex=(abc|de|fghi) -``` - -### Subtraction / Difference + intersection = await client.intersection(term1, term2) + pattern = await client.get_pattern(intersection) + print(pattern) # de(abc|de|fg)+ -Compute the first term minus the second and return the resulting term. - -#### Request - -```python -term1 = Term.regex(r"(abc|de)") -term2 = Term.regex(r"de") -result = term1.subtraction(term2) -print(result) +asyncio.run(main()) ``` -#### Response +## Key Concepts & Limitations -``` -regex=abc -``` +RegexSolver supports a subset of regular expressions that adhere to the principles of regular languages. Here are the key characteristics and limitations of the regular expressions supported by RegexSolver: +- **Anchored Expressions:** All regular expressions in RegexSolver are anchored. This means that the expressions are treated as if they start and end at the boundaries of the input text. For example, the expression `abc` will match the string "abc" but not "xabc" or "abcx". +- **Lookahead/Lookbehind:** RegexSolver does not support lookahead (`(?=...)`) or lookbehind (`(?<=...)`) assertions. Using them returns an error. +- **Pure Regular Expressions:** RegexSolver focuses on pure regular expressions as defined in regular language theory. This means features that extend beyond regular languages, such as backreferences (`\1`, `\2`, etc.), are not supported. Any use of backreference would return an error. +- **Greedy/Ungreedy Quantifiers:** The concept of ungreedy (`*?`, `+?`, `??`) quantifiers is not supported. All quantifiers are treated as greedy. For example, `a*` or `a*?` will match the longest possible sequence of "a"s. +- **Line Feed and Dot:** RegexSolver handles all characters the same way. The dot `.` matches any Unicode character including line feed (`\n`). +- **Empty Regular Expressions:** The empty language (matches no string) is represented by constructs like `[]` (empty character class). This is distinct from the empty string. -### Equivalence +## Response Formats -Analyze if the two provided terms are equivalent. +The API can handle terms in two formats: +- `regex`: a regular expression pattern +- `fair`: FAIR (Fast Automaton Internal Representation), a stable, signed format used internally by the engine -#### Request +By default, the engine returns whatever the operation produces, with no extra conversion. Override with `response_format`, accepted by the operations that return a term: ```python -term1 = Term.regex(r"(abc|de)") -term2 = Term.regex(r"(abc|de)*") +from regexsolver import ResponseFormat -result = term1.is_equivalent_to(term2) -print(result) -``` +term1 = Term.regex(r"abcde") +term2 = Term.regex(r"de") -#### Response +result = client.union(term1, term2, response_format=ResponseFormat.REGEX) +print(result) # regex=(abc)?de -``` -False +result = client.union(term1, term2, response_format=ResponseFormat.FAIR) +print(result) # fair=... ``` -### Subset +If the format does not matter, omit `response_format` or set it to `ResponseFormat.ANY`. -Analyze if the second term is a subset of the first. +Regardless of the format, you can always call `get_pattern()` to obtain the regex pattern of a term. -#### Request +## Bounding execution time -```java -term1 = Term.regex(r"de") -term2 = Term.regex(r"(abc|de)") +Set a server-side compute timeout in milliseconds with `execution_timeout`: -result = term1.is_subset_of(term2) -print(result) -``` - -#### Response +```python +from regexsolver.exceptions import TimeoutExceededError -``` -True +# Limit the server-side compute time to 100 ms +try: + term1 = Term.regex(r".*ab.*c(de|fg).*dab.*c(de|fg).*ab.*c(de|fg).*dab.*c") + term2 = Term.regex(r".*abc.*") + + res = client.difference(term1, term2, execution_timeout=100) +except TimeoutExceededError as error: + print(error) # The API returned the following error: The operation took too much time. ``` -### Details +Timeout is best effort. The exact time is not guaranteed. -Compute the details of the provided term. +## API Overview -The computed details are: +`RegexSolverClient` and `AsyncRegexSolverClient` expose the following methods. Every method accepts optional keyword arguments: operations that return a term take `response_format`, `deterministic` and `execution_timeout`, while analyze operations and `determinize()` take `execution_timeout` only; the response format is not theirs to choose. `generate_strings()` additionally takes its ordering, seed, length and charset options as keyword arguments. -- **Cardinality:** the number of possible values. -- **Length:** the minimum and maximum length of possible values. -- **Empty:** true if is an empty set (does not contain any value), false otherwise. -- **Total:** true if is a total set (contains all values), false otherwise. +### Analyze -#### Request +| Method | Return | Description | +| -------- | ------- | ------- | +| `client.equivalent(term1, term2, **kwargs)` | `bool` | `True` if `term1` and `term2` accept exactly the same language. | +| `client.get_cardinality(term, **kwargs)` | `Cardinality` | Returns the number of possible matched strings. | +| `client.get_dot(term, **kwargs)` | `str` | Returns a Graphviz DOT representation of the automaton. | +| `client.get_length(term, **kwargs)` | `Length` | Returns the minimum and maximum length of matched strings. | +| `client.get_pattern(term, **kwargs)` | `str` | Returns a regular expression pattern for the term. | +| `client.is_empty(term, **kwargs)` | `bool` | `True` if the term matches no string. | +| `client.is_empty_string(term, **kwargs)` | `bool` | `True` if the term matches only the empty string. | +| `client.is_total(term, **kwargs)` | `bool` | `True` if the term matches all possible strings. | +| `client.is_deterministic(term, **kwargs)` | `bool` | `True` if the term's automaton is deterministic. Only a deterministic FAIR guarantees consistent string ordering across paginated `generate_strings()` calls; call `determinize()` first if this is `False`. | +| `client.subset(term_subset, term_superset, **kwargs)` | `bool` | `True` if every string matched by `term_subset` is also matched by `term_superset`. | -```python -term = Term.regex(r"(abc|de)") +*Note: For `AsyncRegexSolverClient`, these methods are coroutines and must be awaited.* -details = term.get_details() -print(details) -``` +### Compute -#### Response +| Method | Return | Description | +| -------- | ------- | ------- | +| `client.complement(term, **kwargs)` | `Term` | Computes the complement of the given term. | +| `client.concat(term1, term2, ..., **kwargs)` | `Term` | Concatenates multiple terms in order. | +| `client.determinize(term, **kwargs)` | `Term` | Computes a deterministic FAIR for the given term, suitable for consistent pagination with `generate_strings()`. | +| `client.difference(base_term, excluded_term, **kwargs)` | `Term` | Computes the difference `base_term - excluded_term`. | +| `client.intersection(term1, term2, ..., **kwargs)` | `Term` | Computes the intersection of the given terms. | +| `client.repeat(term, min_val, max_val, **kwargs)` | `Term` | Computes the repetition of the term between `min_val` and `max_val` times. | +| `client.union(term1, term2, ..., **kwargs)` | `Term` | Computes the union of the given terms. | -``` -Details[cardinality=Integer(2), length=Length[minimum=2, maximum=3], empty=false, total=false] -``` +*Note: For `AsyncRegexSolverClient`, these methods are coroutines and must be awaited.* -### Generate Strings +### Generate -Generate the given number of strings that can be matched by the provided term. +| Method | Return | Description | +| -------- | ------- | ------- | +| `client.generate_strings(term, limit, offset, **kwargs)` | `List[str]` | Generates up to `limit` unique strings matched by `term`, skipping the first `offset` strings. Keyword arguments control `path_order`, `character_order`, `seed`, `min_length`, `max_length` and `charset`. | -The maximum number of strings to generate is currently limited to 200. +*Note: For `AsyncRegexSolverClient`, this method is a coroutine and must be awaited.* -#### Request +## Cross-Language Support -```python -term = Term.regex(r"(abc|de){2}") +If you want to use this library with other programming languages, we provide: +- [regexsolver-java](https://github.com/RegexSolver/regexsolver-java) +- [regexsolver-js](https://github.com/RegexSolver/regexsolver-js) -strings = term.generate_strings(3) -print(strings) -``` +For more information about how to use the wrappers, you can refer to our [guide](https://docs.regexsolver.com/getting-started.html). -#### Response +You can also take a look at [regexsolver](https://github.com/RegexSolver/regexsolver) which contains the source code of the engine. -``` -['deabc', 'abcde', 'dede'] -``` +## License +This project is licensed under the MIT License. diff --git a/generate-api.sh b/generate-api.sh new file mode 100755 index 0000000..e3831c0 --- /dev/null +++ b/generate-api.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +SPEC_FILE="../m-lab/shared/openapi.yaml" +OUT_DIR="./" +PACKAGE_NAME="regexsolver._generated" + +echo "Running openapi-generator-cli..." +openapi-generator-cli generate \ + -i "$SPEC_FILE" \ + -g python \ + -o "$OUT_DIR" \ + --additional-properties=packageName="$PACKAGE_NAME",library=asyncio + +echo "API Generation Complete." diff --git a/openapitools.json b/openapitools.json new file mode 100644 index 0000000..91d9c43 --- /dev/null +++ b/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "7.21.0" + } +} diff --git a/pyproject.toml b/pyproject.toml index bda1679..c280ea7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,19 +4,19 @@ build-backend = "setuptools.build_meta" [project] name = "regexsolver" -version = "1.0.3" +version = "1.1.0" authors = [ { name = "RegexSolver", email = "contact@regexsolver.com" } ] -description = "RegexSolver allows you to manipulate regular expressions as sets, enabling operations such as intersection, union, and subtraction." +description = "RegexSolver is a powerful toolkit for building, combining, and analyzing regular expressions." keywords = [ "Regular Expression", "regex", "regexp", - "set", + "pattern", "intersection", "union", - "subtraction", + "concat", "difference", "equivalence", "subset", @@ -25,28 +25,58 @@ keywords = [ ] readme = "README.md" license = { file = "LICENSE" } +requires-python = ">=3.10" + dependencies = [ - 'requests>=2.20.0', - 'pydantic<=2.5.3, >2.4.0; python_version<"3.8"', - 'pydantic>=2.6.0; python_version>="3.8"', + "aiohttp >= 3.8.4", + "aiohttp-retry >= 2.8.3", + "python-dateutil >= 2.8.2", + "pydantic >= 2.0.0", + "typing-extensions >= 4.7.1", ] -requires-python = ">=3.7" + classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Software Development :: Libraries :: Python Modules", ] +[project.optional-dependencies] +test = [ + "pytest >= 7.2.1", + "pytest-cov >= 2.8.1", + "pytest-asyncio >= 1.3.0", + "tox >= 3.9.0", + "flake8 >= 4.0.0", + "mypy >= 1.5", + "types-python-dateutil >= 2.8.19.14", +] + [project.urls] Homepage = "https://regexsolver.com/" Issues = "https://github.com/RegexSolver/regexsolver-python/issues" Documentation = "https://docs.regexsolver.com/" "Source Code" = "https://github.com/RegexSolver/regexsolver-python" + +[tool.setuptools.packages.find] +include = ["regexsolver*"] + +[tool.mypy] +exclude = ["regexsolver/_generated/"] + +# Generated code is not linted or type checked; it is regenerated from +# ../m-lab/shared/openapi.yaml by ./generate-api.sh. +[[tool.mypy.overrides]] +module = "regexsolver._generated.*" +follow_imports = "silent" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" diff --git a/regexsolver/__init__.py b/regexsolver/__init__.py index ae744d6..1f58c90 100644 --- a/regexsolver/__init__.py +++ b/regexsolver/__init__.py @@ -1,232 +1,67 @@ -from regexsolver.details import Details, Cardinality, Length - - -from typing import List, Optional -from pydantic import BaseModel -import requests - -from regexsolver.details import Details - - -class ApiError(Exception): - """ - Exception raised when the API returns an error. - """ - - def __init__(self, message: str): - super().__init__(f"The API returned the following error: {message}") - - -class RegexSolver: - _instance = None - - def __init__(self): - if RegexSolver._instance is not None: - raise Exception("This class is a singleton.") - else: - RegexSolver._instance = self - self.base_url = "https://api.regexsolver.com/" - self.api_token = None - self.headers = { - 'User-Agent': 'RegexSolver Python / 1.0.3', - 'Content-Type': 'application/json' - } - - @classmethod - def get_instance(cls): - if cls._instance is None: - cls._instance = RegexSolver() - return cls._instance - - @classmethod - def initialize(cls, api_token: str, base_url: str = None): - instance = cls.get_instance() - instance.api_token = api_token - if base_url: - instance.base_url = base_url - - instance.headers['Authorization'] = f'Bearer {instance.api_token}' - - def _get_request_url(self, endpoint: str) -> str: - if self.base_url.endswith('/'): - return self.base_url + endpoint - else: - return self.base_url + '/' + endpoint - - def _request(self, endpoint: str, request: BaseModel) -> dict: - response = requests.post( - self._get_request_url(endpoint), - headers=self.headers, - json=request.model_dump() - ) - - if response.ok: - return response.json() - else: - raise ApiError(response.json().get('message')) - - def compute_intersection(self, request: 'MultiTermsRequest') -> 'Term': - return Term(**self._request('api/compute/intersection', request)) - - def compute_union(self, request: 'MultiTermsRequest') -> 'Term': - return Term(**self._request('api/compute/union', request)) - - def compute_subtraction(self, request: 'MultiTermsRequest') -> 'Term': - return Term(**self._request('api/compute/subtraction', request)) - - def get_details(self, term: 'Term') -> Details: - return Details(**self._request('api/analyze/details', term)) - - def equivalence(self, request: 'MultiTermsRequest') -> bool: - return self._request('api/analyze/equivalence', request).get('value') - - def subset(self, request: 'MultiTermsRequest') -> bool: - return self._request('api/analyze/subset', request).get('value') - - def generate_strings(self, request: 'GenerateStringsRequest') -> List[str]: - return self._request('api/generate/strings', request).get('value') - - -_REGEX_PREFIX = "regex" -_FAIR_PREFIX = "fair" -_UNKNOWN_PREFIX = "unknown" - - -class Term(BaseModel): - """ - This class represents a term on which it is possible to perform operations. - It can either be a regular expression (regex) or a FAIR (Fast Automaton Internal Representation). - """ - - type: str - value: str - _details: Optional['Details'] = None - - @classmethod - def fair(cls, fair: str) -> 'Term': - """ - Initialize a Fast Automaton Internal Representation (FAIR). - """ - return cls(type=_FAIR_PREFIX, value=fair) - - @classmethod - def regex(cls, pattern: str) -> 'Term': - """ - Initialize a regex. - """ - return cls(type=_REGEX_PREFIX, value=pattern) - - def get_fair(self) -> Optional[str]: - """ - Return the Fast Automaton Internal Representation (FAIR). - """ - if type == _FAIR_PREFIX: - return self.value - return None - - def get_pattern(self) -> Optional[str]: - """ - Return the regular expression pattern. - """ - if type == _REGEX_PREFIX: - return self.value - return None - - def get_details(self) -> Details: - """ - Get the details of this term. - Cache the result to avoid calling the API again if this method is called multiple times. - """ - if self._details: - return self._details - else: - self._details = RegexSolver.get_instance().get_details(self) - return self._details - - def generate_strings(self, count: int) -> List[str]: - """ - Generate the given number of unique strings matched by this term. - """ - request = GenerateStringsRequest(term=self, count=count) - return RegexSolver.get_instance().generate_strings(request) - - def intersection(self, *terms: 'Term') -> 'Term': - """ - Compute the intersection with the given terms and return the resulting term. - """ - request = MultiTermsRequest(terms=[self] + list(terms)) - return RegexSolver.get_instance().compute_intersection(request) - - def union(self, *terms: 'Term') -> 'Term': - """ - Compute the union with the given terms and return the resulting term. - """ - request = MultiTermsRequest(terms=[self] + list(terms)) - return RegexSolver.get_instance().compute_union(request) - - def subtraction(self, term: 'Term') -> 'Term': - """ - Compute the subtraction with the given term and return the resulting term. - """ - request = MultiTermsRequest(terms=[self, term]) - return RegexSolver.get_instance().compute_subtraction(request) - - def is_equivalent_to(self, term: 'Term') -> bool: - """ - Check equivalence with the given term. - """ - request = MultiTermsRequest(terms=[self, term]) - return RegexSolver.get_instance().equivalence(request) - - def is_subset_of(self, term: 'Term') -> bool: - """ - Check if is a subset of the given term. - """ - request = MultiTermsRequest(terms=[self, term]) - return RegexSolver.get_instance().subset(request) - - def serialize(self) -> str: - """ - Generate a string representation that can be parsed by deserialize(). - """ - prefix = _UNKNOWN_PREFIX - if self.type == _FAIR_PREFIX: - prefix = _FAIR_PREFIX - elif self.type == _REGEX_PREFIX: - prefix = _REGEX_PREFIX - - return prefix + "=" + self.value - - def deserialize(string: str) -> Optional['Term']: - """ - Parse a string representation of a Term produced by serialize(). - """ - if not string: - return None - - if string.startswith(_REGEX_PREFIX): - return Term.regex(string[len(_REGEX_PREFIX)+1:]) - elif string.startswith(_FAIR_PREFIX): - return Term.fair(string[len(_FAIR_PREFIX)+1:]) - else: - return None - - def __str__(self): - return self.serialize() - - def __eq__(self, other): - if isinstance(other, Term): - return self.type == other.type and self.value == other.value - return False - - def __hash__(self): - return hash(self.serialize()) - - -class MultiTermsRequest(BaseModel): - terms: List[Term] - - -class GenerateStringsRequest(BaseModel): - term: Term - count: int +from regexsolver.clients.asynchronous import AsyncRegexSolverClient +from regexsolver.clients.synchronous import RegexSolverClient +from regexsolver.exceptions import ( + ApiError, + AutomatonTooManyStatesError, + BadRequestError, + FairSyntaxError, + ForbiddenError, + InternalServerError, + InvalidJsonError, + InvalidNumberOfStringsToGenerateError, + InvalidTokenError, + MissingOrMalformedTokenError, + NotFoundError, + QuotaExceededError, + RegexSolverError, + RegexSyntaxError, + TimeoutExceededError, + TimeoutTooLargeError, + TooFewTermsError, + TooManyRequestsError, + TooManyTermsError, + UnauthorizedError, +) +from regexsolver.models.account_limits import AccountLimits +from regexsolver.models.cardinality import BigInteger, Cardinality, Infinite, Integer +from regexsolver.models.generate_order import CharacterOrder, PathOrder +from regexsolver.models.length import Length +from regexsolver.models.response_format import ResponseFormat +from regexsolver.models.term import FairTerm, RegexTerm, Term + +__all__ = [ + "AsyncRegexSolverClient", + "RegexSolverClient", + "Term", + "FairTerm", + "RegexTerm", + "ApiError", + "AutomatonTooManyStatesError", + "BadRequestError", + "FairSyntaxError", + "ForbiddenError", + "InternalServerError", + "InvalidJsonError", + "InvalidTokenError", + "MissingOrMalformedTokenError", + "NotFoundError", + "QuotaExceededError", + "RegexSolverError", + "RegexSyntaxError", + "TimeoutExceededError", + "TimeoutTooLargeError", + "TooManyRequestsError", + "InvalidNumberOfStringsToGenerateError", + "TooFewTermsError", + "TooManyTermsError", + "UnauthorizedError", + "BigInteger", + "Infinite", + "Integer", + "AccountLimits", + "Cardinality", + "CharacterOrder", + "Length", + "PathOrder", + "ResponseFormat", +] diff --git a/regexsolver/_generated/__init__.py b/regexsolver/_generated/__init__.py new file mode 100644 index 0000000..3568daa --- /dev/null +++ b/regexsolver/_generated/__init__.py @@ -0,0 +1,126 @@ +# coding: utf-8 + +# flake8: noqa + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +__version__ = "1.0.0" + +# Define package exports +__all__ = [ + "AccountApi", + "AnalyzeApi", + "ComputeApi", + "GenerateApi", + "ApiResponse", + "ApiClient", + "Configuration", + "OpenApiException", + "ApiTypeError", + "ApiValueError", + "ApiKeyError", + "ApiAttributeError", + "ApiException", + "AccountLimits", + "Boolean", + "Cardinality", + "Cardinality200Response", + "CardinalityBigInteger", + "CardinalityInfinite", + "CardinalityInteger", + "Concat200Response", + "Dot200Response", + "Empty200Response", + "ErrorResponse", + "ErrorResponse400", + "ErrorResponse401", + "ErrorResponse403", + "ExecutionOptions", + "FairResponseOptions", + "GenerateStringsCharacterOrder", + "GenerateStringsPathOrder", + "GenerateStringsRequest", + "GenerateStringsResponse", + "Length", + "Length200Response", + "Limits200Response", + "MultiTermsRequest", + "RepeatRequest", + "RequestOptions", + "ResponseOptions", + "String", + "Strings", + "Strings200Response", + "Term", + "TermFair", + "TermFairMetadata", + "TermRegex", + "TermRequest", + "TwoTermsRequest", +] + +# import apis into sdk package +from regexsolver._generated.api.account_api import AccountApi as AccountApi +from regexsolver._generated.api.analyze_api import AnalyzeApi as AnalyzeApi +from regexsolver._generated.api.compute_api import ComputeApi as ComputeApi +from regexsolver._generated.api.generate_api import GenerateApi as GenerateApi + +# import ApiClient +from regexsolver._generated.api_response import ApiResponse as ApiResponse +from regexsolver._generated.api_client import ApiClient as ApiClient +from regexsolver._generated.configuration import Configuration as Configuration +from regexsolver._generated.exceptions import OpenApiException as OpenApiException +from regexsolver._generated.exceptions import ApiTypeError as ApiTypeError +from regexsolver._generated.exceptions import ApiValueError as ApiValueError +from regexsolver._generated.exceptions import ApiKeyError as ApiKeyError +from regexsolver._generated.exceptions import ApiAttributeError as ApiAttributeError +from regexsolver._generated.exceptions import ApiException as ApiException + +# import models into sdk package +from regexsolver._generated.models.account_limits import AccountLimits as AccountLimits +from regexsolver._generated.models.boolean import Boolean as Boolean +from regexsolver._generated.models.cardinality import Cardinality as Cardinality +from regexsolver._generated.models.cardinality200_response import Cardinality200Response as Cardinality200Response +from regexsolver._generated.models.cardinality_big_integer import CardinalityBigInteger as CardinalityBigInteger +from regexsolver._generated.models.cardinality_infinite import CardinalityInfinite as CardinalityInfinite +from regexsolver._generated.models.cardinality_integer import CardinalityInteger as CardinalityInteger +from regexsolver._generated.models.concat200_response import Concat200Response as Concat200Response +from regexsolver._generated.models.dot200_response import Dot200Response as Dot200Response +from regexsolver._generated.models.empty200_response import Empty200Response as Empty200Response +from regexsolver._generated.models.error_response import ErrorResponse as ErrorResponse +from regexsolver._generated.models.error_response400 import ErrorResponse400 as ErrorResponse400 +from regexsolver._generated.models.error_response401 import ErrorResponse401 as ErrorResponse401 +from regexsolver._generated.models.error_response403 import ErrorResponse403 as ErrorResponse403 +from regexsolver._generated.models.execution_options import ExecutionOptions as ExecutionOptions +from regexsolver._generated.models.fair_response_options import FairResponseOptions as FairResponseOptions +from regexsolver._generated.models.generate_strings_character_order import GenerateStringsCharacterOrder as GenerateStringsCharacterOrder +from regexsolver._generated.models.generate_strings_path_order import GenerateStringsPathOrder as GenerateStringsPathOrder +from regexsolver._generated.models.generate_strings_request import GenerateStringsRequest as GenerateStringsRequest +from regexsolver._generated.models.generate_strings_response import GenerateStringsResponse as GenerateStringsResponse +from regexsolver._generated.models.length import Length as Length +from regexsolver._generated.models.length200_response import Length200Response as Length200Response +from regexsolver._generated.models.limits200_response import Limits200Response as Limits200Response +from regexsolver._generated.models.multi_terms_request import MultiTermsRequest as MultiTermsRequest +from regexsolver._generated.models.repeat_request import RepeatRequest as RepeatRequest +from regexsolver._generated.models.request_options import RequestOptions as RequestOptions +from regexsolver._generated.models.response_options import ResponseOptions as ResponseOptions +from regexsolver._generated.models.string import String as String +from regexsolver._generated.models.strings import Strings as Strings +from regexsolver._generated.models.strings200_response import Strings200Response as Strings200Response +from regexsolver._generated.models.term import Term as Term +from regexsolver._generated.models.term_fair import TermFair as TermFair +from regexsolver._generated.models.term_fair_metadata import TermFairMetadata as TermFairMetadata +from regexsolver._generated.models.term_regex import TermRegex as TermRegex +from regexsolver._generated.models.term_request import TermRequest as TermRequest +from regexsolver._generated.models.two_terms_request import TwoTermsRequest as TwoTermsRequest + diff --git a/regexsolver/_generated/api/__init__.py b/regexsolver/_generated/api/__init__.py new file mode 100644 index 0000000..32a174c --- /dev/null +++ b/regexsolver/_generated/api/__init__.py @@ -0,0 +1,8 @@ +# flake8: noqa + +# import apis into api package +from regexsolver._generated.api.account_api import AccountApi +from regexsolver._generated.api.analyze_api import AnalyzeApi +from regexsolver._generated.api.compute_api import ComputeApi +from regexsolver._generated.api.generate_api import GenerateApi + diff --git a/regexsolver/_generated/api/account_api.py b/regexsolver/_generated/api/account_api.py new file mode 100644 index 0000000..dd716d6 --- /dev/null +++ b/regexsolver/_generated/api/account_api.py @@ -0,0 +1,293 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import warnings +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Any, Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated + +from regexsolver._generated.models.limits200_response import Limits200Response + +from regexsolver._generated.api_client import ApiClient, RequestSerialized +from regexsolver._generated.api_response import ApiResponse +from regexsolver._generated.rest import RESTResponseType + + +class AccountApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + async def limits( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Limits200Response: + """Limits + + Return the plan limits applying to the account. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._limits_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Limits200Response", + '401': "ErrorResponse401", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def limits_with_http_info( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Limits200Response]: + """Limits + + Return the plan limits applying to the account. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._limits_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Limits200Response", + '401': "ErrorResponse401", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def limits_without_preload_content( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Limits + + Return the plan limits applying to the account. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._limits_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Limits200Response", + '401': "ErrorResponse401", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _limits_serialize( + self, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/account/limits', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/regexsolver/_generated/api/analyze_api.py b/regexsolver/_generated/api/analyze_api.py new file mode 100644 index 0000000..ab308d5 --- /dev/null +++ b/regexsolver/_generated/api/analyze_api.py @@ -0,0 +1,2960 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import warnings +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Any, Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated + +from regexsolver._generated.models.cardinality200_response import Cardinality200Response +from regexsolver._generated.models.dot200_response import Dot200Response +from regexsolver._generated.models.empty200_response import Empty200Response +from regexsolver._generated.models.length200_response import Length200Response +from regexsolver._generated.models.term_request import TermRequest +from regexsolver._generated.models.two_terms_request import TwoTermsRequest + +from regexsolver._generated.api_client import ApiClient, RequestSerialized +from regexsolver._generated.api_response import ApiResponse +from regexsolver._generated.rest import RESTResponseType + + +class AnalyzeApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + async def cardinality( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Cardinality200Response: + """Cardinality + + Compute how many strings the term matches. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._cardinality_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Cardinality200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def cardinality_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Cardinality200Response]: + """Cardinality + + Compute how many strings the term matches. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._cardinality_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Cardinality200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def cardinality_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Cardinality + + Compute how many strings the term matches. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._cardinality_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Cardinality200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _cardinality_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/cardinality', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def deterministic( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Empty200Response: + """Deterministic + + Check if the term's automaton is deterministic. Only a deterministic FAIR guarantees consistent string ordering across paginated /generate/strings requests; call /compute/determinize first if this is false. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._deterministic_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def deterministic_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Empty200Response]: + """Deterministic + + Check if the term's automaton is deterministic. Only a deterministic FAIR guarantees consistent string ordering across paginated /generate/strings requests; call /compute/determinize first if this is false. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._deterministic_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def deterministic_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Deterministic + + Check if the term's automaton is deterministic. Only a deterministic FAIR guarantees consistent string ordering across paginated /generate/strings requests; call /compute/determinize first if this is false. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._deterministic_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _deterministic_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/deterministic', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def dot( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Dot200Response: + """Graphviz DOT + + Build a Graphviz DOT representation of the term's automaton. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._dot_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Dot200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def dot_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Dot200Response]: + """Graphviz DOT + + Build a Graphviz DOT representation of the term's automaton. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._dot_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Dot200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def dot_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Graphviz DOT + + Build a Graphviz DOT representation of the term's automaton. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._dot_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Dot200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _dot_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/dot', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def empty( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Empty200Response: + """Empty + + Check if the term matches no strings. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._empty_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def empty_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Empty200Response]: + """Empty + + Check if the term matches no strings. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._empty_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def empty_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Empty + + Check if the term matches no strings. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._empty_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _empty_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/empty', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def empty_string( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Empty200Response: + """Empty String Only + + Check if the term matches only the empty string. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._empty_string_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def empty_string_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Empty200Response]: + """Empty String Only + + Check if the term matches only the empty string. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._empty_string_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def empty_string_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Empty String Only + + Check if the term matches only the empty string. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._empty_string_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _empty_string_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/empty_string', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def equivalent( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Empty200Response: + """Equivalent + + Check if the two terms accept exactly the same language. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._equivalent_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def equivalent_with_http_info( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Empty200Response]: + """Equivalent + + Check if the two terms accept exactly the same language. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._equivalent_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def equivalent_without_preload_content( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Equivalent + + Check if the two terms accept exactly the same language. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._equivalent_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _equivalent_serialize( + self, + two_terms_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if two_terms_request is not None: + _body_params = two_terms_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/equivalent', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def length( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Length200Response: + """Length + + Compute the minimum and maximum length of strings matched by the term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._length_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Length200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def length_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Length200Response]: + """Length + + Compute the minimum and maximum length of strings matched by the term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._length_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Length200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def length_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Length + + Compute the minimum and maximum length of strings matched by the term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._length_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Length200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _length_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/length', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def pattern( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Dot200Response: + """Pattern + + Return a regular expression pattern that represents the term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._pattern_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Dot200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def pattern_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Dot200Response]: + """Pattern + + Return a regular expression pattern that represents the term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._pattern_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Dot200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def pattern_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Pattern + + Return a regular expression pattern that represents the term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._pattern_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Dot200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _pattern_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/pattern', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def subset( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Empty200Response: + """Subset + + Check if the first term's language is a subset of the second term's language. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._subset_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def subset_with_http_info( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Empty200Response]: + """Subset + + Check if the first term's language is a subset of the second term's language. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._subset_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def subset_without_preload_content( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Subset + + Check if the first term's language is a subset of the second term's language. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._subset_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _subset_serialize( + self, + two_terms_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if two_terms_request is not None: + _body_params = two_terms_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/subset', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def total( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Empty200Response: + """Totality + + Check if the term matches all the possible strings. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._total_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def total_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Empty200Response]: + """Totality + + Check if the term matches all the possible strings. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._total_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def total_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Totality + + Check if the term matches all the possible strings. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._total_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Empty200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _total_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/analyze/total', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/regexsolver/_generated/api/compute_api.py b/regexsolver/_generated/api/compute_api.py new file mode 100644 index 0000000..569b479 --- /dev/null +++ b/regexsolver/_generated/api/compute_api.py @@ -0,0 +1,2083 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import warnings +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Any, Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated + +from regexsolver._generated.models.concat200_response import Concat200Response +from regexsolver._generated.models.multi_terms_request import MultiTermsRequest +from regexsolver._generated.models.repeat_request import RepeatRequest +from regexsolver._generated.models.term_request import TermRequest +from regexsolver._generated.models.two_terms_request import TwoTermsRequest + +from regexsolver._generated.api_client import ApiClient, RequestSerialized +from regexsolver._generated.api_response import ApiResponse +from regexsolver._generated.rest import RESTResponseType + + +class ComputeApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + async def complement( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Complement + + Compute the complement of the given term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._complement_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def complement_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Complement + + Compute the complement of the given term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._complement_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def complement_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Complement + + Compute the complement of the given term. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._complement_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _complement_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/complement', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def concat( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Concatenation + + Concatenate the given terms in order. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._concat_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def concat_with_http_info( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Concatenation + + Concatenate the given terms in order. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._concat_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def concat_without_preload_content( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Concatenation + + Concatenate the given terms in order. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._concat_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _concat_serialize( + self, + multi_terms_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if multi_terms_request is not None: + _body_params = multi_terms_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/concat', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def determinize( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Determinize + + Compute a deterministic FAIR. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._determinize_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def determinize_with_http_info( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Determinize + + Compute a deterministic FAIR. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._determinize_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def determinize_without_preload_content( + self, + term_request: TermRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Determinize + + Compute a deterministic FAIR. + + :param term_request: (required) + :type term_request: TermRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._determinize_serialize( + term_request=term_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _determinize_serialize( + self, + term_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if term_request is not None: + _body_params = term_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/determinize', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def difference( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Difference + + Compute the difference between the two given terms. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._difference_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def difference_with_http_info( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Difference + + Compute the difference between the two given terms. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._difference_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def difference_without_preload_content( + self, + two_terms_request: TwoTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Difference + + Compute the difference between the two given terms. + + :param two_terms_request: (required) + :type two_terms_request: TwoTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._difference_serialize( + two_terms_request=two_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _difference_serialize( + self, + two_terms_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if two_terms_request is not None: + _body_params = two_terms_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/difference', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def intersection( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Intersection + + Compute the intersection of the given terms. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._intersection_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def intersection_with_http_info( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Intersection + + Compute the intersection of the given terms. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._intersection_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def intersection_without_preload_content( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Intersection + + Compute the intersection of the given terms. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._intersection_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _intersection_serialize( + self, + multi_terms_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if multi_terms_request is not None: + _body_params = multi_terms_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/intersection', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def repeat( + self, + repeat_request: RepeatRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Repeat + + Repeat a term between `min` and `max` times. + + :param repeat_request: (required) + :type repeat_request: RepeatRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._repeat_serialize( + repeat_request=repeat_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def repeat_with_http_info( + self, + repeat_request: RepeatRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Repeat + + Repeat a term between `min` and `max` times. + + :param repeat_request: (required) + :type repeat_request: RepeatRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._repeat_serialize( + repeat_request=repeat_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def repeat_without_preload_content( + self, + repeat_request: RepeatRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Repeat + + Repeat a term between `min` and `max` times. + + :param repeat_request: (required) + :type repeat_request: RepeatRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._repeat_serialize( + repeat_request=repeat_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _repeat_serialize( + self, + repeat_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if repeat_request is not None: + _body_params = repeat_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/repeat', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + + + + @validate_call + async def union( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Concat200Response: + """Union + + Compute the union of the given terms. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._union_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def union_with_http_info( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Concat200Response]: + """Union + + Compute the union of the given terms. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._union_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def union_without_preload_content( + self, + multi_terms_request: MultiTermsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Union + + Compute the union of the given terms. + + :param multi_terms_request: (required) + :type multi_terms_request: MultiTermsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._union_serialize( + multi_terms_request=multi_terms_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Concat200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _union_serialize( + self, + multi_terms_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if multi_terms_request is not None: + _body_params = multi_terms_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/compute/union', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/regexsolver/_generated/api/generate_api.py b/regexsolver/_generated/api/generate_api.py new file mode 100644 index 0000000..364d5b7 --- /dev/null +++ b/regexsolver/_generated/api/generate_api.py @@ -0,0 +1,328 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import warnings +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Any, Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated + +from regexsolver._generated.models.generate_strings_request import GenerateStringsRequest +from regexsolver._generated.models.strings200_response import Strings200Response + +from regexsolver._generated.api_client import ApiClient, RequestSerialized +from regexsolver._generated.api_response import ApiResponse +from regexsolver._generated.rest import RESTResponseType + + +class GenerateApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + async def strings( + self, + generate_strings_request: GenerateStringsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> Strings200Response: + """Strings + + Generate up to `limit` distinct strings matched by `term`, skipping the first `offset` strings, scheduling the paths of the language in `pathOrder`, producing the strings within each path in `characterOrder`, confined to lengths between `minLength` and `maxLength` and to the characters of `charset`. Strings are only guaranteed to be distinct within a single call; pagination across calls is only consistent (no repeats or gaps) if `term` is deterministic. Call `/analyze/deterministic` to check, and `/compute/determinize` first if needed. + + :param generate_strings_request: (required) + :type generate_strings_request: GenerateStringsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._strings_serialize( + generate_strings_request=generate_strings_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Strings200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + async def strings_with_http_info( + self, + generate_strings_request: GenerateStringsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[Strings200Response]: + """Strings + + Generate up to `limit` distinct strings matched by `term`, skipping the first `offset` strings, scheduling the paths of the language in `pathOrder`, producing the strings within each path in `characterOrder`, confined to lengths between `minLength` and `maxLength` and to the characters of `charset`. Strings are only guaranteed to be distinct within a single call; pagination across calls is only consistent (no repeats or gaps) if `term` is deterministic. Call `/analyze/deterministic` to check, and `/compute/determinize` first if needed. + + :param generate_strings_request: (required) + :type generate_strings_request: GenerateStringsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._strings_serialize( + generate_strings_request=generate_strings_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Strings200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + await response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + async def strings_without_preload_content( + self, + generate_strings_request: GenerateStringsRequest, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """Strings + + Generate up to `limit` distinct strings matched by `term`, skipping the first `offset` strings, scheduling the paths of the language in `pathOrder`, producing the strings within each path in `characterOrder`, confined to lengths between `minLength` and `maxLength` and to the characters of `charset`. Strings are only guaranteed to be distinct within a single call; pagination across calls is only consistent (no repeats or gaps) if `term` is deterministic. Call `/analyze/deterministic` to check, and `/compute/determinize` first if needed. + + :param generate_strings_request: (required) + :type generate_strings_request: GenerateStringsRequest + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._strings_serialize( + generate_strings_request=generate_strings_request, + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "Strings200Response", + '400': "ErrorResponse400", + '401': "ErrorResponse401", + '403': "ErrorResponse403", + '404': "ErrorResponse", + '429': "ErrorResponse", + '500': "ErrorResponse", + } + response_data = await self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _strings_serialize( + self, + generate_strings_request, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + if generate_strings_request is not None: + _body_params = generate_strings_request + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + # set the HTTP header `Content-Type` + if _content_type: + _header_params['Content-Type'] = _content_type + else: + _default_content_type = ( + self.api_client.select_header_content_type( + [ + 'application/json' + ] + ) + ) + if _default_content_type is not None: + _header_params['Content-Type'] = _default_content_type + + # authentication setting + _auth_settings: List[str] = [ + 'BearerAuth' + ] + + return self.api_client.param_serialize( + method='POST', + resource_path='/generate/strings', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/regexsolver/_generated/api_client.py b/regexsolver/_generated/api_client.py new file mode 100644 index 0000000..8c8549a --- /dev/null +++ b/regexsolver/_generated/api_client.py @@ -0,0 +1,811 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + + +import datetime +from dateutil.parser import parse +from enum import Enum +import decimal +import json +import mimetypes +import os +import re +import tempfile +import uuid + +from urllib.parse import quote +from typing import Tuple, Optional, List, Dict, Union +from pydantic import SecretStr + +from regexsolver._generated.configuration import Configuration +from regexsolver._generated.api_response import ApiResponse, T as ApiResponseT +import regexsolver._generated.models +from regexsolver._generated import rest +from regexsolver._generated.exceptions import ( + ApiValueError, + ApiException, + BadRequestException, + UnauthorizedException, + ForbiddenException, + NotFoundException, + ServiceException +) + +RequestSerialized = Tuple[str, str, Dict[str, str], Optional[str], List[str]] + +class ApiClient: + """Generic API client for OpenAPI client library builds. + + OpenAPI generic API client. This client handles the client- + server communication, and is invariant across implementations. Specifics of + the methods and models for each application are generated from the OpenAPI + templates. + + :param configuration: .Configuration object for this client + :param header_name: a header to pass when making calls to the API. + :param header_value: a header value to pass when making calls to + the API. + :param cookie: a cookie to include in the header when making calls + to the API + """ + + PRIMITIVE_TYPES = (float, bool, bytes, str, int) + NATIVE_TYPES_MAPPING = { + 'int': int, + 'long': int, # TODO remove as only py3 is supported? + 'float': float, + 'str': str, + 'bool': bool, + 'date': datetime.date, + 'datetime': datetime.datetime, + 'decimal': decimal.Decimal, + 'UUID': uuid.UUID, + 'object': object, + } + _pool = None + + def __init__( + self, + configuration=None, + header_name=None, + header_value=None, + cookie=None + ) -> None: + # use default configuration if none is provided + if configuration is None: + configuration = Configuration.get_default() + self.configuration = configuration + + self.rest_client = rest.RESTClientObject(configuration) + self.default_headers = {} + if header_name is not None: + self.default_headers[header_name] = header_value + self.cookie = cookie + # Set default User-Agent. + self.user_agent = 'OpenAPI-Generator/1.0.0/python' + self.client_side_validation = configuration.client_side_validation + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_value, traceback): + await self.close() + + async def close(self): + await self.rest_client.close() + + @property + def user_agent(self): + """User agent for this API client""" + return self.default_headers['User-Agent'] + + @user_agent.setter + def user_agent(self, value): + self.default_headers['User-Agent'] = value + + def set_default_header(self, header_name, header_value): + self.default_headers[header_name] = header_value + + + _default = None + + @classmethod + def get_default(cls): + """Return new instance of ApiClient. + + This method returns newly created, based on default constructor, + object of ApiClient class or returns a copy of default + ApiClient. + + :return: The ApiClient object. + """ + if cls._default is None: + cls._default = ApiClient() + return cls._default + + @classmethod + def set_default(cls, default): + """Set default instance of ApiClient. + + It stores default ApiClient. + + :param default: object of ApiClient. + """ + cls._default = default + + def param_serialize( + self, + method, + resource_path, + path_params=None, + query_params=None, + header_params=None, + body=None, + post_params=None, + files=None, auth_settings=None, + collection_formats=None, + _host=None, + _request_auth=None + ) -> RequestSerialized: + + """Builds the HTTP request params needed by the request. + :param method: Method to call. + :param resource_path: Path to method endpoint. + :param path_params: Path parameters in the url. + :param query_params: Query parameters in the url. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param auth_settings list: Auth Settings names for the request. + :param files dict: key -> filename, value -> filepath, + for `multipart/form-data`. + :param collection_formats: dict of collection formats for path, query, + header, and post parameters. + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the authentication + in the spec for a single request. + :return: tuple of form (path, http_method, query_params, header_params, + body, post_params, files) + """ + + config = self.configuration + + # header parameters + header_params = header_params or {} + header_params.update(self.default_headers) + if self.cookie: + header_params['Cookie'] = self.cookie + if header_params: + header_params = self.sanitize_for_serialization(header_params) + header_params = dict( + self.parameters_to_tuples(header_params,collection_formats) + ) + + # path parameters + if path_params: + path_params = self.sanitize_for_serialization(path_params) + path_params = self.parameters_to_tuples( + path_params, + collection_formats + ) + for k, v in path_params: + # specified safe chars, encode everything + resource_path = resource_path.replace( + '{%s}' % k, + quote(str(v), safe=config.safe_chars_for_path_param) + ) + + # post parameters + if post_params or files: + post_params = post_params if post_params else [] + post_params = self.sanitize_for_serialization(post_params) + post_params = self.parameters_to_tuples( + post_params, + collection_formats + ) + if files: + post_params.extend(self.files_parameters(files)) + + # auth setting + self.update_params_for_auth( + header_params, + query_params, + auth_settings, + resource_path, + method, + body, + request_auth=_request_auth + ) + + # body + if body: + body = self.sanitize_for_serialization(body) + + # request url + if _host is None or self.configuration.ignore_operation_servers: + url = self.configuration.host + resource_path + else: + # use server/host defined in path or operation instead + url = _host + resource_path + + # query parameters + if query_params: + query_params = self.sanitize_for_serialization(query_params) + url_query = self.parameters_to_url_query( + query_params, + collection_formats + ) + url += "?" + url_query + + return method, url, header_params, body, post_params + + + async def call_api( + self, + method, + url, + header_params=None, + body=None, + post_params=None, + _request_timeout=None + ) -> rest.RESTResponse: + """Makes the HTTP request (synchronous) + :param method: Method to call. + :param url: Path to method endpoint. + :param header_params: Header parameters to be + placed in the request header. + :param body: Request body. + :param post_params dict: Request post form parameters, + for `application/x-www-form-urlencoded`, `multipart/form-data`. + :param _request_timeout: timeout setting for this request. + :return: RESTResponse + """ + + try: + # perform request and return response + response_data = await self.rest_client.request( + method, url, + headers=header_params, + body=body, post_params=post_params, + _request_timeout=_request_timeout + ) + + except ApiException as e: + raise e + + return response_data + + def response_deserialize( + self, + response_data: rest.RESTResponse, + response_types_map: Optional[Dict[str, ApiResponseT]]=None + ) -> ApiResponse[ApiResponseT]: + """Deserializes response into an object. + :param response_data: RESTResponse object to be deserialized. + :param response_types_map: dict of response types. + :return: ApiResponse + """ + + msg = "RESTResponse.read() must be called before passing it to response_deserialize()" + assert response_data.data is not None, msg + + response_type = response_types_map.get(str(response_data.status), None) + if not response_type and isinstance(response_data.status, int) and 100 <= response_data.status <= 599: + # if not found, look for '1XX', '2XX', etc. + response_type = response_types_map.get(str(response_data.status)[0] + "XX", None) + + # deserialize response data + response_text = None + return_data = None + try: + if response_type in ("bytearray", "bytes"): + return_data = response_data.data + elif response_type == "file": + return_data = self.__deserialize_file(response_data) + elif response_type is not None: + match = None + content_type = response_data.headers.get('content-type') + if content_type is not None: + match = re.search(r"charset=([a-zA-Z\-\d]+)[\s;]?", content_type) + encoding = match.group(1) if match else "utf-8" + response_text = response_data.data.decode(encoding) + return_data = self.deserialize(response_text, response_type, content_type) + finally: + if not 200 <= response_data.status <= 299: + raise ApiException.from_response( + http_resp=response_data, + body=response_text, + data=return_data, + ) + + return ApiResponse( + status_code = response_data.status, + data = return_data, + headers = response_data.headers, + raw_data = response_data.data + ) + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is SecretStr, return obj.get_secret_value() + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date + convert to string in iso8601 format. + If obj is decimal.Decimal return string representation. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, Enum): + return obj.value + elif isinstance(obj, SecretStr): + return obj.get_secret_value() + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, uuid.UUID): + return str(obj) + elif isinstance(obj, list): + return [ + self.sanitize_for_serialization(sub_obj) for sub_obj in obj + ] + elif isinstance(obj, tuple): + return tuple( + self.sanitize_for_serialization(sub_obj) for sub_obj in obj + ) + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + elif isinstance(obj, decimal.Decimal): + return str(obj) + + elif isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `openapi_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')): + obj_dict = obj.to_dict() + else: + obj_dict = obj.__dict__ + + if isinstance(obj_dict, list): + # here we handle instances that can either be a list or something else, and only became a real list by calling to_dict() + return self.sanitize_for_serialization(obj_dict) + + return { + key: self.sanitize_for_serialization(val) + for key, val in obj_dict.items() + } + + def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + :param content_type: content type of response. + + :return: deserialized object. + """ + + # fetch data from response object + if content_type is None: + try: + data = json.loads(response_text) + except ValueError: + data = response_text + elif re.match(r'^application/(json|[\w!#$&.+\-^_]+\+json)\s*(;|$)', content_type, re.IGNORECASE): + if response_text == "": + data = "" + else: + data = json.loads(response_text) + elif re.match(r'^text\/[a-z.+-]+\s*(;|$)', content_type, re.IGNORECASE): + data = response_text + else: + raise ApiException( + status=0, + reason="Unsupported content type: {0}".format(content_type) + ) + + return self.__deserialize(data, response_type) + + def __deserialize(self, data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if isinstance(klass, str): + if klass.startswith('List['): + m = re.match(r'List\[(.*)]', klass) + assert m is not None, "Malformed List type definition" + sub_kls = m.group(1) + return [self.__deserialize(sub_data, sub_kls) + for sub_data in data] + + if klass.startswith('Dict['): + m = re.match(r'Dict\[([^,]*), (.*)]', klass) + assert m is not None, "Malformed Dict type definition" + sub_kls = m.group(2) + return {k: self.__deserialize(v, sub_kls) + for k, v in data.items()} + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr(regexsolver._generated.models, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass is object: + return self.__deserialize_object(data) + elif klass is datetime.date: + return self.__deserialize_date(data) + elif klass is datetime.datetime: + return self.__deserialize_datetime(data) + elif klass is decimal.Decimal: + return decimal.Decimal(data) + elif klass is uuid.UUID: + return uuid.UUID(data) + elif issubclass(klass, Enum): + return self.__deserialize_enum(data, klass) + else: + return self.__deserialize_model(data, klass) + + def parameters_to_tuples(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: Parameters as list of tuples, collections formatted + """ + new_params: List[Tuple[str, str]] = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, value) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(str(value) for value in v))) + else: + new_params.append((k, v)) + return new_params + + def parameters_to_url_query(self, params, collection_formats): + """Get parameters as list of tuples, formatting collections. + + :param params: Parameters as dict or list of two-tuples + :param dict collection_formats: Parameter collection formats + :return: URL query string (e.g. a=Hello%20World&b=123) + """ + new_params: List[Tuple[str, str]] = [] + if collection_formats is None: + collection_formats = {} + for k, v in params.items() if isinstance(params, dict) else params: + if isinstance(v, bool): + v = str(v).lower() + if isinstance(v, (int, float)): + v = str(v) + if isinstance(v, dict): + v = json.dumps(v) + + if k in collection_formats: + collection_format = collection_formats[k] + if collection_format == 'multi': + new_params.extend((k, quote(str(value))) for value in v) + else: + if collection_format == 'ssv': + delimiter = ' ' + elif collection_format == 'tsv': + delimiter = '\t' + elif collection_format == 'pipes': + delimiter = '|' + else: # csv is the default + delimiter = ',' + new_params.append( + (k, delimiter.join(quote(str(value)) for value in v)) + ) + else: + new_params.append((k, quote(str(v)))) + + return "&".join(["=".join(map(str, item)) for item in new_params]) + + def files_parameters( + self, + files: Dict[str, Union[str, bytes, List[str], List[bytes], Tuple[str, bytes]]], + ): + """Builds form parameters. + + :param files: File parameters. + :return: Form parameters with files. + """ + params = [] + for k, v in files.items(): + if isinstance(v, str): + with open(v, 'rb') as f: + filename = os.path.basename(f.name) + filedata = f.read() + elif isinstance(v, bytes): + filename = k + filedata = v + elif isinstance(v, tuple): + filename, filedata = v + elif isinstance(v, list): + for file_param in v: + params.extend(self.files_parameters({k: file_param})) + continue + else: + raise ValueError("Unsupported file value") + mimetype = ( + mimetypes.guess_type(filename)[0] + or 'application/octet-stream' + ) + params.append( + tuple([k, tuple([filename, filedata, mimetype])]) + ) + return params + + def select_header_accept(self, accepts: List[str]) -> Optional[str]: + """Returns `Accept` based on an array of accepts provided. + + :param accepts: List of headers. + :return: Accept (e.g. application/json). + """ + if not accepts: + return None + + for accept in accepts: + if re.search('json', accept, re.IGNORECASE): + return accept + + return accepts[0] + + def select_header_content_type(self, content_types): + """Returns `Content-Type` based on an array of content_types provided. + + :param content_types: List of content-types. + :return: Content-Type (e.g. application/json). + """ + if not content_types: + return None + + for content_type in content_types: + if re.search('json', content_type, re.IGNORECASE): + return content_type + + return content_types[0] + + def update_params_for_auth( + self, + headers, + queries, + auth_settings, + resource_path, + method, + body, + request_auth=None + ) -> None: + """Updates header and query params based on authentication setting. + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :param auth_settings: Authentication setting identifiers list. + :resource_path: A string representation of the HTTP request resource path. + :method: A string representation of the HTTP request method. + :body: A object representing the body of the HTTP request. + The object type is the return value of sanitize_for_serialization(). + :param request_auth: if set, the provided settings will + override the token in the configuration. + """ + if not auth_settings: + return + + if request_auth: + self._apply_auth_params( + headers, + queries, + resource_path, + method, + body, + request_auth + ) + else: + for auth in auth_settings: + auth_setting = self.configuration.auth_settings().get(auth) + if auth_setting: + self._apply_auth_params( + headers, + queries, + resource_path, + method, + body, + auth_setting + ) + + def _apply_auth_params( + self, + headers, + queries, + resource_path, + method, + body, + auth_setting + ) -> None: + """Updates the request parameters based on a single auth_setting + + :param headers: Header parameters dict to be updated. + :param queries: Query parameters tuple list to be updated. + :resource_path: A string representation of the HTTP request resource path. + :method: A string representation of the HTTP request method. + :body: A object representing the body of the HTTP request. + The object type is the return value of sanitize_for_serialization(). + :param auth_setting: auth settings for the endpoint + """ + if auth_setting['in'] == 'cookie': + headers['Cookie'] = auth_setting['value'] + elif auth_setting['in'] == 'header': + if auth_setting['type'] != 'http-signature': + headers[auth_setting['key']] = auth_setting['value'] + elif auth_setting['in'] == 'query': + queries.append((auth_setting['key'], auth_setting['value'])) + else: + raise ApiValueError( + 'Authentication token must be in `query` or `header`' + ) + + def __deserialize_file(self, response): + """Deserializes body to file + + Saves response body into a file in a temporary folder, + using the filename from the `Content-Disposition` header if provided. + + handle file downloading + save response body into a tmp file and return the instance + + :param response: RESTResponse. + :return: file path. + """ + fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path) + os.close(fd) + os.remove(path) + + content_disposition = response.headers.get("Content-Disposition") + if content_disposition: + m = re.search( + r'filename=[\'"]?([^\'"\s]+)[\'"]?', + content_disposition + ) + assert m is not None, "Unexpected 'content-disposition' header value" + filename = os.path.basename(m.group(1)) # Strip any directory traversal + if filename in ("", ".", ".."): # fall back to tmp filename + filename = os.path.basename(path) + path = os.path.join(os.path.dirname(path), filename) + + with open(path, "wb") as f: + f.write(response.data) + + return path + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return str(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return an original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + return parse(string).date() + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason="Failed to parse `{0}` as date object".format(string) + ) + + def __deserialize_datetime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + return parse(string) + except ImportError: + return string + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as datetime object" + .format(string) + ) + ) + + def __deserialize_enum(self, data, klass): + """Deserializes primitive type to enum. + + :param data: primitive type. + :param klass: class literal. + :return: enum value. + """ + try: + return klass(data) + except ValueError: + raise rest.ApiException( + status=0, + reason=( + "Failed to parse `{0}` as `{1}`" + .format(data, klass) + ) + ) + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + + return klass.from_dict(data) diff --git a/regexsolver/_generated/api_response.py b/regexsolver/_generated/api_response.py new file mode 100644 index 0000000..9bc7c11 --- /dev/null +++ b/regexsolver/_generated/api_response.py @@ -0,0 +1,21 @@ +"""API response object.""" + +from __future__ import annotations +from typing import Optional, Generic, Mapping, TypeVar +from pydantic import Field, StrictInt, StrictBytes, BaseModel + +T = TypeVar("T") + +class ApiResponse(BaseModel, Generic[T]): + """ + API response object + """ + + status_code: StrictInt = Field(description="HTTP status code") + headers: Optional[Mapping[str, str]] = Field(None, description="HTTP headers") + data: T = Field(description="Deserialized data given the data type") + raw_data: StrictBytes = Field(description="Raw data (HTTP response body)") + + model_config = { + "arbitrary_types_allowed": True + } diff --git a/regexsolver/_generated/configuration.py b/regexsolver/_generated/configuration.py new file mode 100644 index 0000000..a2d0239 --- /dev/null +++ b/regexsolver/_generated/configuration.py @@ -0,0 +1,604 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import base64 +import copy +import http.client as httplib +import logging +from logging import FileHandler +import sys +from typing import Any, ClassVar, Dict, List, Literal, Optional, TypedDict, Union +from typing_extensions import NotRequired, Self + + + +JSON_SCHEMA_VALIDATION_KEYWORDS = { + 'multipleOf', 'maximum', 'exclusiveMaximum', + 'minimum', 'exclusiveMinimum', 'maxLength', + 'minLength', 'pattern', 'maxItems', 'minItems' +} + +ServerVariablesT = Dict[str, str] + +GenericAuthSetting = TypedDict( + "GenericAuthSetting", + { + "type": str, + "in": str, + "key": str, + "value": str, + }, +) + + +OAuth2AuthSetting = TypedDict( + "OAuth2AuthSetting", + { + "type": Literal["oauth2"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": str, + }, +) + + +APIKeyAuthSetting = TypedDict( + "APIKeyAuthSetting", + { + "type": Literal["api_key"], + "in": str, + "key": str, + "value": Optional[str], + }, +) + + +BasicAuthSetting = TypedDict( + "BasicAuthSetting", + { + "type": Literal["basic"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": Optional[str], + }, +) + + +BearerFormatAuthSetting = TypedDict( + "BearerFormatAuthSetting", + { + "type": Literal["bearer"], + "in": Literal["header"], + "format": Literal["JWT"], + "key": Literal["Authorization"], + "value": str, + }, +) + + +BearerAuthSetting = TypedDict( + "BearerAuthSetting", + { + "type": Literal["bearer"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": str, + }, +) + + +HTTPSignatureAuthSetting = TypedDict( + "HTTPSignatureAuthSetting", + { + "type": Literal["http-signature"], + "in": Literal["header"], + "key": Literal["Authorization"], + "value": None, + }, +) + + +AuthSettings = TypedDict( + "AuthSettings", + { + "BearerAuth": BearerFormatAuthSetting, + }, + total=False, +) + + +class HostSettingVariable(TypedDict): + description: str + default_value: str + enum_values: List[str] + + +class HostSetting(TypedDict): + url: str + description: str + variables: NotRequired[Dict[str, HostSettingVariable]] + + +class Configuration: + """This class contains various settings of the API client. + + :param host: Base url. + :param ignore_operation_servers + Boolean to ignore operation servers for the API client. + Config will use `host` as the base url regardless of the operation servers. + :param api_key: Dict to store API key(s). + Each entry in the dict specifies an API key. + The dict key is the name of the security scheme in the OAS specification. + The dict value is the API key secret. + :param api_key_prefix: Dict to store API prefix (e.g. Bearer). + The dict key is the name of the security scheme in the OAS specification. + The dict value is an API key prefix when generating the auth data. + :param username: Username for HTTP basic authentication. + :param password: Password for HTTP basic authentication. + :param access_token: Access token. + :param server_index: Index to servers configuration. + :param server_variables: Mapping with string values to replace variables in + templated server configuration. The validation of enums is performed for + variables with defined enum values before. + :param server_operation_index: Mapping from operation ID to an index to server + configuration. + :param server_operation_variables: Mapping from operation ID to a mapping with + string values to replace variables in templated server configuration. + The validation of enums is performed for variables with defined enum + values before. + :param verify_ssl: bool - Set this to false to skip verifying SSL certificate + when calling API from https server. + :param ssl_ca_cert: str - the path to a file of concatenated CA certificates + in PEM format. + :param retries: int | aiohttp_retry.RetryOptionsBase - Retry configuration. + :param ca_cert_data: verify the peer using concatenated CA certificate data + in PEM (str) or DER (bytes) format. + :param cert_file: the path to a client certificate file, for mTLS. + :param key_file: the path to a client key file, for mTLS. + :param assert_hostname: Set this to True/False to enable/disable SSL hostname verification. + :param tls_server_name: SSL/TLS Server Name Indication (SNI). Set this to the SNI value expected by the server. + :param connection_pool_maxsize: Connection pool max size. None in the constructor is coerced to 100 for async and cpu_count * 5 for sync. + :param proxy: Proxy URL. + :param proxy_headers: Proxy headers. + :param safe_chars_for_path_param: Safe characters for path parameter encoding. + :param client_side_validation: Enable client-side validation. Default True. + :param socket_options: Options to pass down to the underlying urllib3 socket. + :param datetime_format: Datetime format string for serialization. + :param date_format: Date format string for serialization. + + :Example: + """ + + _default: ClassVar[Optional[Self]] = None + + def __init__( + self, + host: Optional[str]=None, + api_key: Optional[Dict[str, str]]=None, + api_key_prefix: Optional[Dict[str, str]]=None, + username: Optional[str]=None, + password: Optional[str]=None, + access_token: Optional[str]=None, + server_index: Optional[int]=None, + server_variables: Optional[ServerVariablesT]=None, + server_operation_index: Optional[Dict[int, int]]=None, + server_operation_variables: Optional[Dict[int, ServerVariablesT]]=None, + ignore_operation_servers: bool=False, + ssl_ca_cert: Optional[str]=None, + retries: Optional[Union[int, Any]] = None, + ca_cert_data: Optional[Union[str, bytes]] = None, + cert_file: Optional[str]=None, + key_file: Optional[str]=None, + verify_ssl: bool=True, + assert_hostname: Optional[bool]=None, + tls_server_name: Optional[str]=None, + connection_pool_maxsize: Optional[int]=None, + proxy: Optional[str]=None, + proxy_headers: Optional[Any]=None, + safe_chars_for_path_param: str='', + client_side_validation: bool=True, + socket_options: Optional[Any]=None, + datetime_format: str="%Y-%m-%dT%H:%M:%S.%f%z", + date_format: str="%Y-%m-%d", + *, + debug: Optional[bool] = None, + ) -> None: + """Constructor + """ + self._base_path = "https://api.regexsolver.com/v1" if host is None else host + """Default Base url + """ + self.server_index = 0 if server_index is None and host is None else server_index + self.server_operation_index = server_operation_index or {} + """Default server index + """ + self.server_variables = server_variables or {} + self.server_operation_variables = server_operation_variables or {} + """Default server variables + """ + self.ignore_operation_servers = ignore_operation_servers + """Ignore operation servers + """ + self.temp_folder_path = None + """Temp file folder for downloading files + """ + # Authentication Settings + self.api_key = {} + if api_key: + self.api_key = api_key + """dict to store API key(s) + """ + self.api_key_prefix = {} + if api_key_prefix: + self.api_key_prefix = api_key_prefix + """dict to store API prefix (e.g. Bearer) + """ + self.refresh_api_key_hook = None + """function hook to refresh API key if expired + """ + self.username = username + """Username for HTTP basic authentication + """ + self.password = password + """Password for HTTP basic authentication + """ + self.access_token = access_token + """Access token + """ + self.logger = {} + """Logging Settings + """ + self.logger["package_logger"] = logging.getLogger("regexsolver._generated") + self.logger_format = '%(asctime)s %(levelname)s %(message)s' + """Log format + """ + self.logger_stream_handler = None + """Log stream handler + """ + self.logger_file_handler: Optional[FileHandler] = None + """Log file handler + """ + self.logger_file = None + """Debug file location + """ + if debug is not None: + self.debug = debug + else: + self.__debug = False + """Debug switch + """ + + self.verify_ssl = verify_ssl + """SSL/TLS verification + Set this to false to skip verifying SSL certificate when calling API + from https server. + """ + self.ssl_ca_cert = ssl_ca_cert + """Set this to customize the certificate file to verify the peer. + """ + self.ca_cert_data = ca_cert_data + """Set this to verify the peer using PEM (str) or DER (bytes) + certificate data. + """ + self.cert_file = cert_file + """client certificate file + """ + self.key_file = key_file + """client key file + """ + self.assert_hostname = assert_hostname + """Set this to True/False to enable/disable SSL hostname verification. + """ + self.tls_server_name = tls_server_name + """SSL/TLS Server Name Indication (SNI) + Set this to the SNI value expected by the server. + """ + + self.connection_pool_maxsize = connection_pool_maxsize if connection_pool_maxsize is not None else 100 + """This value is passed to the aiohttp to limit simultaneous connections. + None in the constructor is coerced to default 100. + """ + + self.proxy = proxy + """Proxy URL + """ + self.proxy_headers = proxy_headers + """Proxy headers + """ + self.safe_chars_for_path_param = safe_chars_for_path_param + """Safe chars for path_param + """ + self.retries = retries + """Retry configuration + """ + # Enable client side validation + self.client_side_validation = client_side_validation + + self.socket_options = socket_options + """Options to pass down to the underlying urllib3 socket + """ + + self.datetime_format = datetime_format + """datetime format + """ + + self.date_format = date_format + """date format + """ + + def __deepcopy__(self, memo: Dict[int, Any]) -> Self: + cls = self.__class__ + result = cls.__new__(cls) + memo[id(self)] = result + for k, v in self.__dict__.items(): + if k not in ('logger', 'logger_file_handler'): + setattr(result, k, copy.deepcopy(v, memo)) + # shallow copy of loggers + result.logger = copy.copy(self.logger) + # use setters to configure loggers + result.logger_file = self.logger_file + result.debug = self.debug + return result + + def __setattr__(self, name: str, value: Any) -> None: + object.__setattr__(self, name, value) + + @classmethod + def set_default(cls, default: Optional[Self]) -> None: + """Set default instance of configuration. + + It stores default configuration, which can be + returned by get_default_copy method. + + :param default: object of Configuration + """ + cls._default = default + + @classmethod + def get_default_copy(cls) -> Self: + """Deprecated. Please use `get_default` instead. + + Deprecated. Please use `get_default` instead. + + :return: The configuration object. + """ + return cls.get_default() + + @classmethod + def get_default(cls) -> Self: + """Return the default configuration. + + This method returns newly created, based on default constructor, + object of Configuration class or returns a copy of default + configuration. + + :return: The configuration object. + """ + if cls._default is None: + cls._default = cls() + return cls._default + + @property + def logger_file(self) -> Optional[str]: + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + return self.__logger_file + + @logger_file.setter + def logger_file(self, value: Optional[str]) -> None: + """The logger file. + + If the logger_file is None, then add stream handler and remove file + handler. Otherwise, add file handler and remove stream handler. + + :param value: The logger_file path. + :type: str + """ + self.__logger_file = value + if self.__logger_file: + # If set logging file, + # then add file handler and remove stream handler. + self.logger_file_handler = logging.FileHandler(self.__logger_file) + self.logger_file_handler.setFormatter(self.logger_formatter) + for _, logger in self.logger.items(): + logger.addHandler(self.logger_file_handler) + + @property + def debug(self) -> bool: + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + return self.__debug + + @debug.setter + def debug(self, value: bool) -> None: + """Debug status + + :param value: The debug status, True or False. + :type: bool + """ + self.__debug = value + if self.__debug: + # if debug status is True, turn on debug logging + for _, logger in self.logger.items(): + logger.setLevel(logging.DEBUG) + # turn on httplib debug + httplib.HTTPConnection.debuglevel = 1 + else: + # if debug status is False, turn off debug logging, + # setting log level to default `logging.WARNING` + for _, logger in self.logger.items(): + logger.setLevel(logging.WARNING) + # turn off httplib debug + httplib.HTTPConnection.debuglevel = 0 + + @property + def logger_format(self) -> str: + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + return self.__logger_format + + @logger_format.setter + def logger_format(self, value: str) -> None: + """The logger format. + + The logger_formatter will be updated when sets logger_format. + + :param value: The format string. + :type: str + """ + self.__logger_format = value + self.logger_formatter = logging.Formatter(self.__logger_format) + + def get_api_key_with_prefix(self, identifier: str, alias: Optional[str]=None) -> Optional[str]: + """Gets API key (with prefix if set). + + :param identifier: The identifier of apiKey. + :param alias: The alternative identifier of apiKey. + :return: The token for api key authentication. + """ + if self.refresh_api_key_hook is not None: + self.refresh_api_key_hook(self) + key = self.api_key.get(identifier, self.api_key.get(alias) if alias is not None else None) + if key: + prefix = self.api_key_prefix.get(identifier) + if prefix: + return "%s %s" % (prefix, key) + else: + return key + + return None + + def get_basic_auth_token(self) -> Optional[str]: + """Gets HTTP basic authentication header (string). + + :return: The token for basic HTTP authentication. + """ + username = "" + if self.username is not None: + username = self.username + password = "" + if self.password is not None: + password = self.password + + return "Basic " + base64.b64encode( + (username + ":" + password).encode('utf-8') + ).decode('utf-8') + + def auth_settings(self)-> AuthSettings: + """Gets Auth Settings dict for api client. + + :return: The Auth Settings information dict. + """ + auth: AuthSettings = {} + if self.access_token is not None: + auth['BearerAuth'] = { + 'type': 'bearer', + 'in': 'header', + 'format': 'JWT', + 'key': 'Authorization', + 'value': 'Bearer ' + self.access_token + } + return auth + + def to_debug_report(self) -> str: + """Gets the essential information for debugging. + + :return: The report for debugging. + """ + return "Python SDK Debug Report:\n"\ + "OS: {env}\n"\ + "Python Version: {pyversion}\n"\ + "Version of the API: 1.1.0\n"\ + "SDK Package Version: 1.0.0".\ + format(env=sys.platform, pyversion=sys.version) + + def get_host_settings(self) -> List[HostSetting]: + """Gets an array of host settings + + :return: An array of host settings + """ + return [ + { + 'url': "https://api.regexsolver.com/v1", + 'description': "No description provided", + } + ] + + def get_host_from_settings( + self, + index: Optional[int], + variables: Optional[ServerVariablesT]=None, + servers: Optional[List[HostSetting]]=None, + ) -> str: + """Gets host URL based on the index and variables + :param index: array index of the host settings + :param variables: hash of variable and the corresponding value + :param servers: an array of host settings or None + :return: URL based on host settings + """ + if index is None: + return self._base_path + + variables = {} if variables is None else variables + servers = self.get_host_settings() if servers is None else servers + + try: + server = servers[index] + except IndexError: + raise ValueError( + "Invalid index {0} when selecting the host settings. " + "Must be less than {1}".format(index, len(servers))) + + url = server['url'] + + # go through variables and replace placeholders + for variable_name, variable in server.get('variables', {}).items(): + used_value = variables.get( + variable_name, variable['default_value']) + + if 'enum_values' in variable \ + and variable['enum_values'] \ + and used_value not in variable['enum_values']: + raise ValueError( + "The variable `{0}` in the host URL has invalid value " + "{1}. Must be {2}.".format( + variable_name, variables[variable_name], + variable['enum_values'])) + + url = url.replace("{" + variable_name + "}", used_value) + + return url + + @property + def host(self) -> str: + """Return generated host.""" + return self.get_host_from_settings(self.server_index, variables=self.server_variables) + + @host.setter + def host(self, value: str) -> None: + """Fix base path.""" + self._base_path = value + self.server_index = None diff --git a/regexsolver/_generated/exceptions.py b/regexsolver/_generated/exceptions.py new file mode 100644 index 0000000..5846240 --- /dev/null +++ b/regexsolver/_generated/exceptions.py @@ -0,0 +1,218 @@ +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from typing import Any, Optional +from typing_extensions import Self + +class OpenApiException(Exception): + """The base exception class for all OpenAPIExceptions""" + + +class ApiTypeError(OpenApiException, TypeError): + def __init__(self, msg, path_to_item=None, valid_classes=None, + key_type=None) -> None: + """ Raises an exception for TypeErrors + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list): a list of keys an indices to get to the + current_item + None if unset + valid_classes (tuple): the primitive classes that current item + should be an instance of + None if unset + key_type (bool): False if our value is a value in a dict + True if it is a key in a dict + False if our item is an item in a list + None if unset + """ + self.path_to_item = path_to_item + self.valid_classes = valid_classes + self.key_type = key_type + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiTypeError, self).__init__(full_msg) + + +class ApiValueError(OpenApiException, ValueError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (list) the path to the exception in the + received_data dict. None if unset + """ + + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiValueError, self).__init__(full_msg) + + +class ApiAttributeError(OpenApiException, AttributeError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Raised when an attribute reference or assignment fails. + + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiAttributeError, self).__init__(full_msg) + + +class ApiKeyError(OpenApiException, KeyError): + def __init__(self, msg, path_to_item=None) -> None: + """ + Args: + msg (str): the exception message + + Keyword Args: + path_to_item (None/list) the path to the exception in the + received_data dict + """ + self.path_to_item = path_to_item + full_msg = msg + if path_to_item: + full_msg = "{0} at {1}".format(msg, render_path(path_to_item)) + super(ApiKeyError, self).__init__(full_msg) + + +class ApiException(OpenApiException): + + def __init__( + self, + status=None, + reason=None, + http_resp=None, + *, + body: Optional[str] = None, + data: Optional[Any] = None, + ) -> None: + self.status = status + self.reason = reason + self.body = body + self.data = data + self.headers = None + + if http_resp: + if self.status is None: + self.status = http_resp.status + if self.reason is None: + self.reason = http_resp.reason + if self.body is None: + try: + self.body = http_resp.data.decode('utf-8') + except Exception: + pass + self.headers = http_resp.headers + + @classmethod + def from_response( + cls, + *, + http_resp, + body: Optional[str], + data: Optional[Any], + ) -> Self: + if http_resp.status == 400: + raise BadRequestException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 401: + raise UnauthorizedException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 403: + raise ForbiddenException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 404: + raise NotFoundException(http_resp=http_resp, body=body, data=data) + + # Added new conditions for 409 and 422 + if http_resp.status == 409: + raise ConflictException(http_resp=http_resp, body=body, data=data) + + if http_resp.status == 422: + raise UnprocessableEntityException(http_resp=http_resp, body=body, data=data) + + if 500 <= http_resp.status <= 599: + raise ServiceException(http_resp=http_resp, body=body, data=data) + raise ApiException(http_resp=http_resp, body=body, data=data) + + def __str__(self): + """Custom error messages for exception""" + error_message = "({0})\n"\ + "Reason: {1}\n".format(self.status, self.reason) + if self.headers: + error_message += "HTTP response headers: {0}\n".format( + self.headers) + + if self.body: + error_message += "HTTP response body: {0}\n".format(self.body) + + if self.data: + error_message += "HTTP response data: {0}\n".format(self.data) + + return error_message + + +class BadRequestException(ApiException): + pass + + +class NotFoundException(ApiException): + pass + + +class UnauthorizedException(ApiException): + pass + + +class ForbiddenException(ApiException): + pass + + +class ServiceException(ApiException): + pass + + +class ConflictException(ApiException): + """Exception for HTTP 409 Conflict.""" + pass + + +class UnprocessableEntityException(ApiException): + """Exception for HTTP 422 Unprocessable Entity.""" + pass + + +def render_path(path_to_item): + """Returns a string representation of a path""" + result = "" + for pth in path_to_item: + if isinstance(pth, int): + result += "[{0}]".format(pth) + else: + result += "['{0}']".format(pth) + return result diff --git a/regexsolver/_generated/models/__init__.py b/regexsolver/_generated/models/__init__.py new file mode 100644 index 0000000..77f658d --- /dev/null +++ b/regexsolver/_generated/models/__init__.py @@ -0,0 +1,52 @@ +# coding: utf-8 + +# flake8: noqa +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + +# import models into model package +from regexsolver._generated.models.account_limits import AccountLimits +from regexsolver._generated.models.boolean import Boolean +from regexsolver._generated.models.cardinality import Cardinality +from regexsolver._generated.models.cardinality200_response import Cardinality200Response +from regexsolver._generated.models.cardinality_big_integer import CardinalityBigInteger +from regexsolver._generated.models.cardinality_infinite import CardinalityInfinite +from regexsolver._generated.models.cardinality_integer import CardinalityInteger +from regexsolver._generated.models.concat200_response import Concat200Response +from regexsolver._generated.models.dot200_response import Dot200Response +from regexsolver._generated.models.empty200_response import Empty200Response +from regexsolver._generated.models.error_response import ErrorResponse +from regexsolver._generated.models.error_response400 import ErrorResponse400 +from regexsolver._generated.models.error_response401 import ErrorResponse401 +from regexsolver._generated.models.error_response403 import ErrorResponse403 +from regexsolver._generated.models.execution_options import ExecutionOptions +from regexsolver._generated.models.fair_response_options import FairResponseOptions +from regexsolver._generated.models.generate_strings_character_order import GenerateStringsCharacterOrder +from regexsolver._generated.models.generate_strings_path_order import GenerateStringsPathOrder +from regexsolver._generated.models.generate_strings_request import GenerateStringsRequest +from regexsolver._generated.models.generate_strings_response import GenerateStringsResponse +from regexsolver._generated.models.length import Length +from regexsolver._generated.models.length200_response import Length200Response +from regexsolver._generated.models.limits200_response import Limits200Response +from regexsolver._generated.models.multi_terms_request import MultiTermsRequest +from regexsolver._generated.models.repeat_request import RepeatRequest +from regexsolver._generated.models.request_options import RequestOptions +from regexsolver._generated.models.response_options import ResponseOptions +from regexsolver._generated.models.string import String +from regexsolver._generated.models.strings import Strings +from regexsolver._generated.models.strings200_response import Strings200Response +from regexsolver._generated.models.term import Term +from regexsolver._generated.models.term_fair import TermFair +from regexsolver._generated.models.term_fair_metadata import TermFairMetadata +from regexsolver._generated.models.term_regex import TermRegex +from regexsolver._generated.models.term_request import TermRequest +from regexsolver._generated.models.two_terms_request import TwoTermsRequest + diff --git a/regexsolver/_generated/models/account_limits.py b/regexsolver/_generated/models/account_limits.py new file mode 100644 index 0000000..9d9e1f0 --- /dev/null +++ b/regexsolver/_generated/models/account_limits.py @@ -0,0 +1,105 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class AccountLimits(BaseModel): + """ + The plan limits currently applying to the account. + """ # noqa: E501 + type: StrictStr + max_requests_count: StrictInt = Field(description="Maximum number of requests allowed per billing period.", alias="maxRequestsCount") + max_requests_rate: StrictInt = Field(description="Maximum number of requests allowed per second. `0` means no rate limit is enforced.", alias="maxRequestsRate") + max_terms_count: StrictInt = Field(description="Maximum number of terms accepted in a single request.", alias="maxTermsCount") + max_timeout: StrictInt = Field(description="Maximum execution timeout per request, in milliseconds.", alias="maxTimeout") + max_states_count: StrictInt = Field(description="Maximum number of automaton states an operation may build.", alias="maxStatesCount") + __properties: ClassVar[List[str]] = ["type", "maxRequestsCount", "maxRequestsRate", "maxTermsCount", "maxTimeout", "maxStatesCount"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['accountLimits']): + raise ValueError("must be one of enum values ('accountLimits')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of AccountLimits from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of AccountLimits from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "maxRequestsCount": obj.get("maxRequestsCount"), + "maxRequestsRate": obj.get("maxRequestsRate"), + "maxTermsCount": obj.get("maxTermsCount"), + "maxTimeout": obj.get("maxTimeout"), + "maxStatesCount": obj.get("maxStatesCount") + }) + return _obj + + diff --git a/regexsolver/_generated/models/boolean.py b/regexsolver/_generated/models/boolean.py new file mode 100644 index 0000000..8af12ad --- /dev/null +++ b/regexsolver/_generated/models/boolean.py @@ -0,0 +1,97 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Boolean(BaseModel): + """ + Wrapper for a boolean value. + """ # noqa: E501 + type: StrictStr + value: StrictBool = Field(description="Boolean value.") + __properties: ClassVar[List[str]] = ["type", "value"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['boolean']): + raise ValueError("must be one of enum values ('boolean')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Boolean from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Boolean from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "value": obj.get("value") + }) + return _obj + + diff --git a/regexsolver/_generated/models/cardinality.py b/regexsolver/_generated/models/cardinality.py new file mode 100644 index 0000000..aca5f20 --- /dev/null +++ b/regexsolver/_generated/models/cardinality.py @@ -0,0 +1,154 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator +from typing import Any, List, Optional +from regexsolver._generated.models.cardinality_big_integer import CardinalityBigInteger +from regexsolver._generated.models.cardinality_infinite import CardinalityInfinite +from regexsolver._generated.models.cardinality_integer import CardinalityInteger +from pydantic import StrictStr, Field +from typing import Union, List, Set, Optional, Dict +from typing_extensions import Literal, Self + +CARDINALITY_ONE_OF_SCHEMAS = ["CardinalityBigInteger", "CardinalityInfinite", "CardinalityInteger"] + +class Cardinality(BaseModel): + """ + Number of unique strings matched by a term. + """ + # data type: CardinalityInfinite + oneof_schema_1_validator: Optional[CardinalityInfinite] = None + # data type: CardinalityBigInteger + oneof_schema_2_validator: Optional[CardinalityBigInteger] = None + # data type: CardinalityInteger + oneof_schema_3_validator: Optional[CardinalityInteger] = None + actual_instance: Optional[Union[CardinalityBigInteger, CardinalityInfinite, CardinalityInteger]] = None + one_of_schemas: Set[str] = { "CardinalityBigInteger", "CardinalityInfinite", "CardinalityInteger" } + + model_config = ConfigDict( + validate_assignment=True, + protected_namespaces=(), + ) + + + discriminator_value_class_map: Dict[str, str] = { + } + + def __init__(self, *args, **kwargs) -> None: + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @field_validator('actual_instance') + def actual_instance_must_validate_oneof(cls, v): + instance = Cardinality.model_construct() + error_messages = [] + match = 0 + # validate data type: CardinalityInfinite + if not isinstance(v, CardinalityInfinite): + error_messages.append(f"Error! Input type `{type(v)}` is not `CardinalityInfinite`") + else: + match += 1 + # validate data type: CardinalityBigInteger + if not isinstance(v, CardinalityBigInteger): + error_messages.append(f"Error! Input type `{type(v)}` is not `CardinalityBigInteger`") + else: + match += 1 + # validate data type: CardinalityInteger + if not isinstance(v, CardinalityInteger): + error_messages.append(f"Error! Input type `{type(v)}` is not `CardinalityInteger`") + else: + match += 1 + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when setting `actual_instance` in Cardinality with oneOf schemas: CardinalityBigInteger, CardinalityInfinite, CardinalityInteger. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when setting `actual_instance` in Cardinality with oneOf schemas: CardinalityBigInteger, CardinalityInfinite, CardinalityInteger. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: Union[str, Dict[str, Any]]) -> Self: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Returns the object represented by the json string""" + instance = cls.model_construct() + error_messages = [] + match = 0 + + # deserialize data into CardinalityInfinite + try: + instance.actual_instance = CardinalityInfinite.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # deserialize data into CardinalityBigInteger + try: + instance.actual_instance = CardinalityBigInteger.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # deserialize data into CardinalityInteger + try: + instance.actual_instance = CardinalityInteger.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when deserializing the JSON string into Cardinality with oneOf schemas: CardinalityBigInteger, CardinalityInfinite, CardinalityInteger. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when deserializing the JSON string into Cardinality with oneOf schemas: CardinalityBigInteger, CardinalityInfinite, CardinalityInteger. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> Optional[Union[Dict[str, Any], CardinalityBigInteger, CardinalityInfinite, CardinalityInteger]]: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict): + return self.actual_instance.to_dict() + else: + # primitive type + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.model_dump()) + + diff --git a/regexsolver/_generated/models/cardinality200_response.py b/regexsolver/_generated/models/cardinality200_response.py new file mode 100644 index 0000000..41527de --- /dev/null +++ b/regexsolver/_generated/models/cardinality200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.cardinality import Cardinality +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Cardinality200Response(BaseModel): + """ + Cardinality200Response + """ # noqa: E501 + success: StrictBool + data: Cardinality + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Cardinality200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Cardinality200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": Cardinality.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/cardinality_big_integer.py b/regexsolver/_generated/models/cardinality_big_integer.py new file mode 100644 index 0000000..f5997f6 --- /dev/null +++ b/regexsolver/_generated/models/cardinality_big_integer.py @@ -0,0 +1,95 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class CardinalityBigInteger(BaseModel): + """ + The set of matched strings is finite but too large to be returned. + """ # noqa: E501 + type: StrictStr + __properties: ClassVar[List[str]] = ["type"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['bigInteger']): + raise ValueError("must be one of enum values ('bigInteger')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of CardinalityBigInteger from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of CardinalityBigInteger from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type") + }) + return _obj + + diff --git a/regexsolver/_generated/models/cardinality_infinite.py b/regexsolver/_generated/models/cardinality_infinite.py new file mode 100644 index 0000000..4446c6c --- /dev/null +++ b/regexsolver/_generated/models/cardinality_infinite.py @@ -0,0 +1,95 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class CardinalityInfinite(BaseModel): + """ + The set of matched strings is infinite. + """ # noqa: E501 + type: StrictStr + __properties: ClassVar[List[str]] = ["type"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['infinite']): + raise ValueError("must be one of enum values ('infinite')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of CardinalityInfinite from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of CardinalityInfinite from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type") + }) + return _obj + + diff --git a/regexsolver/_generated/models/cardinality_integer.py b/regexsolver/_generated/models/cardinality_integer.py new file mode 100644 index 0000000..07a3bd9 --- /dev/null +++ b/regexsolver/_generated/models/cardinality_integer.py @@ -0,0 +1,98 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing_extensions import Annotated +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class CardinalityInteger(BaseModel): + """ + The set of matched strings is finite. + """ # noqa: E501 + type: StrictStr + value: Annotated[int, Field(strict=True, ge=0)] = Field(description="Exact count.") + __properties: ClassVar[List[str]] = ["type", "value"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['integer']): + raise ValueError("must be one of enum values ('integer')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of CardinalityInteger from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of CardinalityInteger from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "value": obj.get("value") + }) + return _obj + + diff --git a/regexsolver/_generated/models/concat200_response.py b/regexsolver/_generated/models/concat200_response.py new file mode 100644 index 0000000..019dfcd --- /dev/null +++ b/regexsolver/_generated/models/concat200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.term import Term +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Concat200Response(BaseModel): + """ + Concat200Response + """ # noqa: E501 + success: StrictBool + data: Term + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Concat200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Concat200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": Term.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/dot200_response.py b/regexsolver/_generated/models/dot200_response.py new file mode 100644 index 0000000..28986a9 --- /dev/null +++ b/regexsolver/_generated/models/dot200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.string import String +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Dot200Response(BaseModel): + """ + Dot200Response + """ # noqa: E501 + success: StrictBool + data: String + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Dot200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Dot200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": String.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/empty200_response.py b/regexsolver/_generated/models/empty200_response.py new file mode 100644 index 0000000..8722e0c --- /dev/null +++ b/regexsolver/_generated/models/empty200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.boolean import Boolean +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Empty200Response(BaseModel): + """ + Empty200Response + """ # noqa: E501 + success: StrictBool + data: Boolean + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Empty200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Empty200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": Boolean.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/error_response.py b/regexsolver/_generated/models/error_response.py new file mode 100644 index 0000000..a050b28 --- /dev/null +++ b/regexsolver/_generated/models/error_response.py @@ -0,0 +1,92 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ErrorResponse(BaseModel): + """ + ErrorResponse + """ # noqa: E501 + success: StrictBool + error: StrictStr = Field(description="Human readable error message.") + error_code: Optional[StrictStr] = Field(default=None, description="The error code.", alias="errorCode") + __properties: ClassVar[List[str]] = ["success", "error", "errorCode"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ErrorResponse from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ErrorResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "error": obj.get("error"), + "errorCode": obj.get("errorCode") + }) + return _obj + + diff --git a/regexsolver/_generated/models/error_response400.py b/regexsolver/_generated/models/error_response400.py new file mode 100644 index 0000000..613bdd7 --- /dev/null +++ b/regexsolver/_generated/models/error_response400.py @@ -0,0 +1,102 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ErrorResponse400(BaseModel): + """ + ErrorResponse400 + """ # noqa: E501 + success: StrictBool + error: StrictStr = Field(description="Human readable error message.") + error_code: Optional[StrictStr] = Field(default=None, alias="errorCode") + __properties: ClassVar[List[str]] = ["success", "error", "errorCode"] + + @field_validator('error_code') + def error_code_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['InvalidJson', 'TooManyTerms', 'TooFewTerms', 'TimeoutTooLarge', 'TimeoutExceeded', 'InvalidNumberOfStringsToGenerate', 'AutomatonTooManyStates', 'RegexSyntaxError', 'FairSyntaxError']): + raise ValueError("must be one of enum values ('InvalidJson', 'TooManyTerms', 'TooFewTerms', 'TimeoutTooLarge', 'TimeoutExceeded', 'InvalidNumberOfStringsToGenerate', 'AutomatonTooManyStates', 'RegexSyntaxError', 'FairSyntaxError')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ErrorResponse400 from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ErrorResponse400 from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "error": obj.get("error"), + "errorCode": obj.get("errorCode") + }) + return _obj + + diff --git a/regexsolver/_generated/models/error_response401.py b/regexsolver/_generated/models/error_response401.py new file mode 100644 index 0000000..ead4c50 --- /dev/null +++ b/regexsolver/_generated/models/error_response401.py @@ -0,0 +1,102 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ErrorResponse401(BaseModel): + """ + ErrorResponse401 + """ # noqa: E501 + success: StrictBool + error: StrictStr = Field(description="Human readable error message.") + error_code: Optional[StrictStr] = Field(default=None, alias="errorCode") + __properties: ClassVar[List[str]] = ["success", "error", "errorCode"] + + @field_validator('error_code') + def error_code_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['MissingOrMalformedToken', 'InvalidToken']): + raise ValueError("must be one of enum values ('MissingOrMalformedToken', 'InvalidToken')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ErrorResponse401 from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ErrorResponse401 from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "error": obj.get("error"), + "errorCode": obj.get("errorCode") + }) + return _obj + + diff --git a/regexsolver/_generated/models/error_response403.py b/regexsolver/_generated/models/error_response403.py new file mode 100644 index 0000000..8aeba51 --- /dev/null +++ b/regexsolver/_generated/models/error_response403.py @@ -0,0 +1,102 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ErrorResponse403(BaseModel): + """ + ErrorResponse403 + """ # noqa: E501 + success: StrictBool + error: StrictStr = Field(description="Human readable error message.") + error_code: Optional[StrictStr] = Field(default=None, alias="errorCode") + __properties: ClassVar[List[str]] = ["success", "error", "errorCode"] + + @field_validator('error_code') + def error_code_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['QuotaExceeded']): + raise ValueError("must be one of enum values ('QuotaExceeded')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ErrorResponse403 from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ErrorResponse403 from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "error": obj.get("error"), + "errorCode": obj.get("errorCode") + }) + return _obj + + diff --git a/regexsolver/_generated/models/execution_options.py b/regexsolver/_generated/models/execution_options.py new file mode 100644 index 0000000..087d724 --- /dev/null +++ b/regexsolver/_generated/models/execution_options.py @@ -0,0 +1,89 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ExecutionOptions(BaseModel): + """ + Change how the engine executes the operation. + """ # noqa: E501 + timeout: Optional[Annotated[int, Field(strict=True, ge=1)]] = Field(default=None, description="Timeout in milliseconds for the operation.") + __properties: ClassVar[List[str]] = ["timeout"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ExecutionOptions from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ExecutionOptions from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "timeout": obj.get("timeout") + }) + return _obj + + diff --git a/regexsolver/_generated/models/fair_response_options.py b/regexsolver/_generated/models/fair_response_options.py new file mode 100644 index 0000000..5ef2b09 --- /dev/null +++ b/regexsolver/_generated/models/fair_response_options.py @@ -0,0 +1,88 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class FairResponseOptions(BaseModel): + """ + Options controlling the FAIR output. Only applied when response format is \"fair\". + """ # noqa: E501 + deterministic: Optional[StrictBool] = Field(default=None, description="When true, the returned FAIR is guaranteed to be a deterministic automaton, suitable for consistent pagination with /generate/strings.") + __properties: ClassVar[List[str]] = ["deterministic"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of FairResponseOptions from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of FairResponseOptions from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "deterministic": obj.get("deterministic") + }) + return _obj + + diff --git a/regexsolver/_generated/models/generate_strings_character_order.py b/regexsolver/_generated/models/generate_strings_character_order.py new file mode 100644 index 0000000..75acf10 --- /dev/null +++ b/regexsolver/_generated/models/generate_strings_character_order.py @@ -0,0 +1,37 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +from enum import Enum +from typing_extensions import Self + + +class GenerateStringsCharacterOrder(str, Enum): + """ + Order in which the strings within each path are produced. Orthogonal to `pathOrder`: it does not change *what* can be generated, only which strings are reached first. `ascending` expands each position from the low end of its character range first, so `[a-z]{8}` yields `aaaaaaaa`, `aaaaaaab`, ... — a stable, spec-defined order returning the smallest witnesses of a path first. `shuffled` applies a permutation drawn from `seed`, so `[a-z]{8}` yields something like `sjtwsive` instead: the strings look like real inputs. Random in look only — generation stays reproducible and pages with `offset`, though offsets are only consistent between calls sharing the same `seed`, and the exact sequence may change between releases. Use `charset` to restrict generation to specific characters. + """ + + """ + allowed enum values + """ + ASCENDING = 'ascending' + SHUFFLED = 'shuffled' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of GenerateStringsCharacterOrder from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/regexsolver/_generated/models/generate_strings_path_order.py b/regexsolver/_generated/models/generate_strings_path_order.py new file mode 100644 index 0000000..275b5bd --- /dev/null +++ b/regexsolver/_generated/models/generate_strings_path_order.py @@ -0,0 +1,38 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +from enum import Enum +from typing_extensions import Self + + +class GenerateStringsPathOrder(str, Enum): + """ + Order in which the paths of the language are scheduled — the *shapes* the term allows, as opposed to the characters filling them (`characterOrder`). `sweep` expands one path in full, shortest first, before moving to the next one: the cheapest way to page through a whole language with `offset`. `interleave` covers every path the term holds before any path is asked for a second string, so a `limit` smaller than the number of shapes is spent entirely on distinct shapes; slower than `sweep`, but better suited to deriving test cases. `shuffled` is `interleave` with same-length paths visited in an order drawn by `seed`. Shorter paths still come first, so the seed only draws among paths of equal length. All three are deterministic and page with `offset`; for `shuffled`, offsets are only consistent between calls sharing the same `seed`. + """ + + """ + allowed enum values + """ + SWEEP = 'sweep' + INTERLEAVE = 'interleave' + SHUFFLED = 'shuffled' + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Create an instance of GenerateStringsPathOrder from a JSON string""" + return cls(json.loads(json_str)) + + diff --git a/regexsolver/_generated/models/generate_strings_request.py b/regexsolver/_generated/models/generate_strings_request.py new file mode 100644 index 0000000..1dcf3cc --- /dev/null +++ b/regexsolver/_generated/models/generate_strings_request.py @@ -0,0 +1,122 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from regexsolver._generated.models.generate_strings_character_order import GenerateStringsCharacterOrder +from regexsolver._generated.models.generate_strings_path_order import GenerateStringsPathOrder +from regexsolver._generated.models.request_options import RequestOptions +from regexsolver._generated.models.term import Term +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class GenerateStringsRequest(BaseModel): + """ + Request to generate up to `limit` distinct strings matched by `term`, skipping the first `offset` strings and confined to lengths between `minLength` and `maxLength`. For consistent pagination, `term` should be deterministic. + """ # noqa: E501 + term: Term = Field(description="Source term to generate strings from.") + limit: Annotated[int, Field(le=100, strict=True, ge=1)] = Field(description="Maximum number of unique strings to return.") + offset: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=0, description="Number of matched strings to skip before starting to collect the results. Used for pagination.") + min_length: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=0, description="Shortest string to generate. Strings shorter than this are left out of the enumeration entirely, `offset` never counting them.", alias="minLength") + max_length: Optional[Annotated[int, Field(le=100, strict=True, ge=1)]] = Field(default=100, description="Longest string to generate. Strings longer than this are left out of the enumeration entirely, `offset` never counting them. A value below `minLength` leaves nothing to generate.", alias="maxLength") + path_order: Optional[GenerateStringsPathOrder] = Field(default=None, description="Order in which the paths of the language are scheduled. Defaults to `sweep`.", alias="pathOrder") + character_order: Optional[GenerateStringsCharacterOrder] = Field(default=None, description="Order in which the strings within each path are produced. Defaults to `ascending`.", alias="characterOrder") + seed: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=0, description="Seed behind the `shuffled` modes of `pathOrder` and `characterOrder`; ignored when neither is used. The default seed is fixed rather than random, so two calls sharing a seed generate the same strings and `offset` pages through them consistently. Change it to draw a different sequence from the same term.") + charset: Optional[StrictStr] = Field(default=None, description="Character class the generated strings are restricted to, such as `[a-z]` or `\\P{C}`. Paths needing a character outside of it are dropped entirely. If omitted, every character the term allows is used.") + options: Optional[RequestOptions] = None + __properties: ClassVar[List[str]] = ["term", "limit", "offset", "minLength", "maxLength", "pathOrder", "characterOrder", "seed", "charset", "options"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of GenerateStringsRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of term + if self.term: + _dict['term'] = self.term.to_dict() + # override the default output from pydantic by calling `to_dict()` of options + if self.options: + _dict['options'] = self.options.to_dict() + # set to None if charset (nullable) is None + # and model_fields_set contains the field + if self.charset is None and "charset" in self.model_fields_set: + _dict['charset'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GenerateStringsRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "term": Term.from_dict(obj["term"]) if obj.get("term") is not None else None, + "limit": obj.get("limit"), + "offset": obj.get("offset") if obj.get("offset") is not None else 0, + "minLength": obj.get("minLength") if obj.get("minLength") is not None else 0, + "maxLength": obj.get("maxLength") if obj.get("maxLength") is not None else 100, + "pathOrder": obj.get("pathOrder"), + "characterOrder": obj.get("characterOrder"), + "seed": obj.get("seed") if obj.get("seed") is not None else 0, + "charset": obj.get("charset"), + "options": RequestOptions.from_dict(obj["options"]) if obj.get("options") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/generate_strings_response.py b/regexsolver/_generated/models/generate_strings_response.py new file mode 100644 index 0000000..1050c56 --- /dev/null +++ b/regexsolver/_generated/models/generate_strings_response.py @@ -0,0 +1,101 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.strings import Strings +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class GenerateStringsResponse(BaseModel): + """ + Response containing distinct strings generated from the requested `term`. + """ # noqa: E501 + type: StrictStr + strings: Strings = Field(description="The generated distinct strings.") + __properties: ClassVar[List[str]] = ["type", "strings"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['generatedStrings']): + raise ValueError("must be one of enum values ('generatedStrings')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of GenerateStringsResponse from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of strings + if self.strings: + _dict['strings'] = self.strings.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of GenerateStringsResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "strings": Strings.from_dict(obj["strings"]) if obj.get("strings") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/length.py b/regexsolver/_generated/models/length.py new file mode 100644 index 0000000..5392fa9 --- /dev/null +++ b/regexsolver/_generated/models/length.py @@ -0,0 +1,109 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Length(BaseModel): + """ + Minimum and maximum length of any string in the language. + """ # noqa: E501 + type: StrictStr + min: Optional[StrictInt] = Field(description="Shortest possible length, or null if empty.") + max: Optional[StrictInt] = Field(description="Longest possible length, or null if unbounded.") + __properties: ClassVar[List[str]] = ["type", "min", "max"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['length']): + raise ValueError("must be one of enum values ('length')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Length from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # set to None if min (nullable) is None + # and model_fields_set contains the field + if self.min is None and "min" in self.model_fields_set: + _dict['min'] = None + + # set to None if max (nullable) is None + # and model_fields_set contains the field + if self.max is None and "max" in self.model_fields_set: + _dict['max'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Length from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "min": obj.get("min"), + "max": obj.get("max") + }) + return _obj + + diff --git a/regexsolver/_generated/models/length200_response.py b/regexsolver/_generated/models/length200_response.py new file mode 100644 index 0000000..e2df0fd --- /dev/null +++ b/regexsolver/_generated/models/length200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.length import Length +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Length200Response(BaseModel): + """ + Length200Response + """ # noqa: E501 + success: StrictBool + data: Length + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Length200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Length200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": Length.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/limits200_response.py b/regexsolver/_generated/models/limits200_response.py new file mode 100644 index 0000000..05bb854 --- /dev/null +++ b/regexsolver/_generated/models/limits200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.account_limits import AccountLimits +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Limits200Response(BaseModel): + """ + Limits200Response + """ # noqa: E501 + success: StrictBool + data: AccountLimits + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Limits200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Limits200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": AccountLimits.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/multi_terms_request.py b/regexsolver/_generated/models/multi_terms_request.py new file mode 100644 index 0000000..809ad84 --- /dev/null +++ b/regexsolver/_generated/models/multi_terms_request.py @@ -0,0 +1,103 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from regexsolver._generated.models.request_options import RequestOptions +from regexsolver._generated.models.term import Term +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class MultiTermsRequest(BaseModel): + """ + Request carrying 2 or more terms for n-ary operations. + """ # noqa: E501 + terms: Annotated[List[Term], Field(min_length=2)] = Field(description="Terms to process. Order matters for some operations.") + options: Optional[RequestOptions] = None + __properties: ClassVar[List[str]] = ["terms", "options"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of MultiTermsRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in terms (list) + _items = [] + if self.terms: + for _item_terms in self.terms: + if _item_terms: + _items.append(_item_terms.to_dict()) + _dict['terms'] = _items + # override the default output from pydantic by calling `to_dict()` of options + if self.options: + _dict['options'] = self.options.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of MultiTermsRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "terms": [Term.from_dict(_item) for _item in obj["terms"]] if obj.get("terms") is not None else None, + "options": RequestOptions.from_dict(obj["options"]) if obj.get("options") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/repeat_request.py b/regexsolver/_generated/models/repeat_request.py new file mode 100644 index 0000000..bac18f2 --- /dev/null +++ b/regexsolver/_generated/models/repeat_request.py @@ -0,0 +1,108 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from regexsolver._generated.models.request_options import RequestOptions +from regexsolver._generated.models.term import Term +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class RepeatRequest(BaseModel): + """ + Request to repeat a term between `min` and `max` times. + """ # noqa: E501 + term: Term = Field(description="Term to repeat.") + min: Annotated[int, Field(strict=True, ge=0)] = Field(description="Inclusive lower bound of repetitions.") + max: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=None, description="Inclusive upper bound. If omitted or null, the repetition is unbounded.") + options: Optional[RequestOptions] = None + __properties: ClassVar[List[str]] = ["term", "min", "max", "options"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of RepeatRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of term + if self.term: + _dict['term'] = self.term.to_dict() + # override the default output from pydantic by calling `to_dict()` of options + if self.options: + _dict['options'] = self.options.to_dict() + # set to None if max (nullable) is None + # and model_fields_set contains the field + if self.max is None and "max" in self.model_fields_set: + _dict['max'] = None + + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of RepeatRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "term": Term.from_dict(obj["term"]) if obj.get("term") is not None else None, + "min": obj.get("min"), + "max": obj.get("max"), + "options": RequestOptions.from_dict(obj["options"]) if obj.get("options") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/request_options.py b/regexsolver/_generated/models/request_options.py new file mode 100644 index 0000000..8930b0d --- /dev/null +++ b/regexsolver/_generated/models/request_options.py @@ -0,0 +1,100 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictInt +from typing import Any, ClassVar, Dict, List, Optional +from regexsolver._generated.models.execution_options import ExecutionOptions +from regexsolver._generated.models.response_options import ResponseOptions +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class RequestOptions(BaseModel): + """ + Change how the engine handles the operation. + """ # noqa: E501 + schema_version: StrictInt = Field(description="Client-expected schema version.", alias="schemaVersion") + response: Optional[ResponseOptions] = None + execution: Optional[ExecutionOptions] = None + __properties: ClassVar[List[str]] = ["schemaVersion", "response", "execution"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of RequestOptions from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of response + if self.response: + _dict['response'] = self.response.to_dict() + # override the default output from pydantic by calling `to_dict()` of execution + if self.execution: + _dict['execution'] = self.execution.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of RequestOptions from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "schemaVersion": obj.get("schemaVersion"), + "response": ResponseOptions.from_dict(obj["response"]) if obj.get("response") is not None else None, + "execution": ExecutionOptions.from_dict(obj["execution"]) if obj.get("execution") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/response_options.py b/regexsolver/_generated/models/response_options.py new file mode 100644 index 0000000..4fe4b7e --- /dev/null +++ b/regexsolver/_generated/models/response_options.py @@ -0,0 +1,104 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from regexsolver._generated.models.fair_response_options import FairResponseOptions +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ResponseOptions(BaseModel): + """ + Change how the engine returns results. + """ # noqa: E501 + format: Optional[StrictStr] = Field(default=None, description="Return format of the term.") + fair: Optional[FairResponseOptions] = Field(default=None, description="Options applied when format is \"fair\". Ignored otherwise.") + __properties: ClassVar[List[str]] = ["format", "fair"] + + @field_validator('format') + def format_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['any', 'fair', 'regex']): + raise ValueError("must be one of enum values ('any', 'fair', 'regex')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ResponseOptions from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of fair + if self.fair: + _dict['fair'] = self.fair.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ResponseOptions from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "format": obj.get("format"), + "fair": FairResponseOptions.from_dict(obj["fair"]) if obj.get("fair") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/string.py b/regexsolver/_generated/models/string.py new file mode 100644 index 0000000..6ec5858 --- /dev/null +++ b/regexsolver/_generated/models/string.py @@ -0,0 +1,97 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class String(BaseModel): + """ + Wrapper for a string value. + """ # noqa: E501 + type: StrictStr + value: StrictStr = Field(description="String value.") + __properties: ClassVar[List[str]] = ["type", "value"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['string']): + raise ValueError("must be one of enum values ('string')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of String from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of String from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "value": obj.get("value") + }) + return _obj + + diff --git a/regexsolver/_generated/models/strings.py b/regexsolver/_generated/models/strings.py new file mode 100644 index 0000000..2040c68 --- /dev/null +++ b/regexsolver/_generated/models/strings.py @@ -0,0 +1,97 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Strings(BaseModel): + """ + Wrapper for a list of strings. + """ # noqa: E501 + type: StrictStr + value: List[StrictStr] = Field(description="Array of unique strings.") + __properties: ClassVar[List[str]] = ["type", "value"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['strings']): + raise ValueError("must be one of enum values ('strings')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Strings from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Strings from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "value": obj.get("value") + }) + return _obj + + diff --git a/regexsolver/_generated/models/strings200_response.py b/regexsolver/_generated/models/strings200_response.py new file mode 100644 index 0000000..0122f98 --- /dev/null +++ b/regexsolver/_generated/models/strings200_response.py @@ -0,0 +1,94 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictBool +from typing import Any, ClassVar, Dict, List +from regexsolver._generated.models.generate_strings_response import GenerateStringsResponse +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class Strings200Response(BaseModel): + """ + Strings200Response + """ # noqa: E501 + success: StrictBool + data: GenerateStringsResponse + __properties: ClassVar[List[str]] = ["success", "data"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of Strings200Response from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of data + if self.data: + _dict['data'] = self.data.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of Strings200Response from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "success": obj.get("success"), + "data": GenerateStringsResponse.from_dict(obj["data"]) if obj.get("data") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/term.py b/regexsolver/_generated/models/term.py new file mode 100644 index 0000000..e817cdc --- /dev/null +++ b/regexsolver/_generated/models/term.py @@ -0,0 +1,140 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import json +import pprint +from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator +from typing import Any, List, Optional +from regexsolver._generated.models.term_fair import TermFair +from regexsolver._generated.models.term_regex import TermRegex +from pydantic import StrictStr, Field +from typing import Union, List, Set, Optional, Dict +from typing_extensions import Literal, Self + +TERM_ONE_OF_SCHEMAS = ["TermFair", "TermRegex"] + +class Term(BaseModel): + """ + Serialized term. + """ + # data type: TermRegex + oneof_schema_1_validator: Optional[TermRegex] = None + # data type: TermFair + oneof_schema_2_validator: Optional[TermFair] = None + actual_instance: Optional[Union[TermFair, TermRegex]] = None + one_of_schemas: Set[str] = { "TermFair", "TermRegex" } + + model_config = ConfigDict( + validate_assignment=True, + protected_namespaces=(), + ) + + + discriminator_value_class_map: Dict[str, str] = { + } + + def __init__(self, *args, **kwargs) -> None: + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @field_validator('actual_instance') + def actual_instance_must_validate_oneof(cls, v): + instance = Term.model_construct() + error_messages = [] + match = 0 + # validate data type: TermRegex + if not isinstance(v, TermRegex): + error_messages.append(f"Error! Input type `{type(v)}` is not `TermRegex`") + else: + match += 1 + # validate data type: TermFair + if not isinstance(v, TermFair): + error_messages.append(f"Error! Input type `{type(v)}` is not `TermFair`") + else: + match += 1 + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when setting `actual_instance` in Term with oneOf schemas: TermFair, TermRegex. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when setting `actual_instance` in Term with oneOf schemas: TermFair, TermRegex. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: Union[str, Dict[str, Any]]) -> Self: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Returns the object represented by the json string""" + instance = cls.model_construct() + error_messages = [] + match = 0 + + # deserialize data into TermRegex + try: + instance.actual_instance = TermRegex.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # deserialize data into TermFair + try: + instance.actual_instance = TermFair.from_json(json_str) + match += 1 + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + + if match > 1: + # more than 1 match + raise ValueError("Multiple matches found when deserializing the JSON string into Term with oneOf schemas: TermFair, TermRegex. Details: " + ", ".join(error_messages)) + elif match == 0: + # no match + raise ValueError("No match found when deserializing the JSON string into Term with oneOf schemas: TermFair, TermRegex. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> Optional[Union[Dict[str, Any], TermFair, TermRegex]]: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict): + return self.actual_instance.to_dict() + else: + # primitive type + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.model_dump()) + + diff --git a/regexsolver/_generated/models/term_fair.py b/regexsolver/_generated/models/term_fair.py new file mode 100644 index 0000000..33a1d00 --- /dev/null +++ b/regexsolver/_generated/models/term_fair.py @@ -0,0 +1,105 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from regexsolver._generated.models.term_fair_metadata import TermFairMetadata +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class TermFair(BaseModel): + """ + Term encoded as FAIR (Fast Automaton Internal Representation), a stable, signed format used internally by the engine. + """ # noqa: E501 + type: StrictStr + value: StrictStr = Field(description="FAIR payload.") + metadata: Optional[TermFairMetadata] = None + __properties: ClassVar[List[str]] = ["type", "value", "metadata"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['fair']): + raise ValueError("must be one of enum values ('fair')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of TermFair from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + * OpenAPI `readOnly` fields are excluded. + """ + excluded_fields: Set[str] = set([ + "metadata", + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of metadata + if self.metadata: + _dict['metadata'] = self.metadata.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of TermFair from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "value": obj.get("value"), + "metadata": TermFairMetadata.from_dict(obj["metadata"]) if obj.get("metadata") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/term_fair_metadata.py b/regexsolver/_generated/models/term_fair_metadata.py new file mode 100644 index 0000000..bbaf575 --- /dev/null +++ b/regexsolver/_generated/models/term_fair_metadata.py @@ -0,0 +1,88 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class TermFairMetadata(BaseModel): + """ + Metadata describing properties of a FAIR automaton. + """ # noqa: E501 + deterministic: Optional[StrictBool] = Field(default=None, description="Whether this FAIR encodes a deterministic automaton. Only a deterministic FAIR guarantees consistent string ordering across paginated /generate/strings requests; call /compute/determinize first if this is false.") + __properties: ClassVar[List[str]] = ["deterministic"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of TermFairMetadata from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of TermFairMetadata from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "deterministic": obj.get("deterministic") + }) + return _obj + + diff --git a/regexsolver/_generated/models/term_regex.py b/regexsolver/_generated/models/term_regex.py new file mode 100644 index 0000000..b05ba1a --- /dev/null +++ b/regexsolver/_generated/models/term_regex.py @@ -0,0 +1,97 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class TermRegex(BaseModel): + """ + Term encoded as a regular expression pattern. + """ # noqa: E501 + type: StrictStr + value: StrictStr = Field(description="Regular expression pattern.") + __properties: ClassVar[List[str]] = ["type", "value"] + + @field_validator('type') + def type_validate_enum(cls, value): + """Validates the enum""" + if value not in set(['regex']): + raise ValueError("must be one of enum values ('regex')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of TermRegex from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of TermRegex from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "type": obj.get("type"), + "value": obj.get("value") + }) + return _obj + + diff --git a/regexsolver/_generated/models/term_request.py b/regexsolver/_generated/models/term_request.py new file mode 100644 index 0000000..920e644 --- /dev/null +++ b/regexsolver/_generated/models/term_request.py @@ -0,0 +1,98 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict +from typing import Any, ClassVar, Dict, List, Optional +from regexsolver._generated.models.request_options import RequestOptions +from regexsolver._generated.models.term import Term +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class TermRequest(BaseModel): + """ + Request carrying a single term. + """ # noqa: E501 + term: Term + options: Optional[RequestOptions] = None + __properties: ClassVar[List[str]] = ["term", "options"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of TermRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of term + if self.term: + _dict['term'] = self.term.to_dict() + # override the default output from pydantic by calling `to_dict()` of options + if self.options: + _dict['options'] = self.options.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of TermRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "term": Term.from_dict(obj["term"]) if obj.get("term") is not None else None, + "options": RequestOptions.from_dict(obj["options"]) if obj.get("options") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/models/two_terms_request.py b/regexsolver/_generated/models/two_terms_request.py new file mode 100644 index 0000000..e84c0c3 --- /dev/null +++ b/regexsolver/_generated/models/two_terms_request.py @@ -0,0 +1,103 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field +from typing import Any, ClassVar, Dict, List, Optional +from typing_extensions import Annotated +from regexsolver._generated.models.request_options import RequestOptions +from regexsolver._generated.models.term import Term +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class TwoTermsRequest(BaseModel): + """ + Request carrying exactly 2 terms. + """ # noqa: E501 + terms: Annotated[List[Term], Field(min_length=2, max_length=2)] = Field(description="Exactly 2 terms.") + options: Optional[RequestOptions] = None + __properties: ClassVar[List[str]] = ["terms", "options"] + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of TwoTermsRequest from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in terms (list) + _items = [] + if self.terms: + for _item_terms in self.terms: + if _item_terms: + _items.append(_item_terms.to_dict()) + _dict['terms'] = _items + # override the default output from pydantic by calling `to_dict()` of options + if self.options: + _dict['options'] = self.options.to_dict() + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of TwoTermsRequest from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "terms": [Term.from_dict(_item) for _item in obj["terms"]] if obj.get("terms") is not None else None, + "options": RequestOptions.from_dict(obj["options"]) if obj.get("options") is not None else None + }) + return _obj + + diff --git a/regexsolver/_generated/py.typed b/regexsolver/_generated/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/regexsolver/_generated/rest.py b/regexsolver/_generated/rest.py new file mode 100644 index 0000000..094286e --- /dev/null +++ b/regexsolver/_generated/rest.py @@ -0,0 +1,226 @@ +# coding: utf-8 + +""" + RegexSolver API + + No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + + The version of the OpenAPI document: 1.1.0 + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import io +import json +import re +import ssl +from typing import Optional, Union + +import aiohttp +import aiohttp_retry + +from regexsolver._generated.exceptions import ApiException, ApiValueError + +RESTResponseType = aiohttp.ClientResponse + +ALLOW_RETRY_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'}) + +class RESTResponse(io.IOBase): + + def __init__(self, resp) -> None: + self.response = resp + self.status = resp.status + self.reason = resp.reason + self.data = None + + async def read(self): + if self.data is None: + self.data = await self.response.read() + return self.data + + @property + def headers(self): + """Returns a CIMultiDictProxy of response headers.""" + return self.response.headers + + def getheaders(self): + """Returns a CIMultiDictProxy of the response headers; use ``headers`` instead.""" + return self.response.headers + + def getheader(self, name, default=None): + """Returns a given response header; use ``headers.get()`` instead.""" + return self.response.headers.get(name, default) + + +class RESTClientObject: + + def __init__(self, configuration) -> None: + + # maxsize is number of requests to host that are allowed in parallel + self.maxsize = configuration.connection_pool_maxsize + + self.ssl_context = ssl.create_default_context( + cafile=configuration.ssl_ca_cert, + cadata=configuration.ca_cert_data, + ) + if configuration.cert_file: + self.ssl_context.load_cert_chain( + configuration.cert_file, keyfile=configuration.key_file + ) + + if not configuration.verify_ssl: + self.ssl_context.check_hostname = False + self.ssl_context.verify_mode = ssl.CERT_NONE + + self.proxy = configuration.proxy + self.proxy_headers = configuration.proxy_headers + + retries = configuration.retries + if retries is None: + self._effective_retry_options = None + elif isinstance(retries, aiohttp_retry.RetryOptionsBase): + self._effective_retry_options = retries + elif isinstance(retries, int): + self._effective_retry_options = aiohttp_retry.ExponentialRetry( + attempts=retries, + factor=2.0, + start_timeout=0.1, + max_timeout=120.0 + ) + else: + self._effective_retry_options = None + + self.pool_manager: Optional[aiohttp.ClientSession] = None + self.retry_client: Optional[aiohttp_retry.RetryClient] = None + + async def close(self) -> None: + if self.pool_manager: + await self.pool_manager.close() + if self.retry_client is not None: + await self.retry_client.close() + + async def request( + self, + method, + url, + headers=None, + body=None, + post_params=None, + _request_timeout=None + ): + """Execute request + + :param method: http request method + :param url: http request url + :param headers: http request headers + :param body: request json body, for `application/json` + :param post_params: request post parameters, + `application/x-www-form-urlencoded` + and `multipart/form-data` + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + """ + method = method.upper() + assert method in [ + 'GET', + 'HEAD', + 'DELETE', + 'POST', + 'PUT', + 'PATCH', + 'OPTIONS' + ] + + if post_params and body: + raise ApiValueError( + "body parameter cannot be used with post_params parameter." + ) + + post_params = post_params or {} + headers = headers or {} + # url already contains the URL query string + timeout = _request_timeout or 5 * 60 + + if 'Content-Type' not in headers: + headers['Content-Type'] = 'application/json' + + args = { + "method": method, + "url": url, + "timeout": timeout, + "headers": headers + } + + if self.proxy: + args["proxy"] = self.proxy + if self.proxy_headers: + args["proxy_headers"] = self.proxy_headers + + # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE` + if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']: + if re.search('json', headers['Content-Type'], re.IGNORECASE): + if body is not None: + body = json.dumps(body) + args["data"] = body + elif headers['Content-Type'] == 'application/x-www-form-urlencoded': + args["data"] = aiohttp.FormData(post_params) + elif headers['Content-Type'] == 'multipart/form-data': + # must del headers['Content-Type'], or the correct + # Content-Type which generated by aiohttp + del headers['Content-Type'] + data = aiohttp.FormData() + for param in post_params: + k, v = param + if isinstance(v, tuple) and len(v) == 3: + data.add_field( + k, + value=v[1], + filename=v[0], + content_type=v[2] + ) + else: + # Ensures that dict objects are serialized + if isinstance(v, dict): + v = json.dumps(v) + elif isinstance(v, int): + v = str(v) + data.add_field(k, v) + args["data"] = data + + # Pass a `bytes` or `str` parameter directly in the body to support + # other content types than Json when `body` argument is provided + # in serialized form + elif isinstance(body, str) or isinstance(body, bytes): + args["data"] = body + else: + # Cannot generate the request from given parameters + msg = """Cannot prepare a request message for provided + arguments. Please check that your arguments match + declared content type.""" + raise ApiException(status=0, reason=msg) + + pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient] + + # https pool manager + if self.pool_manager is None: + self.pool_manager = aiohttp.ClientSession( + connector=aiohttp.TCPConnector(limit=self.maxsize, ssl=self.ssl_context), + trust_env=True, + ) + pool_manager = self.pool_manager + + if self._effective_retry_options is not None and method in ALLOW_RETRY_METHODS: + if self.retry_client is None: + self.retry_client = aiohttp_retry.RetryClient( + client_session=self.pool_manager, + retry_options=self._effective_retry_options + ) + pool_manager = self.retry_client + + r = await pool_manager.request(**args) + + return RESTResponse(r) diff --git a/regexsolver/clients/__init__.py b/regexsolver/clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/regexsolver/clients/asynchronous.py b/regexsolver/clients/asynchronous.py new file mode 100644 index 0000000..a64d750 --- /dev/null +++ b/regexsolver/clients/asynchronous.py @@ -0,0 +1,974 @@ +import asyncio +import logging +import random +import time +import weakref +from typing import Awaitable, Callable, List, Optional, Union + +from pydantic import ValidationError + +from regexsolver._generated import ( + AccountApi, + AnalyzeApi, + ApiClient, + ApiException, + ComputeApi, + Configuration, + ErrorResponse, + ExecutionOptions, + FairResponseOptions, + GenerateApi, + GenerateStringsRequest, + MultiTermsRequest, + RepeatRequest, + RequestOptions, + ResponseOptions, + TermRequest, + TwoTermsRequest, +) +from regexsolver.clients.rate_limiter import get_rate_limiter +from regexsolver.exceptions import ( + ApiError, + AutomatonTooManyStatesError, + BadRequestError, + FairSyntaxError, + ForbiddenError, + InternalServerError, + InvalidJsonError, + InvalidNumberOfStringsToGenerateError, + InvalidTokenError, + MissingOrMalformedTokenError, + NotFoundError, + QuotaExceededError, + RegexSolverError, + RegexSyntaxError, + TimeoutExceededError, + TimeoutTooLargeError, + TooFewTermsError, + TooManyRequestsError, + TooManyTermsError, + UnauthorizedError, +) +from regexsolver.models.account_limits import AccountLimits +from regexsolver.models.cardinality import Cardinality, Infinite, Integer +from regexsolver.models.generate_order import CharacterOrder, PathOrder +from regexsolver.models.length import Length +from regexsolver.models.response_format import ResponseFormat +from regexsolver.models.term import FairTerm, Term + +logger = logging.getLogger(__name__) + +# Retry policy for 429 responses: retry as long as the total wait stays +# within the budget, adding full jitter on top of `Retry-After` so concurrent +# waiters do not re-collide as a single burst. The values are shared across +# all the official clients — change them together. +_RETRY_BUDGET_S = 300.0 +_JITTER_BASE_S = 0.25 +_JITTER_CAP_S = 2.0 +_DEFAULT_RETRY_AFTER_S = 1.0 + + +def _get_retry_after(headers) -> float: + """Case-insensitively read the Retry-After header, in seconds.""" + for key, value in (headers or {}).items(): + if str(key).lower() == "retry-after": + try: + return float(value) + except (TypeError, ValueError): + break + return _DEFAULT_RETRY_AFTER_S + + +def _build_request(model, **kwargs): + """Build a generated request model, keeping pydantic out of the public surface. + + The generated models carry the constraints declared in openapi.yaml (`terms` + minItems, `limit` range), so an invalid call is rejected before it is sent -- + which is good, it saves a round trip. But the raw `pydantic.ValidationError` + is not a `RegexSolverError`, so callers writing `except RegexSolverError` + would miss it. Translate it into the same error the API would have returned. + """ + try: + return model(**kwargs) + except ValidationError as e: + raise _map_validation_error(e) from e + + +def _map_validation_error(e: ValidationError) -> RegexSolverError: + errors = e.errors() + fields = {str(err["loc"][0]) for err in errors if err.get("loc")} + message = "; ".join( + f"{'.'.join(str(part) for part in err.get('loc', ()))}: {err['msg']}" + for err in errors + ) + + if "terms" in fields: + return TooFewTermsError(message, status_code=400) + if fields & {"limit", "offset"}: + return InvalidNumberOfStringsToGenerateError(message, status_code=400) + return BadRequestError(message, status_code=400) + + +class AsyncRegexSolverClient: + """The Asynchronous Client for RegexSolver. + + Provides non-blocking access to all RegexSolver API endpoints. + Can be used as a standalone object or as an `async with` context manager. + """ + + def __init__( + self, + api_token: str, + base_url: str = "https://api.regexsolver.com/v1", + auto_batch: bool = True, + max_terms_per_request: Optional[int] = None, + ): + if not api_token: + raise ValueError("api_token is required") + if max_terms_per_request is not None and max_terms_per_request < 2: + raise ValueError("max_terms_per_request must be at least 2") + + logger.debug("Initializing AsyncRegexSolverClient.") + self.configuration = Configuration(host=base_url, access_token=api_token) + self.api_client = ApiClient(self.configuration) + self.api_client.user_agent = "RegexSolver Python / 1.1.0" + + self._account_api = AccountApi(self.api_client) + self._analyze_api = AnalyzeApi(self.api_client) + self._compute_api = ComputeApi(self.api_client) + self._generate_api = GenerateApi(self.api_client) + + self._rate_limiter = get_rate_limiter(api_token) + + self._auto_batch = auto_batch + self._max_terms_per_request = max_terms_per_request + self._limits: Optional[AccountLimits] = None + # Created lazily: asyncio primitives must be born on the running loop. + self._limits_lock: Optional[asyncio.Lock] = None + + # Ensure the underlying aiohttp session is closed when the client is GC'd. + self._finalizer = weakref.finalize(self, self._run_cleanup, self.api_client) + + @staticmethod + def _run_cleanup(api_client: ApiClient): + """Finalizer callback to safely close the async client. + + Since we cannot await in a finalizer, we try to create a task in the + currently running loop, or just let the session be collected by aiohttp. + """ + try: + loop = asyncio.get_running_loop() + if loop.is_running(): + loop.create_task(api_client.close()) + except RuntimeError: + # No loop is running, we can't do much here. + # aiohttp will eventually emit a warning about unclosed session. + pass + + async def aclose(self): + """Closes the underlying HTTP client session.""" + if self._finalizer.detach(): + logger.debug("Closing AsyncRegexSolverClient.") + await self.api_client.close() + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.aclose() + + # --- HELPER --- + async def _execute_with_retry(self, api_method, **kwargs): + attempt = 0 + first_failure_at: Optional[float] = None + while True: + await self._rate_limiter.wait() + if attempt > 0: + await asyncio.sleep( + random.uniform(0.0, min(_JITTER_BASE_S * 2**attempt, _JITTER_CAP_S)) + ) + try: + return await api_method(**kwargs) + except ApiException as e: + if e.status != 429: + raise self._map_error(e) + + retry_after = _get_retry_after(e.headers) + now = time.monotonic() + if first_failure_at is None: + first_failure_at = now + if now - first_failure_at + retry_after > _RETRY_BUDGET_S: + raise self._map_error(e) + + logger.debug( + "429 Too Many Requests hit. " + f"Triggering rate limiter for {retry_after} seconds." + ) + self._rate_limiter.trigger(retry_after) + attempt += 1 + + def _map_error(self, e: ApiException) -> Exception: + status_code = e.status + error_msg = e.reason + error_code = None + + if e.body: + try: + parsed_error = ErrorResponse.from_json(e.body) + if parsed_error is not None: + error_msg = parsed_error.error + error_code = parsed_error.error_code + else: + error_msg = e.body + except Exception: + error_msg = e.body + + error_msg = str(error_msg) if error_msg else "Unknown API Error" + error_code = str(error_code) if error_code else "UnknownError" + + logger.error( + f"RegexSolver API request failed with status {status_code}: {error_code}/{error_msg}" + ) + + if status_code == 400: + if error_code == "InvalidJson": + return InvalidJsonError(error_msg, status_code=status_code, body=e.body) + if error_code == "TooManyTerms": + return TooManyTermsError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "TooFewTerms": + return TooFewTermsError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "TimeoutTooLarge": + return TimeoutTooLargeError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "TimeoutExceeded": + return TimeoutExceededError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "InvalidNumberOfStringsToGenerate": + return InvalidNumberOfStringsToGenerateError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "AutomatonTooManyStates": + return AutomatonTooManyStatesError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "RegexSyntaxError": + return RegexSyntaxError(error_msg, status_code=status_code, body=e.body) + if error_code == "FairSyntaxError": + return FairSyntaxError(error_msg, status_code=status_code, body=e.body) + return BadRequestError(error_msg, status_code=status_code, body=e.body) + + elif status_code == 401: + if error_code == "MissingOrMalformedToken": + return MissingOrMalformedTokenError( + error_msg, status_code=status_code, body=e.body + ) + if error_code == "InvalidToken": + return InvalidTokenError( + error_msg, status_code=status_code, body=e.body + ) + return UnauthorizedError(error_msg, status_code=status_code, body=e.body) + + elif status_code == 403: + if error_code == "QuotaExceeded": + return QuotaExceededError( + error_msg, status_code=status_code, body=e.body + ) + return ForbiddenError(error_msg, status_code=status_code, body=e.body) + + elif status_code == 404: + return NotFoundError(error_msg, status_code=status_code, body=e.body) + + elif status_code == 429: + msg = ( + "Max retries exceeded for 429 Too Many Requests." + if error_msg == "Unknown API Error" + else error_msg + ) + return TooManyRequestsError(msg, status_code=429) + + elif status_code == 500: + return InternalServerError(error_msg, status_code=status_code, body=e.body) + + else: + return ApiError(error_msg, status_code=status_code, body=e.body) + + def _build_options( + self, + execution_timeout: Optional[int] = None, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + ) -> RequestOptions: + if deterministic is not None and response_format is not None: + fmt = ( + ResponseFormat(response_format) + if isinstance(response_format, str) + else response_format + ) + if fmt != ResponseFormat.FAIR: + raise ValueError( + f"deterministic can only be used with response_format=ResponseFormat.FAIR, got {fmt!r}" + ) + options = RequestOptions(schemaVersion=1) + if execution_timeout is not None: + options.execution = ExecutionOptions(timeout=execution_timeout) + response_opts = ResponseOptions() + if response_format is not None: + response_opts.format = str(response_format) + if deterministic is not None: + response_opts.fair = FairResponseOptions(deterministic=deterministic) + if response_format is None: + # FairResponseOptions is only applied when the response format is + # "fair", so default to it to honor the deterministic request. + response_opts.format = str(ResponseFormat.FAIR) + if response_format is not None or deterministic is not None: + options.response = response_opts + return options + + # --- ACCOUNT --- + async def get_account_limits(self) -> AccountLimits: + """Fetches the plan limits applying to the account. + + The call never consumes request quota (it is only rate-limited) and + the result is cached on the client, so calling it again is free. The + cached `max_terms_count` also drives auto-batching. + + Returns: + AccountLimits: The five plan limits. + """ + if self._limits is not None: + return self._limits + if self._limits_lock is None: + self._limits_lock = asyncio.Lock() + async with self._limits_lock: + if self._limits is None: + response = await self._execute_with_retry(self._account_api.limits) + self._limits = AccountLimits.from_dto(response.data) + return self._limits + + # --- BATCHING --- + def _effective_max_terms(self) -> Optional[int]: + """The largest term count to send in one request, when known.""" + server_max = self._limits.max_terms_count if self._limits else None + if self._max_terms_per_request is not None: + if server_max is not None: + return min(self._max_terms_per_request, server_max) + return self._max_terms_per_request + return server_max + + async def _run_nary( + self, + api_method, + terms, + response_format: Optional[Union[ResponseFormat, str]], + deterministic: Optional[bool], + execution_timeout: Optional[int], + ) -> Term: + """Run an n-ary operation (concat/intersection/union), transparently + splitting the terms into several requests when they exceed the + account's terms-per-request limit (auto-batching). + """ + terms = list(terms) + + async def call(batch: List[Term], final: bool) -> Term: + # Intermediate results are fed straight back into the next + # request, so only the final call carries the caller's response + # options; execution_timeout bounds every constituent request. + request = _build_request( + MultiTermsRequest, + terms=[t.to_dto() for t in batch], + options=self._build_options( + execution_timeout, + response_format if final else None, + deterministic if final else None, + ), + ) + response = await self._execute_with_retry( + api_method, multi_terms_request=request + ) + return Term.from_dto(response.data) + + max_terms = self._effective_max_terms() if self._auto_batch else None + if max_terms is not None and len(terms) > max_terms: + return await self._fold(call, terms, max_terms) + + try: + return await call(terms, True) + except TooManyTermsError as too_many: + if not self._auto_batch or max_terms is not None: + raise + try: + await self.get_account_limits() + except RegexSolverError as fetch_error: + logger.debug(f"Fetching account limits failed: {fetch_error}") + raise too_many from None + max_terms = self._effective_max_terms() + if max_terms is None or max_terms < 2 or len(terms) <= max_terms: + raise + return await self._fold(call, terms, max_terms) + + @staticmethod + async def _fold( + call: Callable[[List[Term], bool], Awaitable[Term]], + terms: List[Term], + max_terms: int, + ) -> Term: + """Left fold: combine the first `max_terms` terms, then keep feeding + the accumulated result back with the next `max_terms - 1` terms. + Left-associative, so `concat` order is preserved; `union` and + `intersection` are commutative and unaffected. + """ + acc = await call(terms[:max_terms], False) + index = max_terms + while index < len(terms): + batch = [acc] + terms[index: index + max_terms - 1] + index += max_terms - 1 + acc = await call(batch, index >= len(terms)) + return acc + + # --- ANALYZE --- + async def get_cardinality( + self, term: Term, execution_timeout: Optional[int] = None + ) -> Cardinality: + """Computes how many unique strings the term matches. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Cardinality: An object representing either an exact Integer, a BigInteger, or Infinite cardinality. + """ + if term._cardinality is not None: + return term._cardinality + + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.cardinality, term_request=request + ) + + term._cardinality = Cardinality.from_dto(response.data) + term._set_properties_mixin(term._cardinality) + return term._cardinality + + async def get_length( + self, term: Term, execution_timeout: Optional[int] = None + ) -> Length: + """Computes the minimum and maximum length of strings matched by the term. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Length: An object containing `min` and `max` integers. Limits are `None` if unbounded or undefined. + """ + if term._length is not None: + return term._length + + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.length, term_request=request + ) + + term._length = Length.from_dto(response.data) + term._set_properties_mixin(term._length) + return term._length + + async def equivalent( + self, term1: Term, term2: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Checks if the two terms accept exactly the same language. + + Args: + term1: The first term. + term2: The second term to compare against. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if they are entirely equivalent, False otherwise. + """ + request = _build_request( + TwoTermsRequest, + terms=[term1.to_dto(), term2.to_dto()], + options=self._build_options(execution_timeout), + ) + response = await self._execute_with_retry( + self._analyze_api.equivalent, two_terms_request=request + ) + return response.data.value + + async def subset( + self, + term_subset: Term, + term_superset: Term, + execution_timeout: Optional[int] = None, + ) -> bool: + """Checks if the first term's language is a subset of the second term's language. + + Args: + term_subset: The term to test as the subset. + term_superset: The term representing the entire set space. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if every string matched by `term_subset` is also matched by `term_superset`. + """ + request = _build_request( + TwoTermsRequest, + terms=[term_subset.to_dto(), term_superset.to_dto()], + options=self._build_options(execution_timeout), + ) + response = await self._execute_with_retry( + self._analyze_api.subset, two_terms_request=request + ) + return response.data.value + + async def is_empty( + self, term: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Checks if the term matches no strings at all. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the language is completely empty. + """ + if term._empty is not None: + return term._empty + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.empty, term_request=request + ) + term._empty = response.data.value + if term._empty: + term._cardinality = Integer(0) + term._length = Length(min=None, max=None) + return response.data.value + + async def is_empty_string( + self, term: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Checks if the term matches only the empty string. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the term strictly matches the empty string ("") and nothing else. + """ + if term._empty_string is not None: + return term._empty_string + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.empty_string, term_request=request + ) + term._empty_string = response.data.value + if term._empty_string: + term._cardinality = Integer(1) + term._length = Length(min=0, max=0) + return response.data.value + + async def is_total( + self, term: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Checks if the term matches all possible strings. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the term matches every possible string. + """ + if term._total is not None: + return term._total + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.total, term_request=request + ) + term._total = response.data.value + if term._total: + term._cardinality = Infinite() + term._length = Length(min=0, max=None) + return response.data.value + + async def is_deterministic( + self, term: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Check if the term's automaton is deterministic. + Only a deterministic FAIR guarantees consistent string ordering across + paginated generate_strings requests; call determinize first if this is false. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the term's automaton is deterministic. + """ + if not isinstance(term, FairTerm): + return False + + if term._deterministic is not None: + return term._deterministic + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.deterministic, term_request=request + ) + term._deterministic = response.data.value + return response.data.value + + async def get_pattern( + self, term: Term, execution_timeout: Optional[int] = None + ) -> str: + """Returns a regular expression pattern that represents the term. + + Args: + term: The term to extract the pattern from. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + str: A valid regular expression string representing the language. + """ + pattern = term._pattern + if pattern is not None: + return pattern + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.pattern, term_request=request + ) + term._pattern = response.data.value + return response.data.value + + async def get_dot(self, term: Term, execution_timeout: Optional[int] = None) -> str: + """Builds a Graphviz DOT representation of the term's automaton. + + Args: + term: The term to visualize. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + str: The raw DOT syntax for Graphviz compilation. + """ + if term._dot is not None: + return term._dot + request = _build_request( + TermRequest, + term=term.to_dto(), options=self._build_options(execution_timeout) + ) + response = await self._execute_with_retry( + self._analyze_api.dot, term_request=request + ) + term._dot = response.data.value + return response.data.value + + # --- COMPUTE --- + async def concat( + self, + *terms: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Concatenates the given terms sequentially. + + Args: + *terms: A dynamic list of terms to concatenate in order. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A newly computed concatenated term. + """ + return await self._run_nary( + self._compute_api.concat, + terms, + response_format, + deterministic, + execution_timeout, + ) + + async def intersection( + self, + *terms: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the intersection of the given terms. + + Args: + *terms: A dynamic list of terms to intersect. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A term representing only strings matched by ALL provided terms. + """ + return await self._run_nary( + self._compute_api.intersection, + terms, + response_format, + deterministic, + execution_timeout, + ) + + async def union( + self, + *terms: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the union of the given terms. + + Args: + *terms: A dynamic list of terms to combine. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A term representing strings matched by ANY of the provided terms. + """ + return await self._run_nary( + self._compute_api.union, + terms, + response_format, + deterministic, + execution_timeout, + ) + + async def difference( + self, + base_term: Term, + excluded_term: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the difference between the two given terms. + + Args: + base_term: The base language term to subtract from. + excluded_term: The term whose language should be removed from the base. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A computed difference term. + """ + request = _build_request( + TwoTermsRequest, + terms=[base_term.to_dto(), excluded_term.to_dto()], + options=self._build_options( + execution_timeout, response_format, deterministic + ), + ) + response = await self._execute_with_retry( + self._compute_api.difference, two_terms_request=request + ) + return Term.from_dto(response.data) + + async def repeat( + self, + term: Term, + min_val: int, + max_val: Optional[int] = None, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Repeats a term between a minimum and maximum number of times. + + Args: + term: The term to repeat. + min_val: The inclusive lower bound of repetitions. + max_val: The inclusive upper bound. If None, repetitions are unbounded. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A computed repeated term. + """ + request = _build_request( + RepeatRequest, + term=term.to_dto(), + min=min_val, + max=max_val, + options=self._build_options( + execution_timeout, response_format, deterministic + ), + ) + response = await self._execute_with_retry( + self._compute_api.repeat, repeat_request=request + ) + return Term.from_dto(response.data) + + async def complement( + self, + term: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the complement of the given term. + + Args: + term: The term to complement. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: The complemented term. + """ + request = _build_request( + TermRequest, + term=term.to_dto(), + options=self._build_options( + execution_timeout, response_format, deterministic + ), + ) + response = await self._execute_with_retry( + self._compute_api.complement, term_request=request + ) + return Term.from_dto(response.data) + + async def determinize( + self, + term: Term, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes a deterministic FAIR automaton from the given term. + + A deterministic FAIR guarantees consistent string ordering across paginated + generate_strings requests. Use this when term.is_deterministic is False or None + before calling generate_strings with an offset. + + Args: + term: The term to determinize. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A deterministic FAIR. + """ + request = _build_request( + TermRequest, + term=term.to_dto(), + options=self._build_options(execution_timeout), + ) + response = await self._execute_with_retry( + self._compute_api.determinize, term_request=request + ) + return Term.from_dto(response.data) + + # --- GENERATE --- + async def generate_strings( + self, + term: Term, + limit: int, + offset: int, + execution_timeout: Optional[int] = None, + *, + path_order: Optional[Union[PathOrder, str]] = None, + character_order: Optional[Union[CharacterOrder, str]] = None, + seed: Optional[int] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + charset: Optional[str] = None, + ) -> List[str]: + """Generates up to `limit` distinct strings matched by `term`, skipping the first `offset` strings. + + Args: + term: The term to sample generated strings from. + limit: The maximum number of unique strings to return. + offset: Number of matched strings to skip before starting to collect the results. Used for pagination. + execution_timeout: Timeout in milliseconds for the operation. + path_order: Order in which the paths (shapes) of the language are + scheduled (sweep, interleave or shuffled). Defaults to sweep. + character_order: Order in which the strings within each path are + produced (ascending or shuffled). Defaults to ascending. + seed: Seed behind the shuffled modes. The default seed is fixed, + so two calls sharing a seed generate the same strings and + `offset` pages through them consistently. + min_length: Shortest string to generate. Shorter strings are left + out of the enumeration entirely, `offset` never counting them. + max_length: Longest string to generate. + charset: Restricts generation to the given characters, e.g. + `[a-z]`. Paths requiring a character outside it are dropped. + + Returns: + List[str]: A list of strings that match the term. + """ + kwargs = dict( + term=term.to_dto(), + limit=limit, + offset=offset, + options=self._build_options(execution_timeout), + ) + if path_order is not None: + kwargs["path_order"] = str(path_order) + if character_order is not None: + kwargs["character_order"] = str(character_order) + if seed is not None: + kwargs["seed"] = seed + if min_length is not None: + kwargs["min_length"] = min_length + if max_length is not None: + kwargs["max_length"] = max_length + if charset is not None: + kwargs["charset"] = charset + + request = _build_request(GenerateStringsRequest, **kwargs) + response = await self._execute_with_retry( + self._generate_api.strings, generate_strings_request=request + ) + + return response.data.strings.value diff --git a/regexsolver/clients/rate_limiter.py b/regexsolver/clients/rate_limiter.py new file mode 100644 index 0000000..ee33078 --- /dev/null +++ b/regexsolver/clients/rate_limiter.py @@ -0,0 +1,52 @@ +import asyncio +import logging +import threading +import time +from typing import Dict + +logger = logging.getLogger(__name__) + + +class RateLimiter: + """Shared across all client instances with the same API token. + + Holds a single deadline on the monotonic clock. `trigger` keeps the later + of the current and the new deadline, so a longer `Retry-After` arriving + while the limiter is already engaged is never dropped. `wait` sleeps until + the deadline and re-checks it after every wake, so a deadline extended by + a concurrent 429 is honored too. Being a plain timestamp, the limiter is + not bound to any event loop and is safe to share across loops. + """ + + def __init__(self): + self._deadline = 0.0 + + def trigger(self, retry_after: float) -> None: + """Blocks operations for `retry_after` seconds from now. + + Keeps the later deadline when one is already pending. + """ + deadline = time.monotonic() + retry_after + if deadline > self._deadline: + logger.debug( + f"Rate limit triggered. Delaying operations for {retry_after} seconds." + ) + self._deadline = deadline + + async def wait(self) -> None: + """Asynchronously waits until the rate limit is no longer triggered.""" + while (remaining := self._deadline - time.monotonic()) > 0: + await asyncio.sleep(remaining) + + +_rate_limiters: Dict[str, RateLimiter] = {} +_registry_lock = threading.Lock() + + +def get_rate_limiter(api_token: str) -> RateLimiter: + """Returns the RateLimiter shared by every client using the given token.""" + with _registry_lock: + if api_token not in _rate_limiters: + logger.debug("Creating new RateLimiter instance.") + _rate_limiters[api_token] = RateLimiter() + return _rate_limiters[api_token] diff --git a/regexsolver/clients/synchronous.py b/regexsolver/clients/synchronous.py new file mode 100644 index 0000000..8ab095c --- /dev/null +++ b/regexsolver/clients/synchronous.py @@ -0,0 +1,509 @@ +import asyncio +import logging +import threading +import weakref +from typing import List, Optional, Union + +from regexsolver.clients.asynchronous import AsyncRegexSolverClient +from regexsolver.models.account_limits import AccountLimits +from regexsolver.models.cardinality import Cardinality +from regexsolver.models.generate_order import CharacterOrder, PathOrder +from regexsolver.models.length import Length +from regexsolver.models.response_format import ResponseFormat +from regexsolver.models.term import Term + +logger = logging.getLogger(__name__) + +# Global state for the shared background event loop +_SHARED_LOOP: Optional[asyncio.AbstractEventLoop] = None +_SHARED_THREAD: Optional[threading.Thread] = None +_SHARED_LOCK = threading.Lock() + + +def _get_or_create_shared_loop() -> asyncio.AbstractEventLoop: + """Retrieves the shared global event loop, creating and starting it if necessary.""" + global _SHARED_LOOP, _SHARED_THREAD + with _SHARED_LOCK: + if ( + _SHARED_LOOP is None + or _SHARED_THREAD is None + or not _SHARED_THREAD.is_alive() + ): + logger.debug("Starting shared RegexSolver background event loop thread.") + _SHARED_LOOP = asyncio.new_event_loop() + _SHARED_THREAD = threading.Thread( + target=_SHARED_LOOP.run_forever, + name="RegexSolverSyncWorker", + daemon=True, + ) + _SHARED_THREAD.start() + return _SHARED_LOOP + + +class RegexSolverClient: + """Synchronous Client for RegexSolver. + + Exposes all endpoints synchronously by managing a shared background event loop. + While it supports manual `.close()`, it is best used as a context manager. + """ + + def __init__( + self, + api_token: str, + base_url: str = "https://api.regexsolver.com/v1", + auto_batch: bool = True, + max_terms_per_request: Optional[int] = None, + ): + logger.debug("Initializing RegexSolverClient.") + self._loop = _get_or_create_shared_loop() + self._aio = AsyncRegexSolverClient( + api_token, base_url, auto_batch, max_terms_per_request + ) + + # Ensure the async client is closed even if the user forgets to call close() or use 'with' + self._finalizer = weakref.finalize( + self, self._run_cleanup, self._aio, self._loop + ) + + @staticmethod + def _run_cleanup( + aio_client: AsyncRegexSolverClient, loop: asyncio.AbstractEventLoop + ): + """Finalizer callback to safely close the async client in the background loop.""" + if loop.is_running(): + logger.debug("Closing RegexSolverClient.") + asyncio.run_coroutine_threadsafe(aio_client.aclose(), loop) + + def _run_sync(self, coro): + """Helper to execute async methods safely from the sync wrapper.""" + # Use a longer timeout or allow it to be infinite since the server + # already has its own execution_timeout logic. + future = asyncio.run_coroutine_threadsafe(coro, self._loop) + return future.result(timeout=None) + + def close(self): + """Closes the underlying HTTP client session. + + The shared background thread remains running for other client instances. + """ + if self._finalizer.detach(): + logger.debug("Closing RegexSolverClient.") + self._run_sync(self._aio.aclose()) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + # --- ACCOUNT --- + def get_account_limits(self) -> AccountLimits: + """Fetches the plan limits applying to the account. + + The call never consumes request quota (it is only rate-limited) and + the result is cached on the client, so calling it again is free. The + cached `max_terms_count` also drives auto-batching. + + Returns: + AccountLimits: The five plan limits. + """ + return self._run_sync(self._aio.get_account_limits()) + + # --- ANALYZE --- + def get_cardinality( + self, term: Term, execution_timeout: Optional[int] = None + ) -> Cardinality: + """Computes how many unique strings the term matches. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Cardinality: An object representing either an exact Integer, a BigInteger, or Infinite cardinality. + """ + return self._run_sync(self._aio.get_cardinality(term, execution_timeout)) + + def get_length( + self, term: Term, execution_timeout: Optional[int] = None + ) -> Length: + """Computes the minimum and maximum length of strings matched by the term. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Length: An object containing `min` and `max` integers. Limits are `None` if unbounded or undefined. + """ + return self._run_sync(self._aio.get_length(term, execution_timeout)) + + def equivalent( + self, term1: Term, term2: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Checks if the two terms accept exactly the same language. + + Args: + term1: The first term. + term2: The second term to compare against. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if they are entirely equivalent, False otherwise. + """ + return self._run_sync(self._aio.equivalent(term1, term2, execution_timeout)) + + def subset( + self, + term_subset: Term, + term_superset: Term, + execution_timeout: Optional[int] = None, + ) -> bool: + """Checks if the first term's language is a subset of the second term's language. + + Args: + term_subset: The term to test as the subset. + term_superset: The term representing the entire set space. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if every string matched by `term_subset` is also matched by `term_superset`. + """ + return self._run_sync( + self._aio.subset(term_subset, term_superset, execution_timeout) + ) + + def is_empty(self, term: Term, execution_timeout: Optional[int] = None) -> bool: + """Checks if the term matches no strings at all. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the language is completely empty. + """ + return self._run_sync(self._aio.is_empty(term, execution_timeout)) + + def is_empty_string( + self, term: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Checks if the term matches only the empty string. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the term strictly matches the empty string ("") and nothing else. + """ + return self._run_sync(self._aio.is_empty_string(term, execution_timeout)) + + def is_total(self, term: Term, execution_timeout: Optional[int] = None) -> bool: + """Checks if the term matches all possible strings. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the term matches every possible string. + """ + return self._run_sync(self._aio.is_total(term, execution_timeout)) + + def is_deterministic( + self, term: Term, execution_timeout: Optional[int] = None + ) -> bool: + """Check if the term's automaton is deterministic. + Only a deterministic FAIR guarantees consistent string ordering across + paginated generate_strings requests; call determinize first if this is false. + + Args: + term: The term to analyze. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + bool: True if the term's automaton is deterministic. + """ + return self._run_sync(self._aio.is_deterministic(term, execution_timeout)) + + def get_pattern(self, term: Term, execution_timeout: Optional[int] = None) -> str: + """Returns a regular expression pattern that represents the term. + + Args: + term: The term to extract the pattern from. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + str: A valid regular expression string representing the language. + """ + return self._run_sync(self._aio.get_pattern(term, execution_timeout)) + + def get_dot(self, term: Term, execution_timeout: Optional[int] = None) -> str: + """Builds a Graphviz DOT representation of the term's automaton. + + Args: + term: The term to visualize. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + str: The raw DOT syntax for Graphviz compilation. + """ + return self._run_sync(self._aio.get_dot(term, execution_timeout)) + + # --- COMPUTE --- + def concat( + self, + *terms: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Concatenates the given terms sequentially. + + Args: + *terms: A dynamic list of terms to concatenate in order. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A newly computed concatenated term. + """ + return self._run_sync( + self._aio.concat( + *terms, + response_format=response_format, + deterministic=deterministic, + execution_timeout=execution_timeout, + ) + ) + + def intersection( + self, + *terms: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the intersection of the given terms. + + Args: + *terms: A dynamic list of terms to intersect. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A term representing only strings matched by ALL provided terms. + """ + return self._run_sync( + self._aio.intersection( + *terms, + response_format=response_format, + deterministic=deterministic, + execution_timeout=execution_timeout, + ) + ) + + def union( + self, + *terms: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the union of the given terms. + + Args: + *terms: A dynamic list of terms to combine. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A term representing strings matched by ANY of the provided terms. + """ + return self._run_sync( + self._aio.union( + *terms, + response_format=response_format, + deterministic=deterministic, + execution_timeout=execution_timeout, + ) + ) + + def difference( + self, + base_term: Term, + excluded_term: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the difference between the two given terms. + + Args: + base_term: The base language term to subtract from. + excluded_term: The term whose language should be removed from the base. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A computed difference term. + """ + return self._run_sync( + self._aio.difference( + base_term, + excluded_term, + response_format=response_format, + deterministic=deterministic, + execution_timeout=execution_timeout, + ) + ) + + def repeat( + self, + term: Term, + min_val: int, + max_val: Optional[int] = None, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Repeats a term between a minimum and maximum number of times. + + Args: + term: The term to repeat. + min_val: The inclusive lower bound of repetitions. + max_val: The inclusive upper bound. If None, repetitions are unbounded. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A computed repeated term. + """ + return self._run_sync( + self._aio.repeat( + term, + min_val, + max_val, + response_format=response_format, + deterministic=deterministic, + execution_timeout=execution_timeout, + ) + ) + + def complement( + self, + term: Term, + response_format: Optional[Union[ResponseFormat, str]] = None, + deterministic: Optional[bool] = None, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes the complement of the given term. + + Args: + term: The term to complement. + response_format: The return format of the term (any, regex or fair). + deterministic: When True, guarantees the returned FAIR encodes a deterministic + automaton. Only valid with response_format=ResponseFormat.FAIR or when + response_format is unset. Raises ValueError otherwise. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: The complemented term. + """ + return self._run_sync( + self._aio.complement( + term, + response_format=response_format, + deterministic=deterministic, + execution_timeout=execution_timeout, + ) + ) + + def determinize( + self, + term: Term, + execution_timeout: Optional[int] = None, + ) -> Term: + """Computes a deterministic FAIR automaton from the given term. + + A deterministic FAIR guarantees consistent string ordering across paginated + generate_strings requests. Use this when term.is_deterministic is False or None + before calling generate_strings with an offset. + + Args: + term: The term to determinize. + execution_timeout: Timeout in milliseconds for the operation. + + Returns: + Term: A deterministic FAIR. + """ + return self._run_sync(self._aio.determinize(term, execution_timeout)) + + # --- GENERATE --- + def generate_strings( + self, + term: Term, + limit: int, + offset: int, + execution_timeout: Optional[int] = None, + *, + path_order: Optional[Union[PathOrder, str]] = None, + character_order: Optional[Union[CharacterOrder, str]] = None, + seed: Optional[int] = None, + min_length: Optional[int] = None, + max_length: Optional[int] = None, + charset: Optional[str] = None, + ) -> List[str]: + """Generates up to `limit` distinct strings matched by `term`, skipping the first `offset` strings. + + Args: + term: The term to sample generated strings from. + limit: The maximum number of unique strings to return. + offset: Number of matched strings to skip before starting to collect the results. Used for pagination. + execution_timeout: Timeout in milliseconds for the operation. + path_order: Order in which the paths (shapes) of the language are + scheduled (sweep, interleave or shuffled). Defaults to sweep. + character_order: Order in which the strings within each path are + produced (ascending or shuffled). Defaults to ascending. + seed: Seed behind the shuffled modes. The default seed is fixed, + so two calls sharing a seed generate the same strings and + `offset` pages through them consistently. + min_length: Shortest string to generate. Shorter strings are left + out of the enumeration entirely, `offset` never counting them. + max_length: Longest string to generate. + charset: Restricts generation to the given characters, e.g. + `[a-z]`. Paths requiring a character outside it are dropped. + + Returns: + List[str]: A list of strings that match the term. + """ + return self._run_sync( + self._aio.generate_strings( + term, + limit, + offset, + execution_timeout, + path_order=path_order, + character_order=character_order, + seed=seed, + min_length=min_length, + max_length=max_length, + charset=charset, + ) + ) diff --git a/regexsolver/details.py b/regexsolver/details.py deleted file mode 100644 index ec799c1..0000000 --- a/regexsolver/details.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, model_validator - - -class Cardinality(BaseModel): - """ - Class that represent the number of possible values. - """ - type: str - value: Optional[int] = None - - def is_infinite(self) -> bool: - """ - True if it has a finite number of values, False otherwise. - """ - if self.type == 'Infinite': - return True - else: - return False - - def __str__(self): - if self.type == 'Infinite': - return "Infinite" - elif self.type == 'BigInteger': - return 'BigInteger' - elif self.type == 'Integer': - return "Integer({})".format(self.value) - else: - return 'Unknown' - - -class Length(BaseModel): - """ - Contains the minimum and maximum length of possible values. - """ - - minimum: Optional[int] - maximum: Optional[int] - - @model_validator(mode="before") - def from_list(cls, values: list): - if len(values) != 2: - raise ValueError("List must contain exactly two elements") - return {'minimum': values[0], 'maximum': values[1]} - - def __str__(self): - return "Length[minimum={}, maximum={}]".format( - self.minimum, - self.maximum - ) - - -class Details(BaseModel): - """ - Contains details about the requested Term. - """ - type: str = 'details' - - cardinality: Cardinality - length: Length - empty: bool - total: bool - - def __str__(self): - return "Details[cardinality={}, length={}, empty={}, total={}]".format( - self.cardinality, - self.length, - self.empty, - self.total - ) diff --git a/regexsolver/exceptions.py b/regexsolver/exceptions.py new file mode 100644 index 0000000..876fc0d --- /dev/null +++ b/regexsolver/exceptions.py @@ -0,0 +1,144 @@ +from typing import Optional + + +class RegexSolverError(Exception): + """Base exception for all RegexSolver errors.""" + + pass + + +class ApiError(RegexSolverError): + """Base exception raised when the RegexSolver API returns an error response. + + Attributes: + status_code (Optional[int]): The HTTP status code returned by the API. + body (Optional[str]): The raw string body of the error response. + """ + + def __init__( + self, + message: str, + status_code: Optional[int] = None, + body: Optional[str] = None, + ): + super().__init__(message) + self.status_code = status_code + self.body = body + + +class BadRequestError(ApiError): + """Raised when the API returns a 400 Bad Request error.""" + + pass + + +class InvalidJsonError(BadRequestError): + """Raised when the provided JSON is invalid or cannot be parsed.""" + + pass + + +class TooManyTermsError(BadRequestError): + """Raised when the number of terms provided exceeds the maximum allowed.""" + + pass + + +class TooFewTermsError(BadRequestError): + """Raised when fewer terms are provided than the operation requires.""" + + pass + + +class TimeoutTooLargeError(BadRequestError): + """Raised when the requested `execution_timeout` exceeds the maximum allowed for your current plan.""" + + pass + + +class TimeoutExceededError(BadRequestError): + """Raised when the execution of the request exceeds the provided + `execution_timeout` or the maximum allowed for your current plan.""" + + pass + + +class InvalidNumberOfStringsToGenerateError(BadRequestError): + """Raised when the requested number of strings to generate is below the minimum or exceeds the maximum allowed.""" + + pass + + +class AutomatonTooManyStatesError(BadRequestError): + """Raised when the NFA/DFA exceeds the maximum allowed number of states for your current plan.""" + + pass + + +class RegexSyntaxError(BadRequestError): + """Raised when the provided regular expression has invalid syntax.""" + + pass + + +class FairSyntaxError(BadRequestError): + """Raised when the provided FAIR value is malformed or cannot be decoded.""" + + pass + + +class UnauthorizedError(ApiError): + """Raised when the API returns a 401 Unauthorized error.""" + + pass + + +class MissingOrMalformedTokenError(UnauthorizedError): + """Raised when the provided authentication token is missing or malformed.""" + + pass + + +class InvalidTokenError(UnauthorizedError): + """Raised when the provided authentication token is invalid.""" + + pass + + +class ForbiddenError(ApiError): + """Raised when the API returns a 403 Forbidden error.""" + + pass + + +class QuotaExceededError(ForbiddenError): + """Raised when your account's monthly compute quota has been exceeded.""" + + pass + + +class NotFoundError(ApiError): + """Raised when the API returns a 404 Not Found error. + + Indicates that the requested API endpoint or resource does not exist. + """ + + pass + + +class TooManyRequestsError(ApiError): + """Raised when the API returns a 429 Too Many Requests error and max retries are exceeded. + + Indicates that your requests-per-second (req/s) rate limit has been exceeded. + """ + + pass + + +class InternalServerError(ApiError): + """Raised when the API returns a 500 Internal Server Error. + + Indicates an unexpected failure or panic on the RegexSolver compute servers. + """ + + pass diff --git a/regexsolver/models/__init__.py b/regexsolver/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/regexsolver/models/account_limits.py b/regexsolver/models/account_limits.py new file mode 100644 index 0000000..c8ce771 --- /dev/null +++ b/regexsolver/models/account_limits.py @@ -0,0 +1,49 @@ +from dataclasses import dataclass + +from regexsolver._generated.models import AccountLimits as GeneratedAccountLimits + + +@dataclass(frozen=True) +class AccountLimits: + """The plan limits currently applying to the account. + + Attributes: + max_requests_count (int): Maximum number of requests allowed per billing period. + max_requests_rate (int): Maximum number of requests allowed per second. 0 means no rate limit is enforced. + max_terms_count (int): Maximum number of terms accepted in a single request. + max_timeout (int): Maximum execution timeout per request, in milliseconds. + max_states_count (int): Maximum number of automaton states an operation may build. + """ + + max_requests_count: int + max_requests_rate: int + max_terms_count: int + max_timeout: int + max_states_count: int + + @classmethod + def from_dto(cls, dto: GeneratedAccountLimits) -> "AccountLimits": + """Converts a generated API model into a high-level AccountLimits object. + + Args: + dto (GeneratedAccountLimits): The raw model from the generated API. + + Returns: + AccountLimits: A high-level instance carrying the five plan limits. + """ + return cls( + max_requests_count=dto.max_requests_count, + max_requests_rate=dto.max_requests_rate, + max_terms_count=dto.max_terms_count, + max_timeout=dto.max_timeout, + max_states_count=dto.max_states_count, + ) + + def __repr__(self) -> str: + return ( + f"" + ) diff --git a/regexsolver/models/cardinality.py b/regexsolver/models/cardinality.py new file mode 100644 index 0000000..06f0104 --- /dev/null +++ b/regexsolver/models/cardinality.py @@ -0,0 +1,93 @@ +from dataclasses import dataclass +from typing import Any, Optional, cast + +from regexsolver._generated.models import Cardinality as GeneratedCardinality +from regexsolver.models.term_properties_mixin import TermPropertiesMixin + + +class Cardinality(TermPropertiesMixin): + """Base class representing the number of unique strings matched by a term.""" + + @classmethod + def from_dto(cls, dto: GeneratedCardinality) -> "Cardinality": + """Converts a generated API model into a high-level Cardinality object. + + Args: + dto (GeneratedCardinality): The raw model from the generated API. + + Returns: + Cardinality: A specialized instance (Integer, BigInteger, or Infinite). + + Raises: + ValueError: If the DTO contains an unknown cardinality type. + """ + actual_model = cast(Any, getattr(dto, "actual_instance", dto)) + c_type = actual_model.type + + if c_type == "infinite": + return Infinite() + elif c_type == "bigInteger": + return BigInteger() + elif c_type == "integer": + return Integer(actual_model.value) + else: + raise ValueError(f"Unknown cardinality type: {c_type}") + + def __repr__(self) -> str: + return "" + + +@dataclass(frozen=True) +class Infinite(Cardinality): + """Indicates that the set of matched strings is infinite.""" + + def is_empty(self) -> Optional[bool]: + return False + + def is_empty_string(self) -> Optional[bool]: + return False + + def __repr__(self) -> str: + return "" + + +@dataclass(frozen=True) +class BigInteger(Cardinality): + """Indicates that the set of matched strings is finite but too large to be returned as a standard integer.""" + + def is_empty(self) -> Optional[bool]: + return False + + def is_empty_string(self) -> Optional[bool]: + return False + + def is_total(self) -> Optional[bool]: + return False + + def __repr__(self) -> str: + return "" + + +@dataclass(frozen=True) +class Integer(Cardinality): + """Indicates that the set of matched strings is finite and exactly calculable. + + Attributes: + value (int): The exact count of uniquely matched strings. + """ + + value: int + + def is_empty(self) -> bool: + return self.value == 0 + + def is_empty_string(self) -> Optional[bool]: + if self.value == 1: + return None + return False + + def is_total(self) -> Optional[bool]: + return False + + def __repr__(self) -> str: + return f"" diff --git a/regexsolver/models/generate_order.py b/regexsolver/models/generate_order.py new file mode 100644 index 0000000..e2fdab7 --- /dev/null +++ b/regexsolver/models/generate_order.py @@ -0,0 +1,43 @@ +from enum import Enum + + +class PathOrder(str, Enum): + """Order in which the paths of the language are scheduled when generating + strings — the *shapes* the term allows, as opposed to the characters + filling them. + + Attributes: + SWEEP: Expand one path in full, shortest first, before moving to the + next one. The cheapest way to page through a whole language. + INTERLEAVE: Cover every path once before any path yields a second + string. Best suited to deriving test cases. + SHUFFLED: Interleave with same-length paths visited in an order drawn + from the seed. + """ + + SWEEP = "sweep" + INTERLEAVE = "interleave" + SHUFFLED = "shuffled" + + def __str__(self) -> str: + return str(self.value) + + +class CharacterOrder(str, Enum): + """Order in which the strings within each path are produced when + generating strings. Orthogonal to PathOrder: it does not change *what* can + be generated, only which strings are reached first. + + Attributes: + ASCENDING: Expand each position from the low end of its character + range first — a stable order returning the smallest witnesses of a + path first. + SHUFFLED: A permutation drawn from the seed, so the strings look like + real inputs. Random in look only — generation stays reproducible. + """ + + ASCENDING = "ascending" + SHUFFLED = "shuffled" + + def __str__(self) -> str: + return str(self.value) diff --git a/regexsolver/models/length.py b/regexsolver/models/length.py new file mode 100644 index 0000000..8fc30c1 --- /dev/null +++ b/regexsolver/models/length.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass +from typing import Optional + +from regexsolver._generated.models import Length as GeneratedLength +from regexsolver.models.term_properties_mixin import TermPropertiesMixin + + +@dataclass +class Length(TermPropertiesMixin): + """Represents the minimum and maximum lengths of any string matched by the term. + + Attributes: + min (Optional[int]): The shortest possible matched string length, or None if the language is empty. + max (Optional[int]): The longest possible matched string length, or None if the length is unbounded. + """ + + min: Optional[int] + max: Optional[int] + + @classmethod + def from_dto(cls, dto: GeneratedLength) -> "Length": + """Converts a generated API model into a high-level Length object. + + Args: + dto (GeneratedLength): The raw model from the generated API. + + Returns: + Length: A high-level instance representing the min/max limits. + """ + return cls(min=dto.min, max=dto.max) + + def __repr__(self) -> str: + return f"" + + def is_empty(self) -> Optional[bool]: + return self.min is None and self.max is None + + def is_empty_string(self) -> Optional[bool]: + return self.min == 0 and self.max == 0 + + def is_total(self) -> Optional[bool]: + if self.min != 0 or self.max is not None: + return False + else: + return None + + def is_infinite(self) -> bool: + return self.min is not None and self.max is None diff --git a/regexsolver/models/response_format.py b/regexsolver/models/response_format.py new file mode 100644 index 0000000..0d97f39 --- /dev/null +++ b/regexsolver/models/response_format.py @@ -0,0 +1,18 @@ +from enum import Enum + + +class ResponseFormat(str, Enum): + """Defines the format in which the engine should return computed Terms. + + Attributes: + ANY: Allows the engine to return the result in the most efficient format. + FAIR: Fast Automaton Internal Representation, a stable internal format. + REGEX: Standard regular expression pattern. + """ + + ANY = "any" + FAIR = "fair" + REGEX = "regex" + + def __str__(self) -> str: + return str(self.value) diff --git a/regexsolver/models/term.py b/regexsolver/models/term.py new file mode 100644 index 0000000..5fbfcf2 --- /dev/null +++ b/regexsolver/models/term.py @@ -0,0 +1,183 @@ +import re +from abc import ABC, abstractmethod +from re import Pattern +from typing import Any, Optional + +from regexsolver._generated.models import Term as GeneratedTerm +from regexsolver._generated.models.term_fair import TermFair +from regexsolver._generated.models.term_regex import TermRegex +from regexsolver.models.cardinality import Cardinality +from regexsolver.models.length import Length +from regexsolver.models.term_properties_mixin import TermPropertiesMixin + + +EMPTY_LANGUAGE_PATTERN = "[]" +"""How the engine renders a language that matches no string at all.""" + + +class Term(ABC): + """Represents a mathematical term (Regex or FAIR) on which operations can be performed.""" + + def __init__(self, value: str): + self._value = value + + # Shared Cache (Internal) + self._cardinality: Optional[Cardinality] = None + self._length: Optional[Length] = None + self._empty: Optional[bool] = None + self._empty_string: Optional[bool] = None + self._total: Optional[bool] = None + self._pattern: Optional[str] = None + self._dot: Optional[str] = None + + self._compiled_regex: Optional[Pattern] = None + + @abstractmethod + def get_pattern(self) -> Optional[str]: + pass + + @abstractmethod + def get_fair(self) -> Optional[str]: + pass + + @abstractmethod + def to_dto(self) -> GeneratedTerm: + pass + + @abstractmethod + def serialize(self) -> str: + pass + + @classmethod + def regex(cls, pattern: str) -> "Term": + return RegexTerm(pattern) + + @classmethod + def fair(cls, payload: str) -> "Term": + return FairTerm(payload) + + # --- Shared Behavior --- + + def get_value(self) -> str: + return self._value + + def _set_properties_mixin(self, properties_mixin: TermPropertiesMixin): + empty = properties_mixin.is_empty() + if empty is not None: + self._empty = empty + + empty_string = properties_mixin.is_empty_string() + if empty_string is not None: + self._empty_string = empty_string + + total = properties_mixin.is_total() + if total is not None: + self._total = total + + def matches(self, string: str) -> bool: + """Client-side matching implementation.""" + pattern = self.get_pattern() + if pattern is None: + raise RuntimeError( + "The regex pattern of this term is not defined yet, call get_pattern() on the client to set it." + ) + + # The engine renders the empty language as "[]". By definition it matches + # nothing, and `re` rejects the pattern outright. + if pattern == EMPTY_LANGUAGE_PATTERN: + return False + + if self._compiled_regex is None: + try: + self._compiled_regex = re.compile(rf"\A(?:{pattern})\Z", re.DOTALL) + except re.error as e: + raise ValueError( + f"Invalid regular expression for Python's `re` engine: {pattern}" + ) from e + + return self._compiled_regex.match(string) is not None + + @classmethod + def deserialize(cls, serialized: str) -> Optional["Term"]: + if not serialized or "=" not in serialized: + return None + + index = serialized.find("=") + type_str = serialized[:index] + val = serialized[index + 1 :] + + if type_str.lower() == "regex": + return cls.regex(val) + elif type_str.lower() == "fair": + return cls.fair(val) + + return None + + @classmethod + def from_dto(cls, dto: GeneratedTerm) -> "Term": + actual_instance = dto.actual_instance + if actual_instance is None: + raise RuntimeError("Invalid Term DTO provided.") + if isinstance(actual_instance, TermFair): + deterministic = ( + actual_instance.metadata.deterministic + if actual_instance.metadata is not None + else None + ) + return FairTerm(actual_instance.value, deterministic=deterministic) + return cls.regex(actual_instance.value) + + # --- Shared Getters/Setters --- + + def __eq__(self, other: Any) -> bool: + if self is other: + return True + if not isinstance(other, Term): + return False + return self.serialize() == other.serialize() + + def __hash__(self) -> int: + return hash(self.serialize()) + + def __str__(self) -> str: + return self.serialize() + + def __repr__(self) -> str: + return self.serialize() + + +class RegexTerm(Term): + def get_pattern(self) -> Optional[str]: + return self.get_value() + + def get_fair(self) -> Optional[str]: + return None + + def to_dto(self) -> GeneratedTerm: + return GeneratedTerm(TermRegex(type="regex", value=self.get_value())) + + def serialize(self) -> str: + return f"regex={self.get_value()}" + + +class FairTerm(Term): + def __init__(self, value: str, deterministic: Optional[bool] = None): + super().__init__(value) + self._deterministic = deterministic + + @property + def is_deterministic(self) -> Optional[bool]: + """Whether this FAIR encodes a deterministic automaton, or None if unknown.""" + return self._deterministic + + def get_pattern(self) -> Optional[str]: + return self._pattern + + def get_fair(self) -> Optional[str]: + return self.get_value() + + def to_dto(self) -> GeneratedTerm: + return GeneratedTerm(TermFair(type="fair", value=self.get_value())) + + def serialize(self) -> str: + return f"fair={self.get_value()}" diff --git a/regexsolver/models/term_properties_mixin.py b/regexsolver/models/term_properties_mixin.py new file mode 100644 index 0000000..4ec117a --- /dev/null +++ b/regexsolver/models/term_properties_mixin.py @@ -0,0 +1,35 @@ +from typing import Optional + + +class TermPropertiesMixin: + """A mixin providing default property inference for Term analytics. + + Returns `None` when a property cannot be strictly inferred from the current data alone. + """ + + def is_empty(self) -> Optional[bool]: + """Infers whether the term matches no strings at all. + + Returns: + Optional[bool]: True if it definitely matches no strings, False if it + matches at least one, or None if it cannot be inferred. + """ + return None + + def is_empty_string(self) -> Optional[bool]: + """Infers whether the term matches strictly the empty string (""). + + Returns: + Optional[bool]: True if it definitely matches only the empty string, + False if it matches other strings, or None if it cannot be inferred. + """ + return None + + def is_total(self) -> Optional[bool]: + """Infers whether the term matches all possible strings. + + Returns: + Optional[bool]: True if it definitely matches all strings, False if it + misses at least one string, or None if it cannot be inferred. + """ + return None diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 63b3919..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -requests>=2.20.0 -pydantic<=2.5.3, >2.4.0; python_version<"3.8" -pydantic>=2.6.0; python_version>="3.8" \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index d6b4483..0000000 --- a/setup.py +++ /dev/null @@ -1,41 +0,0 @@ -from setuptools import setup, find_packages - -setup( - name="regexsolver", - version="1.0.3", - description="RegexSolver allows you to manipulate regular expressions as sets, enabling operations such as intersection, union, and subtraction.", - long_description=open('README.md').read(), - long_description_content_type='text/markdown', - author="RegexSolver", - author_email="contact@regexsolver.com", - url="https://github.com/RegexSolver/regexsolver-python", - license="MIT", - keywords="regex regexp set intersection union subtraction difference equivalence subset nfa dfa", - packages=find_packages(exclude=["tests", "tests.*"]), - - install_requires=[ - 'requests>=2.20.0', - 'pydantic<=2.5.3, >2.4.0; python_version<"3.8"', - 'pydantic>=2.6.0; python_version>="3.8"' - ], - python_requires='>=3.7', - project_urls={ - "Homepage": "https://regexsolver.com/", - "Issues": "https://github.com/RegexSolver/regexsolver-python/issues", - "Documentation": "https://docs.regexsolver.com/", - "Source Code": "https://github.com/RegexSolver/regexsolver-python", - }, - classifiers=[ - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Software Development :: Libraries :: Python Modules", - ], -) diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 7a9c72b..0000000 --- a/test-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -requests_mock>=1.9.0 \ No newline at end of file diff --git a/tests/assets/response_error.json b/tests/assets/response_error.json deleted file mode 100644 index 0faf2d7..0000000 --- a/tests/assets/response_error.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "error", - "message": "A random error." -} \ No newline at end of file diff --git a/tests/assets/response_generateStrings.json b/tests/assets/response_generateStrings.json deleted file mode 100644 index 9ee8883..0000000 --- a/tests/assets/response_generateStrings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "type": "strings", - "value": [ - "abcde", - "dede", - "deabc", - "abcabc" - ] -} \ No newline at end of file diff --git a/tests/assets/response_getDetails.json b/tests/assets/response_getDetails.json deleted file mode 100644 index 65e0539..0000000 --- a/tests/assets/response_getDetails.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "details", - "cardinality": { - "type": "Integer", - "value": 2 - }, - "length": [ - 2, - 3 - ], - "empty": false, - "total": false -} \ No newline at end of file diff --git a/tests/assets/response_getDetails_empty.json b/tests/assets/response_getDetails_empty.json deleted file mode 100644 index d33b6f3..0000000 --- a/tests/assets/response_getDetails_empty.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "details", - "cardinality": { - "type": "Integer", - "value": 0 - }, - "length": [ - null, - null - ], - "empty": true, - "total": false -} \ No newline at end of file diff --git a/tests/assets/response_getDetails_infinite.json b/tests/assets/response_getDetails_infinite.json deleted file mode 100644 index ae72fc8..0000000 --- a/tests/assets/response_getDetails_infinite.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "details", - "cardinality": { - "type": "Infinite" - }, - "length": [ - 0, - null - ], - "empty": false, - "total": true -} \ No newline at end of file diff --git a/tests/assets/response_intersection.json b/tests/assets/response_intersection.json deleted file mode 100644 index e6b1a7a..0000000 --- a/tests/assets/response_intersection.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "regex", - "value": "deabc" -} \ No newline at end of file diff --git a/tests/assets/response_isEquivalentTo.json b/tests/assets/response_isEquivalentTo.json deleted file mode 100644 index 25147f3..0000000 --- a/tests/assets/response_isEquivalentTo.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "boolean", - "value": false -} \ No newline at end of file diff --git a/tests/assets/response_isSubsetOf.json b/tests/assets/response_isSubsetOf.json deleted file mode 100644 index 84ed493..0000000 --- a/tests/assets/response_isSubsetOf.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "boolean", - "value": true -} \ No newline at end of file diff --git a/tests/assets/response_subtraction.json b/tests/assets/response_subtraction.json deleted file mode 100644 index 478ac72..0000000 --- a/tests/assets/response_subtraction.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "regex", - "value": "abc" -} \ No newline at end of file diff --git a/tests/assets/response_union.json b/tests/assets/response_union.json deleted file mode 100644 index 27dae5e..0000000 --- a/tests/assets/response_union.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "type": "regex", - "value": "(abc|de|fghi)" -} \ No newline at end of file diff --git a/tests/serialization_test.py b/tests/serialization_test.py deleted file mode 100644 index 8682208..0000000 --- a/tests/serialization_test.py +++ /dev/null @@ -1,55 +0,0 @@ -import unittest - -from regexsolver import GenerateStringsRequest, MultiTermsRequest, Term - - -class SerializationTest(unittest.TestCase): - def test_serialize_term(self): - self.assert_serialization(Term.regex(r".*")) - self.assert_serialization(Term.regex(r"")) - - self.assert_serialization(Term.fair( - "rgmsW[1g2LvP=Gr&V>sLc#w-!No&(opHq@B-9o[LpP-a#fYI+" - )) - self.assert_serialization(Term.fair("=rgmsW[1g2LvP=Gr&+")) - self.assert_serialization(Term.fair("")) - - def assert_serialization(self, term: Term): - serialized = term.serialize() - deserialized = Term.deserialize(serialized) - - self.assertEqual(term, deserialized) - - def test_serialize_requests(self): - request = MultiTermsRequest( - terms=[Term.regex(r"abc"), Term.regex(r"def"), Term.regex(r"ghi")]) - self.assertEqual( - { - "terms": [ - {"type": "regex", "value": "abc"}, - {"type": "regex", "value": "def"}, - {"type": "regex", "value": "ghi"} - ] - }, - request.model_dump() - ) - - request = GenerateStringsRequest( - term=Term.regex(r"(abc|de){2,3}"), count=10) - self.assertEqual( - { - "term": {"type": "regex", "value": "(abc|de){2,3}"}, - "count": 10 - }, - request.model_dump() - ) - - request = Term.regex(r"(abc|de){2,3}") - self.assertEqual( - {"type": "regex", "value": "(abc|de){2,3}"}, - request.model_dump() - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/term_operation_test.py b/tests/term_operation_test.py deleted file mode 100644 index 94a680a..0000000 --- a/tests/term_operation_test.py +++ /dev/null @@ -1,182 +0,0 @@ -import json -import requests_mock -import unittest - -from regexsolver import ApiError, RegexSolver, Term - - -class TermsOperationTest(unittest.TestCase): - def setUp(self): - RegexSolver.get_instance().initialize("TOKEN") - - def test_get_details(self): - with open('tests/assets/response_getDetails.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/analyze/details", - json=json_response, status_code=200 - ) - - term = Term.regex(r"(abc|de)") - details = term.get_details() - - self.assertEqual( - "Details[cardinality=Integer(2), length=Length[minimum=2, maximum=3], empty=False, total=False]", - str(details) - ) - - def test_get_details_infinite(self): - with open('tests/assets/response_getDetails_infinite.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/analyze/details", - json=json_response, status_code=200 - ) - - term = Term.regex(r".*") - details = term.get_details() - - self.assertEqual( - "Details[cardinality=Infinite, length=Length[minimum=0, maximum=None], empty=False, total=True]", - str(details) - ) - - def test_get_details_empty(self): - with open('tests/assets/response_getDetails_empty.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/analyze/details", - json=json_response, status_code=200 - ) - - term = Term.regex(r"a.") - details = term.get_details() - - self.assertEqual( - "Details[cardinality=Integer(0), length=Length[minimum=None, maximum=None], empty=True, total=False]", - str(details) - ) - - def test_generate_strings(self): - with open('tests/assets/response_generateStrings.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/generate/strings", - json=json_response, status_code=200 - ) - - term = Term.regex(r"(abc|de){2}") - strings = term.generate_strings(10) - - self.assertEqual(4, len(strings)) - - def test_intersection(self): - with open('tests/assets/response_intersection.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/compute/intersection", - json=json_response, status_code=200 - ) - - term1 = Term.regex(r"(abc|de){2}") - term2 = Term.regex(r"de.*") - term3 = Term.regex(r".*abc") - - result = term1.intersection(term2, term3) - - self.assertEqual("regex=deabc", str(result)) - - def test_union(self): - with open('tests/assets/response_union.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/compute/union", - json=json_response, status_code=200 - ) - - term1 = Term.regex(r"abc") - term2 = Term.regex(r"de") - term3 = Term.regex(r"fghi") - - result = term1.union(term2, term3) - - self.assertEqual("regex=(abc|de|fghi)", str(result)) - - def test_subtraction(self): - with open('tests/assets/response_subtraction.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/compute/subtraction", - json=json_response, status_code=200 - ) - - term1 = Term.regex(r"(abc|de)") - term2 = Term.regex(r"de") - - result = term1.subtraction(term2) - - self.assertEqual("regex=abc", str(result)) - - def test_is_equivalent_to(self): - with open('tests/assets/response_isEquivalentTo.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/analyze/equivalence", - json=json_response, status_code=200 - ) - - term1 = Term.regex(r"(abc|de)") - term2 = Term.fair( - "rgmsW[1g2LvP=Gr&V>sLc#w-!No&(oq@Sf>X).?lI3{uh{80qWEH[#0.pHq@B-9o[LpP-a#fYI+") - - result = term1.is_equivalent_to(term2) - - self.assertEqual(False, result) - - def test_is_subset_of(self): - with open('tests/assets/response_isSubsetOf.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/analyze/subset", - json=json_response, status_code=200 - ) - - term1 = Term.regex(r"de") - term2 = Term.regex(r"(abc|de)") - - result = term1.is_subset_of(term2) - - self.assertEqual(True, result) - - def test_error_response(self): - with open('tests/assets/response_error.json') as response: - json_response = json.load(response) - with requests_mock.Mocker() as mock: - mock.post( - "https://api.regexsolver.com/api/compute/intersection", - json=json_response, status_code=400 - ) - - term1 = Term.regex(r"abc") - term2 = Term.regex(r"de") - - try: - term1.intersection(term2) - except ApiError as err: - self.assertEqual( - "The API returned the following error: A random error.", - err.args[0] - ) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_async_client.py b/tests/test_async_client.py new file mode 100644 index 0000000..c1b3e80 --- /dev/null +++ b/tests/test_async_client.py @@ -0,0 +1,679 @@ +import asyncio +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from regexsolver import ( + ApiError, + AsyncRegexSolverClient, + BadRequestError, + ForbiddenError, + Infinite, + Integer, + InvalidJsonError, + InvalidNumberOfStringsToGenerateError, + InvalidTokenError, + MissingOrMalformedTokenError, + NotFoundError, + QuotaExceededError, + Term, + TimeoutExceededError, + TimeoutTooLargeError, + TooManyTermsError, + UnauthorizedError, +) +from regexsolver._generated import ApiException + + +@pytest.fixture +async def async_client(): + client = AsyncRegexSolverClient(api_token="test-token") + client._account_api = AsyncMock() + client._analyze_api = AsyncMock() + client._compute_api = AsyncMock() + client._generate_api = AsyncMock() + yield client + await client.aclose() + + +def _term_response(value: str): + mock_response = MagicMock() + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = value + return mock_response + + +def _limits_response(max_terms: int = 4): + mock_response = MagicMock() + mock_response.data.max_requests_count = 1000 + mock_response.data.max_requests_rate = 10 + mock_response.data.max_terms_count = max_terms + mock_response.data.max_timeout = 60000 + mock_response.data.max_states_count = 8192 + return mock_response + + +def _too_many_terms_error(provided: int, allowed: int) -> ApiException: + error = ApiException(status=400) + error.body = ( + '{"success": false, ' + f'"error": "{provided} terms provided. Maximum allowed is {allowed}.", ' + '"errorCode": "TooManyTerms"}' + ) + return error + + +@pytest.mark.asyncio +async def test_get_cardinality_integer(async_client): + term = Term.regex("abc") + + mock_response = MagicMock() + mock_response.data.actual_instance.type = "integer" + mock_response.data.actual_instance.value = 42 + async_client._analyze_api.cardinality.return_value = mock_response + + result = await async_client.get_cardinality(term) + assert isinstance(result, Integer) + assert result.value == 42 + assert term._cardinality == result + + +@pytest.mark.asyncio +async def test_get_cardinality_infinite(async_client): + term = Term.regex(".*") + + mock_response = MagicMock() + mock_response.data.actual_instance.type = "infinite" + async_client._analyze_api.cardinality.return_value = mock_response + + result = await async_client.get_cardinality(term) + assert isinstance(result, Infinite) + + +@pytest.mark.asyncio +async def test_get_cardinality_big_integer(async_client): + term = Term.regex(".{100}") + + mock_response = MagicMock() + mock_response.data.actual_instance.type = "bigInteger" + async_client._analyze_api.cardinality.return_value = mock_response + + from regexsolver import BigInteger + + result = await async_client.get_cardinality(term) + assert isinstance(result, BigInteger) + + +@pytest.mark.asyncio +async def test_get_length(async_client): + term = Term.regex("abc") + + mock_response = MagicMock() + mock_response.data.min = 3 + mock_response.data.max = 3 + async_client._analyze_api.length.return_value = mock_response + + result = await async_client.get_length(term) + assert result.min == 3 + assert result.max == 3 + + +@pytest.mark.asyncio +async def test_is_empty(async_client): + term = Term.regex("[]") + + mock_response = MagicMock() + mock_response.data.value = True + async_client._analyze_api.empty.return_value = mock_response + + result = await async_client.is_empty(term) + assert result is True + assert term._empty is True + + +@pytest.mark.asyncio +async def test_compute_union(async_client): + term1 = Term.regex("a") + term2 = Term.regex("b") + + mock_response = MagicMock() + # mock_response.data should be a GeneratedTerm + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = "a|b" + async_client._compute_api.union.return_value = mock_response + + result = await async_client.union(term1, term2) + assert isinstance(result, Term) + assert result.get_value() == "a|b" + + +@pytest.mark.asyncio +async def test_error_handling_400(async_client): + term = Term.regex("invalid[") + + error_400 = ApiException(status=400, reason="Bad Request") + error_400.body = '{"error": "Invalid regex"}' + async_client._analyze_api.empty.side_effect = error_400 + + with pytest.raises(BadRequestError) as exc_info: + await async_client.is_empty(term) + assert "Invalid regex" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_error_handling_invalid_json(async_client): + error_400 = ApiException(status=400) + error_400.body = ( + '{"success": false, "error": "Invalid JSON", "errorCode": "InvalidJson"}' + ) + async_client._analyze_api.empty.side_effect = error_400 + with pytest.raises(InvalidJsonError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_too_many_terms(async_client): + error_400 = ApiException(status=400) + error_400.body = ( + '{"success": false, "error": "Too many terms", "errorCode": "TooManyTerms"}' + ) + # Auto-batching reacts to TooManyTerms by fetching the limits; when the + # call is already within them, the original error is re-raised. + async_client._account_api.limits.return_value = _limits_response(max_terms=4) + async_client._compute_api.union.side_effect = error_400 + with pytest.raises(TooManyTermsError): + await async_client.union(Term.regex("a"), Term.regex("b")) + + +@pytest.mark.asyncio +async def test_error_handling_timeout_too_large(async_client): + error_400 = ApiException(status=400) + error_400.body = '{"success": false, "error": "Timeout too large", "errorCode": "TimeoutTooLarge"}' + async_client._analyze_api.empty.side_effect = error_400 + with pytest.raises(TimeoutTooLargeError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_timeout_exceeded(async_client): + error_400 = ApiException(status=400) + error_400.body = '{"success": false, "error": "Timeout exceeded", "errorCode": "TimeoutExceeded"}' + async_client._analyze_api.empty.side_effect = error_400 + with pytest.raises(TimeoutExceededError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_invalid_number_of_strings_to_generate(async_client): + error_400 = ApiException(status=400) + error_400.body = '{"success": false, "error": "Too many strings", "errorCode": "InvalidNumberOfStringsToGenerate"}' + async_client._generate_api.strings.side_effect = error_400 + with pytest.raises(InvalidNumberOfStringsToGenerateError): + await async_client.generate_strings(Term.regex("abc"), 100, 0) + + +@pytest.mark.asyncio +async def test_error_handling_missing_or_malformed_token(async_client): + error_401 = ApiException(status=401) + error_401.body = '{"success": false, "error": "Missing token", "errorCode": "MissingOrMalformedToken"}' + async_client._analyze_api.empty.side_effect = error_401 + with pytest.raises(MissingOrMalformedTokenError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_invalid_token(async_client): + error_401 = ApiException(status=401) + error_401.body = ( + '{"success": false, "error": "Invalid token", "errorCode": "InvalidToken"}' + ) + async_client._analyze_api.empty.side_effect = error_401 + with pytest.raises(InvalidTokenError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_quota_exceeded(async_client): + error_403 = ApiException(status=403) + error_403.body = ( + '{"success": false, "error": "Quota exceeded", "errorCode": "QuotaExceeded"}' + ) + async_client._analyze_api.empty.side_effect = error_403 + with pytest.raises(QuotaExceededError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_401(async_client): + async_client._analyze_api.empty.side_effect = ApiException( + status=401, reason="Unauthorized" + ) + + with pytest.raises(UnauthorizedError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_403(async_client): + async_client._analyze_api.empty.side_effect = ApiException( + status=403, reason="Forbidden" + ) + + with pytest.raises(ForbiddenError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_404(async_client): + async_client._analyze_api.empty.side_effect = ApiException( + status=404, reason="Not Found" + ) + + with pytest.raises(NotFoundError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_500(async_client): + async_client._analyze_api.empty.side_effect = ApiException( + status=500, reason="Internal Server Error" + ) + from regexsolver import InternalServerError + + with pytest.raises(InternalServerError): + await async_client.is_empty(Term.regex("abc")) + + +@pytest.mark.asyncio +async def test_error_handling_other(async_client): + async_client._analyze_api.empty.side_effect = ApiException( + status=418, reason="I'm a teapot" + ) + with pytest.raises(ApiError) as exc_info: + await async_client.is_empty(Term.regex("abc")) + assert exc_info.value.status_code == 418 + + +@pytest.mark.asyncio +async def test_retry_on_429(async_client): + term = Term.regex("abc") + + error_429 = ApiException(status=429) + error_429.headers = {"Retry-After": "0.05"} + + success_response = MagicMock() + success_response.data.value = True + + async_client._analyze_api.empty.side_effect = [error_429, success_response] + + result = await async_client.is_empty(term) + assert result is True + assert async_client._analyze_api.empty.call_count == 2 + + +@pytest.mark.asyncio +async def test_retry_on_429_lowercase_header(async_client): + error_429 = ApiException(status=429) + error_429.headers = {"retry-after": "0.05"} + + success_response = MagicMock() + success_response.data.value = True + + async_client._analyze_api.empty.side_effect = [error_429, success_response] + + assert await async_client.is_empty(Term.regex("abc")) is True + assert async_client._analyze_api.empty.call_count == 2 + + +@pytest.mark.asyncio +async def test_retry_survives_many_consecutive_429s(async_client): + error_429 = ApiException(status=429) + error_429.headers = {"Retry-After": "0.01"} + + success_response = MagicMock() + success_response.data.value = True + + async_client._analyze_api.empty.side_effect = [error_429] * 8 + [success_response] + + with patch( + "regexsolver.clients.asynchronous.random.uniform", return_value=0.0 + ): + assert await async_client.is_empty(Term.regex("abc")) is True + assert async_client._analyze_api.empty.call_count == 9 + + +@pytest.mark.asyncio +async def test_retry_budget_exhausted(): + from regexsolver import TooManyRequestsError + + # A dedicated token: this test runs on a fake clock, which leaves the + # shared per-token limiter with a nonsense deadline afterwards. + client = AsyncRegexSolverClient(api_token="budget-token") + client._analyze_api = AsyncMock() + + error_429 = ApiException(status=429) + error_429.headers = {"Retry-After": "10"} + client._analyze_api.empty.side_effect = error_429 + + fake_now = [0.0] + + async def fake_sleep(seconds): + fake_now[0] += seconds + + with ( + patch("time.monotonic", side_effect=lambda: fake_now[0]), + patch("asyncio.sleep", new=fake_sleep), + patch("regexsolver.clients.asynchronous.random.uniform", return_value=0.0), + ): + with pytest.raises(TooManyRequestsError) as exc_info: + await client.is_empty(Term.regex("abc")) + assert "Max retries exceeded" in str(exc_info.value) + await client.aclose() + + +@pytest.mark.asyncio +async def test_concurrent_429s_never_surface(async_client): + error_429 = ApiException(status=429) + error_429.headers = {"Retry-After": "0.02"} + + success_response = MagicMock() + success_response.data.value = True + + async_client._analyze_api.empty.side_effect = [error_429, error_429] + [ + success_response + ] * 7 + + results = await asyncio.gather( + *(async_client.is_empty(Term.regex("abc")) for _ in range(5)) + ) + assert results == [True] * 5 + + +@pytest.mark.asyncio +async def test_equivalent(async_client): + term1 = Term.regex("a") + term2 = Term.regex("a") + mock_response = MagicMock() + mock_response.data.value = True + async_client._analyze_api.equivalent.return_value = mock_response + assert await async_client.equivalent(term1, term2) is True + + +@pytest.mark.asyncio +async def test_subset(async_client): + term1 = Term.regex("a") + term2 = Term.regex("a|b") + mock_response = MagicMock() + mock_response.data.value = True + async_client._analyze_api.subset.return_value = mock_response + assert await async_client.subset(term1, term2) is True + + +@pytest.mark.asyncio +async def test_is_empty_string(async_client): + term = Term.regex("") + mock_response = MagicMock() + mock_response.data.value = True + async_client._analyze_api.empty_string.return_value = mock_response + assert await async_client.is_empty_string(term) is True + + +@pytest.mark.asyncio +async def test_is_total(async_client): + term = Term.regex(".*") + mock_response = MagicMock() + mock_response.data.value = True + async_client._analyze_api.total.return_value = mock_response + assert await async_client.is_total(term) is True + + +@pytest.mark.asyncio +async def test_get_pattern(async_client): + term = Term.regex("a") + mock_response = MagicMock() + mock_response.data.value = "a" + async_client._analyze_api.pattern.return_value = mock_response + assert await async_client.get_pattern(term) == "a" + + +@pytest.mark.asyncio +async def test_get_dot(async_client): + term = Term.regex("a") + mock_response = MagicMock() + mock_response.data.value = "digraph {...}" + async_client._analyze_api.dot.return_value = mock_response + assert await async_client.get_dot(term) == "digraph {...}" + + +@pytest.mark.asyncio +async def test_concat(async_client): + term1 = Term.regex("a") + term2 = Term.regex("b") + mock_response = MagicMock() + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = "ab" + async_client._compute_api.concat.return_value = mock_response + result = await async_client.concat(term1, term2) + assert result.get_value() == "ab" + + +@pytest.mark.asyncio +async def test_intersection(async_client): + term1 = Term.regex("a.") + term2 = Term.regex(".b") + mock_response = MagicMock() + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = "ab" + async_client._compute_api.intersection.return_value = mock_response + result = await async_client.intersection(term1, term2) + assert result.get_value() == "ab" + + +@pytest.mark.asyncio +async def test_difference(async_client): + term1 = Term.regex("a|b") + term2 = Term.regex("b") + mock_response = MagicMock() + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = "a" + async_client._compute_api.difference.return_value = mock_response + result = await async_client.difference(term1, term2) + assert result.get_value() == "a" + + +@pytest.mark.asyncio +async def test_repeat(async_client): + term = Term.regex("a") + mock_response = MagicMock() + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = "a{2,3}" + async_client._compute_api.repeat.return_value = mock_response + result = await async_client.repeat(term, 2, 3) + assert result.get_value() == "a{2,3}" + + +@pytest.mark.asyncio +async def test_complement(async_client): + term = Term.regex(".*a.*") + mock_response = MagicMock() + mock_response.data.actual_instance.type = "regex" + mock_response.data.actual_instance.value = "[^a].*" + async_client._compute_api.complement.return_value = mock_response + result = await async_client.complement(term) + assert result.get_value() == "[^a].*" + + +@pytest.mark.asyncio +async def test_generate_strings(async_client): + term = Term.regex("a*") + mock_response = MagicMock() + mock_response.data.strings.value = ["", "a", "aa"] + async_client._generate_api.strings.return_value = mock_response + result = await async_client.generate_strings(term, 3, 0) + assert result == ["", "a", "aa"] + + request = async_client._generate_api.strings.call_args.kwargs[ + "generate_strings_request" + ] + # Omitted options fall back to the spec defaults baked into the model. + assert request.path_order is None + assert request.character_order is None + assert request.seed == 0 + assert request.min_length == 0 + assert request.max_length == 100 + assert request.charset is None + + +@pytest.mark.asyncio +async def test_generate_strings_with_options(async_client): + from regexsolver import CharacterOrder, PathOrder + + mock_response = MagicMock() + mock_response.data.strings.value = ["xy"] + async_client._generate_api.strings.return_value = mock_response + + result = await async_client.generate_strings( + Term.regex("[a-z]{2}"), + 5, + 0, + path_order=PathOrder.INTERLEAVE, + character_order=CharacterOrder.SHUFFLED, + seed=42, + min_length=1, + max_length=10, + charset="[a-z]", + ) + assert result == ["xy"] + + request = async_client._generate_api.strings.call_args.kwargs[ + "generate_strings_request" + ] + assert request.path_order == "interleave" + assert request.character_order == "shuffled" + assert request.seed == 42 + assert request.min_length == 1 + assert request.max_length == 10 + assert request.charset == "[a-z]" + + +# --- ACCOUNT LIMITS --- +@pytest.mark.asyncio +async def test_get_account_limits_memoized(async_client): + async_client._account_api.limits.return_value = _limits_response(max_terms=4) + + limits = await async_client.get_account_limits() + assert limits.max_requests_count == 1000 + assert limits.max_requests_rate == 10 + assert limits.max_terms_count == 4 + assert limits.max_timeout == 60000 + assert limits.max_states_count == 8192 + + await async_client.get_account_limits() + assert async_client._account_api.limits.call_count == 1 + + +@pytest.mark.asyncio +async def test_get_account_limits_single_flight(async_client): + async_client._account_api.limits.return_value = _limits_response() + + await asyncio.gather( + async_client.get_account_limits(), async_client.get_account_limits() + ) + assert async_client._account_api.limits.call_count == 1 + + +# --- AUTO-BATCHING --- +def _request_values(call): + request = call.kwargs["multi_terms_request"] + return [t.actual_instance.value for t in request.terms] + + +@pytest.mark.asyncio +async def test_proactive_batching_with_override(): + client = AsyncRegexSolverClient(api_token="batch-token", max_terms_per_request=3) + client._account_api = AsyncMock() + client._compute_api = AsyncMock() + client._compute_api.concat.side_effect = [ + _term_response(f"r{i}") for i in range(4) + ] + + terms = [Term.regex(f"t{i}") for i in range(8)] + result = await client.concat(*terms, response_format="regex") + assert result.get_value() == "r3" + + calls = client._compute_api.concat.call_args_list + assert len(calls) == 4 + # Left fold preserves concat order: contiguous chunks, accumulator first. + assert _request_values(calls[0]) == ["t0", "t1", "t2"] + assert _request_values(calls[1]) == ["r0", "t3", "t4"] + assert _request_values(calls[2]) == ["r1", "t5", "t6"] + assert _request_values(calls[3]) == ["r2", "t7"] + # Only the final request carries the caller's response options. + for call in calls[:3]: + assert call.kwargs["multi_terms_request"].options.response is None + final_options = calls[3].kwargs["multi_terms_request"].options + assert final_options.response.format == "regex" + # The limit was known up front, so no limits fetch happened. + client._account_api.limits.assert_not_called() + await client.aclose() + + +@pytest.mark.asyncio +async def test_reactive_batching_fetches_limits(async_client): + async_client._account_api.limits.return_value = _limits_response(max_terms=4) + async_client._compute_api.union.side_effect = [ + _too_many_terms_error(9, 4), + _term_response("r0"), + _term_response("r1"), + _term_response("r2"), + ] + + terms = [Term.regex(f"t{i}") for i in range(9)] + result = await async_client.union(*terms) + assert result.get_value() == "r2" + + calls = async_client._compute_api.union.call_args_list + assert len(calls) == 4 + assert _request_values(calls[0]) == [f"t{i}" for i in range(9)] + assert _request_values(calls[1]) == ["t0", "t1", "t2", "t3"] + assert _request_values(calls[2]) == ["r0", "t4", "t5", "t6"] + assert _request_values(calls[3]) == ["r1", "t7", "t8"] + assert async_client._account_api.limits.call_count == 1 + + +@pytest.mark.asyncio +async def test_batching_opt_out(): + client = AsyncRegexSolverClient(api_token="no-batch-token", auto_batch=False) + client._account_api = AsyncMock() + client._compute_api = AsyncMock() + client._compute_api.union.side_effect = _too_many_terms_error(9, 4) + + terms = [Term.regex(f"t{i}") for i in range(9)] + with pytest.raises(TooManyTermsError): + await client.union(*terms) + client._account_api.limits.assert_not_called() + await client.aclose() + + +@pytest.mark.asyncio +async def test_batching_limits_fetch_failure_rethrows_original(async_client): + async_client._account_api.limits.side_effect = ApiException( + status=500, reason="Internal Server Error" + ) + async_client._compute_api.union.side_effect = _too_many_terms_error(9, 4) + + terms = [Term.regex(f"t{i}") for i in range(9)] + with pytest.raises(TooManyTermsError): + await async_client.union(*terms) + assert async_client._account_api.limits.call_count == 1 + + +# --- CONSTRUCTOR VALIDATION --- +def test_constructor_rejects_empty_token(): + with pytest.raises(ValueError, match="api_token is required"): + AsyncRegexSolverClient(api_token="") + + +def test_constructor_rejects_invalid_max_terms_per_request(): + with pytest.raises(ValueError, match="max_terms_per_request"): + AsyncRegexSolverClient(api_token="test-token", max_terms_per_request=1) diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 0000000..a7d66ec --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,303 @@ +import pytest + +from regexsolver._generated.models import Cardinality as GeneratedCardinality +from regexsolver._generated.models import ( + CardinalityBigInteger, + CardinalityInfinite, + CardinalityInteger, + TermFair, + TermRegex, +) +from regexsolver._generated.models import Length as GeneratedLength +from regexsolver._generated.models import Term as GeneratedTerm +from regexsolver._generated.models.term_fair_metadata import TermFairMetadata +from regexsolver.models.cardinality import BigInteger, Cardinality, Infinite, Integer +from regexsolver.models.length import Length +from regexsolver.models.term import FairTerm, RegexTerm, Term + + +def test_term_creation_regex(): + term = Term.regex("abc") + assert isinstance(term, RegexTerm) + assert term.get_value() == "abc" + assert isinstance(term.to_dto(), GeneratedTerm) + + +def test_term_creation_fair(): + term = Term.fair("fair_payload") + assert isinstance(term, FairTerm) + assert term.get_value() == "fair_payload" + + +def test_term_from_dto_regex(): + gen_term = GeneratedTerm(TermRegex(type="regex", value="abc")) + term = Term.from_dto(gen_term) + assert isinstance(term, RegexTerm) + assert term.get_value() == "abc" + + +def test_term_from_dto_fair(): + gen_term = GeneratedTerm(TermFair(type="fair", value="payload")) + term = Term.from_dto(gen_term) + assert isinstance(term, FairTerm) + assert term.get_value() == "payload" + + +def test_cardinality_integer(): + c = Integer(10) + assert c.value == 10 + assert c.is_empty() is False + assert c.is_empty_string() is False + assert c.is_total() is False + assert repr(c) == "" + + +def test_cardinality_from_dto_integer(): + gen_card = GeneratedCardinality(CardinalityInteger(type="integer", value=10)) + c = Cardinality.from_dto(gen_card) + assert isinstance(c, Integer) + assert c.value == 10 + + +def test_cardinality_from_dto_big_integer(): + gen_card = GeneratedCardinality(CardinalityBigInteger(type="bigInteger")) + c = Cardinality.from_dto(gen_card) + assert isinstance(c, BigInteger) + + +def test_cardinality_from_dto_infinite(): + gen_card = GeneratedCardinality(CardinalityInfinite(type="infinite")) + c = Cardinality.from_dto(gen_card) + assert isinstance(c, Infinite) + + +def test_cardinality_integer_zero(): + c = Integer(0) + assert c.is_empty() is True + assert c.is_empty_string() is False + + +def test_cardinality_integer_one(): + c = Integer(1) + assert c.is_empty() is False + assert c.is_empty_string() is None # Per implementation + + +def test_cardinality_big_integer(): + c = BigInteger() + assert c.is_empty() is False + assert c.is_empty_string() is False + assert c.is_total() is False + assert repr(c) == "" + + +def test_cardinality_infinite(): + c = Infinite() + assert c.is_empty() is False + assert c.is_empty_string() is False + assert repr(c) == "" + + +def test_length(): + length = Length(min=1, max=5) + assert length.min == 1 + assert length.max == 5 + assert length.is_empty() is False + assert length.is_empty_string() is False + assert length.is_total() is False + assert repr(length) == "" + + +def test_length_from_dto(): + gen_len = GeneratedLength(type="length", min=1, max=5) + length_obj = Length.from_dto(gen_len) + assert length_obj.min == 1 + assert length_obj.max == 5 + + +def test_length_empty(): + length = Length(min=None, max=None) + assert length.is_empty() is True + + +def test_length_empty_string(): + length = Length(min=0, max=0) + assert length.is_empty_string() is True + + +def test_length_total_candidate(): + length = Length(min=0, max=None) + assert length.is_total() is None # Implementation returns None if it COULD be total + + +def test_term_properties_caching(): + term = Term.regex("abc") + assert term._cardinality is None + + c = Integer(5) + term._cardinality = c + # Simulate AsyncRegexSolverClient behavior + term._set_properties_mixin(c) + + assert term._cardinality == c + # Since Integer(5).is_empty() is False, it should set _empty to False + assert term._empty is False + + +def test_term_get_fair_and_pattern(): + regex_term = Term.regex("abc") + assert regex_term.get_pattern() == "abc" + assert regex_term.get_fair() is None + + fair_term = Term.fair("payload") + assert fair_term.get_fair() == "payload" + assert fair_term.get_pattern() is None + + fair_term._pattern = "abc" + assert fair_term.get_pattern() == "abc" + + +def test_term_serialize_deserialize(): + term = Term.regex("abc") + serialized = term.serialize() + assert serialized == "regex=abc" + assert str(term) == "regex=abc" + + deserialized = Term.deserialize(serialized) + assert deserialized == term + assert hash(deserialized) == hash(term) + + fair_term = Term.fair("payload") + assert Term.deserialize(fair_term.serialize()) == fair_term + + assert Term.deserialize("invalid") is None + assert Term.deserialize("unknown=value") is None + + +def test_term_matches(): + term = Term.regex("a.b") + assert term.matches("axb") is True + assert term.matches("a\nb") is True # DOTALL + assert term.matches("ab") is False + assert term.matches("axxb") is False # anchored (fullmatch) + + fair_term = Term.fair("payload") + # Matches the new Java-aligned behavior of throwing an exception + with pytest.raises(RuntimeError, match="not defined yet"): + fair_term.matches("abc") + + +# --- FairTerm.is_deterministic --- + + +def test_fair_term_is_deterministic_unknown_by_default(): + term = Term.fair("payload") + assert isinstance(term, FairTerm) + assert term.is_deterministic is None + + +def test_fair_term_public_factory_has_no_deterministic_param(): + with pytest.raises(TypeError): + Term.fair("payload", deterministic=True) # type: ignore[call-arg] + + +def test_from_dto_fair_with_deterministic_true(): + dto = GeneratedTerm( + TermFair( + type="fair", value="payload", metadata=TermFairMetadata(deterministic=True) + ) + ) + term = Term.from_dto(dto) + assert isinstance(term, FairTerm) + assert term.is_deterministic is True + + +def test_from_dto_fair_with_deterministic_false(): + dto = GeneratedTerm( + TermFair( + type="fair", value="payload", metadata=TermFairMetadata(deterministic=False) + ) + ) + term = Term.from_dto(dto) + assert isinstance(term, FairTerm) + assert term.is_deterministic is False + + +def test_from_dto_fair_without_metadata(): + dto = GeneratedTerm(TermFair(type="fair", value="payload")) + term = Term.from_dto(dto) + assert isinstance(term, FairTerm) + assert term.is_deterministic is None + + +# --- metadata is never sent to the server --- + + +def test_fair_term_to_dto_excludes_metadata(): + # FairTerm.to_dto() always builds a fresh TermFair without metadata — + # metadata is never round-tripped back to the server. + term = Term.from_dto( + GeneratedTerm( + TermFair( + type="fair", + value="payload", + metadata=TermFairMetadata(deterministic=True), + ) + ) + ) + instance = term.to_dto().actual_instance + assert instance is not None + dto_dict = instance.to_dict() + assert "metadata" not in dto_dict + assert dto_dict == {"type": "fair", "value": "payload"} + + +# --- deterministic + response_format validation --- + + +def _make_client(): + from regexsolver.clients.asynchronous import AsyncRegexSolverClient + + return AsyncRegexSolverClient.__new__(AsyncRegexSolverClient) + + +def test_build_options_deterministic_with_fair_format_ok(): + from regexsolver.models.response_format import ResponseFormat + + client = _make_client() + opts = client._build_options( + response_format=ResponseFormat.FAIR, deterministic=True + ) + assert opts.response is not None + assert opts.response.fair is not None + assert opts.response.fair.deterministic is True + + +def test_build_options_deterministic_without_format_ok(): + client = _make_client() + opts = client._build_options(deterministic=True) + assert opts.response is not None + assert opts.response.fair is not None + assert opts.response.fair.deterministic is True + + +def test_build_options_deterministic_with_regex_format_raises(): + from regexsolver.models.response_format import ResponseFormat + + client = _make_client() + with pytest.raises(ValueError, match="deterministic"): + client._build_options(response_format=ResponseFormat.REGEX, deterministic=True) + + +def test_build_options_deterministic_with_any_format_raises(): + from regexsolver.models.response_format import ResponseFormat + + client = _make_client() + with pytest.raises(ValueError, match="deterministic"): + client._build_options(response_format=ResponseFormat.ANY, deterministic=True) + + +def test_build_options_deterministic_with_string_regex_raises(): + client = _make_client() + with pytest.raises(ValueError, match="deterministic"): + client._build_options(response_format="regex", deterministic=True) diff --git a/tests/test_rate_limiter.py b/tests/test_rate_limiter.py new file mode 100644 index 0000000..03bb935 --- /dev/null +++ b/tests/test_rate_limiter.py @@ -0,0 +1,84 @@ +import asyncio +import threading +import time + +import pytest + +from regexsolver.clients.rate_limiter import RateLimiter, get_rate_limiter + + +@pytest.mark.asyncio +async def test_rate_limiter_wait_without_trigger_returns_immediately(): + rl = RateLimiter() + start = time.monotonic() + await rl.wait() + assert time.monotonic() - start < 0.05 + + +@pytest.mark.asyncio +async def test_rate_limiter_trigger(): + rl = RateLimiter() + rl.trigger(0.1) + start = time.monotonic() + await rl.wait() + assert time.monotonic() - start >= 0.09 + + +@pytest.mark.asyncio +async def test_rate_limiter_trigger_keeps_later_deadline(): + rl = RateLimiter() + + # A shorter Retry-After arriving second must not shrink the deadline. + rl.trigger(0.2) + rl.trigger(0.05) + start = time.monotonic() + await rl.wait() + assert time.monotonic() - start >= 0.15 + + # A longer Retry-After arriving second must extend it. + rl.trigger(0.05) + rl.trigger(0.2) + start = time.monotonic() + await rl.wait() + assert time.monotonic() - start >= 0.15 + + +@pytest.mark.asyncio +async def test_rate_limiter_deadline_extended_while_waiting(): + rl = RateLimiter() + rl.trigger(0.1) + + async def extend(): + await asyncio.sleep(0.05) + rl.trigger(0.2) + + start = time.monotonic() + await asyncio.gather(rl.wait(), extend()) + # The waiter woke at the original deadline, re-checked, and slept again. + assert time.monotonic() - start >= 0.2 + + +def test_get_rate_limiter_shared_by_token(): + rl1 = get_rate_limiter("token1") + rl2 = get_rate_limiter("token1") + assert rl1 is rl2 + assert get_rate_limiter("token2") is not rl1 + + +def test_get_rate_limiter_shared_across_loops(): + # The limiter holds only a timestamp, so the registry is keyed by token + # alone and the same instance is shared across event loops and threads. + rl1 = get_rate_limiter("token1") + container = [] + + def thread_target(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + container.append(get_rate_limiter("token1")) + loop.close() + + t = threading.Thread(target=thread_target) + t.start() + t.join() + + assert rl1 is container[0] diff --git a/tests/test_sync_client.py b/tests/test_sync_client.py new file mode 100644 index 0000000..937d4ce --- /dev/null +++ b/tests/test_sync_client.py @@ -0,0 +1,85 @@ +from unittest.mock import AsyncMock + +from regexsolver import Integer, RegexSolverClient, Term + + +def test_sync_client_get_cardinality(): + with RegexSolverClient(api_token="test-token") as client: + # Mock the underlying async client's method + client._aio.get_cardinality = AsyncMock(return_value=Integer(42)) + + term = Term.regex("abc") + result = client.get_cardinality(term) + + assert isinstance(result, Integer) + assert result.value == 42 + client._aio.get_cardinality.assert_called_once_with(term, None) + + +def test_sync_client_is_empty(): + with RegexSolverClient(api_token="test-token") as client: + client._aio.is_empty = AsyncMock(return_value=False) + + term = Term.regex("abc") + result = client.is_empty(term) + + assert result is False + client._aio.is_empty.assert_called_once_with(term, None) + + +def test_sync_client_union(): + with RegexSolverClient(api_token="test-token") as client: + mock_result_term = Term.regex("a|b") + client._aio.union = AsyncMock(return_value=mock_result_term) + + term1 = Term.regex("a") + term2 = Term.regex("b") + result = client.union(term1, term2) + + assert result == mock_result_term + client._aio.union.assert_called_once_with( + term1, term2, response_format=None, deterministic=None, execution_timeout=None + ) + + +def test_sync_client_complement(): + with RegexSolverClient(api_token="test-token") as client: + mock_result_term = Term.regex("[^a].*") + client._aio.complement = AsyncMock(return_value=mock_result_term) + + term = Term.regex(".*a.*") + result = client.complement(term) + + assert result == mock_result_term + client._aio.complement.assert_called_once_with( + term, response_format=None, deterministic=None, execution_timeout=None + ) + + +def test_sync_client_get_length(): + with RegexSolverClient(api_token="test-token") as client: + from regexsolver.models.length import Length + + client._aio.get_length = AsyncMock(return_value=Length(1, 4)) + term = Term.regex("(abc)?d") + result = client.get_length(term) + assert result.min == 1 + assert result.max == 4 + + +def test_sync_client_intersection(): + with RegexSolverClient(api_token="test-token") as client: + mock_result_term = Term.regex("a") + client._aio.intersection = AsyncMock(return_value=mock_result_term) + t1 = Term.regex("a") + t2 = Term.regex("ab") + result = client.intersection(t1, t2) + assert result == mock_result_term + + +def test_sync_client_generate_strings(): + with RegexSolverClient(api_token="test-token") as client: + client._aio.generate_strings = AsyncMock(return_value=["", "a", "aa"]) + term = Term.regex("a*") + result = client.generate_strings(term, 3, 0) + assert result == ["", "a", "aa"]