From eb3cf5d7ead49213b7eccc08627044dcb6a03def Mon Sep 17 00:00:00 2001 From: Buried-In-Code <6057651+Buried-In-Code@users.noreply.github.com> Date: Fri, 25 Nov 2022 13:13:37 +1300 Subject: [PATCH] Migrate to hatch (#149) - Change build-system to hatch/hatchling - Updated dependencies (Closes #142 #143 #145 #147 #148) - Updated editorconfig and .gitattributes - Updated readthedocs build config - Add Py3.11 support --- .editorconfig | 44 +- .flake8 | 4 +- .gitattributes | 60 +- .github/workflows/code-analysis.yaml | 82 +- .github/workflows/publishing.yaml | 86 +- .github/workflows/testing.yaml | 109 +-- .pre-commit-config.yaml | 166 ++-- .readthedocs.yaml | 6 +- LICENSE => LICENSE.txt | 1242 +++++++++++++------------- README.md | 36 +- mkdocs.yaml | 96 +- poetry.lock | 1026 --------------------- pyproject.toml | 165 ++-- simyan/__init__.py | 34 +- simyan/comicvine.py | 1206 ++++++++++++------------- simyan/exceptions.py | 56 +- simyan/schemas/__init__.py | 44 +- simyan/schemas/character.py | 290 +++--- simyan/schemas/creator.py | 298 +++--- simyan/schemas/generic_entries.py | 314 +++---- simyan/schemas/issue.py | 342 +++---- simyan/schemas/location.py | 228 ++--- simyan/schemas/publisher.py | 230 ++--- simyan/schemas/story_arc.py | 218 ++--- simyan/schemas/team.py | 254 +++--- simyan/schemas/volume.py | 282 +++--- simyan/sqlite_cache.py | 214 ++--- tests/README.md | 2 +- tests/__init__.py | 2 +- tests/conftest.py | 50 +- tests/test_characters.py | 226 ++--- tests/test_creators.py | 194 ++-- tests/test_exceptions.py | 58 +- tests/test_issues.py | 294 +++--- tests/test_locations.py | 154 ++-- tests/test_publishers.py | 208 ++--- tests/test_story_arcs.py | 218 ++--- tests/test_teams.py | 170 ++-- tests/test_volumes.py | 302 +++---- 39 files changed, 4023 insertions(+), 4987 deletions(-) rename LICENSE => LICENSE.txt (98%) delete mode 100644 poetry.lock diff --git a/.editorconfig b/.editorconfig index 4a0e789..cdb262a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,28 +1,40 @@ root = true [*] -charset = UTF-8 -end_of_line = CRLF +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 100 + +[*.bat] +indent_style = tab + +[*.cs] indent_size = 4 indent_style = tab insert_final_newline = false -max_line_length = 100 -tab_width = 4 -[{*.bash,*.sh,*.zsh}] -indent_size = 2 -tab_width = 2 +[*.java] +indent_size = 4 +indent_style = tab +insert_final_newline = false -[{*.har,*.json}] -indent_size = 2 -tab_width = 2 +[*.json] +insert_final_newline = false -[{*.markdown,*.md}] -indent_style = space +[*.md] +trim_trailing_whitespace = false -[{*.py,*.pyw}] -indent_style = space +[*.py] +indent_size = 4 + +[{*.xml,*.xsd}] +indent_size = 4 +indent_style = tab +insert_final_newline = false [{*.yaml,*.yml}] -indent_size = 2 -tab_width = 2 +indent_style = tab diff --git a/.flake8 b/.flake8 index 13d9346..6e48d78 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -max-line-length = 100 +extend-ignore = D107,E203,W503 max-complexity = 18 +max-line-length = 100 select = B,C,D,E,F,W,T4,B9 -extend-ignore = E203,W503,D107 diff --git a/.gitattributes b/.gitattributes index ffb7b90..9620051 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,57 @@ -# Don't do text manipulations (line ending changes). Tests require -# unchanged files. -* -text +* text=auto + +.gitattributes export-ignore +.gitignore export-ignore + +*.md text diff=markdown +*.rtf diff=astextplain +*.sql text +*.txt text + +*.jpg binary +*.png binary + +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.fish text eol=lf +*.ps1 text eol=crlf +*.sh text eol=lf +*.zsh text eol=lf + +*.csv text +*.json text +*.toml text +*.xml text +*.yaml text +*.yml text + +*.7z binary +*.gz binary +*.tar binary +*.tgz binary +*.zip binary + +# C# +*.cs text diff=csharp +*.cshtml text diff=html +*.csproj text eol=crlf +*.csx text diff=csharp +*.sln text eol=crlf + +# Java +*.gradle text diff=java +*.gradle.kts text diff=kotlin +*.groovy text diff=java +*.java text diff=java +*.kt text diff=kotlin +*.scala text diff=java + +# Python +*.pxd text diff=python +*.py text diff=python +*.py3 text diff=python +*.pyi text diff=python +*.pyw text diff=python +*.pyx text diff=python +*.pyz text diff=python diff --git a/.github/workflows/code-analysis.yaml b/.github/workflows/code-analysis.yaml index 2f4a813..b1e5d18 100644 --- a/.github/workflows/code-analysis.yaml +++ b/.github/workflows/code-analysis.yaml @@ -1,41 +1,41 @@ -name: Code Analysis - -on: - push: - branches: - - main - pull_request: - branches: - - main - schedule: - - cron: 30 0 * * 1 - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: - - python - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 +name: Code Analysis + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: 30 0 * * 1 + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: + - python + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/publishing.yaml b/.github/workflows/publishing.yaml index 676fb07..0e832a3 100644 --- a/.github/workflows/publishing.yaml +++ b/.github/workflows/publishing.yaml @@ -1,50 +1,36 @@ -name: Publishing - -on: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+' - -jobs: - pypi: - name: Publish to PyPI - strategy: - fail-fast: false - matrix: - python-version: - - '3.10' - os: - - ubuntu-latest - runs-on: ${{ matrix.os }} - steps: - #---------------------------------------------- - - name: Checkout repository - uses: actions/checkout@v3 - #---------------------------------------------- - - name: Setup python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Setup poetry - uses: snok/install-poetry@v1 - with: - virtualenvs-create: true - virtualenvs-in-project: true - #---------------------------------------------- - - name: Load cached venv - id: cached-venv - uses: actions/cache@v3 - with: - path: .venv - key: venv-${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - #---------------------------------------------- - - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction --no-root - - name: Install project - run: poetry install --no-interaction - #---------------------------------------------- - - name: Set PyPI Token - run: poetry config pypi-token.pypi '${{ secrets.PYPI_TOKEN }}' - - name: Publish Project - run: poetry publish --build +name: Publishing + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+' + +jobs: + pypi: + name: Publish to PyPI + strategy: + fail-fast: false + matrix: + python-version: + - '3.11' + os: + - ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + #---------------------------------------------- + - name: Checkout repository + uses: actions/checkout@v3 + #---------------------------------------------- + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Setup environment + run: pip install twine build packaging + #---------------------------------------------- + - name: Build project + run: python -m build + - name: Check dist + run: twine check dist/* + - name: Publish project + run: twine upload --username __token__ --password ${{ secrets.PYPI_TOKEN }} dist/* diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index fad8b70..0e81432 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -1,62 +1,47 @@ -name: Testing - -on: - push: - branches: - - main - paths-ignore: - - docs/** - pull_request: - branches: - - main - paths-ignore: - - docs/** - -jobs: - tox: - name: Tox Tests - strategy: - fail-fast: false - matrix: - python-version: - - 3.7 - - 3.8 - - 3.9 - - '3.10' - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - steps: - #---------------------------------------------- - - name: Checkout repository - uses: actions/checkout@v3 - #---------------------------------------------- - - name: Setup python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Setup poetry - uses: snok/install-poetry@v1 - with: - virtualenvs-create: true - virtualenvs-in-project: true - #---------------------------------------------- - - name: Load cached venv - id: cached-venv - uses: actions/cache@v3 - with: - path: .venv - key: venv-${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} - #---------------------------------------------- - - name: Install dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --no-interaction --no-root - - name: Install project - run: poetry install --no-interaction - #---------------------------------------------- - - name: Test with tox - env: - COMICVINE__API_KEY: IGNORED - run: poetry run tox -e py +name: Testing + +on: + push: + branches: + - main + paths-ignore: + - docs/** + pull_request: + branches: + - main + paths-ignore: + - docs/** + +jobs: + tox: + name: Tox Tests + strategy: + fail-fast: false + matrix: + python-version: + - 3.7 + - 3.8 + - 3.9 + - '3.10' + - '3.11' + os: + - ubuntu-latest + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} + steps: + #---------------------------------------------- + - name: Checkout repository + uses: actions/checkout@v3 + #---------------------------------------------- + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + #---------------------------------------------- + - name: Install project + run: pip install .[test] + - name: Test with tox + env: + COMICVINE__API_KEY: IGNORED + run: tox -e py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f9eee15..72b5b77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,80 +1,86 @@ -repos: - - repo: https://github.com/asottile/pyupgrade - rev: v2.37.3 - hooks: - - id: pyupgrade - args: - - --py3-plus - - --py36-plus - - --py37-plus - - repo: https://github.com/executablebooks/mdformat - rev: 0.7.16 - hooks: - - id: mdformat - additional_dependencies: - - mdformat-gfm - - mdformat-tables - args: - - --number - - --wrap=keep - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 - hooks: - - id: check-ast - - id: check-builtin-literals - - id: check-case-conflict - - id: check-docstring-first -# - id: check-json - - id: check-merge-conflict - - id: check-toml -# - id: check-xml - - id: check-yaml - args: - - --allow-multiple-documents - - id: end-of-file-fixer - exclude_types: - - json - - xml - - id: fix-encoding-pragma - args: - - --remove - - id: mixed-line-ending - args: - - --fix=auto - - id: trailing-whitespace - args: - - --markdown-linebreak-ext=md -# - id: pretty-format-json -# args: -# - --autofix -# - --indent=2 - - repo: https://github.com/psf/black - rev: 22.8.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - additional_dependencies: - - flake8-builtins - - flake8-docstrings - - flake8-rst-docstrings - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - args: - - --filter-files - - --profile=black - - repo: https://github.com/pappasam/toml-sort - rev: v0.20.1 - hooks: - - id: toml-sort - args: - - --in-place - - --all - - repo: meta - hooks: - - id: check-hooks-apply - - id: check-useless-excludes +repos: + - repo: https://github.com/asottile/pyupgrade + rev: v3.2.2 + hooks: + - id: pyupgrade + args: + - --py37-plus + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.16 + hooks: + - id: mdformat + additional_dependencies: + - mdformat-gfm + - mdformat-tables + args: + - --number + - --wrap=keep + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-ast + - id: check-builtin-literals + - id: check-case-conflict + - id: check-docstring-first +# - id: check-json + - id: check-merge-conflict + - id: check-toml +# - id: check-xml + - id: check-yaml + args: + - --allow-multiple-documents + - id: end-of-file-fixer + exclude_types: + - json + - xml + - id: fix-encoding-pragma + args: + - --remove + - id: mixed-line-ending + args: + - --fix=auto + - id: trailing-whitespace + args: + - --markdown-linebreak-ext=md +# - id: pretty-format-json +# args: +# - --autofix +# - --indent=2 + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/bandit + rev: 1.7.4 + hooks: + - id: bandit + args: + - -c=pyproject.toml + additional_dependencies: + - bandit[toml] + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + additional_dependencies: + - flake8-builtins + - flake8-docstrings + - flake8-rst-docstrings + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + args: + - --filter-files + - --profile=black + - repo: https://github.com/pappasam/toml-sort + rev: v0.20.1 + hooks: + - id: toml-sort + args: + - --in-place + - --all + - repo: meta + hooks: + - id: check-hooks-apply + - id: check-useless-excludes diff --git a/.readthedocs.yaml b/.readthedocs.yaml index da3397e..0556dc6 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,9 +8,13 @@ version: 2 mkdocs: configuration: mkdocs.yaml +build: + os: ubuntu-22.04 + tools: + python: '3.11' + # Optionally set the version of Python and requirements required to build your docs python: - version: '3.7' install: - method: pip path: . diff --git a/LICENSE b/LICENSE.txt similarity index 98% rename from LICENSE rename to LICENSE.txt index b059d64..810fce6 100644 --- a/LICENSE +++ b/LICENSE.txt @@ -1,621 +1,621 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md index 8b0a5cf..a68e475 100644 --- a/README.md +++ b/README.md @@ -5,35 +5,33 @@ [![PyPI - Version](https://img.shields.io/pypi/v/Simyan.svg?logo=PyPI&label=Version&style=flat-square)](https://pypi.python.org/pypi/Simyan/) [![PyPI - License](https://img.shields.io/pypi/l/Simyan.svg?logo=PyPI&label=License&style=flat-square)](https://opensource.org/licenses/GPL-3.0) -[![Black](https://img.shields.io/badge/Black-Enabled-000000?style=flat-square)](https://github.com/psf/black) -[![Flake8](https://img.shields.io/badge/Flake8-Enabled-informational?style=flat-square)](https://github.com/PyCQA/flake8) -[![Pre-Commit](https://img.shields.io/badge/Pre--Commit-Enabled-informational?logo=pre-commit&style=flat-square)](https://github.com/pre-commit/pre-commit) +[![Hatch](https://img.shields.io/badge/Packaging-Hatch-4051b5?style=flat-square)](https://github.com/pypa/hatch) +[![Pre-Commit](https://img.shields.io/badge/Pre--Commit-Enabled-informational?style=flat-square&logo=pre-commit)](https://github.com/pre-commit/pre-commit) +[![Black](https://img.shields.io/badge/Code--Style-Black-000000?style=flat-square)](https://github.com/psf/black) +[![isort](https://img.shields.io/badge/Imports-isort-informational?style=flat-square)](https://pycqa.github.io/isort/) +[![Flake8](https://img.shields.io/badge/Linter-Flake8-informational?style=flat-square)](https://github.com/PyCQA/flake8) [![Github - Contributors](https://img.shields.io/github/contributors/Metron-Project/Simyan.svg?logo=Github&label=Contributors&style=flat-square)](https://github.com/Metron-Project/Simyan/graphs/contributors) +[![Github Action - Code Analysis](https://img.shields.io/github/workflow/status/Metron-Project/Simyan/Code%20Analysis?logo=Github-Actions&label=Code-Analysis&style=flat-square)](https://github.com/Metron-Project/Simyan/actions/workflows/code-analysis.yaml) +[![Github Action - Testing](https://img.shields.io/github/workflow/status/Metron-Project/Simyan/Testing?logo=Github-Actions&label=Testing&style=flat-square)](https://github.com/Metron-Project/Simyan/actions/workflows/testing.yaml) +[![Github Action - Publishing](https://img.shields.io/github/workflow/status/Metron-Project/Simyan/Publishing?logo=Github-Actions&label=Publishing&style=flat-square)](https://github.com/Metron-Project/Simyan/actions/workflows/publishing.yaml) [![Read the Docs](https://img.shields.io/readthedocs/simyan?label=Read-the-Docs&logo=Read-the-Docs&style=flat-square)](https://simyan.readthedocs.io/en/latest/?badge=latest) -[![Github Action - Code Analysis](https://img.shields.io/github/workflow/status/Metron-Project/Simyan/Code%20Analysis?logo=Github-Actions&label=Code-Analysis&style=flat-square)](https://github.com/Metron-Project/Simyan/actions/workflows/code-analysis.yaml) -[![Github Action - Testing](https://img.shields.io/github/workflow/status/Metron-Project/Simyan/Testing?logo=Github-Actions&label=Tests&style=flat-square)](https://github.com/Metron-Project/Simyan/actions/workflows/testing.yaml) A [Python](https://www.python.org/) wrapper for the [Comicvine](https://comicvine.gamespot.com/api/) API. ## Installation -**Simyan** requires >= 3.7. - -### Installing/Upgrading from PyPI - -To install the latest version from PyPI: +### PyPI -```shell -$ pip3 install -U --user simyan -``` +1. Make sure you have [Python](https://www.python.org/) installed: `python --version` +2. Install the project from PyPI: `pip install simyan` -or via poetry: +### Github -```shell -$ poetry install simyan -``` +1. Make sure you have [Python](https://www.python.org/) installed: `python --version` +2. Clone the repo: `git clone https://github.com/Metron-Project/Simyan` +3. Install the project: `pip install .` ## Example Usage @@ -62,3 +60,7 @@ Who or what is Simyan? > Simyan along with his partner Mokkari, are the diminutive proprietors of the Evil Factory, an evil version of Project Cadmus created by Darkseid and his elite. > > More details at [Simyan (New Earth)]() + +## Socials + +[![Social - Matrix](https://img.shields.io/matrix/metron-general:matrix.org?label=Metron%20General&logo=matrix&style=for-the-badge)](https://matrix.to/#/#metron-general:matrix.org) diff --git a/mkdocs.yaml b/mkdocs.yaml index 025eabd..650eb8e 100644 --- a/mkdocs.yaml +++ b/mkdocs.yaml @@ -1,47 +1,49 @@ -site_name: Simyan -site_url: https://simyan.readthedocs.io/en/latest/ -repo_url: https://github.com/Buried-In-Code/Simyan -repo_name: Github -site_description: A Python wrapper for the Comicvine API. -site_author: Jonah Jackson -copyright: GPL-3.0 - -theme: - name: readthedocs - -nav: - - Home: index.md - - simyan: - - Package: simyan/__init__.md - - exceptions: simyan/exceptions.md - - comicvine: simyan/comicvine.md - - sqlite_cache: simyan/sqlite_cache.md - - simyan.schemas: - - Package: simyan/schemas/__init__.md - - character: simyan/schemas/character.md - - creator: simyan/schemas/creator.md - - generic_entries: simyan/schemas/generic_entries.md - - issue: simyan/schemas/issue.md - - location: simyan/schemas/location.md - - publisher: simyan/schemas/publisher.md - - story_arc: simyan/schemas/story_arc.md - - team: simyan/schemas/team.md - - volume: simyan/schemas/volume.md - -plugins: - - search - - mkdocstrings: - default_handler: python - handlers: - python: - options: - show_root_heading: true - show_root_full_path: false - show_category_heading: true - docstring_style: google - docstring_section_style: spacy - line_length: 100 - merge_init_into_class: true - show_signature_annotations: true - show_source: false - - include-markdown +site_name: Simyan +site_url: https://simyan.readthedocs.io/en/latest/ +repo_url: https://github.com/Buried-In-Code/Simyan +repo_name: Github +site_description: A Python wrapper for the Comicvine API. +site_author: Jonah Jackson +copyright: GPL-3.0 + +theme: + name: readthedocs + +nav: + - Home: index.md + - simyan: + - Package: simyan/__init__.md + - exceptions: simyan/exceptions.md + - comicvine: simyan/comicvine.md + - sqlite_cache: simyan/sqlite_cache.md + - simyan.schemas: + - Package: simyan/schemas/__init__.md + - character: simyan/schemas/character.md + - creator: simyan/schemas/creator.md + - generic_entries: simyan/schemas/generic_entries.md + - issue: simyan/schemas/issue.md + - location: simyan/schemas/location.md + - publisher: simyan/schemas/publisher.md + - story_arc: simyan/schemas/story_arc.md + - team: simyan/schemas/team.md + - volume: simyan/schemas/volume.md + +plugins: + - search + - mkdocstrings: + default_handler: python + handlers: + python: + options: + show_root_heading: True + show_root_full_path: False + show_category_heading: True + # Docstrings + docstring_style: google + docstring_section_style: spacy + line_length: 100 + merge_init_into_class: True + show_signature_annotations: True + # Additional + show_source: False + - include-markdown diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index e6d8afd..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1026 +0,0 @@ -[extras] -docs = ["mkdocs", "mkdocstrings", "mkdocs-include-markdown-plugin"] - -[metadata] -content-hash = "88ad91002b82fe9a3b8430396a0acce251139295923dbb5dad40cef3bb54b1d0" -lock-version = "1.1" -python-versions = "^3.7" - -[metadata.files] -Jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -Markdown = [ - {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, - {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, -] -MarkupSafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -PyYAML = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -cached-property = [ - {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, - {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, -] -certifi = [ - {file = "certifi-2022.6.15.2-py3-none-any.whl", hash = "sha256:0aa1a42fbd57645fabeb6290a7687c21755b0344ecaeaa05f4e9f6207ae2e9a8"}, - {file = "certifi-2022.6.15.2.tar.gz", hash = "sha256:aa08c101214127b9b0472ca6338315113c9487d45376fd3e669201b477c71003"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, - {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, -] -coverage = [ - {file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"}, - {file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"}, - {file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"}, - {file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"}, - {file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"}, - {file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"}, - {file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"}, - {file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"}, - {file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"}, - {file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"}, - {file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"}, - {file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"}, - {file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"}, - {file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"}, - {file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"}, - {file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"}, - {file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"}, - {file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"}, - {file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"}, - {file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"}, - {file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"}, - {file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"}, - {file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"}, - {file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"}, - {file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"}, - {file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"}, - {file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"}, - {file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"}, -] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] -filelock = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, -] -ghp-import = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] -griffe = [ - {file = "griffe-0.22.1-py3-none-any.whl", hash = "sha256:60b7906db5460277afdba17808ade1c9e099b20a6b8d8d1d152714daaa463cb7"}, - {file = "griffe-0.22.1.tar.gz", hash = "sha256:0130019b0b3966e9d755d9acb82fe9b64e354064ce971306e5892c098bf1a5c7"}, -] -identify = [ - {file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"}, - {file = "identify-2.5.5.tar.gz", hash = "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"}, - {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -mergedeep = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] -mkdocs = [ - {file = "mkdocs-1.3.1-py3-none-any.whl", hash = "sha256:fda92466393127d2da830bc6edc3a625a14b436316d1caf347690648e774c4f0"}, - {file = "mkdocs-1.3.1.tar.gz", hash = "sha256:a41a2ff25ce3bbacc953f9844ba07d106233cd76c88bac1f59cb1564ac0d87ed"}, -] -mkdocs-autorefs = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, -] -mkdocs-include-markdown-plugin = [ - {file = "mkdocs_include_markdown_plugin-3.7.1-py3-none-any.whl", hash = "sha256:cd106246eb2adc4818e076aa63e9b26a138d22afc7fb051072f98183be841e75"}, - {file = "mkdocs_include_markdown_plugin-3.7.1.tar.gz", hash = "sha256:7175ace62e81b4a7fe9863b392c6ea0bfdfca45ffedd97e549a2d2ab7b280a23"}, -] -mkdocstrings = [ - {file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"}, - {file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"}, -] -mkdocstrings-python = [ - {file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"}, - {file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"}, -] -nodeenv = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -platformdirs = [ - {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, - {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pre-commit = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pydantic = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, -] -pymdown-extensions = [ - {file = "pymdown_extensions-9.5-py3-none-any.whl", hash = "sha256:ec141c0f4983755349f0c8710416348d1a13753976c028186ed14f190c8061c4"}, - {file = "pymdown_extensions-9.5.tar.gz", hash = "sha256:3ef2d998c0d5fa7eb09291926d90d69391283561cf6306f85cd588a5eb5befa0"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pytest = [ - {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, - {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, -] -pytest-cov = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -pyyaml_env_tag = [ - {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, - {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, -] -ratelimit = [ - {file = "ratelimit-2.2.1.tar.gz", hash = "sha256:af8a9b64b821529aca09ebaf6d8d279100d766f19e90b5059ac6a718ca6dee42"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -setuptools = [ - {file = "setuptools-65.3.0-py3-none-any.whl", hash = "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82"}, - {file = "setuptools-65.3.0.tar.gz", hash = "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -tox = [ - {file = "tox-3.26.0-py2.py3-none-any.whl", hash = "sha256:bf037662d7c740d15c9924ba23bb3e587df20598697bb985ac2b49bdc2d847f6"}, - {file = "tox-3.26.0.tar.gz", hash = "sha256:44f3c347c68c2c68799d7d44f1808f9d396fc8a1a500cbc624253375c7ae107e"}, -] -tox-pyenv = [ - {file = "tox-pyenv-1.1.0.tar.gz", hash = "sha256:916c2213577aec0b3b5452c5bfb32fd077f3a3196f50a81ad57d7ef3fc2599e4"}, - {file = "tox_pyenv-1.1.0-py2.py3-none-any.whl", hash = "sha256:e470c18af115fe52eeff95e7e3cdd0793613eca19709966fc2724b79d55246cb"}, -] -typing-extensions = [ - {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, - {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, -] -urllib3 = [ - {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, - {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, -] -virtualenv = [ - {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, - {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, -] -watchdog = [ - {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"}, - {file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"}, - {file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"}, - {file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"}, - {file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"}, - {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"}, - {file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"}, - {file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"}, - {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"}, - {file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"}, - {file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"}, - {file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"}, - {file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"}, - {file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"}, - {file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"}, - {file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"}, - {file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"}, - {file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"}, - {file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"}, -] -zipp = [ - {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"}, - {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"}, -] - -[[package]] -category = "dev" -description = "Classes Without Boilerplate" -name = "attrs" -optional = false -python-versions = ">=3.5" -version = "22.1.0" - -[package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] - -[[package]] -category = "main" -description = "A decorator for caching properties in classes." -name = "cached-property" -optional = true -python-versions = "*" -version = "1.5.2" - -[[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." -name = "certifi" -optional = false -python-versions = ">=3.6" -version = "2022.6.15.2" - -[[package]] -category = "dev" -description = "Validate configuration and produce human readable error messages." -name = "cfgv" -optional = false -python-versions = ">=3.6.1" -version = "3.3.1" - -[[package]] -category = "main" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -name = "charset-normalizer" -optional = false -python-versions = ">=3.6.0" -version = "2.1.1" - -[package.extras] -unicode_backport = ["unicodedata2"] - -[[package]] -category = "main" -description = "Composable command line interface toolkit" -name = "click" -optional = true -python-versions = ">=3.7" -version = "8.1.3" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -category = "main" -description = "Cross-platform colored terminal text." -name = "colorama" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.5" - -[[package]] -category = "dev" -description = "Code coverage measurement for Python" -name = "coverage" -optional = false -python-versions = ">=3.7" -version = "6.4.4" - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -category = "dev" -description = "Distribution utilities" -name = "distlib" -optional = false -python-versions = "*" -version = "0.3.6" - -[[package]] -category = "dev" -description = "A platform independent file lock." -name = "filelock" -optional = false -python-versions = ">=3.7" -version = "3.8.0" - -[package.extras] -docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] - -[[package]] -category = "main" -description = "Copy your docs directly to the gh-pages branch." -name = "ghp-import" -optional = true -python-versions = "*" -version = "2.1.0" - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -category = "main" -description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." -name = "griffe" -optional = true -python-versions = ">=3.7" -version = "0.22.1" - -[package.dependencies] -cached-property = {version = "*", markers = "python_version < \"3.8\""} - -[package.extras] -async = ["aiofiles (>=0.7,<1.0)"] - -[[package]] -category = "dev" -description = "File identification library for Python" -name = "identify" -optional = false -python-versions = ">=3.7" -version = "2.5.5" - -[package.extras] -license = ["ukkonen"] - -[[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" -optional = false -python-versions = ">=3.5" -version = "3.4" - -[[package]] -category = "main" -description = "Read metadata from Python packages" -name = "importlib-metadata" -optional = false -python-versions = ">=3.7" -version = "4.12.0" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] - -[[package]] -category = "dev" -description = "iniconfig: brain-dead simple config-ini parsing" -name = "iniconfig" -optional = false -python-versions = "*" -version = "1.1.1" - -[[package]] -category = "main" -description = "A very fast and expressive template engine." -name = "Jinja2" -optional = true -python-versions = ">=3.7" -version = "3.1.2" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -category = "main" -description = "Python implementation of Markdown." -name = "Markdown" -optional = true -python-versions = ">=3.6" -version = "3.3.7" - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -testing = ["coverage", "pyyaml"] - -[[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." -name = "MarkupSafe" -optional = true -python-versions = ">=3.7" -version = "2.1.1" - -[[package]] -category = "main" -description = "A deep merge function for 🐍." -name = "mergedeep" -optional = true -python-versions = ">=3.6" -version = "1.3.4" - -[[package]] -category = "main" -description = "Project documentation with Markdown." -name = "mkdocs" -optional = true -python-versions = ">=3.6" -version = "1.3.1" - -[package.dependencies] -Jinja2 = ">=2.10.2" -Markdown = ">=3.2.1,<3.4" -PyYAML = ">=3.10" -click = ">=3.3" -ghp-import = ">=1.0" -importlib-metadata = ">=4.3" -mergedeep = ">=1.3.4" -packaging = ">=20.5" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] - -[[package]] -category = "main" -description = "Automatically link across pages in MkDocs." -name = "mkdocs-autorefs" -optional = true -python-versions = ">=3.7" -version = "0.4.1" - -[package.dependencies] -Markdown = ">=3.3" -mkdocs = ">=1.1" - -[[package]] -category = "main" -description = "Mkdocs Markdown includer plugin." -name = "mkdocs-include-markdown-plugin" -optional = true -python-versions = ">=3.6" -version = "3.7.1" - -[package.extras] -dev = ["bump2version (==1.0.1)", "flake8 (==3.9.2)", "flake8-implicit-str-concat (==0.2.0)", "flake8-print (==4.0.0)", "isort (==5.9.1)", "mdpo (==0.3.61)", "mkdocs (==1.3.1)", "pre-commit (==2.13.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "pyupgrade (==2.19.4)", "yamllint (==1.26.1)"] -test = ["mkdocs (==1.3.1)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)"] - -[[package]] -category = "main" -description = "Automatic documentation from sources, for MkDocs." -name = "mkdocstrings" -optional = true -python-versions = ">=3.7" -version = "0.19.0" - -[package.dependencies] -Jinja2 = ">=2.11.1" -Markdown = ">=3.3" -MarkupSafe = ">=1.1" -mkdocs = ">=1.2" -mkdocs-autorefs = ">=0.3.1" -mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} -pymdown-extensions = ">=6.3" - -[package.extras] -crystal = ["mkdocstrings-crystal (>=0.3.4)"] -python = ["mkdocstrings-python (>=0.5.2)"] -python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] - -[[package]] -category = "main" -description = "A Python handler for mkdocstrings." -name = "mkdocstrings-python" -optional = true -python-versions = ">=3.7" -version = "0.7.1" - -[package.dependencies] -griffe = ">=0.11.1" -mkdocstrings = ">=0.19" - -[[package]] -category = "dev" -description = "Node.js virtual environment builder" -name = "nodeenv" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -version = "1.7.0" - -[package.dependencies] -setuptools = "*" - -[[package]] -category = "main" -description = "Core utilities for Python packages" -name = "packaging" -optional = false -python-versions = ">=3.6" -version = "21.3" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "platformdirs" -optional = false -python-versions = ">=3.7" -version = "2.5.2" - -[package.extras] -docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] - -[[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" -name = "pluggy" -optional = false -python-versions = ">=3.6" -version = "1.0.0" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -category = "dev" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -name = "pre-commit" -optional = false -python-versions = ">=3.7" -version = "2.20.0" - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" - -[[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -name = "py" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "1.11.0" - -[[package]] -category = "main" -description = "Data validation and settings management using python type hints" -name = "pydantic" -optional = false -python-versions = ">=3.7" -version = "1.10.2" - -[package.dependencies] -typing-extensions = ">=4.1.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -category = "main" -description = "Extension pack for Python Markdown." -name = "pymdown-extensions" -optional = true -python-versions = ">=3.7" -version = "9.5" - -[package.dependencies] -markdown = ">=3.2" - -[[package]] -category = "main" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -name = "pyparsing" -optional = false -python-versions = ">=3.6.8" -version = "3.0.9" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" -optional = false -python-versions = ">=3.7" -version = "7.1.3" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -tomli = ">=1.0.0" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -category = "dev" -description = "Pytest plugin for measuring coverage." -name = "pytest-cov" -optional = false -python-versions = ">=3.6" -version = "3.0.0" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -category = "main" -description = "Extensions to the standard Python datetime module" -name = "python-dateutil" -optional = true -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.2" - -[package.dependencies] -six = ">=1.5" - -[[package]] -category = "main" -description = "YAML parser and emitter for Python" -name = "PyYAML" -optional = false -python-versions = ">=3.6" -version = "6.0" - -[[package]] -category = "main" -description = "A custom YAML tag for referencing environment variables in YAML files. " -name = "pyyaml_env_tag" -optional = true -python-versions = ">=3.6" -version = "0.1" - -[package.dependencies] -pyyaml = "*" - -[[package]] -category = "main" -description = "API rate limit decorator" -name = "ratelimit" -optional = false -python-versions = "*" -version = "2.2.1" - -[[package]] -category = "main" -description = "Python HTTP for Humans." -name = "requests" -optional = false -python-versions = ">=3.7, <4" -version = "2.28.1" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -category = "dev" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -name = "setuptools" -optional = false -python-versions = ">=3.7" -version = "65.3.0" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.16.0" - -[[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" -name = "toml" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.10.2" - -[[package]] -category = "dev" -description = "A lil' TOML parser" -name = "tomli" -optional = false -python-versions = ">=3.7" -version = "2.0.1" - -[[package]] -category = "dev" -description = "tox is a generic virtualenv management and test command line tool" -name = "tox" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "3.26.0" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -tomli = {version = ">=2.0.1", markers = "python_version >= \"3.7\" and python_version < \"3.11\""} -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pathlib2 (>=2.3.3)", "psutil (>=5.6.1)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)"] - -[[package]] -category = "dev" -description = "tox plugin that makes tox use `pyenv which` to find python executables" -name = "tox-pyenv" -optional = false -python-versions = "*" -version = "1.1.0" - -[package.dependencies] -tox = ">=2.0" - -[[package]] -category = "main" -description = "Backported and Experimental Type Hints for Python 3.7+" -name = "typing-extensions" -optional = false -python-versions = ">=3.7" -version = "4.3.0" - -[[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." -name = "urllib3" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" -version = "1.26.12" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -category = "dev" -description = "Virtual Python Environment builder" -name = "virtualenv" -optional = false -python-versions = ">=3.6" -version = "20.16.5" - -[package.dependencies] -distlib = ">=0.3.5,<1" -filelock = ">=3.4.1,<4" -importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.8\""} -platformdirs = ">=2.4,<3" - -[package.extras] -docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] -testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] - -[[package]] -category = "main" -description = "Filesystem events monitoring" -name = "watchdog" -optional = true -python-versions = ">=3.6" -version = "2.1.9" - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -category = "main" -description = "Backport of pathlib-compatible object wrapper for zip files" -name = "zipp" -optional = false -python-versions = ">=3.7" -version = "3.8.1" - -[package.extras] -docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] diff --git a/pyproject.toml b/pyproject.toml index 417c1b6..1329286 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,77 +1,88 @@ -[build-system] -build-backend = "poetry.core.masonry.api" -requires = ["poetry-core>=1.0.0"] - -[tool.black] -line-length = 100 -target-version = ["py37", "py38", "py39", "py310"] - -[tool.coverage.report] -show_missing = true - -[tool.coverage.run] -source = ["simyan"] - -[tool.isort] -default_section = "THIRDPARTY" -line_length = 100 -profile = "black" - -[tool.poetry] -authors = ["Buried-In-Code "] -classifiers = [ - "Development Status :: 4 - Beta", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Natural Language :: English", - "Operating System :: MacOS", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Internet", - "Typing :: Typed" -] -description = "A Python wrapper for the Comicvine API." -documentation = "https://simyan.readthedocs.io/en/latest/" -include = [ - { path = "tests", format = "sdist" } -] -keywords = ["comics", "comic", "metadata"] -license = "GPL-3.0-or-later" -maintainers = ["Buried-In-Code "] -name = "Simyan" -packages = [ - { include = "simyan" } -] -readme = "README.md" -repository = "https://github.com/Metron-Project/Simyan" -version = "0.11.0" - -[tool.poetry.dependencies] -mkdocs = {version = "^1.3.1", optional = true} -mkdocs-include-markdown-plugin = {version = "^3.7.1", optional = true} -mkdocstrings = {extras = ["python"], version = "^0.19.0", optional = true} -pydantic = "^1.10.2" -python = "^3.7" -ratelimit = "^2.2.1" -requests = "^2.28.1" - -[tool.poetry.dev-dependencies] -pre-commit = "^2.20.0" -pytest = "^7.1.3" -pytest-cov = "^3.0.0" -tox = "^3.26.0" -tox-pyenv = "^1.1.0" - -[tool.poetry.extras] -docs = ["mkdocs", "mkdocstrings", "mkdocs-include-markdown-plugin"] - -[tool.poetry.urls] -"Issue Tracker" = "https://github.com/Metron-Project/Simyan/issues" - -[tool.pytest.ini_options] -addopts = ["--cov", "-x"] +[build-system] +build-backend = "hatchling.build" +requires = ["hatchling"] + +[project] +authors = [ + { name = "BuriedInCode", email = "BuriedInCode@tuta.io" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "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", + "Topic :: Internet", + "Typing :: Typed" +] +dependencies = [ + "pydantic >= 1.10.2", + "ratelimit >= 2.2.1", + "requests >= 2.28.1" +] +description = "A Python wrapper for the Comicvine API." +dynamic = ["version"] +keywords = ["comics", "comic", "metadata"] +license = "GPL-3.0-or-later" +maintainers = [ + { name = "BuriedInCode", email = "BuriedInCode@tuta.io" }, +] +name = "Simyan" +readme = "README.md" +requires-python = ">= 3.7" + +[project.optional-dependencies] +dev = [ + "pre-commit >= 2.20.0" +] +docs = [ + "mkdocs >= 1.4.2", + "mkdocs-include-markdown-plugin >= 3.9.1", + "mkdocstrings[python] >= 0.19.0" +] +test = [ + "pytest >= 7.2.0", + "pytest-cov >= 4.0.0", + "tox >= 3.27.1", + "tox-pyenv >= 1.1.0" +] + +[project.urls] +Documentation = "https://simyan.readthedocs.io/en/latest/" +Homepage = "https://pypi.org/project/Simyan" +Issues = "https://github.com/Metron-Project/Simyan/issues" +Source = "https://github.com/Metron-Project/Simyan" + +[tool.bandit] +recursive = true +skips = ["B101"] + +[tool.black] +line-length = 100 +target-version = ["py37", "py38", "py39", "py310", "py311"] + +[tool.coverage.report] +show_missing = true + +[tool.coverage.run] +source = ["simyan"] + +[tool.hatch.version] +path = "simyan/__init__.py" + +[tool.isort] +default_section = "THIRDPARTY" +line_length = 100 +profile = "black" + +[tool.pytest.ini_options] +addopts = ["--cov", "-x"] diff --git a/simyan/__init__.py b/simyan/__init__.py index 11d4cb0..1bdee7a 100644 --- a/simyan/__init__.py +++ b/simyan/__init__.py @@ -1,17 +1,17 @@ -"""simyan package entry file.""" -__version__ = "0.11.0" -__all__ = ["__version__", "get_cache_root"] - -from pathlib import Path - - -def get_cache_root() -> Path: - """ - Create and return the path to the cache for simyan. - - Returns: - The path to the simyan cache - """ - folder = Path.home() / ".cache" / "simyan" - folder.mkdir(parents=True, exist_ok=True) - return folder +"""simyan package entry file.""" +__version__ = "0.12.0" +__all__ = ["__version__", "get_cache_root"] + +from pathlib import Path + + +def get_cache_root() -> Path: + """ + Create and return the path to the cache for simyan. + + Returns: + The path to the simyan cache + """ + folder = Path.home() / ".cache" / "simyan" + folder.mkdir(parents=True, exist_ok=True) + return folder diff --git a/simyan/comicvine.py b/simyan/comicvine.py index 6b94571..dfdb499 100644 --- a/simyan/comicvine.py +++ b/simyan/comicvine.py @@ -1,603 +1,603 @@ -""" -The Comicvine module. - -This module provides the following classes: - -- ComicvineResource -- Comicvine -""" -__all__ = ["ComicvineResource", "Comicvine"] -import platform -import re -from enum import Enum -from json import JSONDecodeError -from typing import Any, Dict, List, Optional, Type, TypeVar, Union -from urllib.parse import urlencode - -from pydantic import ValidationError, parse_obj_as -from ratelimit import limits, sleep_and_retry -from requests import get -from requests.exceptions import ConnectionError, HTTPError, ReadTimeout - -from simyan import __version__ -from simyan.exceptions import AuthenticationError, CacheError, ServiceError -from simyan.schemas.character import Character, CharacterEntry -from simyan.schemas.creator import Creator, CreatorEntry -from simyan.schemas.issue import Issue, IssueEntry -from simyan.schemas.location import Location, LocationEntry -from simyan.schemas.publisher import Publisher, PublisherEntry -from simyan.schemas.story_arc import StoryArc, StoryArcEntry -from simyan.schemas.team import Team, TeamEntry -from simyan.schemas.volume import Volume, VolumeEntry -from simyan.sqlite_cache import SQLiteCache - -MINUTE = 60 -T = TypeVar("T") - - -class ComicvineResource(Enum): - """Enum class for Comicvine Resources.""" - - CHARACTER = (4005, "character", List[CharacterEntry]) - """Details for the Character resource on Comicvine.""" - CREATOR = (4040, "person", List[CreatorEntry]) - """Details for the Creator resource on Comicvine.""" - ISSUE = (4000, "issue", List[IssueEntry]) - """Details for the Issue resource on Comicvine.""" - LOCATION = (4020, "location", List[LocationEntry]) - """Details for the Location resource on Comicvine.""" - PUBLISHER = (4010, "publisher", List[PublisherEntry]) - """Details for the Publisher resource on Comicvine.""" - STORY_ARC = (4045, "story_arc", List[StoryArcEntry]) - """Details for the Story Arc resource on Comicvine.""" - TEAM = (4060, "team", List[TeamEntry]) - """Details for the Team resource on Comicvine.""" - VOLUME = (4050, "volume", List[VolumeEntry]) - """Details for the Volume resource on Comicvine.""" - - @property - def resource_id(self) -> int: - """Start of id used by comicvine to create unique ids.""" - return self.value[0] - - @property - def search_resource(self) -> str: - """Resource string for filtering searches.""" - return self.value[1] - - @property - def search_response(self) -> Type[T]: - """Response type for resource when using a search endpoint.""" - return self.value[2] - - -class Comicvine: - """ - Comicvine to request Comicvine API endpoints. - - Args: - api_key: User's API key to access the Comicvine API. - timeout: Set how long requests will wait for a response (in seconds). - cache: SQLiteCache to use if set. - - Attributes: - headers (Dict[str, str]): Header used when requesting from Comicvine API. - api_key (str): User's API key to access the Comicvine API. - timeout (int): How long requests will wait for a response (in seconds). - cache (Optional[SQLiteCache]): SQLiteCache to use if set. - """ - - API_URL = "https://comicvine.gamespot.com/api" - - def __init__(self, api_key: str, timeout: int = 30, cache: Optional[SQLiteCache] = None): - self.headers = { - "Accept": "application/json", - "User-Agent": f"Simyan/{__version__}/{platform.system()}: {platform.release()}", - } - self.api_key = api_key - self.timeout = timeout - self.cache = cache - - @sleep_and_retry - @limits(calls=20, period=MINUTE) - def _perform_get_request(self, url: str, params: Dict[str, str] = None) -> Dict[str, Any]: - """ - Make GET request to Comicvine API endpoint. - - Args: - url: The url to request information from. - params: Parameters to add to the request. - Returns: - Json response from the Comicvine API. - Raises: - ServiceError: If there is an issue with the request or response from the Comicvine API. - """ - if params is None: - params = {} - - try: - response = get(url, params=params, headers=self.headers, timeout=self.timeout) - response.raise_for_status() - return response.json() - except ConnectionError: - raise ServiceError(f"Unable to connect to `{url}`") - except HTTPError as err: - if err.response.status_code == 401: - raise AuthenticationError("Invalid API Key") - elif err.response.status_code == 404: - raise ServiceError("Unknown endpoint") - elif err.response.status_code == 502: - raise ServiceError("Service error, retry again in 30s") - raise ServiceError(err.response.json()["error"]) - except JSONDecodeError: - raise ServiceError(f"Unable to parse response from `{url}` as Json") - except ReadTimeout: - raise ServiceError("Service took too long to respond") - - def _get_request( - self, endpoint: str, params: Dict[str, str] = None, skip_cache: bool = False - ) -> Dict[str, Any]: - """ - Check cache or make GET request to Comicvine API endpoint. - - Args: - endpoint: The endpoint to request information from. - params: Parameters to add to the request. - Returns: - Json response from the Comicvine API. - Raises: - ServiceError: If there is an issue with the request or response from the Comicvine API. - AuthenticationError: If Comicvine returns with an invalid API key response. - CacheError: If it is unable to retrieve or push to the Cache correctly. - """ - if params is None: - params = {} - params["api_key"] = self.api_key - params["format"] = "json" - - cache_params = "" - if params: - cache_params = f"?{urlencode({k: params[k] for k in sorted(params)})}" - - url = self.API_URL + endpoint - cache_key = f"{url}{cache_params}" - cache_key = re.sub(r"(.+api_key=)(.+?)(&.+)", r"\1*****\3", cache_key) - - if self.cache and not skip_cache: - try: - cached_response = self.cache.get(cache_key) - if cached_response is not None: - return cached_response - except AttributeError as e: - raise CacheError(f"Cache object passed in is missing attribute: {repr(e)}") - - response = self._perform_get_request(url=url, params=params) - if "error" in response and response["error"] != "OK": - raise ServiceError(response["error"]) - - if self.cache and not skip_cache: - try: - self.cache.insert(cache_key, response) - except AttributeError as e: - raise CacheError(f"Cache object passed in is missing attribute: {repr(e)}") - - return response - - def publisher(self, publisher_id: int) -> Publisher: - """ - Request data for a Publisher based on its id. - - Args: - publisher_id: The Publisher id. - Returns: - A Publisher object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/publisher/{ComicvineResource.PUBLISHER.resource_id}-{publisher_id}" - )["results"] - return parse_obj_as(Publisher, result) - except ValidationError as err: - raise ServiceError(err) - - def publisher_list( - self, params: Optional[Dict[str, Any]] = None, max_results: int = 500 - ) -> List[PublisherEntry]: - """ - Request data for a list of PublisherEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of PublisherEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/publishers/", params=params, max_results=max_results - ) - return parse_obj_as(List[PublisherEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def volume(self, volume_id: int) -> Volume: - """ - Request data for a Volume based on its id. - - Args: - volume_id: The Volume id. - Returns: - A Volume object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/volume/{ComicvineResource.VOLUME.resource_id}-{volume_id}" - )["results"] - return parse_obj_as(Volume, result) - except ValidationError as err: - raise ServiceError(err) - - def volume_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[VolumeEntry]: - """ - Request data for a list of VolumeEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of VolumeEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/volumes/", params=params, max_results=max_results - ) - return parse_obj_as(List[VolumeEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def issue(self, issue_id: int) -> Issue: - """ - Request data for an Issue based on its id. - - Args: - issue_id: The Issue id. - Returns: - A Issue object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/issue/{ComicvineResource.ISSUE.resource_id}-{issue_id}" - )["results"] - return parse_obj_as(Issue, result) - except ValidationError as err: - raise ServiceError(err) - - def issue_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[IssueEntry]: - """ - Request data for a list of IssueEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of IssueEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/issues/", params=params, max_results=max_results - ) - return parse_obj_as(List[IssueEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def story_arc(self, story_arc_id: int) -> StoryArc: - """ - Request data for a StoryArc based on its id. - - Args: - story_arc_id: The StoryArc id. - Returns: - A StoryArc object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/story_arc/{ComicvineResource.STORY_ARC.resource_id}-{story_arc_id}" - )["results"] - return parse_obj_as(StoryArc, result) - except ValidationError as err: - raise ServiceError(err) - - def story_arc_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[StoryArcEntry]: - """ - Request data for a list of StoryArcEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of StoryArcEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/story_arcs/", params=params, max_results=max_results - ) - return parse_obj_as(List[StoryArcEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def creator(self, creator_id: int) -> Creator: - """ - Request data for a Creator based on its id. - - Args: - creator_id: The Creator id. - Returns: - A Creator object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/person/{ComicvineResource.CREATOR.resource_id}-{creator_id}" - )["results"] - return parse_obj_as(Creator, result) - except ValidationError as err: - raise ServiceError(err) - - def creator_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[CreatorEntry]: - """ - Request data for a list of CreatorEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of CreatorEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/people/", params=params, max_results=max_results - ) - return parse_obj_as(List[CreatorEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def character(self, character_id: int) -> Character: - """ - Request data for a Character based on its id. - - Args: - character_id: The Character id. - Returns: - A Character object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/character/{ComicvineResource.CHARACTER.resource_id}-{character_id}" - )["results"] - return parse_obj_as(Character, result) - except ValidationError as err: - raise ServiceError(err) - - def character_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[CharacterEntry]: - """ - Request data for a list of CharacterEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of CharacterEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/characters/", params=params, max_results=max_results - ) - return parse_obj_as(List[CharacterEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def team(self, team_id: int) -> Team: - """ - Request data for a Team based on its id. - - Args: - team_id: The Team id. - Returns: - A Team object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/team/{ComicvineResource.TEAM.resource_id}-{team_id}" - )["results"] - return parse_obj_as(Team, result) - except ValidationError as err: - raise ServiceError(err) - - def team_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[TeamEntry]: - """ - Request data for a list of TeamEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of TeamEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/teams/", params=params, max_results=max_results - ) - return parse_obj_as(List[TeamEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def location(self, location_id: int) -> Location: - """ - Request data for a Location based on its id. - - Args: - location_id: The Location id. - Returns: - A Location object - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - result = self._get_request( - endpoint=f"/location/{ComicvineResource.LOCATION.resource_id}-{location_id}" - )["results"] - return parse_obj_as(Location, result) - except ValidationError as err: - raise ServiceError(err) - - def location_list( - self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 - ) -> List[LocationEntry]: - """ - Request data for a list of LocationEntries. - - Args: - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of LocationEntry objects. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_offset_results( - endpoint="/locations/", params=params, max_results=max_results - ) - return parse_obj_as(List[LocationEntry], results) - except ValidationError as err: - raise ServiceError(err) - - def search( - self, resource: ComicvineResource, query: str, max_results: int = 500 - ) -> Union[ - List[PublisherEntry], - List[VolumeEntry], - List[IssueEntry], - List[StoryArcEntry], - List[CreatorEntry], - List[CharacterEntry], - List[TeamEntry], - List[LocationEntry], - ]: - """ - Request a list of search results filtered by provided resource. - - Args: - resource: Filter which type of resource to return. - query: Search query string. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of results, mapped to the given resource. - Raises: - ServiceError: If there is an issue with validating the response. - """ - try: - results = self._retrieve_page_results( - endpoint="/search/", - params={"query": query, "resources": resource.search_resource}, - max_results=max_results, - ) - return parse_obj_as(resource.search_response, results) - except ValidationError as err: - raise ServiceError(err) - - def _retrieve_page_results( - self, endpoint: str, params: Optional[Dict[str, Any]] = None, max_results: int = 500 - ) -> List[Dict[str, Any]]: - """ - Get responses until all the results are collected. - - Args: - endpoint: The endpoint to request information from. - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of Json response results. - """ - if params is None: - params = {} - params["page"] = 1 - params["limit"] = 100 - response = self._get_request(endpoint=endpoint, params=params) - results = response["results"] - while ( - response["results"] - and len(results) < response["number_of_total_results"] - and len(results) < max_results - ): - params["page"] += 1 - response = self._get_request(endpoint=endpoint, params=params) - results.extend(response["results"]) - return results[:max_results] - - def _retrieve_offset_results( - self, endpoint: str, params: Optional[Dict[str, Any]] = None, max_results: int = 500 - ) -> List[Dict[str, Any]]: - """ - Get responses until all the results are collected. - - Args: - endpoint: The endpoint to request information from. - params: Parameters to add to the request. - max_results: Limits the amount of results looked up and returned. - Returns: - A list of Json response results. - """ - if params is None: - params = {} - params["limit"] = 100 - response = self._get_request(endpoint=endpoint, params=params) - results = response["results"] - while ( - response["results"] - and len(results) < response["number_of_total_results"] - and len(results) < max_results - ): - params["offset"] = len(results) - response = self._get_request(endpoint=endpoint, params=params) - results.extend(response["results"]) - return results[:max_results] +""" +The Comicvine module. + +This module provides the following classes: + +- ComicvineResource +- Comicvine +""" +__all__ = ["ComicvineResource", "Comicvine"] +import platform +import re +from enum import Enum +from json import JSONDecodeError +from typing import Any, Dict, List, Optional, Type, TypeVar, Union +from urllib.parse import urlencode + +from pydantic import ValidationError, parse_obj_as +from ratelimit import limits, sleep_and_retry +from requests import get +from requests.exceptions import ConnectionError, HTTPError, ReadTimeout + +from simyan import __version__ +from simyan.exceptions import AuthenticationError, CacheError, ServiceError +from simyan.schemas.character import Character, CharacterEntry +from simyan.schemas.creator import Creator, CreatorEntry +from simyan.schemas.issue import Issue, IssueEntry +from simyan.schemas.location import Location, LocationEntry +from simyan.schemas.publisher import Publisher, PublisherEntry +from simyan.schemas.story_arc import StoryArc, StoryArcEntry +from simyan.schemas.team import Team, TeamEntry +from simyan.schemas.volume import Volume, VolumeEntry +from simyan.sqlite_cache import SQLiteCache + +MINUTE = 60 +T = TypeVar("T") + + +class ComicvineResource(Enum): + """Enum class for Comicvine Resources.""" + + CHARACTER = (4005, "character", List[CharacterEntry]) + """Details for the Character resource on Comicvine.""" + CREATOR = (4040, "person", List[CreatorEntry]) + """Details for the Creator resource on Comicvine.""" + ISSUE = (4000, "issue", List[IssueEntry]) + """Details for the Issue resource on Comicvine.""" + LOCATION = (4020, "location", List[LocationEntry]) + """Details for the Location resource on Comicvine.""" + PUBLISHER = (4010, "publisher", List[PublisherEntry]) + """Details for the Publisher resource on Comicvine.""" + STORY_ARC = (4045, "story_arc", List[StoryArcEntry]) + """Details for the Story Arc resource on Comicvine.""" + TEAM = (4060, "team", List[TeamEntry]) + """Details for the Team resource on Comicvine.""" + VOLUME = (4050, "volume", List[VolumeEntry]) + """Details for the Volume resource on Comicvine.""" + + @property + def resource_id(self) -> int: + """Start of id used by comicvine to create unique ids.""" + return self.value[0] + + @property + def search_resource(self) -> str: + """Resource string for filtering searches.""" + return self.value[1] + + @property + def search_response(self) -> Type[T]: + """Response type for resource when using a search endpoint.""" + return self.value[2] + + +class Comicvine: + """ + Comicvine to request Comicvine API endpoints. + + Args: + api_key: User's API key to access the Comicvine API. + timeout: Set how long requests will wait for a response (in seconds). + cache: SQLiteCache to use if set. + + Attributes: + headers (Dict[str, str]): Header used when requesting from Comicvine API. + api_key (str): User's API key to access the Comicvine API. + timeout (int): How long requests will wait for a response (in seconds). + cache (Optional[SQLiteCache]): SQLiteCache to use if set. + """ + + API_URL = "https://comicvine.gamespot.com/api" + + def __init__(self, api_key: str, timeout: int = 30, cache: Optional[SQLiteCache] = None): + self.headers = { + "Accept": "application/json", + "User-Agent": f"Simyan/{__version__}/{platform.system()}: {platform.release()}", + } + self.api_key = api_key + self.timeout = timeout + self.cache = cache + + @sleep_and_retry + @limits(calls=20, period=MINUTE) + def _perform_get_request(self, url: str, params: Dict[str, str] = None) -> Dict[str, Any]: + """ + Make GET request to Comicvine API endpoint. + + Args: + url: The url to request information from. + params: Parameters to add to the request. + Returns: + Json response from the Comicvine API. + Raises: + ServiceError: If there is an issue with the request or response from the Comicvine API. + """ + if params is None: + params = {} + + try: + response = get(url, params=params, headers=self.headers, timeout=self.timeout) + response.raise_for_status() + return response.json() + except ConnectionError: + raise ServiceError(f"Unable to connect to `{url}`") + except HTTPError as err: + if err.response.status_code == 401: + raise AuthenticationError("Invalid API Key") + elif err.response.status_code == 404: + raise ServiceError("Unknown endpoint") + elif err.response.status_code == 502: + raise ServiceError("Service error, retry again in 30s") + raise ServiceError(err.response.json()["error"]) + except JSONDecodeError: + raise ServiceError(f"Unable to parse response from `{url}` as Json") + except ReadTimeout: + raise ServiceError("Service took too long to respond") + + def _get_request( + self, endpoint: str, params: Dict[str, str] = None, skip_cache: bool = False + ) -> Dict[str, Any]: + """ + Check cache or make GET request to Comicvine API endpoint. + + Args: + endpoint: The endpoint to request information from. + params: Parameters to add to the request. + Returns: + Json response from the Comicvine API. + Raises: + ServiceError: If there is an issue with the request or response from the Comicvine API. + AuthenticationError: If Comicvine returns with an invalid API key response. + CacheError: If it is unable to retrieve or push to the Cache correctly. + """ + if params is None: + params = {} + params["api_key"] = self.api_key + params["format"] = "json" + + cache_params = "" + if params: + cache_params = f"?{urlencode({k: params[k] for k in sorted(params)})}" + + url = self.API_URL + endpoint + cache_key = f"{url}{cache_params}" + cache_key = re.sub(r"(.+api_key=)(.+?)(&.+)", r"\1*****\3", cache_key) + + if self.cache and not skip_cache: + try: + cached_response = self.cache.get(cache_key) + if cached_response is not None: + return cached_response + except AttributeError as e: + raise CacheError(f"Cache object passed in is missing attribute: {repr(e)}") + + response = self._perform_get_request(url=url, params=params) + if "error" in response and response["error"] != "OK": + raise ServiceError(response["error"]) + + if self.cache and not skip_cache: + try: + self.cache.insert(cache_key, response) + except AttributeError as e: + raise CacheError(f"Cache object passed in is missing attribute: {repr(e)}") + + return response + + def publisher(self, publisher_id: int) -> Publisher: + """ + Request data for a Publisher based on its id. + + Args: + publisher_id: The Publisher id. + Returns: + A Publisher object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/publisher/{ComicvineResource.PUBLISHER.resource_id}-{publisher_id}" + )["results"] + return parse_obj_as(Publisher, result) + except ValidationError as err: + raise ServiceError(err) + + def publisher_list( + self, params: Optional[Dict[str, Any]] = None, max_results: int = 500 + ) -> List[PublisherEntry]: + """ + Request data for a list of PublisherEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of PublisherEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/publishers/", params=params, max_results=max_results + ) + return parse_obj_as(List[PublisherEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def volume(self, volume_id: int) -> Volume: + """ + Request data for a Volume based on its id. + + Args: + volume_id: The Volume id. + Returns: + A Volume object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/volume/{ComicvineResource.VOLUME.resource_id}-{volume_id}" + )["results"] + return parse_obj_as(Volume, result) + except ValidationError as err: + raise ServiceError(err) + + def volume_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[VolumeEntry]: + """ + Request data for a list of VolumeEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of VolumeEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/volumes/", params=params, max_results=max_results + ) + return parse_obj_as(List[VolumeEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def issue(self, issue_id: int) -> Issue: + """ + Request data for an Issue based on its id. + + Args: + issue_id: The Issue id. + Returns: + A Issue object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/issue/{ComicvineResource.ISSUE.resource_id}-{issue_id}" + )["results"] + return parse_obj_as(Issue, result) + except ValidationError as err: + raise ServiceError(err) + + def issue_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[IssueEntry]: + """ + Request data for a list of IssueEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of IssueEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/issues/", params=params, max_results=max_results + ) + return parse_obj_as(List[IssueEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def story_arc(self, story_arc_id: int) -> StoryArc: + """ + Request data for a StoryArc based on its id. + + Args: + story_arc_id: The StoryArc id. + Returns: + A StoryArc object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/story_arc/{ComicvineResource.STORY_ARC.resource_id}-{story_arc_id}" + )["results"] + return parse_obj_as(StoryArc, result) + except ValidationError as err: + raise ServiceError(err) + + def story_arc_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[StoryArcEntry]: + """ + Request data for a list of StoryArcEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of StoryArcEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/story_arcs/", params=params, max_results=max_results + ) + return parse_obj_as(List[StoryArcEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def creator(self, creator_id: int) -> Creator: + """ + Request data for a Creator based on its id. + + Args: + creator_id: The Creator id. + Returns: + A Creator object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/person/{ComicvineResource.CREATOR.resource_id}-{creator_id}" + )["results"] + return parse_obj_as(Creator, result) + except ValidationError as err: + raise ServiceError(err) + + def creator_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[CreatorEntry]: + """ + Request data for a list of CreatorEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of CreatorEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/people/", params=params, max_results=max_results + ) + return parse_obj_as(List[CreatorEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def character(self, character_id: int) -> Character: + """ + Request data for a Character based on its id. + + Args: + character_id: The Character id. + Returns: + A Character object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/character/{ComicvineResource.CHARACTER.resource_id}-{character_id}" + )["results"] + return parse_obj_as(Character, result) + except ValidationError as err: + raise ServiceError(err) + + def character_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[CharacterEntry]: + """ + Request data for a list of CharacterEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of CharacterEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/characters/", params=params, max_results=max_results + ) + return parse_obj_as(List[CharacterEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def team(self, team_id: int) -> Team: + """ + Request data for a Team based on its id. + + Args: + team_id: The Team id. + Returns: + A Team object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/team/{ComicvineResource.TEAM.resource_id}-{team_id}" + )["results"] + return parse_obj_as(Team, result) + except ValidationError as err: + raise ServiceError(err) + + def team_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[TeamEntry]: + """ + Request data for a list of TeamEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of TeamEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/teams/", params=params, max_results=max_results + ) + return parse_obj_as(List[TeamEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def location(self, location_id: int) -> Location: + """ + Request data for a Location based on its id. + + Args: + location_id: The Location id. + Returns: + A Location object + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + result = self._get_request( + endpoint=f"/location/{ComicvineResource.LOCATION.resource_id}-{location_id}" + )["results"] + return parse_obj_as(Location, result) + except ValidationError as err: + raise ServiceError(err) + + def location_list( + self, params: Optional[Dict[str, Union[str, int]]] = None, max_results: int = 500 + ) -> List[LocationEntry]: + """ + Request data for a list of LocationEntries. + + Args: + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of LocationEntry objects. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_offset_results( + endpoint="/locations/", params=params, max_results=max_results + ) + return parse_obj_as(List[LocationEntry], results) + except ValidationError as err: + raise ServiceError(err) + + def search( + self, resource: ComicvineResource, query: str, max_results: int = 500 + ) -> Union[ + List[PublisherEntry], + List[VolumeEntry], + List[IssueEntry], + List[StoryArcEntry], + List[CreatorEntry], + List[CharacterEntry], + List[TeamEntry], + List[LocationEntry], + ]: + """ + Request a list of search results filtered by provided resource. + + Args: + resource: Filter which type of resource to return. + query: Search query string. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of results, mapped to the given resource. + Raises: + ServiceError: If there is an issue with validating the response. + """ + try: + results = self._retrieve_page_results( + endpoint="/search/", + params={"query": query, "resources": resource.search_resource}, + max_results=max_results, + ) + return parse_obj_as(resource.search_response, results) + except ValidationError as err: + raise ServiceError(err) + + def _retrieve_page_results( + self, endpoint: str, params: Optional[Dict[str, Any]] = None, max_results: int = 500 + ) -> List[Dict[str, Any]]: + """ + Get responses until all the results are collected. + + Args: + endpoint: The endpoint to request information from. + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of Json response results. + """ + if params is None: + params = {} + params["page"] = 1 + params["limit"] = 100 + response = self._get_request(endpoint=endpoint, params=params) + results = response["results"] + while ( + response["results"] + and len(results) < response["number_of_total_results"] + and len(results) < max_results + ): + params["page"] += 1 + response = self._get_request(endpoint=endpoint, params=params) + results.extend(response["results"]) + return results[:max_results] + + def _retrieve_offset_results( + self, endpoint: str, params: Optional[Dict[str, Any]] = None, max_results: int = 500 + ) -> List[Dict[str, Any]]: + """ + Get responses until all the results are collected. + + Args: + endpoint: The endpoint to request information from. + params: Parameters to add to the request. + max_results: Limits the amount of results looked up and returned. + Returns: + A list of Json response results. + """ + if params is None: + params = {} + params["limit"] = 100 + response = self._get_request(endpoint=endpoint, params=params) + results = response["results"] + while ( + response["results"] + and len(results) < response["number_of_total_results"] + and len(results) < max_results + ): + params["offset"] = len(results) + response = self._get_request(endpoint=endpoint, params=params) + results.extend(response["results"]) + return results[:max_results] diff --git a/simyan/exceptions.py b/simyan/exceptions.py index a9f96df..5e74629 100644 --- a/simyan/exceptions.py +++ b/simyan/exceptions.py @@ -1,28 +1,28 @@ -""" -The Exceptions module. - -This module provides the following classes: - -- ServiceError -- AuthenticationError -- CacheError -""" -__all__ = ["ServiceError", "AuthenticationError", "CacheError"] - - -class ServiceError(Exception): - """Class for any API errors.""" - - pass - - -class AuthenticationError(ServiceError): - """Class for any authentication errors.""" - - pass - - -class CacheError(ServiceError): - """Class for any database cache errors.""" - - pass +""" +The Exceptions module. + +This module provides the following classes: + +- ServiceError +- AuthenticationError +- CacheError +""" +__all__ = ["ServiceError", "AuthenticationError", "CacheError"] + + +class ServiceError(Exception): + """Class for any API errors.""" + + pass + + +class AuthenticationError(ServiceError): + """Class for any authentication errors.""" + + pass + + +class CacheError(ServiceError): + """Class for any database cache errors.""" + + pass diff --git a/simyan/schemas/__init__.py b/simyan/schemas/__init__.py index cc3f967..5ea255c 100644 --- a/simyan/schemas/__init__.py +++ b/simyan/schemas/__init__.py @@ -1,22 +1,22 @@ -""" -simyan.schemas package entry file. - -This module provides the following classes: - -- BaseModel -""" -__all__ = ["BaseModel"] - -from pydantic import BaseModel as PydanticModel -from pydantic import Extra - - -class BaseModel(PydanticModel): - """Base model for simyan resources.""" - - class Config: - """Any extra fields will be ignored, strings will have start/end whitespace stripped.""" - - anystr_strip_whitespace = True - allow_population_by_field_name = True - extra = Extra.ignore +""" +simyan.schemas package entry file. + +This module provides the following classes: + +- BaseModel +""" +__all__ = ["BaseModel"] + +from pydantic import BaseModel as PydanticModel +from pydantic import Extra + + +class BaseModel(PydanticModel): + """Base model for simyan resources.""" + + class Config: + """Any extra fields will be ignored, strings will have start/end whitespace stripped.""" + + anystr_strip_whitespace = True + allow_population_by_field_name = True + extra = Extra.ignore diff --git a/simyan/schemas/character.py b/simyan/schemas/character.py index c6ef6d0..5d1126a 100644 --- a/simyan/schemas/character.py +++ b/simyan/schemas/character.py @@ -1,145 +1,145 @@ -""" -The Character module. - -This module provides the following classes: - -- Character -- CharacterEntry -""" -__all__ = ["Character", "CharacterEntry"] -import re -from datetime import date, datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry - - -class Character(BaseModel): - r""" - The Character object contains information for a character. - - Attributes: - aliases: List of names used by the Character, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - creators: List of creators which worked on the Character. - date_added: Date and time when the Character was added. - date_last_updated: Date and time when the Character was last updated. - date_of_birth: Date when the Character was born. - deaths: List of times when the Character has died. - description: Long description of the Character. - enemies: List of enemies the Character has. - enemy_teams: List of enemy teams the Character has. - first_issue: First issue the Character appeared in. - friendly_teams: List of friendly teams the Character has. - friends: List of friends the Character has. - gender: Character gender. - character_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the Character. - issue_count: Number of issues the Character appears in. - issues: List of issues the Character appears in. - name: Real name or public identity of Character. - origin: The type of Character. - powers: List of powers the Character has. - publisher: The publisher of the Character. - real_name: Name of the Character. - site_url: Url to the resource in Comicvine. - story_arcs: List of story arcs the Character appears in. - summary: Short description of the Character. - teams: List of teams the Character appears in. - volumes: List of volumes the Character appears in. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - creators: List[GenericEntry] = Field(default_factory=list) - date_added: datetime - date_last_updated: datetime - date_of_birth: Optional[date] = Field(default=None, alias="birth") - deaths: List[GenericEntry] = Field(default_factory=list, alias="issues_died_in") - description: Optional[str] = None - enemies: List[GenericEntry] = Field(default_factory=list, alias="character_enemies") - enemy_teams: List[GenericEntry] = Field(default_factory=list, alias="team_enemies") - first_issue: IssueEntry = Field(alias="first_appeared_in_issue") - friendly_teams: List[GenericEntry] = Field(default_factory=list, alias="team_friends") - friends: List[GenericEntry] = Field(default_factory=list, alias="character_friends") - gender: int - character_id: int = Field(alias="id") - image: ImageEntry - issue_count: Optional[int] = Field(default=None, alias="count_of_issue_appearances") - issues: List[GenericEntry] = Field(default_factory=list, alias="issue_credits") - name: str - origin: Optional[GenericEntry] = None - powers: List[GenericEntry] = Field(default_factory=list) - publisher: GenericEntry - real_name: Optional[str] = None - site_url: str = Field(alias="site_detail_url") - story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") - summary: Optional[str] = Field(default=None, alias="deck") - teams: List[GenericEntry] = Field(default_factory=list) - volumes: List[GenericEntry] = Field(default_factory=list, alias="volume_credits") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Character has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class CharacterEntry(BaseModel): - r""" - The CharacterEntry object contains information for a character. - - Attributes: - aliases: List of names used by the CharacterEntry, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the CharacterEntry was added. - date_last_updated: Date and time when the CharacterEntry was last updated. - date_of_birth: Date when the CharacterEntry was born. - description: Long description of the CharacterEntry. - first_issue: First issue the CharacterEntry appeared in. - gender: CharacterEntry gender. - character_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the CharacterEntry. - issue_count: Number of issues the CharacterEntry appears in. - name: Real name or public identity of CharacterEntry. - origin: The type of CharacterEntry. - publisher: The publisher of the CharacterEntry. - real_name: Name of the CharacterEntry. - site_url: Url to the resource in Comicvine. - summary: Short description of the CharacterEntry. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - date_of_birth: Optional[date] = Field(default=None, alias="birth") - description: Optional[str] = None - first_issue: IssueEntry = Field(alias="first_appeared_in_issue") - gender: int - character_id: int = Field(alias="id") - image: ImageEntry - issue_count: Optional[int] = Field(default=None, alias="count_of_issue_appearances") - name: str - origin: Optional[GenericEntry] = None - publisher: GenericEntry - real_name: Optional[str] = None - site_url: str = Field(alias="site_detail_url") - summary: Optional[str] = Field(default=None, alias="deck") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the CharacterEntry has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Character module. + +This module provides the following classes: + +- Character +- CharacterEntry +""" +__all__ = ["Character", "CharacterEntry"] +import re +from datetime import date, datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry + + +class Character(BaseModel): + r""" + The Character object contains information for a character. + + Attributes: + aliases: List of names used by the Character, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + creators: List of creators which worked on the Character. + date_added: Date and time when the Character was added. + date_last_updated: Date and time when the Character was last updated. + date_of_birth: Date when the Character was born. + deaths: List of times when the Character has died. + description: Long description of the Character. + enemies: List of enemies the Character has. + enemy_teams: List of enemy teams the Character has. + first_issue: First issue the Character appeared in. + friendly_teams: List of friendly teams the Character has. + friends: List of friends the Character has. + gender: Character gender. + character_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the Character. + issue_count: Number of issues the Character appears in. + issues: List of issues the Character appears in. + name: Real name or public identity of Character. + origin: The type of Character. + powers: List of powers the Character has. + publisher: The publisher of the Character. + real_name: Name of the Character. + site_url: Url to the resource in Comicvine. + story_arcs: List of story arcs the Character appears in. + summary: Short description of the Character. + teams: List of teams the Character appears in. + volumes: List of volumes the Character appears in. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + creators: List[GenericEntry] = Field(default_factory=list) + date_added: datetime + date_last_updated: datetime + date_of_birth: Optional[date] = Field(default=None, alias="birth") + deaths: List[GenericEntry] = Field(default_factory=list, alias="issues_died_in") + description: Optional[str] = None + enemies: List[GenericEntry] = Field(default_factory=list, alias="character_enemies") + enemy_teams: List[GenericEntry] = Field(default_factory=list, alias="team_enemies") + first_issue: IssueEntry = Field(alias="first_appeared_in_issue") + friendly_teams: List[GenericEntry] = Field(default_factory=list, alias="team_friends") + friends: List[GenericEntry] = Field(default_factory=list, alias="character_friends") + gender: int + character_id: int = Field(alias="id") + image: ImageEntry + issue_count: Optional[int] = Field(default=None, alias="count_of_issue_appearances") + issues: List[GenericEntry] = Field(default_factory=list, alias="issue_credits") + name: str + origin: Optional[GenericEntry] = None + powers: List[GenericEntry] = Field(default_factory=list) + publisher: GenericEntry + real_name: Optional[str] = None + site_url: str = Field(alias="site_detail_url") + story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") + summary: Optional[str] = Field(default=None, alias="deck") + teams: List[GenericEntry] = Field(default_factory=list) + volumes: List[GenericEntry] = Field(default_factory=list, alias="volume_credits") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Character has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class CharacterEntry(BaseModel): + r""" + The CharacterEntry object contains information for a character. + + Attributes: + aliases: List of names used by the CharacterEntry, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the CharacterEntry was added. + date_last_updated: Date and time when the CharacterEntry was last updated. + date_of_birth: Date when the CharacterEntry was born. + description: Long description of the CharacterEntry. + first_issue: First issue the CharacterEntry appeared in. + gender: CharacterEntry gender. + character_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the CharacterEntry. + issue_count: Number of issues the CharacterEntry appears in. + name: Real name or public identity of CharacterEntry. + origin: The type of CharacterEntry. + publisher: The publisher of the CharacterEntry. + real_name: Name of the CharacterEntry. + site_url: Url to the resource in Comicvine. + summary: Short description of the CharacterEntry. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + date_of_birth: Optional[date] = Field(default=None, alias="birth") + description: Optional[str] = None + first_issue: IssueEntry = Field(alias="first_appeared_in_issue") + gender: int + character_id: int = Field(alias="id") + image: ImageEntry + issue_count: Optional[int] = Field(default=None, alias="count_of_issue_appearances") + name: str + origin: Optional[GenericEntry] = None + publisher: GenericEntry + real_name: Optional[str] = None + site_url: str = Field(alias="site_detail_url") + summary: Optional[str] = Field(default=None, alias="deck") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the CharacterEntry has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/creator.py b/simyan/schemas/creator.py index 8baaed2..d6f9030 100644 --- a/simyan/schemas/creator.py +++ b/simyan/schemas/creator.py @@ -1,149 +1,149 @@ -""" -The Creator module. - -This module provides the following classes: - -- Creator -- CreatorEntry -""" -__all__ = ["Creator", "CreatorEntry"] -import re -from datetime import date, datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import GenericEntry, ImageEntry - - -class Creator(BaseModel): - r""" - The Creator object contains information for a creator. - - Attributes: - aliases: List of names used by the Creator, separated by `~\r\n` - api_url: Url to the resource in the Comicvine API. - characters: List of characters the Creator has created. - country: Country of origin. - date_added: Date and time when the Creator was added. - date_last_updated: Date and time when the Creator was last updated. - date_of_birth: Date when the Creator was born. - date_of_death: Date when the Creator died. - description: Long description of the Creator. - email: Email address of the Creator. - gender: Creator gender. - hometown: Hometown of the Creator. - creator_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the Creator. - issue_count: Number of issues the Creator appears in. - issues: List of issues the Creator appears in. - name: Name/Title of the Creator. - site_url: Url to the resource in Comicvine. - story_arcs: List of story arcs the Creator appears in. - summary: Short description of the Creator. - volumes: List of volumes the Creator appears in. - website: Url to the Creator's website. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - characters: List[GenericEntry] = Field(default_factory=list, alias="created_characters") - country: Optional[str] = None - date_added: datetime - date_last_updated: datetime - date_of_birth: Optional[date] = Field(default=None, alias="birth") - date_of_death: Optional[date] = Field(default=None, alias="death") - description: Optional[str] = None - email: Optional[str] = None - gender: int - hometown: Optional[str] = None - creator_id: int = Field(alias="id") - image: ImageEntry - issue_count: Optional[int] = Field(default=None, alias="count_of_isssue_appearances") - issues: List[GenericEntry] = Field(default_factory=list) - name: str - site_url: str = Field(alias="site_detail_url") - story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") - summary: Optional[str] = Field(default=None, alias="deck") - volumes: List[GenericEntry] = Field(default_factory=list, alias="volume_credits") - website: Optional[str] = None - - def __init__(self, **data): - if "death" in data and data["death"] is not None: - data["death"] = data["death"]["date"].split()[0] - if data["birth"]: - data["birth"] = data["birth"].split()[0] - super().__init__(**data) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Creator has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class CreatorEntry(BaseModel): - r""" - The CreatorEntry object contains information for a creator. - - Attributes: - aliases: List of names used by the CreatorEntry, separated by `~\r\n` - api_url: Url to the resource in the Comicvine API. - country: Country of origin. - date_added: Date and time when the CreatorEntry was added. - date_last_updated: Date and time when the CreatorEntry was last updated. - date_of_birth: Date when the CreatorEntry was born. - date_of_death: Date when the CreatorEntry died. - description: Long description of the CreatorEntry. - email: Email address of the CreatorEntry. - gender: CreatorEntry gender. - hometown: Hometown of the CreatorEntry. - creator_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the CreatorEntry. - issue_count: Number of issues the CreatorEntry appears in. - name: Name/Title of the CreatorEntry. - site_url: Url to the resource in Comicvine. - summary: Short description of the CreatorEntry. - website: Url to the CreatorEntry's website. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - country: Optional[str] = None - date_added: datetime - date_last_updated: datetime - date_of_birth: Optional[date] = Field(default=None, alias="birth") - date_of_death: Optional[date] = Field(default=None, alias="death") - description: Optional[str] = None - email: Optional[str] = None - gender: int - hometown: Optional[str] = None - creator_id: int = Field(alias="id") - image: ImageEntry - issue_count: Optional[int] = Field(default=None, alias="count_of_isssue_appearances") - name: str - site_url: str = Field(alias="site_detail_url") - summary: Optional[str] = Field(default=None, alias="deck") - website: Optional[str] = None - - def __init__(self, **data): - if "death" in data and data["death"] is not None: - data["death"] = data["death"]["date"].split()[0] - if data["birth"]: - data["birth"] = data["birth"].split()[0] - super().__init__(**data) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the CreatorEntry has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Creator module. + +This module provides the following classes: + +- Creator +- CreatorEntry +""" +__all__ = ["Creator", "CreatorEntry"] +import re +from datetime import date, datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import GenericEntry, ImageEntry + + +class Creator(BaseModel): + r""" + The Creator object contains information for a creator. + + Attributes: + aliases: List of names used by the Creator, separated by `~\r\n` + api_url: Url to the resource in the Comicvine API. + characters: List of characters the Creator has created. + country: Country of origin. + date_added: Date and time when the Creator was added. + date_last_updated: Date and time when the Creator was last updated. + date_of_birth: Date when the Creator was born. + date_of_death: Date when the Creator died. + description: Long description of the Creator. + email: Email address of the Creator. + gender: Creator gender. + hometown: Hometown of the Creator. + creator_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the Creator. + issue_count: Number of issues the Creator appears in. + issues: List of issues the Creator appears in. + name: Name/Title of the Creator. + site_url: Url to the resource in Comicvine. + story_arcs: List of story arcs the Creator appears in. + summary: Short description of the Creator. + volumes: List of volumes the Creator appears in. + website: Url to the Creator's website. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + characters: List[GenericEntry] = Field(default_factory=list, alias="created_characters") + country: Optional[str] = None + date_added: datetime + date_last_updated: datetime + date_of_birth: Optional[date] = Field(default=None, alias="birth") + date_of_death: Optional[date] = Field(default=None, alias="death") + description: Optional[str] = None + email: Optional[str] = None + gender: int + hometown: Optional[str] = None + creator_id: int = Field(alias="id") + image: ImageEntry + issue_count: Optional[int] = Field(default=None, alias="count_of_isssue_appearances") + issues: List[GenericEntry] = Field(default_factory=list) + name: str + site_url: str = Field(alias="site_detail_url") + story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") + summary: Optional[str] = Field(default=None, alias="deck") + volumes: List[GenericEntry] = Field(default_factory=list, alias="volume_credits") + website: Optional[str] = None + + def __init__(self, **data): + if "death" in data and data["death"] is not None: + data["death"] = data["death"]["date"].split()[0] + if data["birth"]: + data["birth"] = data["birth"].split()[0] + super().__init__(**data) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Creator has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class CreatorEntry(BaseModel): + r""" + The CreatorEntry object contains information for a creator. + + Attributes: + aliases: List of names used by the CreatorEntry, separated by `~\r\n` + api_url: Url to the resource in the Comicvine API. + country: Country of origin. + date_added: Date and time when the CreatorEntry was added. + date_last_updated: Date and time when the CreatorEntry was last updated. + date_of_birth: Date when the CreatorEntry was born. + date_of_death: Date when the CreatorEntry died. + description: Long description of the CreatorEntry. + email: Email address of the CreatorEntry. + gender: CreatorEntry gender. + hometown: Hometown of the CreatorEntry. + creator_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the CreatorEntry. + issue_count: Number of issues the CreatorEntry appears in. + name: Name/Title of the CreatorEntry. + site_url: Url to the resource in Comicvine. + summary: Short description of the CreatorEntry. + website: Url to the CreatorEntry's website. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + country: Optional[str] = None + date_added: datetime + date_last_updated: datetime + date_of_birth: Optional[date] = Field(default=None, alias="birth") + date_of_death: Optional[date] = Field(default=None, alias="death") + description: Optional[str] = None + email: Optional[str] = None + gender: int + hometown: Optional[str] = None + creator_id: int = Field(alias="id") + image: ImageEntry + issue_count: Optional[int] = Field(default=None, alias="count_of_isssue_appearances") + name: str + site_url: str = Field(alias="site_detail_url") + summary: Optional[str] = Field(default=None, alias="deck") + website: Optional[str] = None + + def __init__(self, **data): + if "death" in data and data["death"] is not None: + data["death"] = data["death"]["date"].split()[0] + if data["birth"]: + data["birth"] = data["birth"].split()[0] + super().__init__(**data) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the CreatorEntry has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/generic_entries.py b/simyan/schemas/generic_entries.py index 5a50c2f..ace39e6 100644 --- a/simyan/schemas/generic_entries.py +++ b/simyan/schemas/generic_entries.py @@ -1,157 +1,157 @@ -""" -The GenericEntries module. - -This module provides the following classes: - -- GenericEntry -- CountEntry -- IssueEntry -- CreatorEntry -- ImageEntry -- AlternativeImageEntry -""" -__all__ = [ - "GenericEntry", - "CountEntry", - "IssueEntry", - "CreatorEntry", - "ImageEntry", - "AlternativeImageEntry", -] -import re -from typing import List, Optional - -from pydantic import BaseModel, Extra, Field - - -class GenericEntry(BaseModel): - """ - The GenericEntry object contains generic information. - - Attributes: - api_url: Url to the resource in the Comicvine API. - id_: Identifier used by Comicvine. - name: - site_url: Url to the resource in Comicvine. - """ - - api_url: str = Field(alias="api_detail_url") - id_: int = Field(alias="id") - name: Optional[str] = None - site_url: Optional[str] = Field(default=None, alias="site_detail_url") - - class Config: - """Any extra fields will raise an error.""" - - extra = Extra.forbid - - -class CountEntry(GenericEntry): - """ - The CountEntry object contains generic information with an added count field. - - Attributes: - api_url: Url to the resource in the Comicvine API. - id_: Identifier used by Comicvine. - name: - site_url: Url to the resource in Comicvine. - count: - """ - - count: int - - -class IssueEntry(GenericEntry): - """ - The IssueEntry object contains generic information with an added number field. - - Attributes: - api_url: Url to the resource in the Comicvine API. - id_: Identifier used by Comicvine. - name: - site_url: Url to the resource in Comicvine. - number: - """ - - number: Optional[str] = Field(default=None, alias="issue_number") - - -class CreatorEntry(GenericEntry): - r""" - The CreatorEntry object contains generic information with an added roles field. - - Attributes: - api_url: Url to the resource in the Comicvine API. - id_: Identifier used by Comicvine. - name: - site_url: Url to the resource in Comicvine. - roles: List of roles used by the Creator, separated by `~\r\n` - """ - - roles: str = Field(alias="role") - - @property - def role_list(self) -> List[str]: - r""" - List of roles the Creator has used. - - Returns: - List of roles, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.roles) if self.roles else [] - - -class ImageEntry(BaseModel): - """ - The ImageEntry object contains image information. - - Attributes: - icon: Url to image of Icon size. - medium: Url to image of Medium size. - screen: Url to image of Screen size. - screen_large: Url to image of Screen Large size. - small: Url to image of Small size. - super: Url to image of Super size. - thumbnail: Url to image of Thumbnail size. - tiny: Url to image of Tiny size. - original: Url to image of Original size. - tags: - """ - - icon: str = Field(alias="icon_url") - medium: str = Field(alias="medium_url") - screen: str = Field(alias="screen_url") - screen_large: str = Field(alias="screen_large_url") - small: str = Field(alias="small_url") - super: str = Field(alias="super_url") - thumbnail: str = Field(alias="thumb_url") - tiny: str = Field(alias="tiny_url") - original: str = Field(alias="original_url") - tags: Optional[str] = Field(default=None, alias="image_tags") - - class Config: - """Any extra fields will raise an error.""" - - extra = Extra.forbid - - -class AlternativeImageEntry(BaseModel): - """ - The AlternativeImageEntry object contains image information. - - Attributes: - url: Url to image. - id_: Id of image. - caption: Caption/description of the image. - tags: - """ - - url: str = Field(alias="original_url") - id_: int = Field(alias="id") - caption: Optional[str] = None - tags: Optional[str] = Field(default=None, alias="image_tags") - - class Config: - """Any extra fields will raise an error.""" - - extra = Extra.forbid +""" +The GenericEntries module. + +This module provides the following classes: + +- GenericEntry +- CountEntry +- IssueEntry +- CreatorEntry +- ImageEntry +- AlternativeImageEntry +""" +__all__ = [ + "GenericEntry", + "CountEntry", + "IssueEntry", + "CreatorEntry", + "ImageEntry", + "AlternativeImageEntry", +] +import re +from typing import List, Optional + +from pydantic import BaseModel, Extra, Field + + +class GenericEntry(BaseModel): + """ + The GenericEntry object contains generic information. + + Attributes: + api_url: Url to the resource in the Comicvine API. + id_: Identifier used by Comicvine. + name: + site_url: Url to the resource in Comicvine. + """ + + api_url: str = Field(alias="api_detail_url") + id_: int = Field(alias="id") + name: Optional[str] = None + site_url: Optional[str] = Field(default=None, alias="site_detail_url") + + class Config: + """Any extra fields will raise an error.""" + + extra = Extra.forbid + + +class CountEntry(GenericEntry): + """ + The CountEntry object contains generic information with an added count field. + + Attributes: + api_url: Url to the resource in the Comicvine API. + id_: Identifier used by Comicvine. + name: + site_url: Url to the resource in Comicvine. + count: + """ + + count: int + + +class IssueEntry(GenericEntry): + """ + The IssueEntry object contains generic information with an added number field. + + Attributes: + api_url: Url to the resource in the Comicvine API. + id_: Identifier used by Comicvine. + name: + site_url: Url to the resource in Comicvine. + number: + """ + + number: Optional[str] = Field(default=None, alias="issue_number") + + +class CreatorEntry(GenericEntry): + r""" + The CreatorEntry object contains generic information with an added roles field. + + Attributes: + api_url: Url to the resource in the Comicvine API. + id_: Identifier used by Comicvine. + name: + site_url: Url to the resource in Comicvine. + roles: List of roles used by the Creator, separated by `~\r\n` + """ + + roles: str = Field(alias="role") + + @property + def role_list(self) -> List[str]: + r""" + List of roles the Creator has used. + + Returns: + List of roles, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.roles) if self.roles else [] + + +class ImageEntry(BaseModel): + """ + The ImageEntry object contains image information. + + Attributes: + icon: Url to image of Icon size. + medium: Url to image of Medium size. + screen: Url to image of Screen size. + screen_large: Url to image of Screen Large size. + small: Url to image of Small size. + super: Url to image of Super size. + thumbnail: Url to image of Thumbnail size. + tiny: Url to image of Tiny size. + original: Url to image of Original size. + tags: + """ + + icon: str = Field(alias="icon_url") + medium: str = Field(alias="medium_url") + screen: str = Field(alias="screen_url") + screen_large: str = Field(alias="screen_large_url") + small: str = Field(alias="small_url") + super: str = Field(alias="super_url") + thumbnail: str = Field(alias="thumb_url") + tiny: str = Field(alias="tiny_url") + original: str = Field(alias="original_url") + tags: Optional[str] = Field(default=None, alias="image_tags") + + class Config: + """Any extra fields will raise an error.""" + + extra = Extra.forbid + + +class AlternativeImageEntry(BaseModel): + """ + The AlternativeImageEntry object contains image information. + + Attributes: + url: Url to image. + id_: Id of image. + caption: Caption/description of the image. + tags: + """ + + url: str = Field(alias="original_url") + id_: int = Field(alias="id") + caption: Optional[str] = None + tags: Optional[str] = Field(default=None, alias="image_tags") + + class Config: + """Any extra fields will raise an error.""" + + extra = Extra.forbid diff --git a/simyan/schemas/issue.py b/simyan/schemas/issue.py index 17372f8..cd50f88 100644 --- a/simyan/schemas/issue.py +++ b/simyan/schemas/issue.py @@ -1,171 +1,171 @@ -""" -The Issue module. - -This module provides the following classes: - -- Issue -- IssueEntry -""" -__all__ = ["Issue", "IssueEntry"] -import re -from datetime import date, datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import ( - AlternativeImageEntry, - CreatorEntry, - GenericEntry, - ImageEntry, -) - - -class Issue(BaseModel): - r""" - The Issue object contains information for an issue. - - Attributes: - aliases: List of names used by the Issue, separated by `~\r\n`. - alternative_images: List of different images associated with the Issue. - api_url: Url to the resource in the Comicvine API. - characters: List of characters in the Issue. - concepts: List of concepts in the Issue. - cover_date: Date on the cover of the Issue. - creators: List of creators in the Issue. - date_added: Date and time when the Issue was added. - date_last_updated: Date and time when the Issue was last updated. - deaths: List of characters who died in the Issue. - description: Long description of the Issue. - first_appearance_characters: List of characters who first appear in the Issue. - first_appearance_concepts: List of concepts which first appear in the Issue. - first_appearance_locations: List of locations which first appear in the Issue. - first_appearance_objects: List of objects which first appear in the Issue. - first_appearance_story_arcs: List of story arcs which first appear in the Issue. - first_appearance_teams: List of teams who first appear in the Issue. - issue_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the Issue. - locations: List of locations in the Issue. - name: Name/Title of the Issue. - number: The Issue number - objects: List of objects in the Issue. - site_url: Url to the resource in Comicvine. - store_date: Date the Issue went on sale on stores. - story_arcs: List of story arcs in the Issue. - summary: Short description of the Issue. - teams: List of teams in the Issue. - teams_disbanded: List of teams who disbanded in the Issue. - volume: The volume the Issue is in. - """ - - aliases: Optional[str] = None - alternative_images: List[AlternativeImageEntry] = Field( - default_factory=list, alias="associated_images" - ) - api_url: str = Field(alias="api_detail_url") - characters: List[GenericEntry] = Field(default_factory=list, alias="character_credits") - concepts: List[GenericEntry] = Field(default_factory=list, alias="concept_credits") - cover_date: Optional[date] = None - creators: List[CreatorEntry] = Field(default_factory=list, alias="person_credits") - date_added: datetime - date_last_updated: datetime - deaths: List[GenericEntry] = Field(default_factory=list, alias="character_died_in") - description: Optional[str] = None - first_appearance_characters: List[GenericEntry] = Field(default_factory=list) - first_appearance_concepts: List[GenericEntry] = Field(default_factory=list) - first_appearance_locations: List[GenericEntry] = Field(default_factory=list) - first_appearance_objects: List[GenericEntry] = Field(default_factory=list) - first_appearance_story_arcs: List[GenericEntry] = Field( - default_factory=list, alias="first_appearance_storyarcs" - ) - first_appearance_teams: List[GenericEntry] = Field(default_factory=list) - issue_id: int = Field(alias="id") - image: ImageEntry - locations: List[GenericEntry] = Field(default_factory=list, alias="location_credits") - name: Optional[str] = None - number: str = Field(alias="issue_number") - objects: List[GenericEntry] = Field(default_factory=list, alias="object_credits") - site_url: str = Field(alias="site_detail_url") - store_date: Optional[date] = None - story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") - summary: Optional[str] = Field(default=None, alias="deck") - teams: List[GenericEntry] = Field(default_factory=list, alias="team_credits") - teams_disbanded: List[GenericEntry] = Field(default_factory=list, alias="team_disbanded_in") - volume: GenericEntry - - def __init__(self, **data): - if "first_appearance_characters" in data and not data["first_appearance_characters"]: - data["first_appearance_characters"] = [] - if "first_appearance_concepts" in data and not data["first_appearance_concepts"]: - data["first_appearance_concepts"] = [] - if "first_appearance_locations" in data and not data["first_appearance_locations"]: - data["first_appearance_locations"] = [] - if "first_appearance_objects" in data and not data["first_appearance_objects"]: - data["first_appearance_objects"] = [] - if "first_appearance_storyarcs" in data and not data["first_appearance_storyarcs"]: - data["first_appearance_storyarcs"] = [] - if "first_appearance_teams" in data and not data["first_appearance_teams"]: - data["first_appearance_teams"] = [] - super().__init__(**data) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Issue has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class IssueEntry(BaseModel): - r""" - The IssueEntry object contains information for an issue. - - Attributes: - aliases: List of names used by the IssueEntry, separated by `~\r\n`. - alternative_images: List of different images associated with the IssueEntry. - api_url: Url to the resource in the Comicvine API. - cover_date: Date on the cover of the IssueEntry. - date_added: Date and time when the IssueEntry was added. - date_last_updated: Date and time when the IssueEntry was last updated. - description: Long description of the IssueEntry. - issue_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the IssueEntry. - name: Name/Title of the IssueEntry. - number: The IssueEntry number. - site_url: Url to the resource in Comicvine. - store_date: Date the IssueEntry went on sale on stores. - summary: Short description of the IssueEntry. - volume: The volume the IssueEntry is in. - """ - - aliases: Optional[str] = None - alternative_images: List[AlternativeImageEntry] = Field( - default_factory=list, alias="associated_images" - ) - api_url: str = Field(alias="api_detail_url") - cover_date: Optional[date] = None - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - issue_id: int = Field(alias="id") - image: ImageEntry - name: Optional[str] = None - number: str = Field(alias="issue_number") - site_url: str = Field(alias="site_detail_url") - store_date: Optional[date] = None - summary: Optional[str] = Field(default=None, alias="deck") - volume: GenericEntry - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Issue has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Issue module. + +This module provides the following classes: + +- Issue +- IssueEntry +""" +__all__ = ["Issue", "IssueEntry"] +import re +from datetime import date, datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import ( + AlternativeImageEntry, + CreatorEntry, + GenericEntry, + ImageEntry, +) + + +class Issue(BaseModel): + r""" + The Issue object contains information for an issue. + + Attributes: + aliases: List of names used by the Issue, separated by `~\r\n`. + alternative_images: List of different images associated with the Issue. + api_url: Url to the resource in the Comicvine API. + characters: List of characters in the Issue. + concepts: List of concepts in the Issue. + cover_date: Date on the cover of the Issue. + creators: List of creators in the Issue. + date_added: Date and time when the Issue was added. + date_last_updated: Date and time when the Issue was last updated. + deaths: List of characters who died in the Issue. + description: Long description of the Issue. + first_appearance_characters: List of characters who first appear in the Issue. + first_appearance_concepts: List of concepts which first appear in the Issue. + first_appearance_locations: List of locations which first appear in the Issue. + first_appearance_objects: List of objects which first appear in the Issue. + first_appearance_story_arcs: List of story arcs which first appear in the Issue. + first_appearance_teams: List of teams who first appear in the Issue. + issue_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the Issue. + locations: List of locations in the Issue. + name: Name/Title of the Issue. + number: The Issue number + objects: List of objects in the Issue. + site_url: Url to the resource in Comicvine. + store_date: Date the Issue went on sale on stores. + story_arcs: List of story arcs in the Issue. + summary: Short description of the Issue. + teams: List of teams in the Issue. + teams_disbanded: List of teams who disbanded in the Issue. + volume: The volume the Issue is in. + """ + + aliases: Optional[str] = None + alternative_images: List[AlternativeImageEntry] = Field( + default_factory=list, alias="associated_images" + ) + api_url: str = Field(alias="api_detail_url") + characters: List[GenericEntry] = Field(default_factory=list, alias="character_credits") + concepts: List[GenericEntry] = Field(default_factory=list, alias="concept_credits") + cover_date: Optional[date] = None + creators: List[CreatorEntry] = Field(default_factory=list, alias="person_credits") + date_added: datetime + date_last_updated: datetime + deaths: List[GenericEntry] = Field(default_factory=list, alias="character_died_in") + description: Optional[str] = None + first_appearance_characters: List[GenericEntry] = Field(default_factory=list) + first_appearance_concepts: List[GenericEntry] = Field(default_factory=list) + first_appearance_locations: List[GenericEntry] = Field(default_factory=list) + first_appearance_objects: List[GenericEntry] = Field(default_factory=list) + first_appearance_story_arcs: List[GenericEntry] = Field( + default_factory=list, alias="first_appearance_storyarcs" + ) + first_appearance_teams: List[GenericEntry] = Field(default_factory=list) + issue_id: int = Field(alias="id") + image: ImageEntry + locations: List[GenericEntry] = Field(default_factory=list, alias="location_credits") + name: Optional[str] = None + number: str = Field(alias="issue_number") + objects: List[GenericEntry] = Field(default_factory=list, alias="object_credits") + site_url: str = Field(alias="site_detail_url") + store_date: Optional[date] = None + story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") + summary: Optional[str] = Field(default=None, alias="deck") + teams: List[GenericEntry] = Field(default_factory=list, alias="team_credits") + teams_disbanded: List[GenericEntry] = Field(default_factory=list, alias="team_disbanded_in") + volume: GenericEntry + + def __init__(self, **data): + if "first_appearance_characters" in data and not data["first_appearance_characters"]: + data["first_appearance_characters"] = [] + if "first_appearance_concepts" in data and not data["first_appearance_concepts"]: + data["first_appearance_concepts"] = [] + if "first_appearance_locations" in data and not data["first_appearance_locations"]: + data["first_appearance_locations"] = [] + if "first_appearance_objects" in data and not data["first_appearance_objects"]: + data["first_appearance_objects"] = [] + if "first_appearance_storyarcs" in data and not data["first_appearance_storyarcs"]: + data["first_appearance_storyarcs"] = [] + if "first_appearance_teams" in data and not data["first_appearance_teams"]: + data["first_appearance_teams"] = [] + super().__init__(**data) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Issue has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class IssueEntry(BaseModel): + r""" + The IssueEntry object contains information for an issue. + + Attributes: + aliases: List of names used by the IssueEntry, separated by `~\r\n`. + alternative_images: List of different images associated with the IssueEntry. + api_url: Url to the resource in the Comicvine API. + cover_date: Date on the cover of the IssueEntry. + date_added: Date and time when the IssueEntry was added. + date_last_updated: Date and time when the IssueEntry was last updated. + description: Long description of the IssueEntry. + issue_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the IssueEntry. + name: Name/Title of the IssueEntry. + number: The IssueEntry number. + site_url: Url to the resource in Comicvine. + store_date: Date the IssueEntry went on sale on stores. + summary: Short description of the IssueEntry. + volume: The volume the IssueEntry is in. + """ + + aliases: Optional[str] = None + alternative_images: List[AlternativeImageEntry] = Field( + default_factory=list, alias="associated_images" + ) + api_url: str = Field(alias="api_detail_url") + cover_date: Optional[date] = None + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + issue_id: int = Field(alias="id") + image: ImageEntry + name: Optional[str] = None + number: str = Field(alias="issue_number") + site_url: str = Field(alias="site_detail_url") + store_date: Optional[date] = None + summary: Optional[str] = Field(default=None, alias="deck") + volume: GenericEntry + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Issue has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/location.py b/simyan/schemas/location.py index b5d240b..605b5f3 100644 --- a/simyan/schemas/location.py +++ b/simyan/schemas/location.py @@ -1,114 +1,114 @@ -""" -The Location module. - -This module provides the following classes: - -- Location -- LocationEntry -""" -__all__ = ["Location", "LocationEntry"] - -import re -from datetime import datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry - - -class Location(BaseModel): - r""" - The Location object contains information for a location. - - Attributes: - aliases: List of names used by the Location, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the Location was added. - date_last_updated: Date and time when the Location was last updated. - description: Long description of the Location. - first_issue: First issue the Location appeared in. - image: Different sized images, posters and thumbnails for the Location. - issue_count: Number of issues the Location appears in. - issues: List of issues the Location appears in. - location_id: Identifier used by Comicvine. - name: Name/Title of the Location. - site_url: Url to the resource in Comicvine. - start_year: The year the Location was first used. - story_arcs: List of story arcs the Location appears in. - summary: Short description of the Location. - volumes: List of volumes the Location appears in. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") - image: ImageEntry - issue_count: int = Field(alias="count_of_issue_appearances") - issues: List[IssueEntry] = Field(default_factory=list, alias="issue_credits") - location_id: int = Field(alias="id") - name: str - site_url: str = Field(alias="site_detail_url") - start_year: Optional[int] = None - story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") - summary: Optional[str] = Field(default=None, alias="deck") - volumes: List[GenericEntry] = Field(default_factory=list, alias="volume_credits") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Location has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class LocationEntry(BaseModel): - r""" - The LocationEntry object contains information for a location. - - Attributes: - aliases: List of names used by the LocationEntry, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the LocationEntry was added. - date_last_updated: Date and time when the LocationEntry was last updated. - description: Long description of the LocationEntry. - first_issue: First issue the LocationEntry appeared in. - image: Different sized images, posters and thumbnails for the LocationEntry. - issue_count: Number of issues the LocationEntry appears in. - location_id: Identifier used by Comicvine. - name: Name/Title of the LocationEntry. - site_url: Url to the resource in Comicvine. - start_year: The year the LocationEntry was first used. - summary: Short description of the LocationEntry. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") - image: ImageEntry - issue_count: int = Field(alias="count_of_issue_appearances") - location_id: int = Field(alias="id") - name: str - site_url: str = Field(alias="site_detail_url") - start_year: Optional[int] = None - summary: Optional[str] = Field(default=None, alias="deck") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the LocationEntry has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Location module. + +This module provides the following classes: + +- Location +- LocationEntry +""" +__all__ = ["Location", "LocationEntry"] + +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry + + +class Location(BaseModel): + r""" + The Location object contains information for a location. + + Attributes: + aliases: List of names used by the Location, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the Location was added. + date_last_updated: Date and time when the Location was last updated. + description: Long description of the Location. + first_issue: First issue the Location appeared in. + image: Different sized images, posters and thumbnails for the Location. + issue_count: Number of issues the Location appears in. + issues: List of issues the Location appears in. + location_id: Identifier used by Comicvine. + name: Name/Title of the Location. + site_url: Url to the resource in Comicvine. + start_year: The year the Location was first used. + story_arcs: List of story arcs the Location appears in. + summary: Short description of the Location. + volumes: List of volumes the Location appears in. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") + image: ImageEntry + issue_count: int = Field(alias="count_of_issue_appearances") + issues: List[IssueEntry] = Field(default_factory=list, alias="issue_credits") + location_id: int = Field(alias="id") + name: str + site_url: str = Field(alias="site_detail_url") + start_year: Optional[int] = None + story_arcs: List[GenericEntry] = Field(default_factory=list, alias="story_arc_credits") + summary: Optional[str] = Field(default=None, alias="deck") + volumes: List[GenericEntry] = Field(default_factory=list, alias="volume_credits") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Location has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class LocationEntry(BaseModel): + r""" + The LocationEntry object contains information for a location. + + Attributes: + aliases: List of names used by the LocationEntry, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the LocationEntry was added. + date_last_updated: Date and time when the LocationEntry was last updated. + description: Long description of the LocationEntry. + first_issue: First issue the LocationEntry appeared in. + image: Different sized images, posters and thumbnails for the LocationEntry. + issue_count: Number of issues the LocationEntry appears in. + location_id: Identifier used by Comicvine. + name: Name/Title of the LocationEntry. + site_url: Url to the resource in Comicvine. + start_year: The year the LocationEntry was first used. + summary: Short description of the LocationEntry. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") + image: ImageEntry + issue_count: int = Field(alias="count_of_issue_appearances") + location_id: int = Field(alias="id") + name: str + site_url: str = Field(alias="site_detail_url") + start_year: Optional[int] = None + summary: Optional[str] = Field(default=None, alias="deck") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the LocationEntry has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/publisher.py b/simyan/schemas/publisher.py index 3b113bd..edca86b 100644 --- a/simyan/schemas/publisher.py +++ b/simyan/schemas/publisher.py @@ -1,115 +1,115 @@ -""" -The Publisher module. - -This module provides the following classes: - -- Publisher -- PublisherEntry -""" -__all__ = ["Publisher", "PublisherEntry"] -import re -from datetime import datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import GenericEntry, ImageEntry - - -class Publisher(BaseModel): - r""" - The Publisher object contains information for a publisher. - - Attributes: - aliases: List of names used by the Publisher, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - characters: List of characters the Publisher created. - date_added: Date and time when the Publisher was added. - date_last_updated: Date and time when the Publisher was last updated. - description: Long description of the Publisher. - publisher_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the Publisher. - location_address: Address where the Publisher is located. - location_city: City where the Publisher is located. - location_state: State where the Publisher is located. - name: Name/Title of the Publisher. - site_url: Url to the resource in Comicvine. - story_arcs: List of story arcs the Publisher created. - summary: Short description of the Publisher. - teams: List of teams the Publisher created. - volumes: List of volumes the Publisher created. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - characters: List[GenericEntry] = Field(default_factory=list) - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - publisher_id: int = Field(alias="id") - image: ImageEntry - location_address: Optional[str] = None - location_city: Optional[str] = None - location_state: Optional[str] = None - name: str - site_url: str = Field(alias="site_detail_url") - story_arcs: List[GenericEntry] = Field(default_factory=list) - summary: Optional[str] = Field(default=None, alias="deck") - teams: List[GenericEntry] = Field(default_factory=list) - volumes: List[GenericEntry] = Field(default_factory=list) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Publisher has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class PublisherEntry(BaseModel): - r""" - The PublisherEntry object contains information for a publisher. - - Attributes: - aliases: List of names used by the PublisherEntry, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the PublisherEntry was added. - date_last_updated: Date and time when the PublisherEntry was last updated. - description: Long description of the PublisherEntry. - publisher_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the PublisherEntry. - location_address: Address where the PublisherEntry is located. - location_city: City where the PublisherEntry is located. - location_state: State where the PublisherEntry is located. - name: Name/Title of the PublisherEntry. - site_url: Url to the resource in Comicvine. - summary: Short description of the PublisherEntry. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - publisher_id: int = Field(alias="id") - image: ImageEntry - location_address: Optional[str] = None - location_city: Optional[str] = None - location_state: Optional[str] = None - name: str - site_url: str = Field(alias="site_detail_url") - summary: Optional[str] = Field(default=None, alias="deck") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Publisher has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Publisher module. + +This module provides the following classes: + +- Publisher +- PublisherEntry +""" +__all__ = ["Publisher", "PublisherEntry"] +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import GenericEntry, ImageEntry + + +class Publisher(BaseModel): + r""" + The Publisher object contains information for a publisher. + + Attributes: + aliases: List of names used by the Publisher, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + characters: List of characters the Publisher created. + date_added: Date and time when the Publisher was added. + date_last_updated: Date and time when the Publisher was last updated. + description: Long description of the Publisher. + publisher_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the Publisher. + location_address: Address where the Publisher is located. + location_city: City where the Publisher is located. + location_state: State where the Publisher is located. + name: Name/Title of the Publisher. + site_url: Url to the resource in Comicvine. + story_arcs: List of story arcs the Publisher created. + summary: Short description of the Publisher. + teams: List of teams the Publisher created. + volumes: List of volumes the Publisher created. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + characters: List[GenericEntry] = Field(default_factory=list) + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + publisher_id: int = Field(alias="id") + image: ImageEntry + location_address: Optional[str] = None + location_city: Optional[str] = None + location_state: Optional[str] = None + name: str + site_url: str = Field(alias="site_detail_url") + story_arcs: List[GenericEntry] = Field(default_factory=list) + summary: Optional[str] = Field(default=None, alias="deck") + teams: List[GenericEntry] = Field(default_factory=list) + volumes: List[GenericEntry] = Field(default_factory=list) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Publisher has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class PublisherEntry(BaseModel): + r""" + The PublisherEntry object contains information for a publisher. + + Attributes: + aliases: List of names used by the PublisherEntry, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the PublisherEntry was added. + date_last_updated: Date and time when the PublisherEntry was last updated. + description: Long description of the PublisherEntry. + publisher_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the PublisherEntry. + location_address: Address where the PublisherEntry is located. + location_city: City where the PublisherEntry is located. + location_state: State where the PublisherEntry is located. + name: Name/Title of the PublisherEntry. + site_url: Url to the resource in Comicvine. + summary: Short description of the PublisherEntry. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + publisher_id: int = Field(alias="id") + image: ImageEntry + location_address: Optional[str] = None + location_city: Optional[str] = None + location_state: Optional[str] = None + name: str + site_url: str = Field(alias="site_detail_url") + summary: Optional[str] = Field(default=None, alias="deck") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Publisher has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/story_arc.py b/simyan/schemas/story_arc.py index b2a9398..8a64508 100644 --- a/simyan/schemas/story_arc.py +++ b/simyan/schemas/story_arc.py @@ -1,109 +1,109 @@ -""" -The StoryArc module. - -This module provides the following classes: - -- StoryArc -- StoryArcEntry -""" -__all__ = ["StoryArc", "StoryArcEntry"] -import re -from datetime import datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry - - -class StoryArc(BaseModel): - r""" - The StoryArc object contains information for a story arc. - - Attributes: - aliases: List of names used by the StoryArc, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the StoryArc was added. - date_last_updated: Date and time when the StoryArc was last updated. - description: Long description of the StoryArc. - first_issue: First issue of the StoryArc. - story_arc_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the StoryArc. - issue_count: Number of issues in the StoryArc. - issues: List of issues in the StoryArc. - name: Name/Title of the StoryArc. - publisher: The publisher of the StoryArc. - site_url: Url to the resource in Comicvine. - summary: Short description of the StoryArc. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") - story_arc_id: int = Field(alias="id") - image: ImageEntry - issue_count: int = Field(alias="count_of_isssue_appearances") - issues: List[GenericEntry] = Field(default_factory=list) - name: str - publisher: Optional[GenericEntry] = None - site_url: str = Field(alias="site_detail_url") - summary: Optional[str] = Field(default=None, alias="deck") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the StoryArc has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class StoryArcEntry(BaseModel): - r""" - The StoryArcEntry object contains information for a story arc. - - Attributes: - aliases: List of names used by the StoryArcEntry, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the StoryArcEntry was added. - date_last_updated: Date and time when the StoryArcEntry was last updated. - description: Long description of the StoryArcEntry. - first_issue: First issue of the StoryArcEntry. - story_arc_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the StoryArcEntry. - issue_count: Number of issues in the StoryArcEntry. - name: Name/Title of the StoryArcEntry. - publisher: The publisher of the StoryArcEntry. - site_url: Url to the resource in Comicvine. - summary: Short description of the StoryArcEntry. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") - story_arc_id: int = Field(alias="id") - image: ImageEntry - issue_count: int = Field(alias="count_of_isssue_appearances") - name: str - publisher: Optional[GenericEntry] = None - site_url: str = Field(alias="site_detail_url") - summary: Optional[str] = Field(default=None, alias="deck") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the StoryArcEntry has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The StoryArc module. + +This module provides the following classes: + +- StoryArc +- StoryArcEntry +""" +__all__ = ["StoryArc", "StoryArcEntry"] +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry + + +class StoryArc(BaseModel): + r""" + The StoryArc object contains information for a story arc. + + Attributes: + aliases: List of names used by the StoryArc, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the StoryArc was added. + date_last_updated: Date and time when the StoryArc was last updated. + description: Long description of the StoryArc. + first_issue: First issue of the StoryArc. + story_arc_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the StoryArc. + issue_count: Number of issues in the StoryArc. + issues: List of issues in the StoryArc. + name: Name/Title of the StoryArc. + publisher: The publisher of the StoryArc. + site_url: Url to the resource in Comicvine. + summary: Short description of the StoryArc. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") + story_arc_id: int = Field(alias="id") + image: ImageEntry + issue_count: int = Field(alias="count_of_isssue_appearances") + issues: List[GenericEntry] = Field(default_factory=list) + name: str + publisher: Optional[GenericEntry] = None + site_url: str = Field(alias="site_detail_url") + summary: Optional[str] = Field(default=None, alias="deck") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the StoryArc has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class StoryArcEntry(BaseModel): + r""" + The StoryArcEntry object contains information for a story arc. + + Attributes: + aliases: List of names used by the StoryArcEntry, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the StoryArcEntry was added. + date_last_updated: Date and time when the StoryArcEntry was last updated. + description: Long description of the StoryArcEntry. + first_issue: First issue of the StoryArcEntry. + story_arc_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the StoryArcEntry. + issue_count: Number of issues in the StoryArcEntry. + name: Name/Title of the StoryArcEntry. + publisher: The publisher of the StoryArcEntry. + site_url: Url to the resource in Comicvine. + summary: Short description of the StoryArcEntry. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: Optional[IssueEntry] = Field(default=None, alias="first_appeared_in_issue") + story_arc_id: int = Field(alias="id") + image: ImageEntry + issue_count: int = Field(alias="count_of_isssue_appearances") + name: str + publisher: Optional[GenericEntry] = None + site_url: str = Field(alias="site_detail_url") + summary: Optional[str] = Field(default=None, alias="deck") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the StoryArcEntry has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/team.py b/simyan/schemas/team.py index 5da919d..a4b2130 100644 --- a/simyan/schemas/team.py +++ b/simyan/schemas/team.py @@ -1,127 +1,127 @@ -""" -The Team module. - -This module provides the following classes: - -- Team -- TeamEntry -""" -__all__ = ["Team", "TeamEntry"] -import re -from datetime import datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry - - -class Team(BaseModel): - r""" - The Team object contains information for a team. - - Attributes: - aliases: List of names used by the Team, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the Team was added. - date_last_updated: Date and time when the Team was last updated. - description: Long description of the Team. - enemies: List of enemies of the Team. - first_issue: First issue the Team appeared in. - friends: List of friends of the Team. - image: Different sized images, posters and thumbnails for the Team. - issue_count: Number of issues the Team appears in. - issues: List of issues the Team appears in. - issues_disbanded_in: List of issues the Team disbanded in. - member_count: Number of members in the Team. - members: List of members in the Team. - name: Name/Title of the Team. - publisher: The publisher of the Team. - site_url: Url to the resource in Comicvine. - story_arcs: List of story arcs the Team appears in. - summary: Short description of the Team. - team_id: Identifier used by Comicvine. - volumes: List of volumes the Team appears in. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - enemies: List[GenericEntry] = Field(alias="character_enemies", default_factory=list) - first_issue: IssueEntry = Field(alias="first_appeared_in_issue") - friends: List[GenericEntry] = Field(alias="character_friends", default_factory=list) - image: ImageEntry - issue_count: int = Field(alias="count_of_isssue_appearances") - issues: List[GenericEntry] = Field(alias="issue_credits", default_factory=list) - issues_disbanded_in: List[GenericEntry] = Field( - alias="disbanded_in_issues", default_factory=list - ) - member_count: int = Field(alias="count_of_team_members") - members: List[GenericEntry] = Field(alias="characters", default_factory=list) - name: str - publisher: GenericEntry - site_url: str = Field(alias="site_detail_url") - story_arcs: List[GenericEntry] = Field(alias="story_arc_credits", default_factory=list) - summary: Optional[str] = Field(alias="deck", default=None) - team_id: int = Field(alias="id") - volumes: List[GenericEntry] = Field(alias="volume_credits", default_factory=list) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Team has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class TeamEntry(BaseModel): - r""" - The TeamEntry object contains information for a team. - - Attributes: - aliases: List of names used by the TeamEntry, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the TeamEntry was added. - date_last_updated: Date and time when the TeamEntry was last updated. - description: Long description of the TeamEntry. - first_issue: First issue the TeamEntry appeared in. - image: Different sized images, posters and thumbnails for the TeamEntry. - issue_count: Number of issues the TeamEntry appears in. - member_count: Number of members in the TeamEntry. - name: Name/Title of the TeamEntry. - publisher: The publisher of the TeamEntry. - site_url: Url to the resource in Comicvine. - summary: Short description of the TeamEntry. - team_id: Identifier used by Comicvine. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: IssueEntry = Field(alias="first_appeared_in_issue") - image: ImageEntry - issue_count: int = Field(alias="count_of_isssue_appearances") - member_count: int = Field(alias="count_of_team_members") - name: str - publisher: GenericEntry - site_url: str = Field(alias="site_detail_url") - summary: Optional[str] = Field(alias="deck", default=None) - team_id: int = Field(alias="id") - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the TeamEntry has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Team module. + +This module provides the following classes: + +- Team +- TeamEntry +""" +__all__ = ["Team", "TeamEntry"] +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import GenericEntry, ImageEntry, IssueEntry + + +class Team(BaseModel): + r""" + The Team object contains information for a team. + + Attributes: + aliases: List of names used by the Team, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the Team was added. + date_last_updated: Date and time when the Team was last updated. + description: Long description of the Team. + enemies: List of enemies of the Team. + first_issue: First issue the Team appeared in. + friends: List of friends of the Team. + image: Different sized images, posters and thumbnails for the Team. + issue_count: Number of issues the Team appears in. + issues: List of issues the Team appears in. + issues_disbanded_in: List of issues the Team disbanded in. + member_count: Number of members in the Team. + members: List of members in the Team. + name: Name/Title of the Team. + publisher: The publisher of the Team. + site_url: Url to the resource in Comicvine. + story_arcs: List of story arcs the Team appears in. + summary: Short description of the Team. + team_id: Identifier used by Comicvine. + volumes: List of volumes the Team appears in. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + enemies: List[GenericEntry] = Field(alias="character_enemies", default_factory=list) + first_issue: IssueEntry = Field(alias="first_appeared_in_issue") + friends: List[GenericEntry] = Field(alias="character_friends", default_factory=list) + image: ImageEntry + issue_count: int = Field(alias="count_of_isssue_appearances") + issues: List[GenericEntry] = Field(alias="issue_credits", default_factory=list) + issues_disbanded_in: List[GenericEntry] = Field( + alias="disbanded_in_issues", default_factory=list + ) + member_count: int = Field(alias="count_of_team_members") + members: List[GenericEntry] = Field(alias="characters", default_factory=list) + name: str + publisher: GenericEntry + site_url: str = Field(alias="site_detail_url") + story_arcs: List[GenericEntry] = Field(alias="story_arc_credits", default_factory=list) + summary: Optional[str] = Field(alias="deck", default=None) + team_id: int = Field(alias="id") + volumes: List[GenericEntry] = Field(alias="volume_credits", default_factory=list) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Team has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class TeamEntry(BaseModel): + r""" + The TeamEntry object contains information for a team. + + Attributes: + aliases: List of names used by the TeamEntry, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the TeamEntry was added. + date_last_updated: Date and time when the TeamEntry was last updated. + description: Long description of the TeamEntry. + first_issue: First issue the TeamEntry appeared in. + image: Different sized images, posters and thumbnails for the TeamEntry. + issue_count: Number of issues the TeamEntry appears in. + member_count: Number of members in the TeamEntry. + name: Name/Title of the TeamEntry. + publisher: The publisher of the TeamEntry. + site_url: Url to the resource in Comicvine. + summary: Short description of the TeamEntry. + team_id: Identifier used by Comicvine. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: IssueEntry = Field(alias="first_appeared_in_issue") + image: ImageEntry + issue_count: int = Field(alias="count_of_isssue_appearances") + member_count: int = Field(alias="count_of_team_members") + name: str + publisher: GenericEntry + site_url: str = Field(alias="site_detail_url") + summary: Optional[str] = Field(alias="deck", default=None) + team_id: int = Field(alias="id") + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the TeamEntry has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/schemas/volume.py b/simyan/schemas/volume.py index a7deaf4..8102efd 100644 --- a/simyan/schemas/volume.py +++ b/simyan/schemas/volume.py @@ -1,141 +1,141 @@ -""" -The Volume module. - -This module provides the following classes: - -- Volume -- VolumeEntry -""" -__all__ = ["Volume", "VolumeEntry"] -import re -from datetime import datetime -from typing import List, Optional - -from pydantic import Field - -from simyan.schemas import BaseModel -from simyan.schemas.generic_entries import CountEntry, GenericEntry, ImageEntry, IssueEntry - - -class Volume(BaseModel): - r""" - The Volume object contains information for a volume. - - Attributes: - aliases: List of names used by the Volume, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - characters: List of characters in the Volume. - concepts: List of concepts in the Volume. - creators: List of creators in the Volume. - date_added: Date and time when the Volume was added. - date_last_updated: Date and time when the Volume was last updated. - description: Long description of the Volume. - first_issue: First issue of the Volume. - volume_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the Volume. - issue_count: Number of issues in the Volume. - issues: List of issues in the Volume. - last_issue: Last issue of the Volume. - locations: List of locations in the Volume. - name: Name/Title of the Volume. - objects: List of objects in the Volume. - publisher: The publisher of the Volume. - site_url: Url to the resource in Comicvine. - start_year: The year the Volume started. - summary: Short description of the Volume. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - characters: List[CountEntry] = Field(default_factory=list) - concepts: List[CountEntry] = Field(default_factory=list) - creators: List[CountEntry] = Field(default_factory=list, alias="people") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: Optional[IssueEntry] = None - volume_id: int = Field(alias="id") - image: ImageEntry - issue_count: int = Field(alias="count_of_issues") - issues: List[IssueEntry] = Field(default_factory=list) - last_issue: Optional[IssueEntry] = None - locations: List[CountEntry] = Field(default_factory=list) - name: str - objects: List[CountEntry] = Field(default_factory=list) - publisher: Optional[GenericEntry] = None - site_url: str = Field(alias="site_detail_url") - start_year: Optional[int] = None - summary: Optional[str] = Field(default=None, alias="deck") - - def __init__(self, **data): - try: - data["start_year"] = int(data["start_year"] or "") - except ValueError: - data["start_year"] = None - super().__init__(**data) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the Volume has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] - - -class VolumeEntry(BaseModel): - r""" - The VolumeEntry object contains information for a volume. - - Attributes: - aliases: List of names used by the VolumeEntry, separated by `~\r\n`. - api_url: Url to the resource in the Comicvine API. - date_added: Date and time when the VolumeEntry was added. - date_last_updated: Date and time when the VolumeEntry was last updated. - description: Long description of the VolumeEntry. - first_issue: First issue of the VolumeEntry. - volume_id: Identifier used by Comicvine. - image: Different sized images, posters and thumbnails for the VolumeEntry. - issue_count: Number of issues in the VolumeEntry. - last_issue: Last issue of the VolumeEntry. - name: Name/Title of the VolumeEntry. - publisher: The publisher of the VolumeEntry. - site_url: Url to the resource in Comicvine. - start_year: The year the VolumeEntry started. - summary: Short description of the VolumeEntry. - """ - - aliases: Optional[str] = None - api_url: str = Field(alias="api_detail_url") - date_added: datetime - date_last_updated: datetime - description: Optional[str] = None - first_issue: Optional[IssueEntry] = None - volume_id: int = Field(alias="id") - image: ImageEntry - issue_count: int = Field(alias="count_of_issues") - last_issue: Optional[IssueEntry] = None - name: str - publisher: Optional[GenericEntry] = None - site_url: str = Field(alias="site_detail_url") - start_year: Optional[int] = None - summary: Optional[str] = Field(default=None, alias="deck") - - def __init__(self, **data): - try: - data["start_year"] = int(data["start_year"] or "") - except ValueError: - data["start_year"] = None - super().__init__(**data) - - @property - def alias_list(self) -> List[str]: - r""" - List of aliases the VolumeEntry has used. - - Returns: - List of aliases, split by `~\r\n` - """ - return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] +""" +The Volume module. + +This module provides the following classes: + +- Volume +- VolumeEntry +""" +__all__ = ["Volume", "VolumeEntry"] +import re +from datetime import datetime +from typing import List, Optional + +from pydantic import Field + +from simyan.schemas import BaseModel +from simyan.schemas.generic_entries import CountEntry, GenericEntry, ImageEntry, IssueEntry + + +class Volume(BaseModel): + r""" + The Volume object contains information for a volume. + + Attributes: + aliases: List of names used by the Volume, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + characters: List of characters in the Volume. + concepts: List of concepts in the Volume. + creators: List of creators in the Volume. + date_added: Date and time when the Volume was added. + date_last_updated: Date and time when the Volume was last updated. + description: Long description of the Volume. + first_issue: First issue of the Volume. + volume_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the Volume. + issue_count: Number of issues in the Volume. + issues: List of issues in the Volume. + last_issue: Last issue of the Volume. + locations: List of locations in the Volume. + name: Name/Title of the Volume. + objects: List of objects in the Volume. + publisher: The publisher of the Volume. + site_url: Url to the resource in Comicvine. + start_year: The year the Volume started. + summary: Short description of the Volume. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + characters: List[CountEntry] = Field(default_factory=list) + concepts: List[CountEntry] = Field(default_factory=list) + creators: List[CountEntry] = Field(default_factory=list, alias="people") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: Optional[IssueEntry] = None + volume_id: int = Field(alias="id") + image: ImageEntry + issue_count: int = Field(alias="count_of_issues") + issues: List[IssueEntry] = Field(default_factory=list) + last_issue: Optional[IssueEntry] = None + locations: List[CountEntry] = Field(default_factory=list) + name: str + objects: List[CountEntry] = Field(default_factory=list) + publisher: Optional[GenericEntry] = None + site_url: str = Field(alias="site_detail_url") + start_year: Optional[int] = None + summary: Optional[str] = Field(default=None, alias="deck") + + def __init__(self, **data): + try: + data["start_year"] = int(data["start_year"] or "") + except ValueError: + data["start_year"] = None + super().__init__(**data) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the Volume has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] + + +class VolumeEntry(BaseModel): + r""" + The VolumeEntry object contains information for a volume. + + Attributes: + aliases: List of names used by the VolumeEntry, separated by `~\r\n`. + api_url: Url to the resource in the Comicvine API. + date_added: Date and time when the VolumeEntry was added. + date_last_updated: Date and time when the VolumeEntry was last updated. + description: Long description of the VolumeEntry. + first_issue: First issue of the VolumeEntry. + volume_id: Identifier used by Comicvine. + image: Different sized images, posters and thumbnails for the VolumeEntry. + issue_count: Number of issues in the VolumeEntry. + last_issue: Last issue of the VolumeEntry. + name: Name/Title of the VolumeEntry. + publisher: The publisher of the VolumeEntry. + site_url: Url to the resource in Comicvine. + start_year: The year the VolumeEntry started. + summary: Short description of the VolumeEntry. + """ + + aliases: Optional[str] = None + api_url: str = Field(alias="api_detail_url") + date_added: datetime + date_last_updated: datetime + description: Optional[str] = None + first_issue: Optional[IssueEntry] = None + volume_id: int = Field(alias="id") + image: ImageEntry + issue_count: int = Field(alias="count_of_issues") + last_issue: Optional[IssueEntry] = None + name: str + publisher: Optional[GenericEntry] = None + site_url: str = Field(alias="site_detail_url") + start_year: Optional[int] = None + summary: Optional[str] = Field(default=None, alias="deck") + + def __init__(self, **data): + try: + data["start_year"] = int(data["start_year"] or "") + except ValueError: + data["start_year"] = None + super().__init__(**data) + + @property + def alias_list(self) -> List[str]: + r""" + List of aliases the VolumeEntry has used. + + Returns: + List of aliases, split by `~\r\n` + """ + return re.split(r"[~\r\n]+", self.aliases) if self.aliases else [] diff --git a/simyan/sqlite_cache.py b/simyan/sqlite_cache.py index b2bc3f0..2d8cd48 100644 --- a/simyan/sqlite_cache.py +++ b/simyan/sqlite_cache.py @@ -1,107 +1,107 @@ -""" -The SQLiteCache module. - -This module provides the following classes: - -- SQLiteCache -""" -__all__ = ["SQLiteCache"] -import json -import sqlite3 -from datetime import date, timedelta -from typing import Any, Dict, Optional - -from simyan import get_cache_root - - -class SQLiteCache: - """ - The SQLiteCache object to cache search results from ComicVine. - - Args: - path: Path to database. - expiry: How long to keep cache results. - - Attributes: - expiry (Optional[int]): How long to keep cache results. - con (sqlite3.Connection): Database connection - cur (sqlite3.Cursor): Database cursor - """ - - def __init__( - self, - path: str = get_cache_root() / "cache.sqlite", - expiry: Optional[int] = 14, - ): - self.expiry = expiry - self.con = sqlite3.connect(path) - self.cur = self.con.cursor() - self.cur.execute("CREATE TABLE IF NOT EXISTS queries (query, response, expiry);") - self.delete() - - def select(self, query: str) -> Dict[str, Any]: - """ - Retrieve data from the cache database. - - Args: - query: Search string - Returns: - Empty dict or select results. - """ - if self.expiry: - self.cur.execute( - "SELECT response FROM queries WHERE query = ? and expiry > ?;", - (query, date.today().isoformat()), - ) - else: - self.cur.execute("SELECT response FROM queries WHERE query = ?;", (query,)) - results = self.cur.fetchone() - if results: - return json.loads(results[0]) - return {} - - def get(self, key: str) -> Optional[Dict[str, Any]]: - """ - Retrieve data from the cache database. - - Args: - key: Search string - Returns: - None or select results. - """ - return self.select(query=key) or None - - def insert(self, query: str, response: str): - """ - Insert data into the cache database. - - Args: - query: Search string - response: Data to save - """ - if self.expiry: - expiry = date.today() + timedelta(days=self.expiry) - else: - expiry = date.today() - self.cur.execute( - "INSERT INTO queries (query, response, expiry) VALUES (?, ?, ?);", - (query, json.dumps(response), expiry.isoformat()), - ) - self.con.commit() - - def store(self, key: str, value: str): - """ - Insert data into the cache database. - - Args: - key: Search string - value: Data to save - """ - return self.insert(query=key, response=value) - - def delete(self): - """Remove all expired data from the cache database.""" - if not self.expiry: - return - self.cur.execute("DELETE FROM queries WHERE expiry < ?;", (date.today().isoformat(),)) - self.con.commit() +""" +The SQLiteCache module. + +This module provides the following classes: + +- SQLiteCache +""" +__all__ = ["SQLiteCache"] +import json +import sqlite3 +from datetime import date, timedelta +from typing import Any, Dict, Optional + +from simyan import get_cache_root + + +class SQLiteCache: + """ + The SQLiteCache object to cache search results from ComicVine. + + Args: + path: Path to database. + expiry: How long to keep cache results. + + Attributes: + expiry (Optional[int]): How long to keep cache results. + con (sqlite3.Connection): Database connection + cur (sqlite3.Cursor): Database cursor + """ + + def __init__( + self, + path: str = get_cache_root() / "cache.sqlite", + expiry: Optional[int] = 14, + ): + self.expiry = expiry + self.con = sqlite3.connect(path) + self.cur = self.con.cursor() + self.cur.execute("CREATE TABLE IF NOT EXISTS queries (query, response, expiry);") + self.delete() + + def select(self, query: str) -> Dict[str, Any]: + """ + Retrieve data from the cache database. + + Args: + query: Search string + Returns: + Empty dict or select results. + """ + if self.expiry: + self.cur.execute( + "SELECT response FROM queries WHERE query = ? and expiry > ?;", + (query, date.today().isoformat()), + ) + else: + self.cur.execute("SELECT response FROM queries WHERE query = ?;", (query,)) + results = self.cur.fetchone() + if results: + return json.loads(results[0]) + return {} + + def get(self, key: str) -> Optional[Dict[str, Any]]: + """ + Retrieve data from the cache database. + + Args: + key: Search string + Returns: + None or select results. + """ + return self.select(query=key) or None + + def insert(self, query: str, response: str): + """ + Insert data into the cache database. + + Args: + query: Search string + response: Data to save + """ + if self.expiry: + expiry = date.today() + timedelta(days=self.expiry) + else: + expiry = date.today() + self.cur.execute( + "INSERT INTO queries (query, response, expiry) VALUES (?, ?, ?);", + (query, json.dumps(response), expiry.isoformat()), + ) + self.con.commit() + + def store(self, key: str, value: str): + """ + Insert data into the cache database. + + Args: + key: Search string + value: Data to save + """ + return self.insert(query=key, response=value) + + def delete(self): + """Remove all expired data from the cache database.""" + if not self.expiry: + return + self.cur.execute("DELETE FROM queries WHERE expiry < ?;", (date.today().isoformat(),)) + self.con.commit() diff --git a/tests/README.md b/tests/README.md index 17807a2..b970a6c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,7 +2,7 @@ These tests use the Simyan requests caching for mocking tests, so tests will run quickly and not require credentials. -If your code adds a new URL to the cache, set the `COMICVINE_API_KEY` environment variable before running the test, and +If your code adds a new URL to the cache, set the `COMICVINE__API_KEY` environment variable before running the test, and it will be populated in the `tests/cache.sqlite` database. At any point you should be able to delete the database, set any credentials, and run the full test suite to repopulate diff --git a/tests/__init__.py b/tests/__init__.py index 1bbd71f..e492d54 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1 @@ -"""Tests for Simyan.""" +"""Tests for Simyan.""" diff --git a/tests/conftest.py b/tests/conftest.py index dc1e632..6b6ffad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,25 +1,25 @@ -""" -The Conftest module. - -This module contains pytest fixtures. -""" -import os - -import pytest - -from simyan.comicvine import Comicvine -from simyan.sqlite_cache import SQLiteCache - - -@pytest.fixture(scope="session") -def comicvine_api_key(): - """Set the ComicVine API key fixture.""" - return os.getenv("COMICVINE__API_KEY", default="IGNORED") - - -@pytest.fixture(scope="session") -def session(comicvine_api_key) -> Comicvine: - """Set the Simyan session fixture.""" - return Comicvine( - api_key=comicvine_api_key, cache=SQLiteCache("tests/cache.sqlite", expiry=None) - ) +""" +The Conftest module. + +This module contains pytest fixtures. +""" +import os + +import pytest + +from simyan.comicvine import Comicvine +from simyan.sqlite_cache import SQLiteCache + + +@pytest.fixture(scope="session") +def comicvine_api_key(): + """Set the ComicVine API key fixture.""" + return os.getenv("COMICVINE__API_KEY", default="IGNORED") + + +@pytest.fixture(scope="session") +def session(comicvine_api_key) -> Comicvine: + """Set the Simyan session fixture.""" + return Comicvine( + api_key=comicvine_api_key, cache=SQLiteCache("tests/cache.sqlite", expiry=None) + ) diff --git a/tests/test_characters.py b/tests/test_characters.py index 225f0ca..b018ca1 100644 --- a/tests/test_characters.py +++ b/tests/test_characters.py @@ -1,113 +1,113 @@ -""" -The Characters test module. - -This module contains tests for Character and CharacterEntry objects. -""" -from datetime import datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.character import CharacterEntry - - -def test_character(session: Comicvine): - """Test using the character endpoint with a valid character_id.""" - result = session.character(character_id=40431) - assert result is not None - assert result.character_id == 40431 - - assert result.alias_list == [ - "Green Lantern", - "Ion", - "Parallax", - "Torch Bearer", - "White Lantern", - "Green Man", - "Omega Lantern", - ] - assert result.api_url == "https://comicvine.gamespot.com/api/character/4005-40431/" - assert len(result.creators) == 2 - assert result.date_added == datetime(2008, 6, 6, 11, 27, 42) - assert result.date_of_birth is None - assert len(result.deaths) == 2 - assert len(result.enemies) == 146 - assert len(result.enemy_teams) == 24 - assert result.first_issue.id_ == 38445 - assert len(result.friendly_teams) == 16 - assert len(result.friends) == 232 - assert result.gender == 1 - assert result.issue_count == 1565 - assert len(result.issues) == 1565 - assert result.name == "Kyle Rayner" - assert result.origin.id_ == 4 - assert len(result.powers) == 28 - assert result.publisher.id_ == 10 - assert result.real_name == "Kyle Rayner" - assert result.site_url == "https://comicvine.gamespot.com/kyle-rayner/4005-40431/" - assert len(result.story_arcs) == 0 - assert len(result.teams) == 21 - assert len(result.volumes) == 1 - - -def test_character_fail(session: Comicvine): - """Test using the character endpoint with an invalid character_id.""" - with pytest.raises(ServiceError): - session.character(character_id=-1) - - -def test_character_list(session: Comicvine): - """Test using the character_list endpoint with a valid search.""" - search_results = session.character_list({"filter": "name:Kyle Rayner"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.character_id == 40431][0] - assert result is not None - - assert result.alias_list == [ - "Green Lantern", - "Ion", - "Parallax", - "Torch Bearer", - "White Lantern", - "Green Man", - "Omega Lantern", - ] - assert result.api_url == "https://comicvine.gamespot.com/api/character/4005-40431/" - assert result.date_added == datetime(2008, 6, 6, 11, 27, 42) - assert result.date_of_birth is None - assert result.first_issue.id_ == 38445 - assert result.gender == 1 - assert result.issue_count == 1565 - assert result.name == "Kyle Rayner" - assert result.origin.id_ == 4 - assert result.publisher.id_ == 10 - assert result.real_name == "Kyle Rayner" - assert result.site_url == "https://comicvine.gamespot.com/kyle-rayner/4005-40431/" - - -def test_character_list_empty(session: Comicvine): - """Test using the character_list endpoint with an invalid search.""" - results = session.character_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_character_list_max_results(session: Comicvine): - """Test character_list endpoint with max_results.""" - results = session.character_list({"filter": "name:Kyle"}, max_results=10) - assert len(results) == 10 - - -def test_search_character(session: Comicvine): - """Test using the search endpoint for a list of Characters.""" - results = session.search(resource=ComicvineResource.CHARACTER, query="Kyle Rayner") - assert all(isinstance(x, CharacterEntry) for x in results) - - -def test_search_character_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search( - resource=ComicvineResource.CHARACTER, query="Kyle Rayner", max_results=10 - ) - assert all(isinstance(x, CharacterEntry) for x in results) - assert len(results) == 10 +""" +The Characters test module. + +This module contains tests for Character and CharacterEntry objects. +""" +from datetime import datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.character import CharacterEntry + + +def test_character(session: Comicvine): + """Test using the character endpoint with a valid character_id.""" + result = session.character(character_id=40431) + assert result is not None + assert result.character_id == 40431 + + assert result.alias_list == [ + "Green Lantern", + "Ion", + "Parallax", + "Torch Bearer", + "White Lantern", + "Green Man", + "Omega Lantern", + ] + assert result.api_url == "https://comicvine.gamespot.com/api/character/4005-40431/" + assert len(result.creators) == 2 + assert result.date_added == datetime(2008, 6, 6, 11, 27, 42) + assert result.date_of_birth is None + assert len(result.deaths) == 2 + assert len(result.enemies) == 146 + assert len(result.enemy_teams) == 24 + assert result.first_issue.id_ == 38445 + assert len(result.friendly_teams) == 16 + assert len(result.friends) == 232 + assert result.gender == 1 + assert result.issue_count == 1565 + assert len(result.issues) == 1565 + assert result.name == "Kyle Rayner" + assert result.origin.id_ == 4 + assert len(result.powers) == 28 + assert result.publisher.id_ == 10 + assert result.real_name == "Kyle Rayner" + assert result.site_url == "https://comicvine.gamespot.com/kyle-rayner/4005-40431/" + assert len(result.story_arcs) == 0 + assert len(result.teams) == 21 + assert len(result.volumes) == 1 + + +def test_character_fail(session: Comicvine): + """Test using the character endpoint with an invalid character_id.""" + with pytest.raises(ServiceError): + session.character(character_id=-1) + + +def test_character_list(session: Comicvine): + """Test using the character_list endpoint with a valid search.""" + search_results = session.character_list({"filter": "name:Kyle Rayner"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.character_id == 40431][0] + assert result is not None + + assert result.alias_list == [ + "Green Lantern", + "Ion", + "Parallax", + "Torch Bearer", + "White Lantern", + "Green Man", + "Omega Lantern", + ] + assert result.api_url == "https://comicvine.gamespot.com/api/character/4005-40431/" + assert result.date_added == datetime(2008, 6, 6, 11, 27, 42) + assert result.date_of_birth is None + assert result.first_issue.id_ == 38445 + assert result.gender == 1 + assert result.issue_count == 1565 + assert result.name == "Kyle Rayner" + assert result.origin.id_ == 4 + assert result.publisher.id_ == 10 + assert result.real_name == "Kyle Rayner" + assert result.site_url == "https://comicvine.gamespot.com/kyle-rayner/4005-40431/" + + +def test_character_list_empty(session: Comicvine): + """Test using the character_list endpoint with an invalid search.""" + results = session.character_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_character_list_max_results(session: Comicvine): + """Test character_list endpoint with max_results.""" + results = session.character_list({"filter": "name:Kyle"}, max_results=10) + assert len(results) == 10 + + +def test_search_character(session: Comicvine): + """Test using the search endpoint for a list of Characters.""" + results = session.search(resource=ComicvineResource.CHARACTER, query="Kyle Rayner") + assert all(isinstance(x, CharacterEntry) for x in results) + + +def test_search_character_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search( + resource=ComicvineResource.CHARACTER, query="Kyle Rayner", max_results=10 + ) + assert all(isinstance(x, CharacterEntry) for x in results) + assert len(results) == 10 diff --git a/tests/test_creators.py b/tests/test_creators.py index 1906120..09d8c10 100644 --- a/tests/test_creators.py +++ b/tests/test_creators.py @@ -1,97 +1,97 @@ -""" -The Creators test module. - -This module contains tests for Creator and CreatorEntry objects. -""" -from datetime import date, datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.creator import CreatorEntry - - -def test_creator(session: Comicvine): - """Test using the creator endpoint with a valid creator_id.""" - result = session.creator(creator_id=40439) - assert result is not None - assert result.creator_id == 40439 - - assert result.alias_list == ["Geoffrey Johns"] - assert result.api_url == "https://comicvine.gamespot.com/api/person/4040-40439/" - assert len(result.characters) == 234 - assert result.country == "United States" - assert result.date_added == datetime(2008, 6, 6, 11, 28, 14) - assert result.date_of_birth == date(1973, 1, 25) - assert result.date_of_death is None - assert result.email is None - assert result.gender == 1 - assert result.hometown == "Detroit, MI" - assert result.issue_count is None - assert len(result.issues) == 1515 - assert result.name == "Geoff Johns" - assert result.site_url == "https://comicvine.gamespot.com/geoff-johns/4040-40439/" - assert len(result.story_arcs) == 0 - assert len(result.volumes) == 560 - assert result.website == "http://www.geoffjohns.com" - - -def test_creator_fail(session: Comicvine): - """Test using the creator endpoint with an invalid creator_id.""" - with pytest.raises(ServiceError): - session.creator(creator_id=-1) - - -def test_creator_list(session: Comicvine): - """Test using the creator_list endpoint with a valid search.""" - search_results = session.creator_list({"filter": "name:Geoff Johns"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.creator_id == 40439][0] - assert result is not None - - assert result.alias_list == ["Geoffrey Johns"] - assert result.api_url == "https://comicvine.gamespot.com/api/person/4040-40439/" - assert result.country == "United States" - assert result.date_added == datetime(2008, 6, 6, 11, 28, 14) - assert result.date_of_birth == date(1973, 1, 25) - assert result.date_of_death is None - assert result.email is None - assert result.gender == 1 - assert result.hometown == "Detroit, MI" - assert result.issue_count is None - assert result.name == "Geoff Johns" - assert result.site_url == "https://comicvine.gamespot.com/geoff-johns/4040-40439/" - assert result.website == "http://www.geoffjohns.com" - - -def test_creator_list_empty(session: Comicvine): - """Test using the creator_list endpoint with an invalid search.""" - results = session.creator_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_creator_list_max_results(session: Comicvine): - """Test creator_list endpoint with max_results.""" - results = session.creator_list({"filter": "name:Geoff"}, max_results=10) - assert len(results) == 10 - - -def test_search_creator(session: Comicvine): - """Test using the search endpoint for a list of Creators.""" - results = session.search(resource=ComicvineResource.CREATOR, query="Geoff") - assert all(isinstance(x, CreatorEntry) for x in results) - - -def test_search_creator_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search(resource=ComicvineResource.CREATOR, query="Geoff", max_results=10) - assert all(isinstance(x, CreatorEntry) for x in results) - assert len(results) == 10 - - -def test_creator_with_dob(session: Comicvine): - """Test creators date of birth & death.""" - kirby = session.creator(creator_id=5614) - assert kirby.date_of_birth == date(1917, 8, 28) - assert kirby.date_of_death == date(1994, 2, 6) +""" +The Creators test module. + +This module contains tests for Creator and CreatorEntry objects. +""" +from datetime import date, datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.creator import CreatorEntry + + +def test_creator(session: Comicvine): + """Test using the creator endpoint with a valid creator_id.""" + result = session.creator(creator_id=40439) + assert result is not None + assert result.creator_id == 40439 + + assert result.alias_list == ["Geoffrey Johns"] + assert result.api_url == "https://comicvine.gamespot.com/api/person/4040-40439/" + assert len(result.characters) == 234 + assert result.country == "United States" + assert result.date_added == datetime(2008, 6, 6, 11, 28, 14) + assert result.date_of_birth == date(1973, 1, 25) + assert result.date_of_death is None + assert result.email is None + assert result.gender == 1 + assert result.hometown == "Detroit, MI" + assert result.issue_count is None + assert len(result.issues) == 1515 + assert result.name == "Geoff Johns" + assert result.site_url == "https://comicvine.gamespot.com/geoff-johns/4040-40439/" + assert len(result.story_arcs) == 0 + assert len(result.volumes) == 560 + assert result.website == "http://www.geoffjohns.com" + + +def test_creator_fail(session: Comicvine): + """Test using the creator endpoint with an invalid creator_id.""" + with pytest.raises(ServiceError): + session.creator(creator_id=-1) + + +def test_creator_list(session: Comicvine): + """Test using the creator_list endpoint with a valid search.""" + search_results = session.creator_list({"filter": "name:Geoff Johns"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.creator_id == 40439][0] + assert result is not None + + assert result.alias_list == ["Geoffrey Johns"] + assert result.api_url == "https://comicvine.gamespot.com/api/person/4040-40439/" + assert result.country == "United States" + assert result.date_added == datetime(2008, 6, 6, 11, 28, 14) + assert result.date_of_birth == date(1973, 1, 25) + assert result.date_of_death is None + assert result.email is None + assert result.gender == 1 + assert result.hometown == "Detroit, MI" + assert result.issue_count is None + assert result.name == "Geoff Johns" + assert result.site_url == "https://comicvine.gamespot.com/geoff-johns/4040-40439/" + assert result.website == "http://www.geoffjohns.com" + + +def test_creator_list_empty(session: Comicvine): + """Test using the creator_list endpoint with an invalid search.""" + results = session.creator_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_creator_list_max_results(session: Comicvine): + """Test creator_list endpoint with max_results.""" + results = session.creator_list({"filter": "name:Geoff"}, max_results=10) + assert len(results) == 10 + + +def test_search_creator(session: Comicvine): + """Test using the search endpoint for a list of Creators.""" + results = session.search(resource=ComicvineResource.CREATOR, query="Geoff") + assert all(isinstance(x, CreatorEntry) for x in results) + + +def test_search_creator_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search(resource=ComicvineResource.CREATOR, query="Geoff", max_results=10) + assert all(isinstance(x, CreatorEntry) for x in results) + assert len(results) == 10 + + +def test_creator_with_dob(session: Comicvine): + """Test creators date of birth & death.""" + kirby = session.creator(creator_id=5614) + assert kirby.date_of_birth == date(1917, 8, 28) + assert kirby.date_of_death == date(1994, 2, 6) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index dba108d..ac6c271 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,29 +1,29 @@ -""" -The Exceptions test module. - -This module contains tests for Exceptions. -""" -import pytest - -from simyan.comicvine import Comicvine -from simyan.exceptions import AuthenticationError, ServiceError - - -def test_unauthorized(): - """Test generating an AuthenticationError.""" - session = Comicvine(api_key="Invalid", cache=None) - with pytest.raises(AuthenticationError): - session.publisher(publisher_id=1) - - -def test_not_found(session: Comicvine): - """Test a 404 Not Found raises a ServiceError.""" - with pytest.raises(ServiceError): - session._get_request(endpoint="/invalid") - - -def test_timeout(comicvine_api_key: str): - """Test a TimeoutError for slow responses.""" - session = Comicvine(api_key=comicvine_api_key, timeout=0.1, cache=None) - with pytest.raises(ServiceError): - session.publisher(publisher_id=1) +""" +The Exceptions test module. + +This module contains tests for Exceptions. +""" +import pytest + +from simyan.comicvine import Comicvine +from simyan.exceptions import AuthenticationError, ServiceError + + +def test_unauthorized(): + """Test generating an AuthenticationError.""" + session = Comicvine(api_key="Invalid", cache=None) + with pytest.raises(AuthenticationError): + session.publisher(publisher_id=1) + + +def test_not_found(session: Comicvine): + """Test a 404 Not Found raises a ServiceError.""" + with pytest.raises(ServiceError): + session._get_request(endpoint="/invalid") + + +def test_timeout(comicvine_api_key: str): + """Test a TimeoutError for slow responses.""" + session = Comicvine(api_key=comicvine_api_key, timeout=0.1, cache=None) + with pytest.raises(ServiceError): + session.publisher(publisher_id=1) diff --git a/tests/test_issues.py b/tests/test_issues.py index 92903b2..b107218 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -1,147 +1,147 @@ -""" -The Issues test module. - -This module contains tests for Issue and IssueEntry objects. -""" -from datetime import date, datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.issue import IssueEntry - - -def test_issue(session: Comicvine): - """Test using the issue endpoint with a valid issue_id.""" - result = session.issue(issue_id=111265) - assert result is not None - assert result.issue_id == 111265 - - assert result.alias_list == [] - assert len(result.alternative_images) == 1 - assert result.api_url == "https://comicvine.gamespot.com/api/issue/4000-111265/" - assert len(result.characters) == 7 - assert len(result.concepts) == 1 - assert result.cover_date == date(2005, 7, 1) - assert len(result.creators) == 10 - assert result.date_added == datetime(2008, 6, 6, 11, 21, 45) - assert len(result.deaths) == 0 - assert len(result.first_appearance_characters) == 0 - assert len(result.first_appearance_concepts) == 0 - assert len(result.first_appearance_locations) == 0 - assert len(result.first_appearance_objects) == 0 - assert len(result.first_appearance_story_arcs) == 0 - assert len(result.first_appearance_teams) == 0 - assert len(result.locations) == 4 - assert result.name == "Airborne" - assert result.number == "1" - assert len(result.objects) == 1 - assert result.site_url == "https://comicvine.gamespot.com/green-lantern-1-airborne/4000-111265/" - assert result.store_date == date(2005, 5, 18) - assert len(result.story_arcs) == 1 - assert len(result.teams) == 2 - assert len(result.teams_disbanded) == 0 - assert result.volume.id_ == 18216 - - -def test_issue_fail(session: Comicvine): - """Test using the issue endpoint with an invalid issue_id.""" - with pytest.raises(ServiceError): - session.issue(issue_id=-1) - - -def test_issue_list(session: Comicvine): - """Test using the issue_list endpoint with a valid search.""" - search_results = session.issue_list({"filter": "volume:18216,issue_number:1"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.issue_id == 111265][0] - assert result is not None - - assert result.alias_list == [] - assert len(result.alternative_images) == 1 - assert result.api_url == "https://comicvine.gamespot.com/api/issue/4000-111265/" - assert result.cover_date == date(2005, 7, 1) - assert result.date_added == datetime(2008, 6, 6, 11, 21, 45) - assert result.name == "Airborne" - assert result.number == "1" - assert result.site_url == "https://comicvine.gamespot.com/green-lantern-1-airborne/4000-111265/" - assert result.store_date == date(2005, 5, 18) - assert result.volume.id_ == 18216 - - -def test_issue_list_empty(session: Comicvine): - """Test using the issue_list endpoint with an invalid search.""" - results = session.issue_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_issue_list_max_results(session: Comicvine): - """Test issue_list endpoint with max_results.""" - results = session.issue_list({"filter": "volume:18216"}, max_results=10) - assert len(results) == 10 - - -def test_search_issue(session: Comicvine): - """Test using the search endpoint for a list of Issues.""" - results = session.search(resource=ComicvineResource.ISSUE, query="Lantern") - assert all(isinstance(x, IssueEntry) for x in results) - - -def test_search_issue_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search(resource=ComicvineResource.ISSUE, query="Lantern", max_results=10) - assert all(isinstance(x, IssueEntry) for x in results) - assert len(results) == 10 - - -def test_issue_bad_cover_date(session: Comicvine): - """Test for issue with a cover date.""" - xmen_2 = session.issue(issue_id=6787) - assert xmen_2.store_date is None - assert xmen_2.cover_date == date(1963, 11, 1) - assert xmen_2.issue_id == 6787 - assert xmen_2.number == "2" - assert len(xmen_2.creators) == 4 - assert xmen_2.creators[0].name == "Jack Kirby" - assert xmen_2.creators[0].roles == "penciler" - assert len(xmen_2.characters) == 10 - assert xmen_2.characters[0].name == "Angel" - - -def test_issue_no_has_staff_review(session: Comicvine): - """Test issue endpoint to return result without a Staff Review field.""" - result = session.issue(issue_id=505513) - assert "has_staff_review" not in result.__dict__.keys() - - -def test_issue_list_no_has_staff_review(session: Comicvine): - """Test issue_list endpoint to return result without a Staff Review field.""" - result = session.issue_list({"filter": "issue_number:1,volume:85930"}) - assert "has_staff_review" not in result[0].__dict__.keys() - - -def test_issue_no_description(session: Comicvine): - """Test issue endpoint to return result that has a null/no description.""" - result = session.issue(issue_id=134272) - assert result.description is None - - -def test_issue_list_no_description(session: Comicvine): - """Test issue_list endpoint to return result that has a null/no description.""" - results = session.issue_list(params={"filter": "volume:18006"}) - result = [x for x in results if x.issue_id == 134272][0] - assert result.description is None - - -def test_issue_no_cover_date(session: Comicvine): - """Test issue endpoint to return result that has a null/no cover_date.""" - result = session.issue(issue_id=325298) - assert result.cover_date is None - - -def test_issue_list_no_cover_date(session: Comicvine): - """Test issue_list endpoint to return result that has a null/no cover_date.""" - results = session.issue_list(params={"filter": "volume:3088"}) - result = [x for x in results if x.issue_id == 325298][0] - assert result.cover_date is None +""" +The Issues test module. + +This module contains tests for Issue and IssueEntry objects. +""" +from datetime import date, datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.issue import IssueEntry + + +def test_issue(session: Comicvine): + """Test using the issue endpoint with a valid issue_id.""" + result = session.issue(issue_id=111265) + assert result is not None + assert result.issue_id == 111265 + + assert result.alias_list == [] + assert len(result.alternative_images) == 1 + assert result.api_url == "https://comicvine.gamespot.com/api/issue/4000-111265/" + assert len(result.characters) == 7 + assert len(result.concepts) == 1 + assert result.cover_date == date(2005, 7, 1) + assert len(result.creators) == 10 + assert result.date_added == datetime(2008, 6, 6, 11, 21, 45) + assert len(result.deaths) == 0 + assert len(result.first_appearance_characters) == 0 + assert len(result.first_appearance_concepts) == 0 + assert len(result.first_appearance_locations) == 0 + assert len(result.first_appearance_objects) == 0 + assert len(result.first_appearance_story_arcs) == 0 + assert len(result.first_appearance_teams) == 0 + assert len(result.locations) == 4 + assert result.name == "Airborne" + assert result.number == "1" + assert len(result.objects) == 1 + assert result.site_url == "https://comicvine.gamespot.com/green-lantern-1-airborne/4000-111265/" + assert result.store_date == date(2005, 5, 18) + assert len(result.story_arcs) == 1 + assert len(result.teams) == 2 + assert len(result.teams_disbanded) == 0 + assert result.volume.id_ == 18216 + + +def test_issue_fail(session: Comicvine): + """Test using the issue endpoint with an invalid issue_id.""" + with pytest.raises(ServiceError): + session.issue(issue_id=-1) + + +def test_issue_list(session: Comicvine): + """Test using the issue_list endpoint with a valid search.""" + search_results = session.issue_list({"filter": "volume:18216,issue_number:1"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.issue_id == 111265][0] + assert result is not None + + assert result.alias_list == [] + assert len(result.alternative_images) == 1 + assert result.api_url == "https://comicvine.gamespot.com/api/issue/4000-111265/" + assert result.cover_date == date(2005, 7, 1) + assert result.date_added == datetime(2008, 6, 6, 11, 21, 45) + assert result.name == "Airborne" + assert result.number == "1" + assert result.site_url == "https://comicvine.gamespot.com/green-lantern-1-airborne/4000-111265/" + assert result.store_date == date(2005, 5, 18) + assert result.volume.id_ == 18216 + + +def test_issue_list_empty(session: Comicvine): + """Test using the issue_list endpoint with an invalid search.""" + results = session.issue_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_issue_list_max_results(session: Comicvine): + """Test issue_list endpoint with max_results.""" + results = session.issue_list({"filter": "volume:18216"}, max_results=10) + assert len(results) == 10 + + +def test_search_issue(session: Comicvine): + """Test using the search endpoint for a list of Issues.""" + results = session.search(resource=ComicvineResource.ISSUE, query="Lantern") + assert all(isinstance(x, IssueEntry) for x in results) + + +def test_search_issue_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search(resource=ComicvineResource.ISSUE, query="Lantern", max_results=10) + assert all(isinstance(x, IssueEntry) for x in results) + assert len(results) == 10 + + +def test_issue_bad_cover_date(session: Comicvine): + """Test for issue with a cover date.""" + xmen_2 = session.issue(issue_id=6787) + assert xmen_2.store_date is None + assert xmen_2.cover_date == date(1963, 11, 1) + assert xmen_2.issue_id == 6787 + assert xmen_2.number == "2" + assert len(xmen_2.creators) == 4 + assert xmen_2.creators[0].name == "Jack Kirby" + assert xmen_2.creators[0].roles == "penciler" + assert len(xmen_2.characters) == 10 + assert xmen_2.characters[0].name == "Angel" + + +def test_issue_no_has_staff_review(session: Comicvine): + """Test issue endpoint to return result without a Staff Review field.""" + result = session.issue(issue_id=505513) + assert "has_staff_review" not in result.__dict__.keys() + + +def test_issue_list_no_has_staff_review(session: Comicvine): + """Test issue_list endpoint to return result without a Staff Review field.""" + result = session.issue_list({"filter": "issue_number:1,volume:85930"}) + assert "has_staff_review" not in result[0].__dict__.keys() + + +def test_issue_no_description(session: Comicvine): + """Test issue endpoint to return result that has a null/no description.""" + result = session.issue(issue_id=134272) + assert result.description is None + + +def test_issue_list_no_description(session: Comicvine): + """Test issue_list endpoint to return result that has a null/no description.""" + results = session.issue_list(params={"filter": "volume:18006"}) + result = [x for x in results if x.issue_id == 134272][0] + assert result.description is None + + +def test_issue_no_cover_date(session: Comicvine): + """Test issue endpoint to return result that has a null/no cover_date.""" + result = session.issue(issue_id=325298) + assert result.cover_date is None + + +def test_issue_list_no_cover_date(session: Comicvine): + """Test issue_list endpoint to return result that has a null/no cover_date.""" + results = session.issue_list(params={"filter": "volume:3088"}) + result = [x for x in results if x.issue_id == 325298][0] + assert result.cover_date is None diff --git a/tests/test_locations.py b/tests/test_locations.py index 95ea9c9..0eed0a0 100644 --- a/tests/test_locations.py +++ b/tests/test_locations.py @@ -1,77 +1,77 @@ -""" -The Locations test module. - -This module contains tests for Location and LocationEntry objects. -""" -from datetime import datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.location import LocationEntry - - -def test_location(session: Comicvine): - """Test using the location endpoint with a valid location_id.""" - result = session.location(location_id=56000) - assert result is not None - assert result.location_id == 56000 - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/location/4020-56000/" - assert result.issue_count == 26 - assert result.date_added == datetime(2009, 1, 2, 16, 16, 18) - assert result.first_issue.id_ == 149271 - assert len(result.issues) == 26 - assert result.name == "Odym" - assert result.site_url == "https://comicvine.gamespot.com/odym/4020-56000/" - assert len(result.story_arcs) == 0 - assert len(result.volumes) == 1 - - -def test_location_fail(session: Comicvine): - """Test using the location endpoint with an invalid location_id.""" - with pytest.raises(ServiceError): - session.location(location_id=-1) - - -def test_location_list(session: Comicvine): - """Test using the location_list endpoint with a valid search.""" - search_results = session.location_list({"filter": "name:Odym"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.location_id == 56000][0] - assert result is not None - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/location/4020-56000/" - assert result.issue_count == 26 - assert result.date_added == datetime(2009, 1, 2, 16, 16, 18) - assert result.first_issue.id_ == 149271 - assert result.name == "Odym" - assert result.site_url == "https://comicvine.gamespot.com/odym/4020-56000/" - - -def test_location_list_empty(session: Comicvine): - """Test using the location_list endpoint with an invalid search.""" - results = session.location_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_location_list_max_results(session: Comicvine): - """Test location_list endpoint with max_results.""" - results = session.location_list({"filter": "name:Earth"}, max_results=10) - assert len(results) == 10 - - -def test_search_location(session: Comicvine): - """Test using the search endpoint for a list of Locations.""" - results = session.search(resource=ComicvineResource.LOCATION, query="Earth") - assert all(isinstance(x, LocationEntry) for x in results) - - -def test_search_location_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search(resource=ComicvineResource.LOCATION, query="Earth", max_results=10) - assert all(isinstance(x, LocationEntry) for x in results) - assert len(results) == 10 +""" +The Locations test module. + +This module contains tests for Location and LocationEntry objects. +""" +from datetime import datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.location import LocationEntry + + +def test_location(session: Comicvine): + """Test using the location endpoint with a valid location_id.""" + result = session.location(location_id=56000) + assert result is not None + assert result.location_id == 56000 + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/location/4020-56000/" + assert result.issue_count == 26 + assert result.date_added == datetime(2009, 1, 2, 16, 16, 18) + assert result.first_issue.id_ == 149271 + assert len(result.issues) == 26 + assert result.name == "Odym" + assert result.site_url == "https://comicvine.gamespot.com/odym/4020-56000/" + assert len(result.story_arcs) == 0 + assert len(result.volumes) == 1 + + +def test_location_fail(session: Comicvine): + """Test using the location endpoint with an invalid location_id.""" + with pytest.raises(ServiceError): + session.location(location_id=-1) + + +def test_location_list(session: Comicvine): + """Test using the location_list endpoint with a valid search.""" + search_results = session.location_list({"filter": "name:Odym"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.location_id == 56000][0] + assert result is not None + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/location/4020-56000/" + assert result.issue_count == 26 + assert result.date_added == datetime(2009, 1, 2, 16, 16, 18) + assert result.first_issue.id_ == 149271 + assert result.name == "Odym" + assert result.site_url == "https://comicvine.gamespot.com/odym/4020-56000/" + + +def test_location_list_empty(session: Comicvine): + """Test using the location_list endpoint with an invalid search.""" + results = session.location_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_location_list_max_results(session: Comicvine): + """Test location_list endpoint with max_results.""" + results = session.location_list({"filter": "name:Earth"}, max_results=10) + assert len(results) == 10 + + +def test_search_location(session: Comicvine): + """Test using the search endpoint for a list of Locations.""" + results = session.search(resource=ComicvineResource.LOCATION, query="Earth") + assert all(isinstance(x, LocationEntry) for x in results) + + +def test_search_location_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search(resource=ComicvineResource.LOCATION, query="Earth", max_results=10) + assert all(isinstance(x, LocationEntry) for x in results) + assert len(results) == 10 diff --git a/tests/test_publishers.py b/tests/test_publishers.py index 25b7128..c27ded0 100644 --- a/tests/test_publishers.py +++ b/tests/test_publishers.py @@ -1,104 +1,104 @@ -""" -The Publishers test module. - -This module contains tests for Publisher and PublisherEntry objects. -""" -from datetime import datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.publisher import PublisherEntry - - -def test_publisher(session: Comicvine): - """Test using the publisher endpoint with a valid publisher_id.""" - result = session.publisher(publisher_id=10) - assert result is not None - assert result.publisher_id == 10 - - assert result.alias_list == [ - "National Comics", - "Detective Comics Inc.", - "National Periodical Publications", - "National Allied Publications", - "Nicholson Publishing", - "All-American Publications", - "DC Entertainment", - "DC Nation", - "Johnny DC", - "National Comics Publishing", - "National Comics Publications", - ] - assert result.api_url == "https://comicvine.gamespot.com/api/publisher/4010-10/" - assert len(result.characters) == 19641 - assert result.date_added == datetime(2008, 6, 6, 11, 8) - assert result.location_address == "4000 Warner Blvd" - assert result.location_city == "Burbank" - assert result.location_state == "California" - assert result.name == "DC Comics" - assert result.site_url == "https://comicvine.gamespot.com/dc-comics/4010-10/" - assert len(result.story_arcs) == 1281 - assert len(result.teams) == 1530 - assert len(result.volumes) == 7105 - - -def test_publisher_fail(session: Comicvine): - """Test using the publisher endpoint with an invalid publisher_id.""" - with pytest.raises(ServiceError): - session.publisher(publisher_id=-1) - - -def test_publisher_list(session: Comicvine): - """Test using the publisher_list endpoint with a valid search.""" - search_results = session.publisher_list({"filter": "name:DC Comics"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.publisher_id == 10][0] - assert result is not None - - assert result.alias_list == [ - "National Comics", - "Detective Comics Inc.", - "National Periodical Publications", - "National Allied Publications", - "Nicholson Publishing", - "All-American Publications", - "DC Entertainment", - "DC Nation", - "Johnny DC", - "National Comics Publishing", - "National Comics Publications", - ] - assert result.api_url == "https://comicvine.gamespot.com/api/publisher/4010-10/" - assert result.date_added == datetime(2008, 6, 6, 11, 8) - assert result.location_address == "4000 Warner Blvd" - assert result.location_city == "Burbank" - assert result.location_state == "California" - assert result.name == "DC Comics" - assert result.site_url == "https://comicvine.gamespot.com/dc-comics/4010-10/" - - -def test_publisher_list_empty(session: Comicvine): - """Test using the publisher_list endpoint with an invalid search.""" - results = session.publisher_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_publisher_list_max_results(session: Comicvine): - """Test publisher_list endpoint with max_results.""" - results = session.publisher_list({"filter": "name:Comics"}, max_results=10) - assert len(results) == 10 - - -def test_search_publisher(session: Comicvine): - """Test using the search endpoint for a list of Publishers.""" - results = session.search(resource=ComicvineResource.PUBLISHER, query="DC") - assert all(isinstance(x, PublisherEntry) for x in results) - - -def test_search_publisher_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search(resource=ComicvineResource.PUBLISHER, query="DC", max_results=10) - assert all(isinstance(x, PublisherEntry) for x in results) - assert len(results) == 0 +""" +The Publishers test module. + +This module contains tests for Publisher and PublisherEntry objects. +""" +from datetime import datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.publisher import PublisherEntry + + +def test_publisher(session: Comicvine): + """Test using the publisher endpoint with a valid publisher_id.""" + result = session.publisher(publisher_id=10) + assert result is not None + assert result.publisher_id == 10 + + assert result.alias_list == [ + "National Comics", + "Detective Comics Inc.", + "National Periodical Publications", + "National Allied Publications", + "Nicholson Publishing", + "All-American Publications", + "DC Entertainment", + "DC Nation", + "Johnny DC", + "National Comics Publishing", + "National Comics Publications", + ] + assert result.api_url == "https://comicvine.gamespot.com/api/publisher/4010-10/" + assert len(result.characters) == 19641 + assert result.date_added == datetime(2008, 6, 6, 11, 8) + assert result.location_address == "4000 Warner Blvd" + assert result.location_city == "Burbank" + assert result.location_state == "California" + assert result.name == "DC Comics" + assert result.site_url == "https://comicvine.gamespot.com/dc-comics/4010-10/" + assert len(result.story_arcs) == 1281 + assert len(result.teams) == 1530 + assert len(result.volumes) == 7105 + + +def test_publisher_fail(session: Comicvine): + """Test using the publisher endpoint with an invalid publisher_id.""" + with pytest.raises(ServiceError): + session.publisher(publisher_id=-1) + + +def test_publisher_list(session: Comicvine): + """Test using the publisher_list endpoint with a valid search.""" + search_results = session.publisher_list({"filter": "name:DC Comics"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.publisher_id == 10][0] + assert result is not None + + assert result.alias_list == [ + "National Comics", + "Detective Comics Inc.", + "National Periodical Publications", + "National Allied Publications", + "Nicholson Publishing", + "All-American Publications", + "DC Entertainment", + "DC Nation", + "Johnny DC", + "National Comics Publishing", + "National Comics Publications", + ] + assert result.api_url == "https://comicvine.gamespot.com/api/publisher/4010-10/" + assert result.date_added == datetime(2008, 6, 6, 11, 8) + assert result.location_address == "4000 Warner Blvd" + assert result.location_city == "Burbank" + assert result.location_state == "California" + assert result.name == "DC Comics" + assert result.site_url == "https://comicvine.gamespot.com/dc-comics/4010-10/" + + +def test_publisher_list_empty(session: Comicvine): + """Test using the publisher_list endpoint with an invalid search.""" + results = session.publisher_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_publisher_list_max_results(session: Comicvine): + """Test publisher_list endpoint with max_results.""" + results = session.publisher_list({"filter": "name:Comics"}, max_results=10) + assert len(results) == 10 + + +def test_search_publisher(session: Comicvine): + """Test using the search endpoint for a list of Publishers.""" + results = session.search(resource=ComicvineResource.PUBLISHER, query="DC") + assert all(isinstance(x, PublisherEntry) for x in results) + + +def test_search_publisher_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search(resource=ComicvineResource.PUBLISHER, query="DC", max_results=10) + assert all(isinstance(x, PublisherEntry) for x in results) + assert len(results) == 0 diff --git a/tests/test_story_arcs.py b/tests/test_story_arcs.py index ca2eb3e..df3c664 100644 --- a/tests/test_story_arcs.py +++ b/tests/test_story_arcs.py @@ -1,109 +1,109 @@ -""" -The Story Arcs test module. - -This module contains tests for StoryArc and StoryArcEntry objects. -""" -from datetime import datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.story_arc import StoryArcEntry - - -def test_story_arc(session: Comicvine): - """Test using the story_arc endpoint with a valid story_arc_id.""" - result = session.story_arc(story_arc_id=55766) - assert result is not None - assert result.story_arc_id == 55766 - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/story_arc/4045-55766/" - assert result.date_added == datetime(2008, 12, 6, 21, 29, 2) - assert result.first_issue.id_ == 155207 - assert result.issue_count == 0 - assert len(result.issues) == 86 - assert result.name == "Blackest Night" - assert result.publisher.id_ == 10 - assert result.site_url == "https://comicvine.gamespot.com/blackest-night/4045-55766/" - - -def test_story_arc_fail(session: Comicvine): - """Test using the story_arc endpoint with an invalid story_arc_id.""" - with pytest.raises(ServiceError): - session.story_arc(story_arc_id=-1) - - -def test_story_arc_null_first_issue(session: Comicvine): - """Test story_arc endpoint to return result with no first_issue.""" - result = session.story_arc(story_arc_id=56273) - assert result.first_issue is None - - -def test_story_arc_null_publisher(session: Comicvine): - """Test story_arc endpoint to return result with no publisher.""" - result = session.story_arc(story_arc_id=56765) - assert result.publisher is None - - -def test_story_arc_list(session: Comicvine): - """Test using the story_arc_list endpoint with a valid search.""" - results = session.story_arc_list({"filter": "name:Blackest Night"}) - assert len(results) != 0 - result = [x for x in results if x.story_arc_id == 55766][0] - assert result is not None - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/story_arc/4045-55766/" - assert result.date_added == datetime(2008, 12, 6, 21, 29, 2) - assert result.first_issue.id_ == 155207 - assert result.issue_count == 0 - assert result.name == "Blackest Night" - assert result.publisher.id_ == 10 - assert result.site_url == "https://comicvine.gamespot.com/blackest-night/4045-55766/" - - -def test_story_arc_list_empty(session: Comicvine): - """Test using the story_arc_list endpoint with an invalid search.""" - results = session.story_arc_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_story_arc_list_max_results(session: Comicvine): - """Test story_arc_list endpoint with max_results.""" - results = session.story_arc_list({"filter": "name:Night"}, max_results=10) - assert len(results) == 10 - - -def test_story_arc_list_null_first_issue(session: Comicvine): - """Test story_arc_list endpoint to return result with no first_issue.""" - results = session.story_arc_list({"filter": "name:Lo, this Monster"}) - assert len(results) != 0 - result = [x for x in results if x.story_arc_id == 56273][0] - assert result is not None - assert result.first_issue is None - - -def test_story_arc_list_null_publisher(session: Comicvine): - """Test story_arc_list endpoint to return result with no publisher.""" - results = session.story_arc_list({"filter": "name:Lo, this Monster"}) - assert len(results) != 0 - result = [x for x in results if x.story_arc_id == 56765][0] - assert result is not None - assert result.publisher is None - - -def test_search_story_arc(session: Comicvine): - """Test using the search endpoint for a list of Story Arcs.""" - results = session.search(resource=ComicvineResource.STORY_ARC, query="Blackest Night") - assert all(isinstance(x, StoryArcEntry) for x in results) - - -def test_search_story_arc_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search( - resource=ComicvineResource.STORY_ARC, query="Blackest Night", max_results=10 - ) - assert all(isinstance(x, StoryArcEntry) for x in results) - assert len(results) == 0 +""" +The Story Arcs test module. + +This module contains tests for StoryArc and StoryArcEntry objects. +""" +from datetime import datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.story_arc import StoryArcEntry + + +def test_story_arc(session: Comicvine): + """Test using the story_arc endpoint with a valid story_arc_id.""" + result = session.story_arc(story_arc_id=55766) + assert result is not None + assert result.story_arc_id == 55766 + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/story_arc/4045-55766/" + assert result.date_added == datetime(2008, 12, 6, 21, 29, 2) + assert result.first_issue.id_ == 155207 + assert result.issue_count == 0 + assert len(result.issues) == 86 + assert result.name == "Blackest Night" + assert result.publisher.id_ == 10 + assert result.site_url == "https://comicvine.gamespot.com/blackest-night/4045-55766/" + + +def test_story_arc_fail(session: Comicvine): + """Test using the story_arc endpoint with an invalid story_arc_id.""" + with pytest.raises(ServiceError): + session.story_arc(story_arc_id=-1) + + +def test_story_arc_null_first_issue(session: Comicvine): + """Test story_arc endpoint to return result with no first_issue.""" + result = session.story_arc(story_arc_id=56273) + assert result.first_issue is None + + +def test_story_arc_null_publisher(session: Comicvine): + """Test story_arc endpoint to return result with no publisher.""" + result = session.story_arc(story_arc_id=56765) + assert result.publisher is None + + +def test_story_arc_list(session: Comicvine): + """Test using the story_arc_list endpoint with a valid search.""" + results = session.story_arc_list({"filter": "name:Blackest Night"}) + assert len(results) != 0 + result = [x for x in results if x.story_arc_id == 55766][0] + assert result is not None + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/story_arc/4045-55766/" + assert result.date_added == datetime(2008, 12, 6, 21, 29, 2) + assert result.first_issue.id_ == 155207 + assert result.issue_count == 0 + assert result.name == "Blackest Night" + assert result.publisher.id_ == 10 + assert result.site_url == "https://comicvine.gamespot.com/blackest-night/4045-55766/" + + +def test_story_arc_list_empty(session: Comicvine): + """Test using the story_arc_list endpoint with an invalid search.""" + results = session.story_arc_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_story_arc_list_max_results(session: Comicvine): + """Test story_arc_list endpoint with max_results.""" + results = session.story_arc_list({"filter": "name:Night"}, max_results=10) + assert len(results) == 10 + + +def test_story_arc_list_null_first_issue(session: Comicvine): + """Test story_arc_list endpoint to return result with no first_issue.""" + results = session.story_arc_list({"filter": "name:Lo, this Monster"}) + assert len(results) != 0 + result = [x for x in results if x.story_arc_id == 56273][0] + assert result is not None + assert result.first_issue is None + + +def test_story_arc_list_null_publisher(session: Comicvine): + """Test story_arc_list endpoint to return result with no publisher.""" + results = session.story_arc_list({"filter": "name:Lo, this Monster"}) + assert len(results) != 0 + result = [x for x in results if x.story_arc_id == 56765][0] + assert result is not None + assert result.publisher is None + + +def test_search_story_arc(session: Comicvine): + """Test using the search endpoint for a list of Story Arcs.""" + results = session.search(resource=ComicvineResource.STORY_ARC, query="Blackest Night") + assert all(isinstance(x, StoryArcEntry) for x in results) + + +def test_search_story_arc_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search( + resource=ComicvineResource.STORY_ARC, query="Blackest Night", max_results=10 + ) + assert all(isinstance(x, StoryArcEntry) for x in results) + assert len(results) == 0 diff --git a/tests/test_teams.py b/tests/test_teams.py index fb360d5..d412ff1 100644 --- a/tests/test_teams.py +++ b/tests/test_teams.py @@ -1,85 +1,85 @@ -""" -The Teams test module. - -This module contains tests for Team and TeamEntry objects. -""" -from datetime import datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.team import TeamEntry - - -def test_team(session: Comicvine): - """Test using the team endpoint with a valid team_id.""" - result = session.team(team_id=50163) - assert result is not None - assert result.team_id == 50163 - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/team/4060-50163/" - assert len(result.enemies) == 5 - assert len(result.friends) == 10 - assert len(result.members) == 17 - assert result.issue_count == 0 - assert result.member_count == 17 - assert result.date_added == datetime(2008, 6, 6, 11, 27, 45) - assert len(result.issues_disbanded_in) == 1 - assert result.first_issue.id_ == 119950 - assert len(result.issues) == 116 - assert result.name == "Blue Lantern Corps" - assert result.publisher.id_ == 10 - assert result.site_url == "https://comicvine.gamespot.com/blue-lantern-corps/4060-50163/" - assert len(result.story_arcs) == 0 - assert len(result.volumes) == 63 - - -def test_team_fail(session: Comicvine): - """Test using the team endpoint with an invalid team_id.""" - with pytest.raises(ServiceError): - session.team(team_id=-1) - - -def test_team_list(session: Comicvine): - """Test using the team_list endpoint with a valid search.""" - search_results = session.team_list({"filter": "name:Blue Lantern Corps"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.team_id == 50163][0] - assert result is not None - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/team/4060-50163/" - assert result.issue_count == 0 - assert result.member_count == 17 - assert result.date_added == datetime(2008, 6, 6, 11, 27, 45) - assert result.first_issue.id_ == 119950 - assert result.name == "Blue Lantern Corps" - assert result.publisher.id_ == 10 - assert result.site_url == "https://comicvine.gamespot.com/blue-lantern-corps/4060-50163/" - - -def test_team_list_empty(session: Comicvine): - """Test using the team_list endpoint with an invalid search.""" - results = session.team_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_team_list_max_results(session: Comicvine): - """Test team_list endpoint with max_results.""" - results = session.team_list({"filter": "name:Lantern"}, max_results=10) - assert len(results) == 10 - - -def test_search_team(session: Comicvine): - """Test using the search endpoint for a list of Teams.""" - results = session.search(resource=ComicvineResource.TEAM, query="Lantern") - assert all(isinstance(x, TeamEntry) for x in results) - - -def test_search_team_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search(resource=ComicvineResource.TEAM, query="Lantern", max_results=10) - assert all(isinstance(x, TeamEntry) for x in results) - assert len(results) == 10 +""" +The Teams test module. + +This module contains tests for Team and TeamEntry objects. +""" +from datetime import datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.team import TeamEntry + + +def test_team(session: Comicvine): + """Test using the team endpoint with a valid team_id.""" + result = session.team(team_id=50163) + assert result is not None + assert result.team_id == 50163 + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/team/4060-50163/" + assert len(result.enemies) == 5 + assert len(result.friends) == 10 + assert len(result.members) == 17 + assert result.issue_count == 0 + assert result.member_count == 17 + assert result.date_added == datetime(2008, 6, 6, 11, 27, 45) + assert len(result.issues_disbanded_in) == 1 + assert result.first_issue.id_ == 119950 + assert len(result.issues) == 116 + assert result.name == "Blue Lantern Corps" + assert result.publisher.id_ == 10 + assert result.site_url == "https://comicvine.gamespot.com/blue-lantern-corps/4060-50163/" + assert len(result.story_arcs) == 0 + assert len(result.volumes) == 63 + + +def test_team_fail(session: Comicvine): + """Test using the team endpoint with an invalid team_id.""" + with pytest.raises(ServiceError): + session.team(team_id=-1) + + +def test_team_list(session: Comicvine): + """Test using the team_list endpoint with a valid search.""" + search_results = session.team_list({"filter": "name:Blue Lantern Corps"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.team_id == 50163][0] + assert result is not None + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/team/4060-50163/" + assert result.issue_count == 0 + assert result.member_count == 17 + assert result.date_added == datetime(2008, 6, 6, 11, 27, 45) + assert result.first_issue.id_ == 119950 + assert result.name == "Blue Lantern Corps" + assert result.publisher.id_ == 10 + assert result.site_url == "https://comicvine.gamespot.com/blue-lantern-corps/4060-50163/" + + +def test_team_list_empty(session: Comicvine): + """Test using the team_list endpoint with an invalid search.""" + results = session.team_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_team_list_max_results(session: Comicvine): + """Test team_list endpoint with max_results.""" + results = session.team_list({"filter": "name:Lantern"}, max_results=10) + assert len(results) == 10 + + +def test_search_team(session: Comicvine): + """Test using the search endpoint for a list of Teams.""" + results = session.search(resource=ComicvineResource.TEAM, query="Lantern") + assert all(isinstance(x, TeamEntry) for x in results) + + +def test_search_team_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search(resource=ComicvineResource.TEAM, query="Lantern", max_results=10) + assert all(isinstance(x, TeamEntry) for x in results) + assert len(results) == 10 diff --git a/tests/test_volumes.py b/tests/test_volumes.py index e3bf53a..73b6e92 100644 --- a/tests/test_volumes.py +++ b/tests/test_volumes.py @@ -1,151 +1,151 @@ -""" -The Volumes test module. - -This module contains tests for Volume and VolumeEntry objects. -""" -from datetime import datetime - -import pytest - -from simyan.comicvine import Comicvine, ComicvineResource -from simyan.exceptions import ServiceError -from simyan.schemas.volume import VolumeEntry - - -def test_volume(session: Comicvine): - """Test using the volume endpoint with a valid volume_id.""" - result = session.volume(volume_id=18216) - assert result is not None - assert result.volume_id == 18216 - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/volume/4050-18216/" - assert len(result.characters) == 367 - assert len(result.concepts) == 18 - assert len(result.creators) == 95 - assert result.date_added == datetime(2008, 6, 6, 11, 8, 33) - assert result.first_issue.id_ == 111265 - assert result.issue_count == 67 - assert len(result.issues) == 67 - assert result.last_issue.id_ == 278617 - assert len(result.locations) == 48 - assert result.name == "Green Lantern" - assert len(result.objects) == 367 - assert result.publisher.id_ == 10 - assert result.site_url == "https://comicvine.gamespot.com/green-lantern/4050-18216/" - assert result.start_year == 2005 - - -def test_volume_fail(session: Comicvine): - """Test using the volume endpoint with an invalid volume_id.""" - with pytest.raises(ServiceError): - session.volume(volume_id=-1) - - -def test_volume_list(session: Comicvine): - """Test using the volume_list endpoint with a valid search.""" - search_results = session.volume_list({"filter": "name:Green Lantern"}) - assert len(search_results) != 0 - result = [x for x in search_results if x.volume_id == 18216][0] - assert result is not None - - assert result.alias_list == [] - assert result.api_url == "https://comicvine.gamespot.com/api/volume/4050-18216/" - assert result.date_added == datetime(2008, 6, 6, 11, 8, 33) - assert result.first_issue.id_ == 111265 - assert result.issue_count == 67 - assert result.last_issue.id_ == 278617 - assert result.name == "Green Lantern" - assert result.publisher.id_ == 10 - assert result.site_url == "https://comicvine.gamespot.com/green-lantern/4050-18216/" - assert result.start_year == 2005 - - -def test_volume_list_empty(session: Comicvine): - """Test using the volume_list endpoint with an invalid search.""" - results = session.volume_list({"filter": "name:INVALID"}) - assert len(results) == 0 - - -def test_volume_list_max_results(session: Comicvine): - """Test volume_list endpoint with max_results.""" - results = session.volume_list({"filter": "name:Green Lantern"}, max_results=10) - assert len(results) == 10 - - -def test_search_volume(session: Comicvine): - """Test using the search endpoint for a list of Volumes.""" - results = session.search(resource=ComicvineResource.VOLUME, query="Lantern") - assert all(isinstance(x, VolumeEntry) for x in results) - - -def test_search_volume_max_results(session: Comicvine): - """Test search endpoint with max_results.""" - results = session.search(resource=ComicvineResource.VOLUME, query="Lantern", max_results=10) - assert all(isinstance(x, VolumeEntry) for x in results) - assert len(results) == 10 - - -def test_volume_invalid_start_year(session: Comicvine): - """Test volume endpoint to return result with an invalid start year.""" - result = session.volume(volume_id=106032) - assert result.start_year is None - - -def test_volume_list_invalid_start_year(session: Comicvine): - """Test volume_list endpoint to return result with an invalid start year.""" - search_results = session.volume_list({"filter": "name:Archie"}) - result = [x for x in search_results if x.volume_id == 106032][0] - assert result.start_year is None - - -def test_volume_no_start_year(session: Comicvine): - """Test volume endpoint to return result with no start year.""" - result = session.volume(volume_id=88330) - assert result.start_year is None - - -def test_volume_list_no_start_year(session: Comicvine): - """Test volume_list endpoint to return result with no start year.""" - search_results = session.volume_list({"filter": "name:The Flash"}) - result = [x for x in search_results if x.volume_id == 88330][0] - assert result.start_year is None - - -def test_volume_no_publisher(session: Comicvine): - """Test volume endpoint to return result with no publisher.""" - result = session.volume(volume_id=89312) - assert result.publisher is None - - -def test_volume_list_no_publisher(session: Comicvine): - """Test volume_list endpoint to return result with no publisher.""" - search_results = session.volume_list({"filter": "name:Archie"}) - result = [x for x in search_results if x.volume_id == 89312][0] - assert result.publisher is None - - -def test_volume_no_first_issue(session: Comicvine): - """Test volume endpoint to return result with no first issue.""" - result = session.volume(volume_id=92409) - assert result.first_issue is None - - -def test_volume_list_no_first_issue(session: Comicvine): - """Test volume_list endpoint to return result with no first issue.""" - search_results = session.volume_list(params={"filter": "name:Justice League"}) - result = [x for x in search_results if x.volume_id == 92409][0] - assert result.first_issue is None - - -def test_volume_no_last_issue(session: Comicvine): - """Test volume endpoint to return result with no last issue.""" - result = session.volume(volume_id=92409) - assert result.last_issue is None - - -def test_volume_list_no_last_issue(session: Comicvine): - """Test volume_list endpoint to return result with no last issue.""" - search_results = session.volume_list(params={"filter": "name:Justice League"}) - result = [x for x in search_results if x.volume_id == 92409][0] - assert result.last_issue is None +""" +The Volumes test module. + +This module contains tests for Volume and VolumeEntry objects. +""" +from datetime import datetime + +import pytest + +from simyan.comicvine import Comicvine, ComicvineResource +from simyan.exceptions import ServiceError +from simyan.schemas.volume import VolumeEntry + + +def test_volume(session: Comicvine): + """Test using the volume endpoint with a valid volume_id.""" + result = session.volume(volume_id=18216) + assert result is not None + assert result.volume_id == 18216 + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/volume/4050-18216/" + assert len(result.characters) == 367 + assert len(result.concepts) == 18 + assert len(result.creators) == 95 + assert result.date_added == datetime(2008, 6, 6, 11, 8, 33) + assert result.first_issue.id_ == 111265 + assert result.issue_count == 67 + assert len(result.issues) == 67 + assert result.last_issue.id_ == 278617 + assert len(result.locations) == 48 + assert result.name == "Green Lantern" + assert len(result.objects) == 367 + assert result.publisher.id_ == 10 + assert result.site_url == "https://comicvine.gamespot.com/green-lantern/4050-18216/" + assert result.start_year == 2005 + + +def test_volume_fail(session: Comicvine): + """Test using the volume endpoint with an invalid volume_id.""" + with pytest.raises(ServiceError): + session.volume(volume_id=-1) + + +def test_volume_list(session: Comicvine): + """Test using the volume_list endpoint with a valid search.""" + search_results = session.volume_list({"filter": "name:Green Lantern"}) + assert len(search_results) != 0 + result = [x for x in search_results if x.volume_id == 18216][0] + assert result is not None + + assert result.alias_list == [] + assert result.api_url == "https://comicvine.gamespot.com/api/volume/4050-18216/" + assert result.date_added == datetime(2008, 6, 6, 11, 8, 33) + assert result.first_issue.id_ == 111265 + assert result.issue_count == 67 + assert result.last_issue.id_ == 278617 + assert result.name == "Green Lantern" + assert result.publisher.id_ == 10 + assert result.site_url == "https://comicvine.gamespot.com/green-lantern/4050-18216/" + assert result.start_year == 2005 + + +def test_volume_list_empty(session: Comicvine): + """Test using the volume_list endpoint with an invalid search.""" + results = session.volume_list({"filter": "name:INVALID"}) + assert len(results) == 0 + + +def test_volume_list_max_results(session: Comicvine): + """Test volume_list endpoint with max_results.""" + results = session.volume_list({"filter": "name:Green Lantern"}, max_results=10) + assert len(results) == 10 + + +def test_search_volume(session: Comicvine): + """Test using the search endpoint for a list of Volumes.""" + results = session.search(resource=ComicvineResource.VOLUME, query="Lantern") + assert all(isinstance(x, VolumeEntry) for x in results) + + +def test_search_volume_max_results(session: Comicvine): + """Test search endpoint with max_results.""" + results = session.search(resource=ComicvineResource.VOLUME, query="Lantern", max_results=10) + assert all(isinstance(x, VolumeEntry) for x in results) + assert len(results) == 10 + + +def test_volume_invalid_start_year(session: Comicvine): + """Test volume endpoint to return result with an invalid start year.""" + result = session.volume(volume_id=106032) + assert result.start_year is None + + +def test_volume_list_invalid_start_year(session: Comicvine): + """Test volume_list endpoint to return result with an invalid start year.""" + search_results = session.volume_list({"filter": "name:Archie"}) + result = [x for x in search_results if x.volume_id == 106032][0] + assert result.start_year is None + + +def test_volume_no_start_year(session: Comicvine): + """Test volume endpoint to return result with no start year.""" + result = session.volume(volume_id=88330) + assert result.start_year is None + + +def test_volume_list_no_start_year(session: Comicvine): + """Test volume_list endpoint to return result with no start year.""" + search_results = session.volume_list({"filter": "name:The Flash"}) + result = [x for x in search_results if x.volume_id == 88330][0] + assert result.start_year is None + + +def test_volume_no_publisher(session: Comicvine): + """Test volume endpoint to return result with no publisher.""" + result = session.volume(volume_id=89312) + assert result.publisher is None + + +def test_volume_list_no_publisher(session: Comicvine): + """Test volume_list endpoint to return result with no publisher.""" + search_results = session.volume_list({"filter": "name:Archie"}) + result = [x for x in search_results if x.volume_id == 89312][0] + assert result.publisher is None + + +def test_volume_no_first_issue(session: Comicvine): + """Test volume endpoint to return result with no first issue.""" + result = session.volume(volume_id=92409) + assert result.first_issue is None + + +def test_volume_list_no_first_issue(session: Comicvine): + """Test volume_list endpoint to return result with no first issue.""" + search_results = session.volume_list(params={"filter": "name:Justice League"}) + result = [x for x in search_results if x.volume_id == 92409][0] + assert result.first_issue is None + + +def test_volume_no_last_issue(session: Comicvine): + """Test volume endpoint to return result with no last issue.""" + result = session.volume(volume_id=92409) + assert result.last_issue is None + + +def test_volume_list_no_last_issue(session: Comicvine): + """Test volume_list endpoint to return result with no last issue.""" + search_results = session.volume_list(params={"filter": "name:Justice League"}) + result = [x for x in search_results if x.volume_id == 92409][0] + assert result.last_issue is None