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