diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 1b83db3..0000000 --- a/.flake8 +++ /dev/null @@ -1,11 +0,0 @@ -[flake8] -select = B,B9,C,D,DAR,E,F,N,RST,S,W -ignore = E203,E501,RST201,RST203,RST301,W503 -max-line-length = 80 -max-complexity = 10 -docstring-convention = google -per-file-ignores = - tests/*:S101,D212,D415,D205,D104 - __init__.py:F401 -rst-roles = class,const,func,meth,mod,ref -rst-directives = deprecated diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7fad19a..80b8fbd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,6 +31,7 @@ jobs: env: NOXSESSION: ${{ matrix.session }} + TMDB_API_KEY: ${{secrets.TMDB_API_KEY}} steps: - name: Check out the repository diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e7de7f..073fc47 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,9 +27,9 @@ repos: language: system types: [text] stages: [commit, push, manual] - - id: flake8 - name: flake8 - entry: flake8 + - id: ruff + name: ruff + entry: ruff language: system types: [python] require_serial: true diff --git a/docs/tmdb.md b/docs/tmdb.md new file mode 100644 index 0000000..af79c36 --- /dev/null +++ b/docs/tmdb.md @@ -0,0 +1,62 @@ +# Usage + +To access TMDB data points, first ensure you have loaded the TMDB source +through: + +```python +await phylm.load_source("tmdb") +``` + +Alternatively you can instantiate the TMDB source class directly through: + +```python +from phylm.sources import Tmdb + +tmdb = Tmdb(raw_title="The Matrix", raw_year=1999)  # raw_year is optional +await tmdb.load_source() +``` + +## Movie ID + +If you know the TMDB movie ID you can instantiate the `Phylm` class with a `tmdb_id` +property: + +```python +from phylm.sources import Tmdb + +tmdb = Tmdb(raw_title="The Matrix", tmdb_id="609") +``` + +Then, when running `load_source` for `tmdb`, `phylm` will first perform a search based +on the ID. If the ID is valid the result will be selected, if not then it will fall back +to a title search. + +Alternatively, you can pass it to `load_source` with `"tmdb"` as the first argument: + +```python +await phylm.load_source("tmdb", tmdb_id="609") +``` + +Or instantiate the TMDB source class with the ID: + +```python +from phylm.sources import Tmdb + +tmdb = Tmdb(movie_id="0133093") +``` + +!!! warning "" + If instantiating the class directly you must supply at least one of `movie_id` + or `raw_title`, otherwise a `ValueError` will be raised. + +Note that TMDB doesn't provide any fuzzy search for title, only exact matches are +returned. + +# Reference + +::: phylm.sources.tmdb.Tmdb + rendering: + show_signature_annotations: true + heading_level: 2 + show_root_heading: false + show_root_toc_entry: false diff --git a/mkdocs.yml b/mkdocs.yml index b8216b0..8b0de3e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,6 +19,7 @@ nav: IMDb: imdb.md Metacritic: mtc.md Rotten Tomatoes: rt.md + TMDB: tmdb.md - Tools: tools.md - Contributing: contributing.md diff --git a/noxfile.py b/noxfile.py index 1060aaf..e56a6aa 100644 --- a/noxfile.py +++ b/noxfile.py @@ -89,11 +89,7 @@ def precommit(session: Session) -> None: session.install( "black", "darglint", - "flake8", - "flake8-bandit", - "flake8-bugbear", - "flake8-docstrings", - "flake8-rst-docstrings", + "ruff", "pep8-naming", "pre-commit", "pre-commit-hooks", diff --git a/poetry.lock b/poetry.lock index 78908a0..031a466 100644 --- a/poetry.lock +++ b/poetry.lock @@ -196,39 +196,16 @@ files = [ [package.dependencies] pytz = ">=2015.7" -[[package]] -name = "bandit" -version = "1.7.4" -description = "Security oriented static analyser for python code." -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, - {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, -] - -[package.dependencies] -colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -GitPython = ">=1.0.1" -PyYAML = ">=5.3.1" -stevedore = ">=1.20.0" - -[package.extras] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml"] -toml = ["toml"] -yaml = ["PyYAML"] - [[package]] name = "beautifulsoup4" -version = "4.11.1" +version = "4.11.2" description = "Screen-scraping library" category = "main" optional = false python-versions = ">=3.6.0" files = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, + {file = "beautifulsoup4-4.11.2-py3-none-any.whl", hash = "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39"}, + {file = "beautifulsoup4-4.11.2.tar.gz", hash = "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"}, ] [package.dependencies] @@ -473,18 +450,6 @@ files = [ {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, ] -[[package]] -name = "docutils" -version = "0.19" -description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] - [[package]] name = "dparse" version = "0.6.2" @@ -538,57 +503,6 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.10.0,<2.11.0" pyflakes = ">=3.0.0,<3.1.0" -[[package]] -name = "flake8-bandit" -version = "4.1.1" -description = "Automated security testing with bandit and flake8." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "flake8_bandit-4.1.1-py3-none-any.whl", hash = "sha256:4c8a53eb48f23d4ef1e59293657181a3c989d0077c9952717e98a0eace43e06d"}, - {file = "flake8_bandit-4.1.1.tar.gz", hash = "sha256:068e09287189cbfd7f986e92605adea2067630b75380c6b5733dab7d87f9a84e"}, -] - -[package.dependencies] -bandit = ">=1.7.3" -flake8 = ">=5.0.0" - -[[package]] -name = "flake8-bugbear" -version = "21.11.29" -description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "flake8-bugbear-21.11.29.tar.gz", hash = "sha256:8b04cb2fafc6a78e1a9d873bd3988e4282f7959bb6b0d7c1ae648ec09b937a7b"}, - {file = "flake8_bugbear-21.11.29-py36.py37.py38-none-any.whl", hash = "sha256:179e41ddae5de5e3c20d1f61736feeb234e70958fbb56ab3c28a67739c8e9a82"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -flake8 = ">=3.0.0" - -[package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] - -[[package]] -name = "flake8-docstrings" -version = "1.6.0" -description = "Extension for flake8 which uses pydocstyle to check docstrings" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, - {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, -] - -[package.dependencies] -flake8 = ">=3" -pydocstyle = ">=2.1" - [[package]] name = "flake8-polyfill" version = "1.0.2" @@ -604,23 +518,6 @@ files = [ [package.dependencies] flake8 = "*" -[[package]] -name = "flake8-rst-docstrings" -version = "0.2.7" -description = "Python docstring reStructuredText (RST) validator" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "flake8-rst-docstrings-0.2.7.tar.gz", hash = "sha256:2740067ab9237559dd45a3434d8c987792c7b259ca563621a3b95efe201f5382"}, - {file = "flake8_rst_docstrings-0.2.7-py3-none-any.whl", hash = "sha256:5d56075dce360bcc9c6775bfe7cb431aa395de600ca7e8d40580a28d50b2a803"}, -] - -[package.dependencies] -flake8 = ">=3.0.0" -pygments = "*" -restructuredtext-lint = "*" - [[package]] name = "frozenlist" version = "1.3.3" @@ -755,85 +652,88 @@ gitdb = ">=4.0.1,<5" [[package]] name = "greenlet" -version = "2.0.1" +version = "2.0.2" description = "Lightweight in-process concurrent programming" category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ - {file = "greenlet-2.0.1-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:9ed358312e63bf683b9ef22c8e442ef6c5c02973f0c2a939ec1d7b50c974015c"}, - {file = "greenlet-2.0.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4f09b0010e55bec3239278f642a8a506b91034f03a4fb28289a7d448a67f1515"}, - {file = "greenlet-2.0.1-cp27-cp27m-win32.whl", hash = "sha256:1407fe45246632d0ffb7a3f4a520ba4e6051fc2cbd61ba1f806900c27f47706a"}, - {file = "greenlet-2.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:3001d00eba6bbf084ae60ec7f4bb8ed375748f53aeaefaf2a37d9f0370558524"}, - {file = "greenlet-2.0.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d566b82e92ff2e09dd6342df7e0eb4ff6275a3f08db284888dcd98134dbd4243"}, - {file = "greenlet-2.0.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:0722c9be0797f544a3ed212569ca3fe3d9d1a1b13942d10dd6f0e8601e484d26"}, - {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d37990425b4687ade27810e3b1a1c37825d242ebc275066cfee8cb6b8829ccd"}, - {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be35822f35f99dcc48152c9839d0171a06186f2d71ef76dc57fa556cc9bf6b45"}, - {file = "greenlet-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c140e7eb5ce47249668056edf3b7e9900c6a2e22fb0eaf0513f18a1b2c14e1da"}, - {file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d21681f09e297a5adaa73060737e3aa1279a13ecdcfcc6ef66c292cb25125b2d"}, - {file = "greenlet-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb412b7db83fe56847df9c47b6fe3f13911b06339c2aa02dcc09dce8bbf582cd"}, - {file = "greenlet-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6a08799e9e88052221adca55741bf106ec7ea0710bca635c208b751f0d5b617"}, - {file = "greenlet-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e112e03d37987d7b90c1e98ba5e1b59e1645226d78d73282f45b326f7bddcb9"}, - {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56961cfca7da2fdd178f95ca407fa330c64f33289e1804b592a77d5593d9bd94"}, - {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:13ba6e8e326e2116c954074c994da14954982ba2795aebb881c07ac5d093a58a"}, - {file = "greenlet-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bf633a50cc93ed17e494015897361010fc08700d92676c87931d3ea464123ce"}, - {file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9f2c221eecb7ead00b8e3ddb913c67f75cba078fd1d326053225a3f59d850d72"}, - {file = "greenlet-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13ebf93c343dd8bd010cd98e617cb4c1c1f352a0cf2524c82d3814154116aa82"}, - {file = "greenlet-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f61d71bbc9b4a3de768371b210d906726535d6ca43506737682caa754b956cd"}, - {file = "greenlet-2.0.1-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:2d0bac0385d2b43a7bd1d651621a4e0f1380abc63d6fb1012213a401cbd5bf8f"}, - {file = "greenlet-2.0.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f6327b6907b4cb72f650a5b7b1be23a2aab395017aa6f1adb13069d66360eb3f"}, - {file = "greenlet-2.0.1-cp35-cp35m-win32.whl", hash = "sha256:81b0ea3715bf6a848d6f7149d25bf018fd24554a4be01fcbbe3fdc78e890b955"}, - {file = "greenlet-2.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:38255a3f1e8942573b067510f9611fc9e38196077b0c8eb7a8c795e105f9ce77"}, - {file = "greenlet-2.0.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:04957dc96669be041e0c260964cfef4c77287f07c40452e61abe19d647505581"}, - {file = "greenlet-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4aeaebcd91d9fee9aa768c1b39cb12214b30bf36d2b7370505a9f2165fedd8d9"}, - {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974a39bdb8c90a85982cdb78a103a32e0b1be986d411303064b28a80611f6e51"}, - {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dca09dedf1bd8684767bc736cc20c97c29bc0c04c413e3276e0962cd7aeb148"}, - {file = "greenlet-2.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c0757db9bd08470ff8277791795e70d0bf035a011a528ee9a5ce9454b6cba2"}, - {file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:5067920de254f1a2dee8d3d9d7e4e03718e8fd2d2d9db962c8c9fa781ae82a39"}, - {file = "greenlet-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5a8e05057fab2a365c81abc696cb753da7549d20266e8511eb6c9d9f72fe3e92"}, - {file = "greenlet-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:3d75b8d013086b08e801fbbb896f7d5c9e6ccd44f13a9241d2bf7c0df9eda928"}, - {file = "greenlet-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:097e3dae69321e9100202fc62977f687454cd0ea147d0fd5a766e57450c569fd"}, - {file = "greenlet-2.0.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:cb242fc2cda5a307a7698c93173d3627a2a90d00507bccf5bc228851e8304963"}, - {file = "greenlet-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:72b00a8e7c25dcea5946692a2485b1a0c0661ed93ecfedfa9b6687bd89a24ef5"}, - {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b0ff9878333823226d270417f24f4d06f235cb3e54d1103b71ea537a6a86ce"}, - {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be9e0fb2ada7e5124f5282d6381903183ecc73ea019568d6d63d33f25b2a9000"}, - {file = "greenlet-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b493db84d124805865adc587532ebad30efa68f79ad68f11b336e0a51ec86c2"}, - {file = "greenlet-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a20d33124935d27b80e6fdacbd34205732660e0a1d35d8b10b3328179a2b51a1"}, - {file = "greenlet-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:ea688d11707d30e212e0110a1aac7f7f3f542a259235d396f88be68b649e47d1"}, - {file = "greenlet-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:afe07421c969e259e9403c3bb658968702bc3b78ec0b6fde3ae1e73440529c23"}, - {file = "greenlet-2.0.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:cd4ccc364cf75d1422e66e247e52a93da6a9b73cefa8cad696f3cbbb75af179d"}, - {file = "greenlet-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c8b1c43e75c42a6cafcc71defa9e01ead39ae80bd733a2608b297412beede68"}, - {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:659f167f419a4609bc0516fb18ea69ed39dbb25594934bd2dd4d0401660e8a1e"}, - {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:356e4519d4dfa766d50ecc498544b44c0249b6de66426041d7f8b751de4d6b48"}, - {file = "greenlet-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:811e1d37d60b47cb8126e0a929b58c046251f28117cb16fcd371eed61f66b764"}, - {file = "greenlet-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0109af1138afbfb8ae647e31a2b1ab030f58b21dd8528c27beaeb0093b7938a9"}, - {file = "greenlet-2.0.1-cp38-cp38-win32.whl", hash = "sha256:88c8d517e78acdf7df8a2134a3c4b964415b575d2840a2746ddb1cc6175f8608"}, - {file = "greenlet-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:d6ee1aa7ab36475035eb48c01efae87d37936a8173fc4d7b10bb02c2d75dd8f6"}, - {file = "greenlet-2.0.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b1992ba9d4780d9af9726bbcef6a1db12d9ab1ccc35e5773685a24b7fb2758eb"}, - {file = "greenlet-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:b5e83e4de81dcc9425598d9469a624826a0b1211380ac444c7c791d4a2137c19"}, - {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:505138d4fa69462447a562a7c2ef723c6025ba12ac04478bc1ce2fcc279a2db5"}, - {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce1e90dd302f45716a7715517c6aa0468af0bf38e814ad4eab58e88fc09f7f7"}, - {file = "greenlet-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e9744c657d896c7b580455e739899e492a4a452e2dd4d2b3e459f6b244a638d"}, - {file = "greenlet-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:41b825d65f31e394b523c84db84f9383a2f7eefc13d987f308f4663794d2687e"}, - {file = "greenlet-2.0.1-cp39-cp39-win32.whl", hash = "sha256:db38f80540083ea33bdab614a9d28bcec4b54daa5aff1668d7827a9fc769ae0a"}, - {file = "greenlet-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b23d2a46d53210b498e5b701a1913697671988f4bf8e10f935433f6e7c332fb6"}, - {file = "greenlet-2.0.1.tar.gz", hash = "sha256:42e602564460da0e8ee67cb6d7236363ee5e131aa15943b6670e44e5c2ed0f67"}, + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, ] [package.extras] docs = ["Sphinx", "docutils (<0.18)"] -test = ["faulthandler", "objgraph", "psutil"] +test = ["objgraph", "psutil"] [[package]] name = "griffe" -version = "0.25.2" +version = "0.25.4" 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." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "griffe-0.25.2-py3-none-any.whl", hash = "sha256:0868da415c5f43fe186705c041d98b69523c24a6504e841031373eacfdd7ec05"}, - {file = "griffe-0.25.2.tar.gz", hash = "sha256:555707b3417355e015d837845522cb38ee4ffcec485427868648eafacabe142e"}, + {file = "griffe-0.25.4-py3-none-any.whl", hash = "sha256:919f935a358b31074d16e324e26b041883c60a8cf10504655e394afc3a7caad8"}, + {file = "griffe-0.25.4.tar.gz", hash = "sha256:f190edf8ef58d43c856d2d6761ec324a043ff60deb8c14359263571e8b91fe68"}, ] [package.dependencies] @@ -855,14 +755,14 @@ files = [ [[package]] name = "identify" -version = "2.5.12" +version = "2.5.17" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.12-py2.py3-none-any.whl", hash = "sha256:e8a400c3062d980243d27ce10455a52832205649bbcaf27ffddb3dfaaf477bad"}, - {file = "identify-2.5.12.tar.gz", hash = "sha256:0bc96b09c838310b6fcfcc61f78a981ea07f94836ef6ef553da5bb5d4745d662"}, + {file = "identify-2.5.17-py2.py3-none-any.whl", hash = "sha256:7d526dd1283555aafcc91539acc061d8f6f59adb0a7bba462735b0a318bff7ed"}, + {file = "identify-2.5.17.tar.gz", hash = "sha256:93cc61a861052de9d4c541a7acb7e3dcc9c11b398a2144f6e52ae5285f5f4f06"}, ] [package.extras] @@ -902,14 +802,14 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] @@ -1053,66 +953,80 @@ testing = ["coverage", "pyyaml"] [[package]] name = "markdown-include" -version = "0.6.0" -description = "This is an extension to Python-Markdown which provides an \"include\" function, similar to that found in LaTeX (and also the C pre-processor and Fortran). I originally wrote it for my FORD Fortran auto-documentation generator." +version = "0.8.0" +description = "A Python-Markdown extension which provides an 'include' function" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "markdown-include-0.6.0.tar.gz", hash = "sha256:6f5d680e36f7780c7f0f61dca53ca581bd50d1b56137ddcd6353efafa0c3e4a2"}, + {file = "markdown-include-0.8.0.tar.gz", hash = "sha256:b8f6b6f4e8b506cbe773d7e26c74a97d1354c35f3a3452d3449140a8f578d665"}, + {file = "markdown_include-0.8.0-py3-none-any.whl", hash = "sha256:d12fb51500c46334a53608635035c78b7d8ad7f772566f70b8a6a9b2ef2ddbf5"}, ] [package.dependencies] -markdown = "*" +markdown = ">=3.0" + +[package.extras] +tests = ["pytest"] [[package]] name = "markupsafe" -version = "2.1.1" +version = "2.1.2" description = "Safely add untrusted strings to HTML/XML markup." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] [[package]] @@ -1236,14 +1150,14 @@ requests = "*" [[package]] name = "mkdocs-material" -version = "9.0.2" +version = "9.0.10" description = "Documentation that simply works" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-9.0.2-py3-none-any.whl", hash = "sha256:e2f0af24be09c2aaea88a70bac9fcca976276074931c06fd0631b1981f5c74ad"}, - {file = "mkdocs_material-9.0.2.tar.gz", hash = "sha256:c24e2127ada1d4d3deff09fa4bbe0a22f6e6bb7eaf7616e193138bf2a40d6580"}, + {file = "mkdocs_material-9.0.10-py3-none-any.whl", hash = "sha256:0848611d6dff21d5b4cacaf490606d075cd03b49f3c49809c8aa2e45b1f5216c"}, + {file = "mkdocs_material-9.0.10.tar.gz", hash = "sha256:d0f9734ec58b1d0e3e67c1b1941c60a40067b5e708430b0effa3bc3c323b4dd1"}, ] [package.dependencies] @@ -1253,7 +1167,7 @@ markdown = ">=3.2" mkdocs = ">=1.4.2" mkdocs-material-extensions = ">=1.1" pygments = ">=2.14" -pymdown-extensions = ">=9.9" +pymdown-extensions = ">=9.9.1" regex = ">=2022.4.24" requests = ">=2.26" @@ -1289,14 +1203,14 @@ mkdocs = ">=1.4.1" [[package]] name = "mkdocstrings" -version = "0.19.1" +version = "0.20.0" description = "Automatic documentation from sources, for MkDocs." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocstrings-0.19.1-py3-none-any.whl", hash = "sha256:32a38d88f67f65b264184ea71290f9332db750d189dea4200cbbe408d304c261"}, - {file = "mkdocstrings-0.19.1.tar.gz", hash = "sha256:d1037cacb4b522c1e8c164ed5d00d724a82e49dcee0af80db8fb67b384faeef9"}, + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, ] [package.dependencies] @@ -1314,14 +1228,14 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "0.8.2" +version = "0.8.3" description = "A Python handler for mkdocstrings." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocstrings-python-0.8.2.tar.gz", hash = "sha256:b22528b7a7a0589d007eced019d97ad14de4eba4b2b9ba6a013bb66edc74ab43"}, - {file = "mkdocstrings_python-0.8.2-py3-none-any.whl", hash = "sha256:213d9592e66e084a9bd2fa4956d6294a3487c6dc9cc45164058d6317249b7b6e"}, + {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, + {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, ] [package.dependencies] @@ -1498,26 +1412,14 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pathspec" -version = "0.10.3" +version = "0.11.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.10.3-py3-none-any.whl", hash = "sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6"}, - {file = "pathspec-0.10.3.tar.gz", hash = "sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"}, -] - -[[package]] -name = "pbr" -version = "5.11.0" -description = "Python Build Reasonableness" -category = "dev" -optional = false -python-versions = ">=2.6" -files = [ - {file = "pbr-5.11.0-py2.py3-none-any.whl", hash = "sha256:db2317ff07c84c4c63648c9064a79fe9d9f5c7ce85a9099d4b6258b3db83225a"}, - {file = "pbr-5.11.0.tar.gz", hash = "sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe"}, + {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, + {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, ] [[package]] @@ -1627,24 +1529,6 @@ files = [ {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, ] -[[package]] -name = "pydocstyle" -version = "6.2.2" -description = "Python docstring style checker" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "pydocstyle-6.2.2-py3-none-any.whl", hash = "sha256:021b33525927b43b4469c2715694c38082cb98146b52342df652b30806e3cb61"}, - {file = "pydocstyle-6.2.2.tar.gz", hash = "sha256:5714e3a96f6ece848a1c35f581e89f3164867733609f0dd8a99f7e7c6b526bdd"}, -] - -[package.dependencies] -snowballstemmer = ">=2.2.0" - -[package.extras] -toml = ["tomli (>=1.2.3)"] - [[package]] name = "pyflakes" version = "3.0.1" @@ -1674,14 +1558,14 @@ plugins = ["importlib-metadata"] [[package]] name = "pymdown-extensions" -version = "9.9" +version = "9.9.2" description = "Extension pack for Python Markdown." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pymdown_extensions-9.9-py3-none-any.whl", hash = "sha256:ac698c15265680db5eb13cd4342abfcde2079ac01e5486028f47a1b41547b859"}, - {file = "pymdown_extensions-9.9.tar.gz", hash = "sha256:0f8fb7b74a37a61cc34e90b2c91865458b713ec774894ffad64353a5fce85cfc"}, + {file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"}, + {file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"}, ] [package.dependencies] @@ -1778,14 +1662,14 @@ six = ">=1.5" [[package]] name = "pytz" -version = "2022.7" +version = "2022.7.1" description = "World timezone definitions, modern and historical" category = "dev" optional = false python-versions = "*" files = [ - {file = "pytz-2022.7-py2.py3-none-any.whl", hash = "sha256:93007def75ae22f7cd991c84e02d434876818661f8df9ad5df9e950ff4e52cfd"}, - {file = "pytz-2022.7.tar.gz", hash = "sha256:7ccfae7b4b2c067464a6733c6261673fdb8fd1be905460396b97a073e9fa683a"}, + {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, + {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, ] [[package]] @@ -1968,19 +1852,19 @@ files = [ [[package]] name = "requests" -version = "2.28.1" +version = "2.28.2" description = "Python HTTP for Humans." category = "main" optional = false python-versions = ">=3.7, <4" files = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, + {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, + {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" @@ -1988,20 +1872,6 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] -[[package]] -name = "restructuredtext-lint" -version = "1.4.0" -description = "reStructuredText linter" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, -] - -[package.dependencies] -docutils = ">=0.11,<1.0" - [[package]] name = "ruamel-yaml" version = "0.17.21" @@ -2064,6 +1934,32 @@ files = [ {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, ] +[[package]] +name = "ruff" +version = "0.0.240" +description = "An extremely fast Python linter, written in Rust." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.240-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:222dd5a5f7cf2f155d7bb77ac484b9afd6f8aaecd963a91c8dbb93355ef42fd2"}, + {file = "ruff-0.0.240-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:2c956a037671b5ab81546346f3e7f0b3f0e13d0b2e5a3e88c1b2227a1e9aae82"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b43c73fc165f8c7de7c095208d05653744aee6fb0a71680449c2ff1cf59183ea"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f58f1122001150d70909885ccf43d869237be814d4cfc74bb60b3883635e440a"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b427050336b8967755e305f506e84e550591fa47766b5b0cb0c8bcb5c8ca9e7"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:0fe8cc47c4c3423548a074e163388f943a14b1e349be88e5dc4cd43df81b6344"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2f40f07d030e7a8cbe365a62fe8543e146b9bcd2a31f5625c2beaccad0d1b8c1"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c222ad12e4bf795e3cec64d56178af1bfbc5d97929a0abf685564937e52c9862"}, + {file = "ruff-0.0.240-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a26eb3cd68527bcae2543027a0a674d37d03f239f6f025049149115c9775438d"}, + {file = "ruff-0.0.240-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4591c9104b6898cbd0df57f6b6f8e2907b08fa85ff5196750f0a7b370ae9f78e"}, + {file = "ruff-0.0.240-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7fed973319ca0a8c2e5c80732217b9b1ec069305839f480907469791e596b150"}, + {file = "ruff-0.0.240-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4ce049d1fedb1b785fef29403d26e6109b77287b51afd10b74edc986f609c4af"}, + {file = "ruff-0.0.240-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5127cfaec1f78bd7104174eeacee85dea64796905812b448efd60f504cfa5eec"}, + {file = "ruff-0.0.240-py3-none-win32.whl", hash = "sha256:071e01a980ffd638a5ce7960ce662fa9b434962f78e7c575478c64e5f147aac8"}, + {file = "ruff-0.0.240-py3-none-win_amd64.whl", hash = "sha256:d0b1ac5d1d882db25ca4b7dff8aa813ecc7912bdde4ad8f59f2d922b1996cbc7"}, + {file = "ruff-0.0.240.tar.gz", hash = "sha256:0f1a0b04ce6f3d59894c64f3c3a5a0a35ff4803b8dc51e962d7de42fdb0f5eb1"}, +] + [[package]] name = "safety" version = "2.3.5" @@ -2090,18 +1986,18 @@ gitlab = ["python-gitlab (>=1.3.0)"] [[package]] name = "setuptools" -version = "65.6.3" +version = "67.1.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, - {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, + {file = "setuptools-67.1.0-py3-none-any.whl", hash = "sha256:a7687c12b444eaac951ea87a9627c4f904ac757e7abdc5aac32833234af90378"}, + {file = "setuptools-67.1.0.tar.gz", hash = "sha256:e261cdf010c11a41cb5cb5f1bf3338a7433832029f559a6a7614bd42a967c300"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "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)", "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-timeout", "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"] @@ -2129,18 +2025,6 @@ files = [ {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - [[package]] name = "soupsieve" version = "2.3.2.post1" @@ -2155,94 +2039,82 @@ files = [ [[package]] name = "sqlalchemy" -version = "1.4.46" +version = "2.0.1" description = "Database Abstraction Library" category = "main" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" -files = [ - {file = "SQLAlchemy-1.4.46-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:7001f16a9a8e06488c3c7154827c48455d1c1507d7228d43e781afbc8ceccf6d"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c7a46639ba058d320c9f53a81db38119a74b8a7a1884df44d09fbe807d028aaf"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-win32.whl", hash = "sha256:c04144a24103135ea0315d459431ac196fe96f55d3213bfd6d39d0247775c854"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27m-win_amd64.whl", hash = "sha256:7b81b1030c42b003fc10ddd17825571603117f848814a344d305262d370e7c34"}, - {file = "SQLAlchemy-1.4.46-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:939f9a018d2ad04036746e15d119c0428b1e557470361aa798e6e7d7f5875be0"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b7f4b6aa6e87991ec7ce0e769689a977776db6704947e562102431474799a857"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf17ac9a61e7a3f1c7ca47237aac93cabd7f08ad92ac5b96d6f8dea4287fc1"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7f8267682eb41a0584cf66d8a697fef64b53281d01c93a503e1344197f2e01fe"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cb0ad8a190bc22d2112001cfecdec45baffdf41871de777239da6a28ed74b6"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-win32.whl", hash = "sha256:5f752676fc126edc1c4af0ec2e4d2adca48ddfae5de46bb40adbd3f903eb2120"}, - {file = "SQLAlchemy-1.4.46-cp310-cp310-win_amd64.whl", hash = "sha256:31de1e2c45e67a5ec1ecca6ec26aefc299dd5151e355eb5199cd9516b57340be"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d68e1762997bfebf9e5cf2a9fd0bcf9ca2fdd8136ce7b24bbd3bbfa4328f3e4a"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d112b0f3c1bc5ff70554a97344625ef621c1bfe02a73c5d97cac91f8cd7a41e"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69fac0a7054d86b997af12dc23f581cf0b25fb1c7d1fed43257dee3af32d3d6d"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-win32.whl", hash = "sha256:887865924c3d6e9a473dc82b70977395301533b3030d0f020c38fd9eba5419f2"}, - {file = "SQLAlchemy-1.4.46-cp311-cp311-win_amd64.whl", hash = "sha256:984ee13543a346324319a1fb72b698e521506f6f22dc37d7752a329e9cd00a32"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:9167d4227b56591a4cc5524f1b79ccd7ea994f36e4c648ab42ca995d28ebbb96"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d61e9ecc849d8d44d7f80894ecff4abe347136e9d926560b818f6243409f3c86"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3ec187acf85984263299a3f15c34a6c0671f83565d86d10f43ace49881a82718"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9883f5fae4fd8e3f875adc2add69f8b945625811689a6c65866a35ee9c0aea23"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-win32.whl", hash = "sha256:535377e9b10aff5a045e3d9ada8a62d02058b422c0504ebdcf07930599890eb0"}, - {file = "SQLAlchemy-1.4.46-cp36-cp36m-win_amd64.whl", hash = "sha256:18cafdb27834fa03569d29f571df7115812a0e59fd6a3a03ccb0d33678ec8420"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:a1ad90c97029cc3ab4ffd57443a20fac21d2ec3c89532b084b073b3feb5abff3"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4847f4b1d822754e35707db913396a29d874ee77b9c3c3ef3f04d5a9a6209618"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c5a99282848b6cae0056b85da17392a26b2d39178394fc25700bcf967e06e97a"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4b1cc7835b39835c75cf7c20c926b42e97d074147c902a9ebb7cf2c840dc4e2"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-win32.whl", hash = "sha256:c522e496f9b9b70296a7675272ec21937ccfc15da664b74b9f58d98a641ce1b6"}, - {file = "SQLAlchemy-1.4.46-cp37-cp37m-win_amd64.whl", hash = "sha256:ae067ab639fa499f67ded52f5bc8e084f045d10b5ac7bb928ae4ca2b6c0429a5"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:e3c1808008124850115a3f7e793a975cfa5c8a26ceeeb9ff9cbb4485cac556df"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d164df3d83d204c69f840da30b292ac7dc54285096c6171245b8d7807185aa"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b33ffbdbbf5446cf36cd4cc530c9d9905d3c2fe56ed09e25c22c850cdb9fac92"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d94682732d1a0def5672471ba42a29ff5e21bb0aae0afa00bb10796fc1e28dd"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-win32.whl", hash = "sha256:f8cb80fe8d14307e4124f6fad64dfd87ab749c9d275f82b8b4ec84c84ecebdbe"}, - {file = "SQLAlchemy-1.4.46-cp38-cp38-win_amd64.whl", hash = "sha256:07e48cbcdda6b8bc7a59d6728bd3f5f574ffe03f2c9fb384239f3789c2d95c2e"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:1b1e5e96e2789d89f023d080bee432e2fef64d95857969e70d3cadec80bd26f0"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3714e5b33226131ac0da60d18995a102a17dddd42368b7bdd206737297823ad"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:955162ad1a931fe416eded6bb144ba891ccbf9b2e49dc7ded39274dd9c5affc5"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6e4cb5c63f705c9d546a054c60d326cbde7421421e2d2565ce3e2eee4e1a01f"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-win32.whl", hash = "sha256:51e1ba2884c6a2b8e19109dc08c71c49530006c1084156ecadfaadf5f9b8b053"}, - {file = "SQLAlchemy-1.4.46-cp39-cp39-win_amd64.whl", hash = "sha256:315676344e3558f1f80d02535f410e80ea4e8fddba31ec78fe390eff5fb8f466"}, - {file = "SQLAlchemy-1.4.46.tar.gz", hash = "sha256:6913b8247d8a292ef8315162a51931e2b40ce91681f1b6f18f697045200c4a30"}, +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3997238968fa495fac4b17fa18b36616c41a6e6759f323dfb3f83cbcf1d3b1bb"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d1588f6ba25dbb2d6eb1531e56f419e02cdc9ec06d9f082195877c5148f6f6ab"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01704ec4a6877b74608264992a87979a27a8927cefd14ccdc0d478acacc1ed85"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e60bec8fdd753212aa8cec012bbb3060e9c2227496fa935ca8918744a34c864d"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:eff376cc201363634b5b60a828b3998b088a71e16f7a43da26fc0e2201e25a05"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:20b9e36f0219285c580dc5e98cadb59b751e259f3829460bc58d45e7a770dd36"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-win32.whl", hash = "sha256:0186b970fd4561def531b582a86819d8f8af65c8b1a78cf015ee47e526f4cfb6"}, + {file = "SQLAlchemy-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:664a21613d7eff895de9ef731632575cfca773ddbac9b7f7adad288ab971bcbd"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f504779e6e68d0cb7043825958125abd7742c7c73ce9c6b652d20c6b5f17022"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f9085bedb9e2f2bf714cfd86be6deaa7050f998843a3a0e595ec3eb0d25c743"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fcc9b2f5b334fdaf278459dfc0fb86d3a0317ae8ce813a7a3ef8639b44b6e4a"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97c4b5527ea563867ccbee031af93932d9699c6c73f1ea70adcbc935c80379e"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f707729cc35dbd1d672b11037f5464b8a42c1e89772d7fc60648da215fa72fc6"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3846d36c1ca113a7fa078abb5e69a8c3d1c7642baf12267dcd9a0d660cf1bdeb"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-win32.whl", hash = "sha256:aeb49e1436d6558d31c006b385a5071e802be6db257ce36940e66cefce92aa72"}, + {file = "SQLAlchemy-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9efb27e899cf7d43cf42c0852ef772a8b568c39dc7b55768a5a80c67bb64dfc2"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e23205506a437476dce8193357ce47254cce7c94018b1b4856476ad2e74f1ae"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c35b33a0828b4f5ac6e76a1b6a54b27d693599c93ea7a4c8e53ff52796378f"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca2ce5f3125cb6e043c90dd901446b74878f35eb6660e0e58d7ef02832f7d332"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:eeec87ebe90018bc871b84b03e4bff5dbdc722e28b8f5a6e9a94486eb0cb4902"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:476bd377f430b1871f058332696ef61c42dfa8ad242ebb8bcf212c3d4127ea8a"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:101df3fa8f207ade1124d7729f6c9eab28a2560baa31b3e131e76a599d884b33"}, + {file = "SQLAlchemy-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:05b81afdc25d1ce43cb59647c9992559dc7487b1670ccab0426fc8b8f859e933"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7b07b83789997cf83ce9a5e7156a2b9a6cb54a4137add8ad95eff32f6746279b"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e49e9aefffe9c598a6ccf8d2dbb4556f4d93d0ae346b9d199b3712d24af0ce75"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a4e134eac68354cce40b35ccdbc91ff67ce0c791ea4aa81e021f2ee14bfefb"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db3e4db26c1a771d7b23a1031eaf351cfcaaa96d463ae900bb56c6a6f0585fbf"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:54b24a20cca275ada37ba40aa87dd257fda6e7da7f448d3282b6124d940f64d5"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:664ec164bc01ab66dfd19062ca7982a9ea12274596e17732908eb78621adc147"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-win32.whl", hash = "sha256:6dd8405bd1ffcbf11fda0e6b172e7e90044610de16325295efe92367551f666d"}, + {file = "SQLAlchemy-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5bc451ee18776dcb6b2ac8c154db0536f75a2535f5da055179734f5e7f2e7b72"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:655e93fabd11bf53e6af44cee152b608d49ece4b4d9cc29328dd476faaa47c0c"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a5e1826a1ebbbbc26285a0304d7cafff4ec63cdae83fde89d5f2ec67f4444a44"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8770318683c8e08976633cec2c9711eb4279553ecbad1ca97f82c5b9174e0e76"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08c9169692722df8a2ef6c6ff1055e11563c990e9c74df9af62139a0c6397b8c"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c34c6b7975cb9e4848d4366d54a634bbced7b491a36029642c7e738a44b595a3"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31d019c60f4817b24c484d3110c7754cd2b8f7070057eddef5822994bf16da5a"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-win32.whl", hash = "sha256:c681d0f59c8ed12fd3f68d08d423354b1cc501220ddabc7a20b9ca8ed52b8f70"}, + {file = "SQLAlchemy-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:619784c399f5c4240b002e4dba30cfba15696274614da77846b69b2c9a74066b"}, + {file = "SQLAlchemy-2.0.1-py3-none-any.whl", hash = "sha256:f44c37e03cb941dd0db371a9f391cfb586c9966f436bf18b5492ee26f5ac6a5b"}, + {file = "SQLAlchemy-2.0.1.tar.gz", hash = "sha256:70d38432d75f6c95973f9713b30881e40a4e8d8ccfe8bbeb55466d8c737acc79"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +typing-extensions = ">=4.2.0" [package.extras] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql", "pymysql (<1)"] +pymysql = ["pymysql"] sqlcipher = ["sqlcipher3-binary"] -[[package]] -name = "stevedore" -version = "4.1.1" -description = "Manage dynamic plugins for Python applications" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "stevedore-4.1.1-py3-none-any.whl", hash = "sha256:aa6436565c069b2946fe4ebff07f5041e0c8bf18c7376dd29edf80cf7d524e4e"}, - {file = "stevedore-4.1.1.tar.gz", hash = "sha256:7f8aeb6e3f90f96832c301bff21a7eb5eefbe894c88c506483d355565d88cc1a"}, -] - -[package.dependencies] -pbr = ">=2.0.0,<2.1.0 || >2.1.0" - [[package]] name = "toml" version = "0.10.2" @@ -2285,14 +2157,14 @@ test = ["mypy", "pytest", "typing-extensions"] [[package]] name = "types-requests" -version = "2.28.11.7" +version = "2.28.11.8" description = "Typing stubs for requests" category = "dev" optional = false python-versions = "*" files = [ - {file = "types-requests-2.28.11.7.tar.gz", hash = "sha256:0ae38633734990d019b80f5463dfa164ebd3581998ac8435f526da6fe4d598c3"}, - {file = "types_requests-2.28.11.7-py3-none-any.whl", hash = "sha256:b6a2fca8109f4fdba33052f11ed86102bddb2338519e1827387137fefc66a98b"}, + {file = "types-requests-2.28.11.8.tar.gz", hash = "sha256:e67424525f84adfbeab7268a159d3c633862dafae15c5b19547ce1b55954f0a3"}, + {file = "types_requests-2.28.11.8-py3-none-any.whl", hash = "sha256:61960554baca0008ae7e2db2bd3b322ca9a144d3e80ce270f5fb640817e40994"}, ] [package.dependencies] @@ -2314,7 +2186,7 @@ files = [ name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2324,14 +2196,14 @@ files = [ [[package]] name = "urllib3" -version = "1.26.13" +version = "1.26.14" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" files = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, + {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, + {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, ] [package.extras] @@ -2607,21 +2479,21 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.11.0" +version = "3.12.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, + {file = "zipp-3.12.0-py3-none-any.whl", hash = "sha256:9eb0a4c5feab9b08871db0d672745b53450d7f26992fd1e4653aa43345e97b86"}, + {file = "zipp-3.12.0.tar.gz", hash = "sha256:73efd63936398aac78fd92b6f4865190119d6c91b531532e798977ea8dd402eb"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-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)"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "d44b7e2de3c183f975542c8e100bfba1022ca16e0e7e0efa8571d39811ae6554" +content-hash = "140a2e3a149694636ffa8d6b03d8be51037ea941ed66d23cbac49ec3d52fb9ea" diff --git a/pyproject.toml b/pyproject.toml index 1e12db3..af04f62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,17 +39,13 @@ typeguard = "^2.12.1" xdoctest = {extras = ["colors"], version = "^0.15.5"} pre-commit = "^2.13.0" black = "^22.12.0" -flake8 = "^6.0.0" -flake8-bandit = "^4.1.1" -flake8-bugbear = "^21.11.29" -flake8-docstrings = "^1.6.0" -flake8-rst-docstrings = "^0.2.3" pep8-naming = "^0.12.0" darglint = "^1.8.0" reorder-python-imports = "^2.6.0" pre-commit-hooks = "^4.0.1" Pygments = "^2.9.0" types-requests = "^2.25.11" +ruff = "^0.0.240" [tool.poetry.group.docs.dependencies] markdown-include = "*" @@ -78,6 +74,52 @@ source = ["phylm"] show_missing = true fail_under = 90 +[tool.ruff] +ignore = [ + 'B019', + 'D203', + 'D204', + 'D213', + 'D215', + 'D400', + 'D404', + 'D406', + 'D407', + 'D408', + 'D409', + 'D413', + 'E501', + 'S113' +] +line-length = 80 +select = [ + 'B', + 'B9', + 'C', + 'D', + 'E', + 'F', + 'N', + 'S', + 'W', +] + +[tool.ruff.mccabe] +max-complexity = 10 + +[tool.ruff.pydocstyle] +convention = 'google' + +[tool.ruff.per-file-ignores] +"__init__.py" = ['F401'] +"tests/*" = [ + 'S101', + 'D212', + 'D415', + 'D205', + 'D104', +] + [tool.mypy] strict = true pretty = true diff --git a/src/phylm/clients/tmdb.py b/src/phylm/clients/tmdb.py index 9035336..7f38044 100644 --- a/src/phylm/clients/tmdb.py +++ b/src/phylm/clients/tmdb.py @@ -1,24 +1,33 @@ """Client to interact with The Movie DB (TMDB).""" +import os from typing import Any from typing import Dict from typing import List +from typing import Optional +from typing import Union +from aiohttp import ClientSession from requests import Session +from phylm.errors import NoTMDbApiKeyError + class TmdbClient: """Class to abstract to the Tmdb API.""" - def __init__(self, api_key: str) -> None: + def __init__( + self, api_key: str, async_session: Optional[ClientSession] = None + ) -> None: """Initialize the client. Args: api_key: an api_key for authentication + async_session: an optional instance of `aiohttp.ClientSession` """ - super().__init__() self.session = Session() + self.async_session = async_session or ClientSession() self.api_key = api_key - self._base = "https://api.themoviedb.org/3" + self._base_url = "https://api.themoviedb.org/3" def search_movies(self, query: str, region: str = "us") -> List[Dict[str, Any]]: """Search for movies. @@ -37,13 +46,67 @@ def search_movies(self, query: str, region: str = "us") -> List[Dict[str, Any]]: "include_adult": "false", "region": region.upper(), } - res = self.session.get(f"{self._base}/search/movie", params=payload) + res = self.session.get(f"{self._base_url}/search/movie", params=payload) res.raise_for_status() results: List[Dict[str, Any]] = res.json()["results"] return results + async def search_movies_async( + self, query: str, region: str = "us", year: Optional[int] = None + ) -> List[Dict[str, Any]]: + """Search for movies async. + + Args: + query: the search query + region: the region for the query, affects the release date value + year: the year of the movie + + Returns: + List[Dict[str, Any]]: the search results + """ + params: Dict[str, Union[str, int]] = { + "api_key": self.api_key, + "language": "en-US", + "query": query, + "include_adult": "false", + "region": region.upper(), + "page": 1, + } + + if year: + params["year"] = year + + async with self.async_session.get( + f"{self._base_url}/search/movie", params=params + ) as resp: + results = await resp.json() + + movies: List[Dict[str, Any]] = results["results"] + return movies + + async def get_movie(self, movie_id: str) -> Dict[str, Any]: + """Return a movie by id. + + Args: + movie_id: the tmdb id of the movie + + Returns: + Dict[str, Any]: a dictionary of the movie data + """ + params = { + "api_key": self.api_key, + "language": "en-US", + } + + async with self.async_session.get( + f"{self._base_url}/movie/{movie_id}", params=params + ) as resp: + movie: Dict[str, Any] = await resp.json() + + return movie + def get_streaming_providers( self, movie_id: str, regions: List[str] ) -> Dict[str, Any]: @@ -57,8 +120,9 @@ def get_streaming_providers( Dict[str, Any]: a dictionary of streaming providers, keyed by region name """ payload = {"api_key": self.api_key} + res = self.session.get( - f"{self._base}/movie/{movie_id}/watch/providers", params=payload + f"{self._base_url}/movie/{movie_id}/watch/providers", params=payload ) res.raise_for_status() @@ -66,3 +130,27 @@ def get_streaming_providers( results: Dict[str, Any] = res.json()["results"] return {key: results.get(key.upper(), {}) for key in regions} + + +def initialize_tmdb_client( + api_key: Optional[str] = None, + async_session: Optional[ClientSession] = None, +) -> TmdbClient: + """Initialize and return a TmdbClient. + + Args: + api_key: an optional api_key to take precedence over an env var key + async_session: an optional aiohttp ClienSession + + Raises: + NoTMDbApiKeyError: when no api_key has been provided + + Returns: + TmdbClient: an authorized Tmdb client + """ + tmdb_api_key = api_key or os.environ.get("TMDB_API_KEY") + + if not tmdb_api_key: + raise NoTMDbApiKeyError("An `api_key` must be provided to use this service") + + return TmdbClient(api_key=tmdb_api_key, async_session=async_session) diff --git a/src/phylm/phylm.py b/src/phylm/phylm.py index d3dd25a..909cb61 100644 --- a/src/phylm/phylm.py +++ b/src/phylm/phylm.py @@ -10,6 +10,7 @@ from phylm.sources import Imdb from phylm.sources import Mtc from phylm.sources import Rt +from phylm.sources import Tmdb class Phylm: @@ -20,6 +21,7 @@ def __init__( title: str, imdb_id: Optional[str] = None, year: Optional[int] = None, + tmdb_id: Optional[str] = None, ) -> None: """Initialize a `Phylm` object. @@ -27,13 +29,16 @@ def __init__( title: the title of the movie imdb_id: an optional `IMDb` ID of the movie year: an optional year of the movie + tmdb_id: an optional `TMDB` ID of the movie """ self.title = title self.imdb_id = imdb_id self.year = year + self.tmdb_id = tmdb_id self._imdb: Optional[Imdb] = None self._mtc: Optional[Mtc] = None self._rt: Optional[Rt] = None + self._tmdb: Optional[Tmdb] = None def __repr__(self) -> str: """Return the string representation. @@ -96,11 +101,27 @@ def rt(self) -> Rt: return self._rt + @property + def tmdb(self) -> Tmdb: + """Return the TMDB data. + + Returns: + The TMDB data + + Raises: + SourceNotLoadedError: if the source is not loaded + """ + if self._tmdb is None: + raise SourceNotLoadedError("The data for TMDB has not yet been loaded") + + return self._tmdb + async def load_source( self, source: str, imdb_id: Optional[str] = None, session: Optional[ClientSession] = None, + tmdb_id: Optional[str] = None, ) -> "Phylm": """Asynchronously load the film data for a source. @@ -110,6 +131,8 @@ async def load_source( instead of a basic search on the title session: an optional instance of `aiohttp.ClientSession` in which to run the request + tmdb_id: an optional `TMDB` id which will be used to load the TMDB data + instead of a basic search on the title Returns: the instance @@ -140,6 +163,17 @@ async def load_source( await self._rt.load_source(session=session) return self + if source == "tmdb": + if not self._tmdb: + movie_id = tmdb_id or self.tmdb_id + self._tmdb = Tmdb( + raw_title=self.title, + movie_id=movie_id, + raw_year=self.year, + ) + await self._tmdb.load_source(session=session) + return self + raise UnrecognizedSourceError(f"{source} is not a recognized source") async def load_sources( diff --git a/src/phylm/sources/__init__.py b/src/phylm/sources/__init__.py index 8d8997d..dab7c48 100644 --- a/src/phylm/sources/__init__.py +++ b/src/phylm/sources/__init__.py @@ -2,3 +2,4 @@ from .imdb import Imdb from .mtc import Mtc from .rt import Rt +from .tmdb import Tmdb diff --git a/src/phylm/sources/tmdb.py b/src/phylm/sources/tmdb.py new file mode 100644 index 0000000..3d4cab8 --- /dev/null +++ b/src/phylm/sources/tmdb.py @@ -0,0 +1,166 @@ +"""Module to define Tmdb class.""" +from datetime import datetime +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +from aiohttp import ClientSession + +from phylm.tools import initialize_tmdb_client + + +class Tmdb: + """Class to abstract a TMDB result.""" + + def __init__( + self, + raw_title: Optional[str] = None, + movie_id: Optional[str] = None, + raw_year: Optional[int] = None, + api_key: Optional[str] = None, + session: Optional[ClientSession] = None, + ) -> None: + """Initialize the object. + + Note that at least one of `raw_title` or `movie_id` must be given to be used as + a search term. `movie_id` is preferred over `raw_title`. + + Args: + raw_title: the title of the movie. Note that TMDB doesn't support fuzzy + search. + movie_id: the TMDB id of the movie. + raw_year: an optional year for improved matching if only title is given. + api_key: a TMDB api key. Must be supplied here or as an env var + session: a `aiohttp.ClientSession` instance. One will be created if not + supplied. + + Raises: + ValueError: if neither `raw_title` nor `movie_id` is supplied. + """ + if not (raw_title or movie_id): + raise ValueError("At least one of raw_title and movie_id must be given") + + self.raw_title = raw_title + self.movie_id = movie_id + self.raw_year = raw_year + self.low_confidence = False + self.session = session + self._api_key = api_key + self._tmdb_data: Dict[str, Any] = {} + + self._client = initialize_tmdb_client(api_key, async_session=session) + + async def _get_tmdb_data(self) -> Dict[str, Any]: + if self.movie_id: + return await self._client.get_movie(self.movie_id) + + results = await self._client.search_movies_async( + self.raw_title, year=self.raw_year # type: ignore + ) + + if not results: + return {} + + return await self._client.get_movie(results[0]["id"]) + + async def load_source(self, session: Optional[ClientSession] = None) -> None: + """Asynchronously load the data for from the source. + + Args: + session: an optional `aiohttp.ClientSession` instance + """ + if session: + self._client = initialize_tmdb_client(self._api_key, async_session=session) + + self._tmdb_data = await self._get_tmdb_data() + + @property + def title(self) -> Optional[str]: + """Return the TMDB title. + + Returns: + the title of the movie + """ + return self._tmdb_data.get("title") + + @property + def id(self) -> Optional[str]: + """Return the TMDB id. + + Returns: + the id of the movie + """ + return str(self._tmdb_data.get("id")) + + @property + def imdb_id(self) -> Optional[str]: + """Return the IMDb id. + + Returns: + the IMDb id of the movie + """ + return str(self._tmdb_data.get("imdb_id")) + + def genres(self, limit: int = 3) -> List[str]: + """Return the genres. + + Args: + limit (int): an optional number of genres to return + + Returns: + a list of the movie's genres + """ + return [g["name"] for g in self._tmdb_data.get("genres", [])[:limit]] + + @property + def runtime(self) -> Optional[int]: + """Return the runtime. + + Returns: + the runtime of the movie + """ + runtime = self._tmdb_data.get("runtime") + + if not runtime: + return None + + return int(runtime) + + @property + def year(self) -> Optional[int]: + """Return the movie's year. + + Returns: + the year the movie was made + """ + release_date = self._tmdb_data.get("release_date") + + if not release_date: + return None + + date = datetime.strptime(release_date, "%Y-%m-%d") + return int(date.year) + + @property + def rating(self) -> Optional[float]: + """Return the TMDB rating. + + Returns: + the rating of the movie + """ + rating = self._tmdb_data.get("vote_average") + + if not rating: + return None + + return float(rating) + + @property + def plot(self) -> Optional[str]: + """Return the plot. + + Returns: + the plot of the movie + """ + return self._tmdb_data.get("overview") diff --git a/src/phylm/tools.py b/src/phylm/tools.py index 0993f03..fe0dddf 100644 --- a/src/phylm/tools.py +++ b/src/phylm/tools.py @@ -1,5 +1,4 @@ """Module to hold `phylm` tools.""" -import os from typing import Any from typing import Dict from typing import List @@ -8,8 +7,7 @@ from imdb.Movie import Movie -from phylm.clients.tmdb import TmdbClient -from phylm.errors import NoTMDbApiKeyError +from phylm.clients.tmdb import initialize_tmdb_client from phylm.sources.imdb import ia @@ -36,26 +34,6 @@ def search_movies(query: str) -> List[Dict[str, Union[str, int]]]: ] -def _initialize_tmdb_client(api_key: Optional[str] = None) -> TmdbClient: - """Initialize and return a TmdbClient. - - Args: - api_key: an optional api_key to take precedence over an env var key - - Raises: - NoTMDbApiKeyError: when no api_key has been provided - - Returns: - TmdbClient: an authorized Tmdb client - """ - tmdb_api_key = api_key or os.environ.get("TMDB_API_KEY") - - if not tmdb_api_key: - raise NoTMDbApiKeyError("An `api_key` must be provided to use this service") - - return TmdbClient(api_key=tmdb_api_key) - - def search_tmdb_movies( query: str, api_key: Optional[str] = None, region: str = "us" ) -> List[Dict[str, Any]]: @@ -72,7 +50,7 @@ def search_tmdb_movies( Returns: List[Dict[str, Any]]: the search results """ - client = _initialize_tmdb_client(api_key=api_key) + client = initialize_tmdb_client(api_key=api_key) return client.search_movies(query=query, region=region) @@ -93,6 +71,6 @@ def get_streaming_providers( Returns: Dict[str, Any]: a dictionary of streaming providers, keyed by region name """ - client = _initialize_tmdb_client(api_key=api_key) + client = initialize_tmdb_client(api_key=api_key) return client.get_streaming_providers(movie_id=tmdb_movie_id, regions=regions) diff --git a/tests/conftest.py b/tests/conftest.py index add0d4a..b7a153e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,9 @@ """Module to hold fixtures etc. for pytest.""" +from typing import AsyncGenerator + import pytest import vcr +from aiohttp import ClientSession from tests.utils.vcr_serializers import ResponseBodyCompressor my_vcr = vcr.VCR() @@ -13,4 +16,12 @@ def my_vcr_fixture() -> vcr.VCR: return my_vcr +@pytest.fixture(name="async_session") +async def async_session() -> AsyncGenerator[ClientSession, None]: + """Return a client session and close the session after tests.""" + session = ClientSession() + yield session + await session.close() + + FIXTURES_DIR = "tests/fixtures/vcr_cassettes" diff --git a/tests/fixtures/vcr_cassettes/clients/tmdb/get_invalid_id.yaml b/tests/fixtures/vcr_cassettes/clients/tmdb/get_invalid_id.yaml new file mode 100644 index 0000000..2098713 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/clients/tmdb/get_invalid_id.yaml @@ -0,0 +1,52 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/movie/xxxxx?language=en-US + response: + body: + string: + '{"success":false,"status_code":34,"status_message":"The resource you + requested could not be found."}' + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=28800 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Thu, 02 Feb 2023 17:24:21 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 bf7ddccbb38083d1ce515f8046ed53b4.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - -TRoQuyi5kmZUaMoePrnxKkwZYul6YY3oehKAEc-GBXc8DAaqeusMw== + X-Amz-Cf-Pop: + - MAN50-C2 + X-Cache: + - Error from cloudfront + X-Memc: + - MISS + X-Memc-Age: + - "0" + X-Memc-Expires: + - "17215" + X-Memc-Key: + - 771e0d4d040dffcbc522edd2e47ce3ca560b847a + status: + code: 404 + message: Not Found + url: https://api.themoviedb.org/3/movie/xxxxx?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US +version: 1 diff --git a/tests/fixtures/vcr_cassettes/clients/tmdb/get_the_matrix.yaml b/tests/fixtures/vcr_cassettes/clients/tmdb/get_the_matrix.yaml new file mode 100644 index 0000000..33df56c --- /dev/null +++ b/tests/fixtures/vcr_cassettes/clients/tmdb/get_the_matrix.yaml @@ -0,0 +1,64 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/movie/603?language=en-US + response: + body: + string: + '{"adult":false,"backdrop_path":"/waCRuAW5ocONRehP556vPexVXA9.jpg","belongs_to_collection":{"id":2344,"name":"The + Matrix Collection","poster_path":"/bV9qTVHTVf0gkW0j7p7M0ILD4pG.jpg","backdrop_path":"/bRm2DEgUiYciDw3myHuYFInD7la.jpg"},"budget":63000000,"genres":[{"id":28,"name":"Action"},{"id":878,"name":"Science + Fiction"}],"homepage":"http://www.warnerbros.com/matrix","id":603,"imdb_id":"tt0133093","original_language":"en","original_title":"The + Matrix","overview":"Set in the 22nd century, The Matrix tells the story of + a computer hacker who joins a group of underground insurgents fighting the + vast and powerful computers who now rule the earth.","popularity":67.747,"poster_path":"/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg","production_companies":[{"id":79,"logo_path":"/tpFpsqbleCzEE2p5EgvUq6ozfCA.png","name":"Village + Roadshow Pictures","origin_country":"US"},{"id":174,"logo_path":"/IuAlhI9eVC9Z8UQWOIDdWRKSEJ.png","name":"Warner + Bros. Pictures","origin_country":"US"},{"id":372,"logo_path":null,"name":"Groucho + II Film Partnership","origin_country":""},{"id":1885,"logo_path":"/tXMFoE8AtNdnFzWOW0aCLwl7xxS.png","name":"Silver + Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"US","name":"United + States of America"}],"release_date":"1999-03-30","revenue":463517383,"runtime":136,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"Welcome + to the Real World.","title":"The Matrix","video":false,"vote_average":8.196,"vote_count":22738}' + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=28800 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Thu, 02 Feb 2023 17:22:30 GMT + Etag: + - W/"e7efd30af6b1132c27110b0d16be8c1e" + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 d9be5d2eee0a97e8373d3f5d26e3e014.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - lY3p478Nnv5jQF4bxWLd_mFs4w5vot2dPUcVgXmLASV8umwfRxImag== + X-Amz-Cf-Pop: + - MAN50-C2 + X-Cache: + - Miss from cloudfront + X-Memc: + - HIT + X-Memc-Age: + - "14550" + X-Memc-Expires: + - "2110" + X-Memc-Key: + - a52b63b61eb6422a5676648d45b03c1437ec1036 + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/movie/603?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US +version: 1 diff --git a/tests/fixtures/vcr_cassettes/clients/tmdb/search_no_results.yaml b/tests/fixtures/vcr_cassettes/clients/tmdb/search_no_results.yaml new file mode 100644 index 0000000..98c0b84 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/clients/tmdb/search_no_results.yaml @@ -0,0 +1,42 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/search/movie?include_adult=false&language=en-US&page=1&query=askdjhashdaskdasljd®ion=US + response: + body: + string: '{"page":1,"results":[],"total_pages":0,"total_results":0}' + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=600 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 12:32:53 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 1675c96d31e7d6eacf2a9135ddb13210.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - EYUEi2iPEUGJrFUtJnoy6vBb4TjZzSh4xst7rUBPjCSdOQ7zvIvUFw== + X-Amz-Cf-Pop: + - LHR50-P2 + X-Cache: + - Miss from cloudfront + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=askdjhashdaskdasljd&include_adult=false®ion=US&page=1 +version: 1 diff --git a/tests/fixtures/vcr_cassettes/clients/tmdb/the_matrix_async.yaml b/tests/fixtures/vcr_cassettes/clients/tmdb/the_matrix_async.yaml new file mode 100644 index 0000000..97f82b3 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/clients/tmdb/the_matrix_async.yaml @@ -0,0 +1,162 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/search/movie?include_adult=false&language=en-US&page=1&query=The+Matrix®ion=US + response: + body: + string: + "{\"page\":1,\"results\":[{\"adult\":false,\"backdrop_path\":\"/eNI7PtK6DEYgZmHWP9gQNuff8pv.jpg\",\"genre_ids\":[878,28,12],\"id\":624860,\"original_language\":\"en\",\"original_title\":\"The + Matrix Resurrections\",\"overview\":\"Plagued by strange memories, Neo's life + takes an unexpected turn when he finds himself back inside the Matrix.\",\"popularity\":104.234,\"poster_path\":\"/8c4a8kE7PizaGQQnditMmI1xbRp.jpg\",\"release_date\":\"2021-12-22\",\"title\":\"The + Matrix Resurrections\",\"video\":false,\"vote_average\":6.6,\"vote_count\":4516},{\"adult\":false,\"backdrop_path\":\"/waCRuAW5ocONRehP556vPexVXA9.jpg\",\"genre_ids\":[28,878],\"id\":603,\"original_language\":\"en\",\"original_title\":\"The + Matrix\",\"overview\":\"Set in the 22nd century, The Matrix tells the story + of a computer hacker who joins a group of underground insurgents fighting + the vast and powerful computers who now rule the earth.\",\"popularity\":77.213,\"poster_path\":\"/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg\",\"release_date\":\"1999-03-30\",\"title\":\"The + Matrix\",\"video\":false,\"vote_average\":8.2,\"vote_count\":22741},{\"adult\":false,\"backdrop_path\":\"/pxK1iK6anS6erGg4QePmMKbB1E7.jpg\",\"genre_ids\":[12,28,53,878],\"id\":604,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded\",\"overview\":\"Six months after the events depicted in The + Matrix, Neo has proved to be a good omen for the free humans, as more and + more humans are being freed from the matrix and brought to Zion, the one and + only stronghold of the Resistance. Neo himself has discovered his superpowers + including super speed, ability to see the codes of the things inside the matrix + and a certain degree of pre-cognition. But a nasty piece of news hits the + human resistance: 250,000 machine sentinels are digging to Zion and would + reach them in 72 hours. As Zion prepares for the ultimate war, Neo, Morpheus + and Trinity are advised by the Oracle to find the Keymaker who would help + them reach the Source. Meanwhile Neo's recurrent dreams depicting Trinity's + death have got him worried and as if it was not enough, Agent Smith has somehow + escaped deletion, has become more powerful than before and has fixed Neo as + his next target.\",\"popularity\":35.264,\"poster_path\":\"/9TGHDvWrqKBzwDxDodHYXEmOE6J.jpg\",\"release_date\":\"2003-05-15\",\"title\":\"The + Matrix Reloaded\",\"video\":false,\"vote_average\":7,\"vote_count\":9600},{\"adult\":false,\"backdrop_path\":\"/533xAMhhVyjTy8hwMUFEt5TuDfR.jpg\",\"genre_ids\":[12,28,53,878],\"id\":605,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revolutions\",\"overview\":\"The human city of Zion defends itself + against the massive invasion of the machines as Neo fights to end the war + at another front while also opposing the rogue Agent Smith.\",\"popularity\":32.731,\"poster_path\":\"/ynMQ4nVwkoP2gLxXXDgcdltihTD.jpg\",\"release_date\":\"2003-11-05\",\"title\":\"The + Matrix Revolutions\",\"video\":false,\"vote_average\":6.7,\"vote_count\":8631},{\"adult\":false,\"backdrop_path\":\"/lBdXACywnLwKUZmZkZ87djDQBeV.jpg\",\"genre_ids\":[99],\"id\":14543,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revisited\",\"overview\":\"The film goes behind the scenes of the 1999 + sci-fi movie The Matrix.\",\"popularity\":10.63,\"poster_path\":\"/8yxSztoc5sqZiGuKcFuVOh65B6Y.jpg\",\"release_date\":\"2001-11-20\",\"title\":\"The + Matrix Revisited\",\"video\":false,\"vote_average\":6.9,\"vote_count\":169},{\"adult\":false,\"backdrop_path\":\"/5nxDn3mrdmeUHJOZi1Wfqya8Vji.jpg\",\"genre_ids\":[99],\"id\":696109,\"original_language\":\"en\",\"original_title\":\"A + Glitch in the Matrix\",\"overview\":\"Are we in fact living in a simulation? + This is the question postulated, wrestled with, and ultimately argued for + through archival footage, compelling interviews with real people shrouded + in digital avatars, and a collection of cases from some of our most iconoclastic + figures in contemporary culture.\",\"popularity\":7.989,\"poster_path\":\"/bn0BLVadmvzq6MmJ0n97MLxGpNQ.jpg\",\"release_date\":\"2021-02-05\",\"title\":\"A + Glitch in the Matrix\",\"video\":false,\"vote_average\":5.2,\"vote_count\":28},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99,878],\"id\":221495,\"original_language\":\"en\",\"original_title\":\"The + Matrix Recalibrated\",\"overview\":\"The making of Matrix Revolutions, The + (2003) is briefly touched on here in this documentary. Interviews with various + cast and crew members inform us how they were affected by the deaths of Gloria + Foster and Aaliyah, and also delve into the making of the visual effects that + takes up a lot of screen time. Written by Rhyl Donnelly\",\"popularity\":4.1,\"poster_path\":\"/gRni1Q651AZPnLqZczmahiIxG0s.jpg\",\"release_date\":\"2004-04-06\",\"title\":\"The + Matrix Recalibrated\",\"video\":true,\"vote_average\":6.7,\"vote_count\":37},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":591955,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded Revisited\",\"overview\":\"The making of The Matrix Reloaded: + \ Go to the middle movie's furthest reaches via five documentary paths revealing + 21 featurettes.\",\"popularity\":3.031,\"poster_path\":\"/gb7C4oRzYWXWCuZMR1cwtHa53Pz.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Matrix Reloaded Revisited\",\"video\":false,\"vote_average\":7.4,\"vote_count\":16},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":684731,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded: Pre-Load\",\"overview\":\"This making-of piece offers the + standard mix of movie snippets, behind the scenes materials, and interviews + from cast and crew on the making of the film.\",\"popularity\":3.03,\"poster_path\":null,\"release_date\":\"2003-10-14\",\"title\":\"The + Matrix Reloaded: Pre-Load\",\"video\":false,\"vote_average\":6.3,\"vote_count\":15},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":684431,\"original_language\":\"en\",\"original_title\":\"Making + 'The Matrix'\",\"overview\":\"A promotional making-of documentary for the + film Matrix, The (1999) that devotes its time to explaining the digital and + practical effects contained in the film. This is very interesting, seeing + as how they're giving away the cinematic secrets that they created solely + for the this movie, that have now been spoofed and referenced in countless + other films.\",\"popularity\":1.294,\"poster_path\":\"/m2yIfRJYwpCgHON1Ig1pNKMWTxp.jpg\",\"release_date\":\"1999-09-21\",\"title\":\"Making + 'The Matrix'\",\"video\":false,\"vote_average\":7.8,\"vote_count\":12},{\"adult\":false,\"backdrop_path\":\"/b3toMttpZ0cFeYMgLeL042uJJpi.jpg\",\"genre_ids\":[99,28,878],\"id\":684735,\"original_language\":\"en\",\"original_title\":\"Making + 'Enter the Matrix'\",\"overview\":\"A look at Enter the Matrix: The game's + story picks up just before The Matrix Reloaded and runs parallel to that of + the film. Bend the rules of the Matrix with martial arts, pilot the fastest + hovercraft in the fleet, or just fight with lots of guns.\",\"popularity\":1.683,\"poster_path\":\"/zQbJkJkzpB8EthEJ3r2Y6A2kkP6.jpg\",\"release_date\":\"2003-10-14\",\"title\":\"Making + 'Enter the Matrix'\",\"video\":false,\"vote_average\":5.8,\"vote_count\":6},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":503880,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revolutions Revisited\",\"overview\":\"The making of The Matrix Revolutions: + \ The cataclysmic final confrontation chronicled through six documentary pods + revealing 28 featurettes\",\"popularity\":2.179,\"poster_path\":\"/61ASnmqvzpuz9VEfFElo3e2nIft.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Matrix Revolutions Revisited\",\"video\":false,\"vote_average\":7.8,\"vote_count\":13},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99,28,878],\"id\":684428,\"original_language\":\"en\",\"original_title\":\"The + Matrix: What Is Bullet-Time?\",\"overview\":\"Special Effects wizard John + Gaeta demonstrates how the \\\"Bullet-Time\\\" effects were created for the + film Matrix, The (1999).\",\"popularity\":2.171,\"poster_path\":\"/hSTervHaROcTd8Ir3DPfepN80dL.jpg\",\"release_date\":\"1999-09-21\",\"title\":\"The + Matrix: What Is Bullet-Time?\",\"video\":false,\"vote_average\":6.6,\"vote_count\":7},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99,28,878],\"id\":684729,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded: Car Chase\",\"overview\":\"This thirty-minute documentary + follows the Wachowski sisters and the film's crew through production on the + film's famed highway chase sequence. We get interviews with the visual effects + supervisors, the stunt coordinators and even are taken through aspects of + pre-production and planning for the scene. Then the documentary moves into + production, the scariest aspect of which is certainly Carrie Anne-Moss trying + to learn to ride a motorcycle good enough to do so safely without helmet for + the scene. We also watch as the stretch of freeway used in the film is being + built.\",\"popularity\":1.282,\"poster_path\":\"/zkpzfTyF7BjadH1PZKlC6kueWXf.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Matrix Reloaded: Car Chase\",\"video\":false,\"vote_average\":6,\"vote_count\":4},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[],\"id\":344225,\"original_language\":\"en\",\"original_title\":\"Sex + and the Matrix\",\"overview\":\"A spoof created for the 2000 MTV Movie Awards, + combining The Matrix and \\\"Sex in the City\\\".\",\"popularity\":0.943,\"poster_path\":null,\"release_date\":\"2000-06-03\",\"title\":\"Sex + and the Matrix\",\"video\":false,\"vote_average\":6.7,\"vote_count\":3},{\"adult\":false,\"backdrop_path\":\"/htYaLEQ41N2Ls7mhWvgP8JD21E3.jpg\",\"genre_ids\":[99],\"id\":505806,\"original_language\":\"en\",\"original_title\":\"A + Glitch in the Matrix\",\"overview\":\"In 'A Glitch in the Matrix', journalist + David Fuller analyzes and assesses Dr. Jordan B Peterson's recent viral interview + with Cathy Newman, unpacking the deeper political, psychological and archetypal + levels of the clash.\",\"popularity\":0.963,\"poster_path\":\"/7vZPj3lFSc6eJsLk5nzvqacvrGR.jpg\",\"release_date\":\"2018-02-17\",\"title\":\"A + Glitch in the Matrix\",\"video\":false,\"vote_average\":6,\"vote_count\":2},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":274866,\"original_language\":\"en\",\"original_title\":\"The + Roots of the Matrix\",\"overview\":\"Disc 8 of 10 of 'The Matrix: Ultimate + Edition': Probe the philosophical and technological inspirations of The Matrix + Trilogy through two insightful documentaries: - Return to Source: Philosophy + & The Matrix documentary \u2013 Scholars, philosophers and theorists deconstruct + the intellectual underpinnings of the trilogy - The Hard Problem: The Science + Behind the Fiction documentary \u2013 Is the notion of a real Matrix plausible? + An investigation of the technologies that inspire the metaphor of the Matrix.\",\"popularity\":1.094,\"poster_path\":\"/qmOtnpPihV3Wj6ukpiNIgzo4Ax7.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Roots of the Matrix\",\"video\":false,\"vote_average\":7,\"vote_count\":3},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[16],\"id\":919064,\"original_language\":\"en\",\"original_title\":\"Me + and The Matrix\",\"overview\":\"Filmmaker Cressa Maeve Beer recalls her experience + growing up as a fan of the Matrix movies, in celebration of the upcoming release + of The Matrix Resurrections.\",\"popularity\":0.6,\"poster_path\":\"/i9A8PKfzxyp4HxjqoEupKcZfofr.jpg\",\"release_date\":\"2021-12-22\",\"title\":\"Me + and The Matrix\",\"video\":false,\"vote_average\":0,\"vote_count\":0},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[28,99,878],\"id\":684721,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revolutions: Super Big Mini Models\",\"overview\":\"This making-of + piece includes remarks from production designer Owen Paterson, concept artist + George Hull, visual effects supervisor John Gaeta, US unit production manager + L. Dean Jones Jr., US model unit special effects coordinator Geoffrey Heron, + Zion unit sequence lead Rodney Iwashina, high speed first assistant camera + Paul Sanchez, US model unit art director Nanci Noblett, conceptual designer + Geofrey Darrow, model shop supervisor Michael Lynch, US model unit producer + David Dranitzke, prop maker foremen Dave Fogler and Fon Davis, and visual + effects supervisor John \\\"DJ\\\" Des Jardin.\",\"popularity\":1.094,\"poster_path\":\"/2US7uuM36El9pnk8WLdGh1XN39r.jpg\",\"release_date\":\"2004-04-06\",\"title\":\"The + Matrix Revolutions: Super Big Mini Models\",\"video\":false,\"vote_average\":6.8,\"vote_count\":2},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":1038494,\"original_language\":\"en\",\"original_title\":\"Panopticon + Destiny \u2013 The Matrix Files\",\"overview\":\"On August 6th 1977, the world\u2019s + first DOCTOR WHO convention was held at a church hall in Battersea, South + London. Organised by The Doctor Who Appreciation Society (DWAS) , Convention + \u201977 was ground breaking \u2013 featuring sta appearances from the (then) + current Doctor Tom baker with his latest companion Louise Jameson (Leela) + and former Doctor Jon Pertwee. 40 years later, stars, fans and organisers + returned to the church hall to reminisce about that special day! In this + second volume celebrating the anniversary, we feature a convention panel with + organiser Keith Barnfather, contributor Kevin Davies and attendee Andrew Beech, + held at another DWAS event The Capitol II on the 7th May 2017.\",\"popularity\":0.6,\"poster_path\":\"/r1fOhVtFRMqFkG8I6MaK6eDvX8r.jpg\",\"release_date\":\"2021-05-07\",\"title\":\"Panopticon + Destiny \u2013 The Matrix Files\",\"video\":false,\"vote_average\":0,\"vote_count\":0}],\"total_pages\":3,\"total_results\":42}" + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=600 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 12:29:54 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 a5dd833f04c432c7ca32c82296794828.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - A1UCU230vPa-SumvZrHTT2uYHznUxme9T3H-IClwhXpWXeXs-DHLkw== + X-Amz-Cf-Pop: + - LHR61-P7 + X-Cache: + - Miss from cloudfront + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=The+Matrix&include_adult=false®ion=US&page=1 +version: 1 diff --git a/tests/fixtures/vcr_cassettes/clients/tmdb/the_matrix_year_async.yaml b/tests/fixtures/vcr_cassettes/clients/tmdb/the_matrix_year_async.yaml new file mode 100644 index 0000000..da1a4d6 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/clients/tmdb/the_matrix_year_async.yaml @@ -0,0 +1,59 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/search/movie?include_adult=false&language=en-US&page=1&query=The+Matrix®ion=US&year=1999 + response: + body: + string: + '{"page":1,"results":[{"adult":false,"backdrop_path":"/waCRuAW5ocONRehP556vPexVXA9.jpg","genre_ids":[28,878],"id":603,"original_language":"en","original_title":"The + Matrix","overview":"Set in the 22nd century, The Matrix tells the story of + a computer hacker who joins a group of underground insurgents fighting the + vast and powerful computers who now rule the earth.","popularity":77.213,"poster_path":"/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg","release_date":"1999-03-30","title":"The + Matrix","video":false,"vote_average":8.2,"vote_count":22741},{"adult":false,"backdrop_path":null,"genre_ids":[99],"id":684431,"original_language":"en","original_title":"Making + ''The Matrix''","overview":"A promotional making-of documentary for the film + Matrix, The (1999) that devotes its time to explaining the digital and practical + effects contained in the film. This is very interesting, seeing as how they''re + giving away the cinematic secrets that they created solely for the this movie, + that have now been spoofed and referenced in countless other films.","popularity":1.294,"poster_path":"/m2yIfRJYwpCgHON1Ig1pNKMWTxp.jpg","release_date":"1999-09-21","title":"Making + ''The Matrix''","video":false,"vote_average":7.8,"vote_count":12},{"adult":false,"backdrop_path":null,"genre_ids":[99,28,878],"id":684428,"original_language":"en","original_title":"The + Matrix: What Is Bullet-Time?","overview":"Special Effects wizard John Gaeta + demonstrates how the \"Bullet-Time\" effects were created for the film Matrix, + The (1999).","popularity":2.171,"poster_path":"/hSTervHaROcTd8Ir3DPfepN80dL.jpg","release_date":"1999-09-21","title":"The + Matrix: What Is Bullet-Time?","video":false,"vote_average":6.6,"vote_count":7}],"total_pages":1,"total_results":3}' + headers: + Age: + - "28" + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=600 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 12:29:26 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 85c7eb00d1c619654f7c50f01330c9a0.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - hRSoRRNAP30ezpEZ1hCfBBYTKoDjmMZcgLKJD2_jLot4qGG2m1410w== + X-Amz-Cf-Pop: + - LHR61-P7 + X-Cache: + - Hit from cloudfront + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=The+Matrix&include_adult=false®ion=US&page=1&year=1999 +version: 1 diff --git a/tests/fixtures/vcr_cassettes/tmdb/no_results.yaml b/tests/fixtures/vcr_cassettes/tmdb/no_results.yaml new file mode 100644 index 0000000..ddea9d3 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/tmdb/no_results.yaml @@ -0,0 +1,42 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=asldkjnkasnxlajsnxkasjxnas&include_adult=false®ion=US&page=1 + response: + body: + string: '{"page":1,"results":[],"total_pages":0,"total_results":0}' + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=600 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 11:38:26 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 987140ac9e21c2b752b3eb802ce4a7a0.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - GzPcrLjcz60M4_aViEd4CPOJ7MNBHLp4h2VCQlv66-coHuVatd1EJg== + X-Amz-Cf-Pop: + - LHR50-P2 + X-Cache: + - Miss from cloudfront + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=asldkjnkasnxlajsnxkasjxnas&include_adult=false®ion=US&page=1 +version: 1 diff --git a/tests/fixtures/vcr_cassettes/tmdb/the_matrix.yaml b/tests/fixtures/vcr_cassettes/tmdb/the_matrix.yaml new file mode 100644 index 0000000..5690104 --- /dev/null +++ b/tests/fixtures/vcr_cassettes/tmdb/the_matrix.yaml @@ -0,0 +1,226 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=The+Matrix&include_adult=false®ion=US&page=1 + response: + body: + string: + "{\"page\":1,\"results\":[{\"adult\":false,\"backdrop_path\":\"/waCRuAW5ocONRehP556vPexVXA9.jpg\",\"genre_ids\":[28,878],\"id\":603,\"original_language\":\"en\",\"original_title\":\"The + Matrix\",\"overview\":\"Set in the 22nd century, The Matrix tells the story + of a computer hacker who joins a group of underground insurgents fighting + the vast and powerful computers who now rule the earth.\",\"popularity\":77.213,\"poster_path\":\"/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg\",\"release_date\":\"1999-03-30\",\"title\":\"The + Matrix\",\"video\":false,\"vote_average\":8.2,\"vote_count\":22741},{\"adult\":false,\"backdrop_path\":\"/eNI7PtK6DEYgZmHWP9gQNuff8pv.jpg\",\"genre_ids\":[878,28,12],\"id\":624860,\"original_language\":\"en\",\"original_title\":\"The + Matrix Resurrections\",\"overview\":\"Plagued by strange memories, Neo's life + takes an unexpected turn when he finds himself back inside the Matrix.\",\"popularity\":104.234,\"poster_path\":\"/8c4a8kE7PizaGQQnditMmI1xbRp.jpg\",\"release_date\":\"2021-12-22\",\"title\":\"The + Matrix Resurrections\",\"video\":false,\"vote_average\":6.6,\"vote_count\":4516},{\"adult\":false,\"backdrop_path\":\"/pxK1iK6anS6erGg4QePmMKbB1E7.jpg\",\"genre_ids\":[12,28,53,878],\"id\":604,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded\",\"overview\":\"Six months after the events depicted in The + Matrix, Neo has proved to be a good omen for the free humans, as more and + more humans are being freed from the matrix and brought to Zion, the one and + only stronghold of the Resistance. Neo himself has discovered his superpowers + including super speed, ability to see the codes of the things inside the matrix + and a certain degree of pre-cognition. But a nasty piece of news hits the + human resistance: 250,000 machine sentinels are digging to Zion and would + reach them in 72 hours. As Zion prepares for the ultimate war, Neo, Morpheus + and Trinity are advised by the Oracle to find the Keymaker who would help + them reach the Source. Meanwhile Neo's recurrent dreams depicting Trinity's + death have got him worried and as if it was not enough, Agent Smith has somehow + escaped deletion, has become more powerful than before and has fixed Neo as + his next target.\",\"popularity\":35.264,\"poster_path\":\"/9TGHDvWrqKBzwDxDodHYXEmOE6J.jpg\",\"release_date\":\"2003-05-15\",\"title\":\"The + Matrix Reloaded\",\"video\":false,\"vote_average\":7,\"vote_count\":9600},{\"adult\":false,\"backdrop_path\":\"/533xAMhhVyjTy8hwMUFEt5TuDfR.jpg\",\"genre_ids\":[12,28,53,878],\"id\":605,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revolutions\",\"overview\":\"The human city of Zion defends itself + against the massive invasion of the machines as Neo fights to end the war + at another front while also opposing the rogue Agent Smith.\",\"popularity\":32.731,\"poster_path\":\"/ynMQ4nVwkoP2gLxXXDgcdltihTD.jpg\",\"release_date\":\"2003-11-05\",\"title\":\"The + Matrix Revolutions\",\"video\":false,\"vote_average\":6.7,\"vote_count\":8631},{\"adult\":false,\"backdrop_path\":\"/lBdXACywnLwKUZmZkZ87djDQBeV.jpg\",\"genre_ids\":[99],\"id\":14543,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revisited\",\"overview\":\"The film goes behind the scenes of the 1999 + sci-fi movie The Matrix.\",\"popularity\":10.63,\"poster_path\":\"/8yxSztoc5sqZiGuKcFuVOh65B6Y.jpg\",\"release_date\":\"2001-11-20\",\"title\":\"The + Matrix Revisited\",\"video\":false,\"vote_average\":6.9,\"vote_count\":169},{\"adult\":false,\"backdrop_path\":\"/5nxDn3mrdmeUHJOZi1Wfqya8Vji.jpg\",\"genre_ids\":[99],\"id\":696109,\"original_language\":\"en\",\"original_title\":\"A + Glitch in the Matrix\",\"overview\":\"Are we in fact living in a simulation? + This is the question postulated, wrestled with, and ultimately argued for + through archival footage, compelling interviews with real people shrouded + in digital avatars, and a collection of cases from some of our most iconoclastic + figures in contemporary culture.\",\"popularity\":7.989,\"poster_path\":\"/bn0BLVadmvzq6MmJ0n97MLxGpNQ.jpg\",\"release_date\":\"2021-02-05\",\"title\":\"A + Glitch in the Matrix\",\"video\":false,\"vote_average\":5.2,\"vote_count\":28},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99,878],\"id\":221495,\"original_language\":\"en\",\"original_title\":\"The + Matrix Recalibrated\",\"overview\":\"The making of Matrix Revolutions, The + (2003) is briefly touched on here in this documentary. Interviews with various + cast and crew members inform us how they were affected by the deaths of Gloria + Foster and Aaliyah, and also delve into the making of the visual effects that + takes up a lot of screen time. Written by Rhyl Donnelly\",\"popularity\":4.1,\"poster_path\":\"/gRni1Q651AZPnLqZczmahiIxG0s.jpg\",\"release_date\":\"2004-04-06\",\"title\":\"The + Matrix Recalibrated\",\"video\":true,\"vote_average\":6.7,\"vote_count\":37},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":591955,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded Revisited\",\"overview\":\"The making of The Matrix Reloaded: + \ Go to the middle movie's furthest reaches via five documentary paths revealing + 21 featurettes.\",\"popularity\":3.031,\"poster_path\":\"/gb7C4oRzYWXWCuZMR1cwtHa53Pz.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Matrix Reloaded Revisited\",\"video\":false,\"vote_average\":7.4,\"vote_count\":16},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":684731,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded: Pre-Load\",\"overview\":\"This making-of piece offers the + standard mix of movie snippets, behind the scenes materials, and interviews + from cast and crew on the making of the film.\",\"popularity\":3.03,\"poster_path\":null,\"release_date\":\"2003-10-14\",\"title\":\"The + Matrix Reloaded: Pre-Load\",\"video\":false,\"vote_average\":6.3,\"vote_count\":15},{\"adult\":false,\"backdrop_path\":\"/b3toMttpZ0cFeYMgLeL042uJJpi.jpg\",\"genre_ids\":[99,28,878],\"id\":684735,\"original_language\":\"en\",\"original_title\":\"Making + 'Enter the Matrix'\",\"overview\":\"A look at Enter the Matrix: The game's + story picks up just before The Matrix Reloaded and runs parallel to that of + the film. Bend the rules of the Matrix with martial arts, pilot the fastest + hovercraft in the fleet, or just fight with lots of guns.\",\"popularity\":1.683,\"poster_path\":\"/zQbJkJkzpB8EthEJ3r2Y6A2kkP6.jpg\",\"release_date\":\"2003-10-14\",\"title\":\"Making + 'Enter the Matrix'\",\"video\":false,\"vote_average\":5.8,\"vote_count\":6},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":684431,\"original_language\":\"en\",\"original_title\":\"Making + 'The Matrix'\",\"overview\":\"A promotional making-of documentary for the + film Matrix, The (1999) that devotes its time to explaining the digital and + practical effects contained in the film. This is very interesting, seeing + as how they're giving away the cinematic secrets that they created solely + for the this movie, that have now been spoofed and referenced in countless + other films.\",\"popularity\":1.294,\"poster_path\":\"/m2yIfRJYwpCgHON1Ig1pNKMWTxp.jpg\",\"release_date\":\"1999-09-21\",\"title\":\"Making + 'The Matrix'\",\"video\":false,\"vote_average\":7.8,\"vote_count\":12},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":503880,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revolutions Revisited\",\"overview\":\"The making of The Matrix Revolutions: + \ The cataclysmic final confrontation chronicled through six documentary pods + revealing 28 featurettes\",\"popularity\":2.179,\"poster_path\":\"/61ASnmqvzpuz9VEfFElo3e2nIft.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Matrix Revolutions Revisited\",\"video\":false,\"vote_average\":7.8,\"vote_count\":13},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99,28,878],\"id\":684428,\"original_language\":\"en\",\"original_title\":\"The + Matrix: What Is Bullet-Time?\",\"overview\":\"Special Effects wizard John + Gaeta demonstrates how the \\\"Bullet-Time\\\" effects were created for the + film Matrix, The (1999).\",\"popularity\":2.171,\"poster_path\":\"/hSTervHaROcTd8Ir3DPfepN80dL.jpg\",\"release_date\":\"1999-09-21\",\"title\":\"The + Matrix: What Is Bullet-Time?\",\"video\":false,\"vote_average\":6.6,\"vote_count\":7},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99,28,878],\"id\":684729,\"original_language\":\"en\",\"original_title\":\"The + Matrix Reloaded: Car Chase\",\"overview\":\"This thirty-minute documentary + follows the Wachowski sisters and the film's crew through production on the + film's famed highway chase sequence. We get interviews with the visual effects + supervisors, the stunt coordinators and even are taken through aspects of + pre-production and planning for the scene. Then the documentary moves into + production, the scariest aspect of which is certainly Carrie Anne-Moss trying + to learn to ride a motorcycle good enough to do so safely without helmet for + the scene. We also watch as the stretch of freeway used in the film is being + built.\",\"popularity\":1.282,\"poster_path\":\"/zkpzfTyF7BjadH1PZKlC6kueWXf.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Matrix Reloaded: Car Chase\",\"video\":false,\"vote_average\":6,\"vote_count\":4},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[],\"id\":344225,\"original_language\":\"en\",\"original_title\":\"Sex + and the Matrix\",\"overview\":\"A spoof created for the 2000 MTV Movie Awards, + combining The Matrix and \\\"Sex in the City\\\".\",\"popularity\":0.943,\"poster_path\":null,\"release_date\":\"2000-06-03\",\"title\":\"Sex + and the Matrix\",\"video\":false,\"vote_average\":6.7,\"vote_count\":3},{\"adult\":false,\"backdrop_path\":\"/htYaLEQ41N2Ls7mhWvgP8JD21E3.jpg\",\"genre_ids\":[99],\"id\":505806,\"original_language\":\"en\",\"original_title\":\"A + Glitch in the Matrix\",\"overview\":\"In 'A Glitch in the Matrix', journalist + David Fuller analyzes and assesses Dr. Jordan B Peterson's recent viral interview + with Cathy Newman, unpacking the deeper political, psychological and archetypal + levels of the clash.\",\"popularity\":0.963,\"poster_path\":\"/7vZPj3lFSc6eJsLk5nzvqacvrGR.jpg\",\"release_date\":\"2018-02-17\",\"title\":\"A + Glitch in the Matrix\",\"video\":false,\"vote_average\":6,\"vote_count\":2},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":274866,\"original_language\":\"en\",\"original_title\":\"The + Roots of the Matrix\",\"overview\":\"Disc 8 of 10 of 'The Matrix: Ultimate + Edition': Probe the philosophical and technological inspirations of The Matrix + Trilogy through two insightful documentaries: - Return to Source: Philosophy + & The Matrix documentary \u2013 Scholars, philosophers and theorists deconstruct + the intellectual underpinnings of the trilogy - The Hard Problem: The Science + Behind the Fiction documentary \u2013 Is the notion of a real Matrix plausible? + An investigation of the technologies that inspire the metaphor of the Matrix.\",\"popularity\":1.094,\"poster_path\":\"/qmOtnpPihV3Wj6ukpiNIgzo4Ax7.jpg\",\"release_date\":\"2004-12-07\",\"title\":\"The + Roots of the Matrix\",\"video\":false,\"vote_average\":7,\"vote_count\":3},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[16],\"id\":919064,\"original_language\":\"en\",\"original_title\":\"Me + and The Matrix\",\"overview\":\"Filmmaker Cressa Maeve Beer recalls her experience + growing up as a fan of the Matrix movies, in celebration of the upcoming release + of The Matrix Resurrections.\",\"popularity\":0.6,\"poster_path\":\"/i9A8PKfzxyp4HxjqoEupKcZfofr.jpg\",\"release_date\":\"2021-12-22\",\"title\":\"Me + and The Matrix\",\"video\":false,\"vote_average\":0,\"vote_count\":0},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[28,99,878],\"id\":684721,\"original_language\":\"en\",\"original_title\":\"The + Matrix Revolutions: Super Big Mini Models\",\"overview\":\"This making-of + piece includes remarks from production designer Owen Paterson, concept artist + George Hull, visual effects supervisor John Gaeta, US unit production manager + L. Dean Jones Jr., US model unit special effects coordinator Geoffrey Heron, + Zion unit sequence lead Rodney Iwashina, high speed first assistant camera + Paul Sanchez, US model unit art director Nanci Noblett, conceptual designer + Geofrey Darrow, model shop supervisor Michael Lynch, US model unit producer + David Dranitzke, prop maker foremen Dave Fogler and Fon Davis, and visual + effects supervisor John \\\"DJ\\\" Des Jardin.\",\"popularity\":1.094,\"poster_path\":\"/2US7uuM36El9pnk8WLdGh1XN39r.jpg\",\"release_date\":\"2004-04-06\",\"title\":\"The + Matrix Revolutions: Super Big Mini Models\",\"video\":false,\"vote_average\":6.8,\"vote_count\":2},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":1038494,\"original_language\":\"en\",\"original_title\":\"Panopticon + Destiny \u2013 The Matrix Files\",\"overview\":\"On August 6th 1977, the world\u2019s + first DOCTOR WHO convention was held at a church hall in Battersea, South + London. Organised by The Doctor Who Appreciation Society (DWAS) , Convention + \u201977 was ground breaking \u2013 featuring sta appearances from the (then) + current Doctor Tom baker with his latest companion Louise Jameson (Leela) + and former Doctor Jon Pertwee. 40 years later, stars, fans and organisers + returned to the church hall to reminisce about that special day! In this + second volume celebrating the anniversary, we feature a convention panel with + organiser Keith Barnfather, contributor Kevin Davies and attendee Andrew Beech, + held at another DWAS event The Capitol II on the 7th May 2017.\",\"popularity\":0.6,\"poster_path\":\"/r1fOhVtFRMqFkG8I6MaK6eDvX8r.jpg\",\"release_date\":\"2021-05-07\",\"title\":\"Panopticon + Destiny \u2013 The Matrix Files\",\"video\":false,\"vote_average\":0,\"vote_count\":0}],\"total_pages\":3,\"total_results\":42}" + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=600 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 11:38:39 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 97d882fdcaf1da316742a0953a5a0564.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - l6aBIncJQAerEWWLJd_lhUcxV7TzRJ59lod_Ls3Oqmxj4evJtlBTzQ== + X-Amz-Cf-Pop: + - LHR50-P2 + X-Cache: + - Miss from cloudfront + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=The+Matrix&include_adult=false®ion=US&page=1 + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/movie/603?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US + response: + body: + string: + '{"adult":false,"backdrop_path":"/waCRuAW5ocONRehP556vPexVXA9.jpg","belongs_to_collection":{"id":2344,"name":"The + Matrix Collection","poster_path":"/bV9qTVHTVf0gkW0j7p7M0ILD4pG.jpg","backdrop_path":"/bRm2DEgUiYciDw3myHuYFInD7la.jpg"},"budget":63000000,"genres":[{"id":28,"name":"Action"},{"id":878,"name":"Science + Fiction"}],"homepage":"http://www.warnerbros.com/matrix","id":603,"imdb_id":"tt0133093","original_language":"en","original_title":"The + Matrix","overview":"Set in the 22nd century, The Matrix tells the story of + a computer hacker who joins a group of underground insurgents fighting the + vast and powerful computers who now rule the earth.","popularity":77.213,"poster_path":"/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg","production_companies":[{"id":79,"logo_path":"/tpFpsqbleCzEE2p5EgvUq6ozfCA.png","name":"Village + Roadshow Pictures","origin_country":"US"},{"id":174,"logo_path":"/IuAlhI9eVC9Z8UQWOIDdWRKSEJ.png","name":"Warner + Bros. Pictures","origin_country":"US"},{"id":372,"logo_path":null,"name":"Groucho + II Film Partnership","origin_country":""},{"id":1885,"logo_path":"/tXMFoE8AtNdnFzWOW0aCLwl7xxS.png","name":"Silver + Pictures","origin_country":"US"}],"production_countries":[{"iso_3166_1":"US","name":"United + States of America"}],"release_date":"1999-03-30","revenue":463517383,"runtime":136,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"Welcome + to the Real World.","title":"The Matrix","video":false,"vote_average":8.196,"vote_count":22742}' + headers: + Age: + - "2309" + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=28800 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 11:00:10 GMT + Etag: + - W/"f94310a8252a3d2405088d72c3a3ad41" + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 97d882fdcaf1da316742a0953a5a0564.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - 1vejK8DlJgPGY0_4wo17sUtYbbQu_2p4C_HkjwacTHqezCLxpFifYg== + X-Amz-Cf-Pop: + - LHR50-P2 + X-Cache: + - Hit from cloudfront + X-Memc: + - HIT + X-Memc-Age: + - "12546" + X-Memc-Expires: + - "3124" + X-Memc-Key: + - a52b63b61eb6422a5676648d45b03c1437ec1036 + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/movie/603?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US +version: 1 diff --git a/tests/fixtures/vcr_cassettes/tmdb/the_matrix_3.yaml b/tests/fixtures/vcr_cassettes/tmdb/the_matrix_3.yaml new file mode 100644 index 0000000..4e525fb --- /dev/null +++ b/tests/fixtures/vcr_cassettes/tmdb/the_matrix_3.yaml @@ -0,0 +1,139 @@ +interactions: + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=The+Matrix&include_adult=false®ion=US&page=1&year=2021 + response: + body: + string: + "{\"page\":1,\"results\":[{\"adult\":false,\"backdrop_path\":\"/eNI7PtK6DEYgZmHWP9gQNuff8pv.jpg\",\"genre_ids\":[878,28,12],\"id\":624860,\"original_language\":\"en\",\"original_title\":\"The + Matrix Resurrections\",\"overview\":\"Plagued by strange memories, Neo's life + takes an unexpected turn when he finds himself back inside the Matrix.\",\"popularity\":104.234,\"poster_path\":\"/8c4a8kE7PizaGQQnditMmI1xbRp.jpg\",\"release_date\":\"2021-12-22\",\"title\":\"The + Matrix Resurrections\",\"video\":false,\"vote_average\":6.6,\"vote_count\":4516},{\"adult\":false,\"backdrop_path\":\"/waCRuAW5ocONRehP556vPexVXA9.jpg\",\"genre_ids\":[28,878],\"id\":603,\"original_language\":\"en\",\"original_title\":\"The + Matrix\",\"overview\":\"Set in the 22nd century, The Matrix tells the story + of a computer hacker who joins a group of underground insurgents fighting + the vast and powerful computers who now rule the earth.\",\"popularity\":77.213,\"poster_path\":\"/f89U3ADr1oiB1s9GkdPOEpXUk5H.jpg\",\"release_date\":\"1999-03-30\",\"title\":\"The + Matrix\",\"video\":false,\"vote_average\":8.2,\"vote_count\":22741},{\"adult\":false,\"backdrop_path\":\"/5nxDn3mrdmeUHJOZi1Wfqya8Vji.jpg\",\"genre_ids\":[99],\"id\":696109,\"original_language\":\"en\",\"original_title\":\"A + Glitch in the Matrix\",\"overview\":\"Are we in fact living in a simulation? + This is the question postulated, wrestled with, and ultimately argued for + through archival footage, compelling interviews with real people shrouded + in digital avatars, and a collection of cases from some of our most iconoclastic + figures in contemporary culture.\",\"popularity\":7.989,\"poster_path\":\"/bn0BLVadmvzq6MmJ0n97MLxGpNQ.jpg\",\"release_date\":\"2021-02-05\",\"title\":\"A + Glitch in the Matrix\",\"video\":false,\"vote_average\":5.2,\"vote_count\":28},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[16],\"id\":919064,\"original_language\":\"en\",\"original_title\":\"Me + and The Matrix\",\"overview\":\"Filmmaker Cressa Maeve Beer recalls her experience + growing up as a fan of the Matrix movies, in celebration of the upcoming release + of The Matrix Resurrections.\",\"popularity\":0.6,\"poster_path\":\"/i9A8PKfzxyp4HxjqoEupKcZfofr.jpg\",\"release_date\":\"2021-12-22\",\"title\":\"Me + and The Matrix\",\"video\":false,\"vote_average\":0,\"vote_count\":0},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":1038494,\"original_language\":\"en\",\"original_title\":\"Panopticon + Destiny \u2013 The Matrix Files\",\"overview\":\"On August 6th 1977, the world\u2019s + first DOCTOR WHO convention was held at a church hall in Battersea, South + London. Organised by The Doctor Who Appreciation Society (DWAS) , Convention + \u201977 was ground breaking \u2013 featuring sta appearances from the (then) + current Doctor Tom baker with his latest companion Louise Jameson (Leela) + and former Doctor Jon Pertwee. 40 years later, stars, fans and organisers + returned to the church hall to reminisce about that special day! In this + second volume celebrating the anniversary, we feature a convention panel with + organiser Keith Barnfather, contributor Kevin Davies and attendee Andrew Beech, + held at another DWAS event The Capitol II on the 7th May 2017.\",\"popularity\":0.6,\"poster_path\":\"/r1fOhVtFRMqFkG8I6MaK6eDvX8r.jpg\",\"release_date\":\"2021-05-07\",\"title\":\"Panopticon + Destiny \u2013 The Matrix Files\",\"video\":false,\"vote_average\":0,\"vote_count\":0},{\"adult\":false,\"backdrop_path\":null,\"genre_ids\":[99],\"id\":859876,\"original_language\":\"en\",\"original_title\":\"Josh + Allen - A Glitch in the Matrix\",\"overview\":\"Quarterback Josh Allen rises + from obscurity along his hard path from a small town California farm to the + NFL.\",\"popularity\":0.699,\"poster_path\":\"/1kJFHxYpfB9D6REBAJOfp61Jufu.jpg\",\"release_date\":\"2021-08-09\",\"title\":\"Josh + Allen - A Glitch in the Matrix\",\"video\":false,\"vote_average\":0,\"vote_count\":0},{\"adult\":false,\"backdrop_path\":\"/1wdULm68hAqRwaQH9GuVcGO430e.jpg\",\"genre_ids\":[99],\"id\":850111,\"original_language\":\"en\",\"original_title\":\"Delusions + End: Breaking Free of the Matrix\",\"overview\":\"In a world of lies and deception + and false hopes, the time is now to remove the blinders of deception and take + back our lives.\",\"popularity\":0.623,\"poster_path\":\"/5sfH0dXcRrzMmuQ7tBdQl0n9PXv.jpg\",\"release_date\":\"2021-07-13\",\"title\":\"Delusions + End: Breaking Free of the Matrix\",\"video\":false,\"vote_average\":0,\"vote_count\":0}],\"total_pages\":1,\"total_results\":7}" + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=600 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 11:38:01 GMT + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 0049c5577a998a3c21b741181b3609cc.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - hp5RS_v-MW3KsIezur127C3245n7DGqbjSJL0UG902EK8g4XT8ZmIA== + X-Amz-Cf-Pop: + - LHR50-P2 + X-Cache: + - Miss from cloudfront + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/search/movie?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US&query=The+Matrix&include_adult=false®ion=US&page=1&year=2021 + - request: + body: null + headers: {} + method: GET + uri: https://api.themoviedb.org/3/movie/624860?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US + response: + body: + string: + '{"adult":false,"backdrop_path":"/eNI7PtK6DEYgZmHWP9gQNuff8pv.jpg","belongs_to_collection":{"id":2344,"name":"The + Matrix Collection","poster_path":"/bV9qTVHTVf0gkW0j7p7M0ILD4pG.jpg","backdrop_path":"/bRm2DEgUiYciDw3myHuYFInD7la.jpg"},"budget":190000000,"genres":[{"id":878,"name":"Science + Fiction"},{"id":28,"name":"Action"},{"id":12,"name":"Adventure"}],"homepage":"https://www.whatisthematrix.com","id":624860,"imdb_id":"tt10838180","original_language":"en","original_title":"The + Matrix Resurrections","overview":"Plagued by strange memories, Neo''s life + takes an unexpected turn when he finds himself back inside the Matrix.","popularity":104.234,"poster_path":"/8c4a8kE7PizaGQQnditMmI1xbRp.jpg","production_companies":[{"id":79,"logo_path":"/tpFpsqbleCzEE2p5EgvUq6ozfCA.png","name":"Village + Roadshow Pictures","origin_country":"US"},{"id":174,"logo_path":"/IuAlhI9eVC9Z8UQWOIDdWRKSEJ.png","name":"Warner + Bros. Pictures","origin_country":"US"},{"id":84764,"logo_path":null,"name":"Venus + Castina Productions","origin_country":"US"}],"production_countries":[{"iso_3166_1":"CA","name":"Canada"},{"iso_3166_1":"DE","name":"Germany"},{"iso_3166_1":"US","name":"United + States of America"}],"release_date":"2021-12-16","revenue":156497322,"runtime":147,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"Return + to the Source.","title":"The Matrix Resurrections","video":false,"vote_average":6.552,"vote_count":4516}' + headers: + Alt-Svc: + - h3=":443"; ma=86400 + Cache-Control: + - public, max-age=28800 + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json;charset=utf-8 + Date: + - Fri, 03 Feb 2023 11:38:02 GMT + Etag: + - W/"416e9740ce7439b9c1f51c40f28dcb4f" + Server: + - openresty + Transfer-Encoding: + - chunked + Vary: + - Accept-Encoding,Accept-Encoding,Accept-Encoding + - Origin + Via: + - 1.1 0049c5577a998a3c21b741181b3609cc.cloudfront.net (CloudFront) + X-Amz-Cf-Id: + - mDCRSDhvbzcSx9DBXX-j23oUEpWGtm4tBz456qFjreF_bxKt_Fc0pQ== + X-Amz-Cf-Pop: + - LHR50-P2 + X-Cache: + - Miss from cloudfront + X-Memc: + - HIT + X-Memc-Age: + - "8273" + X-Memc-Expires: + - "7008" + X-Memc-Key: + - 8312016bf93f1e2b2b9412af28ac5718a3119176 + status: + code: 200 + message: OK + url: https://api.themoviedb.org/3/movie/624860?api_key=9017d5457c4e8ec5f0999d1b10012ae1&language=en-US +version: 1 diff --git a/tests/unit/clients/test_tmdb.py b/tests/unit/clients/test_tmdb.py index ebdc4d3..d3bb813 100644 --- a/tests/unit/clients/test_tmdb.py +++ b/tests/unit/clients/test_tmdb.py @@ -1,12 +1,20 @@ """Tests for the Tmdb client.""" +import os +from unittest.mock import MagicMock +from unittest.mock import Mock +from unittest.mock import patch + import pytest import vcr from requests.exceptions import HTTPError from tests.conftest import FIXTURES_DIR +from phylm.clients.tmdb import initialize_tmdb_client from phylm.clients.tmdb import TmdbClient +from phylm.errors import NoTMDbApiKeyError VCR_FIXTURES_DIR = f"{FIXTURES_DIR}/clients/tmdb" +CLIENT_MODULE_PATH = "phylm.clients.tmdb" class TestSearchMovies: @@ -72,6 +80,109 @@ def test_api_error(self) -> None: client.search_movies(query="The Matrix") +class TestSearchMoviesAsync: + """Tests for the `search_movies_async` method.""" + + @pytest.mark.asyncio + async def test_success(self) -> None: + """Movies search results are returned.""" + client = TmdbClient(api_key="not_a_key") + + with vcr.use_cassette( + f"{VCR_FIXTURES_DIR}/the_matrix_async.yaml", + filter_query_parameters=["api_key"], + ) as cass: + results = await client.search_movies_async(query="The Matrix") + + assert results[0]["title"] == "The Matrix Resurrections" + + cass.rewind() + + assert len(cass) == 1 + assert cass.requests[0].query == [ + ("include_adult", "false"), + ("language", "en-US"), + ("page", "1"), + ("query", "The Matrix"), + ("region", "US"), + ] + + @pytest.mark.asyncio + async def test_success_year(self) -> None: + """Movies search results filtered by year are returned.""" + client = TmdbClient(api_key="not_a_key") + + with vcr.use_cassette( + f"{VCR_FIXTURES_DIR}/the_matrix_year_async.yaml", + filter_query_parameters=["api_key"], + ) as cass: + results = await client.search_movies_async(query="The Matrix", year=1999) + + assert results[0]["title"] == "The Matrix" + + cass.rewind() + + assert len(cass) == 1 + assert cass.requests[0].query == [ + ("include_adult", "false"), + ("language", "en-US"), + ("page", "1"), + ("query", "The Matrix"), + ("region", "US"), + ("year", "1999"), + ] + + @pytest.mark.asyncio + async def test_no_results(self) -> None: + """Empty list is returned for no results.""" + client = TmdbClient(api_key="not_a_key") + + with vcr.use_cassette( + f"{VCR_FIXTURES_DIR}/search_no_results.yaml", + filter_query_parameters=["api_key"], + ): + results = await client.search_movies_async(query="askdjhashdaskdasljd") + + assert results == [] + + +class TestGetMovie: + """Tests for the `get_movie` method.""" + + @pytest.mark.asyncio + async def test_success(self) -> None: + """Movie result is returned.""" + client = TmdbClient(api_key="not_a_key") + + with vcr.use_cassette( + f"{VCR_FIXTURES_DIR}/get_the_matrix.yaml", + filter_query_parameters=["api_key"], + ) as cass: + results = await client.get_movie("603") + + assert results["title"] == "The Matrix" + + cass.rewind() + + assert len(cass) == 1 + assert cass.requests[0].query == [ + ("language", "en-US"), + ] + + @pytest.mark.asyncio + async def test_not_found(self) -> None: + """Unrecognised movie id is handled.""" + client = TmdbClient(api_key="not_a_key") + + with vcr.use_cassette( + f"{VCR_FIXTURES_DIR}/get_invalid_id.yaml", + filter_query_parameters=["api_key"], + ): + results = await client.get_movie("xxxxx") + + assert results["success"] is False + + class TestStreamingProviders: """Tests for the `get_streaming_providers` method.""" @@ -128,3 +239,56 @@ def test_no_results(self) -> None: with pytest.raises(HTTPError): client.get_streaming_providers(movie_id="-1", regions=["gb"]) + + +class TestInitializeTmdbClient: + """Tests for the `initialize_tmdb_client` function.""" + + @patch.dict(os.environ, {"TMDB_API_KEY": ""}, clear=True) + def test_no_key(self) -> None: + """Raises an error if no key available.""" + with pytest.raises(NoTMDbApiKeyError) as err: + initialize_tmdb_client() + + assert str(err.value) == "An `api_key` must be provided to use this service" + + @patch(f"{CLIENT_MODULE_PATH}.TmdbClient", autospec=True) + def test_supplied_key( + self, + mock_initialize_client: MagicMock, + ) -> None: + """Returns a client with provided key.""" + client = initialize_tmdb_client(api_key="nice_key") + + assert client == mock_initialize_client.return_value + mock_initialize_client.assert_called_once_with( + api_key="nice_key", async_session=None + ) + + @patch.dict(os.environ, {"TMDB_API_KEY": "nice_key"}, clear=True) + @patch(f"{CLIENT_MODULE_PATH}.TmdbClient", autospec=True) + def test_key_from_env( + self, + mock_initialize_client: MagicMock, + ) -> None: + """Returns a client with key from env.""" + client = initialize_tmdb_client() + + assert client == mock_initialize_client.return_value + mock_initialize_client.assert_called_once_with( + api_key="nice_key", async_session=None + ) + + @patch(f"{CLIENT_MODULE_PATH}.TmdbClient", autospec=True) + def test_with_session( + self, + mock_initialize_client: MagicMock, + ) -> None: + """Returns a client with key from env.""" + mock_session = Mock() + client = initialize_tmdb_client(api_key="nice_key", async_session=mock_session) + + assert client == mock_initialize_client.return_value + mock_initialize_client.assert_called_once_with( + api_key="nice_key", async_session=mock_session + ) diff --git a/tests/unit/sources/test_tmdb.py b/tests/unit/sources/test_tmdb.py new file mode 100644 index 0000000..04ccc33 --- /dev/null +++ b/tests/unit/sources/test_tmdb.py @@ -0,0 +1,207 @@ +"""Module for TMDB tests.""" +from unittest.mock import AsyncMock +from unittest.mock import MagicMock +from unittest.mock import Mock +from unittest.mock import patch + +import pytest +from tests.conftest import FIXTURES_DIR +from tests.conftest import vcr + +from phylm.sources.tmdb import Tmdb + +MODULE_PATH = "phylm.sources.tmdb" +VCR_FIXTURES_DIR = f"{FIXTURES_DIR}/tmdb" +pytestmark = pytest.mark.asyncio + + +class TestInit: + """Tests for TMDB init.""" + + def test_no_raw_title_or_movie_id(self) -> None: + """An error is raised if no title or movie_id.""" + with pytest.raises( + ValueError, + match="At least one of raw_title and movie_id must be given", + ): + Tmdb() + + async def test_initial_state(self) -> None: + """All data points start out as none.""" + tmdb = Tmdb("Alien") + + assert tmdb.title is None + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_exact_match(self) -> None: + """Exact title match and `low_confidence` remains `False`.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.title == "The Matrix" + assert tmdb.low_confidence is False + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/no_results.yaml") + async def test_no_results(self) -> None: + """No matches are handled""" + raw_title = "asldkjnkasnxlajsnxkasjxnas" + + tmdb = Tmdb(raw_title) + await tmdb.load_source() + + assert tmdb.title is None + + +class TestYearMatching: + """Tests for the using year with title method.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix_3.yaml") + async def test_year_match_first_result(self) -> None: + """Year data is correctly used for getting search results.""" + tmdb = Tmdb(raw_title="The Matrix", raw_year=2021) + await tmdb.load_source() + + assert tmdb.title == "The Matrix Resurrections" + assert tmdb.year == 2021 + + +class TestLoadSource: + """Tests for the `load_source` method.""" + + @patch(f"{MODULE_PATH}.initialize_tmdb_client") + async def test_movie_id(self, mock_initialize_client: MagicMock) -> None: + """`movie_id` is used to get result.""" + get_movie_mock = AsyncMock() + mock_initialize_client.return_value.get_movie = get_movie_mock + + tmdb = Tmdb(movie_id="abc") + + await tmdb.load_source() + + get_movie_mock.assert_awaited_once_with("abc") + + @patch(f"{MODULE_PATH}.initialize_tmdb_client") + async def test_title_and_year(self, mock_initialize_client: MagicMock) -> None: + """`raw_title` and `raw_year` are used to get result.""" + tmdb_client = mock_initialize_client.return_value + + search_movies_mock = AsyncMock() + search_movies_mock.return_value = [{"id": "abc"}] + tmdb_client.search_movies_async = search_movies_mock + + get_movie_mock = AsyncMock() + tmdb_client.get_movie = get_movie_mock + + tmdb = Tmdb(raw_title="The Matrix", raw_year=1999) + + await tmdb.load_source() + + search_movies_mock.assert_awaited_once_with("The Matrix", year=1999) + get_movie_mock.assert_awaited_once_with("abc") + + @patch(f"{MODULE_PATH}.initialize_tmdb_client") + async def test_no_results(self, mock_initialize_client: MagicMock) -> None: + """No results are returned.""" + tmdb_client = mock_initialize_client.return_value + + search_movies_mock = AsyncMock() + search_movies_mock.return_value = [] + tmdb_client.search_movies_async = search_movies_mock + + get_movie_mock = AsyncMock() + tmdb_client.get_movie = get_movie_mock + + tmdb = Tmdb(raw_title="The Matrix", raw_year=1999) + + await tmdb.load_source() + + search_movies_mock.assert_awaited_once_with("The Matrix", year=1999) + get_movie_mock.assert_not_awaited + + @patch(f"{MODULE_PATH}.initialize_tmdb_client") + async def test_new_session(self, mock_initialize_client: MagicMock) -> None: + """A supplied session is used.""" + get_movie_mock = AsyncMock() + mock_initialize_client.return_value.get_movie = get_movie_mock + + tmdb = Tmdb(movie_id="abc") + + mock_session = Mock() + await tmdb.load_source(session=mock_session) + + assert mock_initialize_client.call_count == 2 + assert mock_initialize_client.call_args_list[1][1] == { + "async_session": mock_session + } + + +class TestId: + """Tests for the `id` property.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_year(self) -> None: + """The TMDB id can be returned.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.id == "603" + + +class TestImdbId: + """Tests for the `imdb_id` property.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_year(self) -> None: + """The IMDb id can be returned.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.imdb_id == "tt0133093" + + +class TestGenres: + """Tests for the `genres` method.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_year(self) -> None: + """The IMDb id can be returned.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.genres() == ["Action", "Science Fiction"] + + +class TestRuntime: + """Tests for the `runtime` property.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_year(self) -> None: + """The runtime can be returned.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.runtime == 136 + + +class TestRating: + """Tests for the `rating` property.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_year(self) -> None: + """The rating can be returned.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.rating == 8.196 + + +class TestPlot: + """Tests for the `plot` property.""" + + @vcr.use_cassette(f"{VCR_FIXTURES_DIR}/the_matrix.yaml") + async def test_year(self) -> None: + """The plot can be returned.""" + tmdb = Tmdb("The Matrix") + await tmdb.load_source() + + assert tmdb.plot diff --git a/tests/unit/test_phylm.py b/tests/unit/test_phylm.py index db93d60..601c1fc 100644 --- a/tests/unit/test_phylm.py +++ b/tests/unit/test_phylm.py @@ -47,6 +47,14 @@ def test_imdb_id(self) -> None: assert phylm.imdb_id == imdb_id + def test_tmdb_id(self) -> None: + """TMDB id can be passed through""" + tmdb_id = "bar" + + phylm = Phylm(title="foo", tmdb_id=tmdb_id) + + assert phylm.tmdb_id == tmdb_id + class TestRepr: """Tests for the __repr__ method.""" @@ -176,7 +184,50 @@ async def test_recognized_source_rt(self) -> None: assert phylm.rt == mock_rt.return_value mock_rt.assert_called_once_with(raw_title="bar", raw_year=2000) - @pytest.mark.parametrize("source_class", ("Rt", "Mtc", "Imdb")) + async def test_recognized_source_tmdb(self) -> None: + """Can load `tmdb` source.""" + phylm = Phylm(title="bar", year=2000) + with pytest.raises( + SourceNotLoadedError, match="The data for TMDB has not yet been loaded" + ): + assert phylm.tmdb is None + + with patch(f"{MODULE_PATH}.Tmdb", autospec=True) as mock_tmdb: + mock_tmdb.return_value.load_source = AsyncMock() + await phylm.load_source("tmdb") + + assert phylm.tmdb == mock_tmdb.return_value + mock_tmdb.assert_called_once_with( + raw_title="bar", movie_id=None, raw_year=2000 + ) + + async def test_recognized_source_tmdb_with_movie_id(self) -> None: + """Can load `tmdb` source with a `movie_id`.""" + phylm = Phylm(title="bar") + + with patch(f"{MODULE_PATH}.Tmdb", autospec=True) as mock_tmdb: + mock_tmdb.return_value.load_source = AsyncMock() + await phylm.load_source("tmdb", tmdb_id="abc") + + assert phylm.tmdb == mock_tmdb.return_value + mock_tmdb.assert_called_once_with( + raw_title="bar", movie_id="abc", raw_year=None + ) + + async def test_recognized_source_tmdb_with_movie_id_instance_variable(self) -> None: + """Can load `tmdb` source with a `movie_id` instance variable.""" + phylm = Phylm(title="foo", tmdb_id="abc") + + with patch(f"{MODULE_PATH}.Tmdb", autospec=True) as mock_tmdb: + mock_tmdb.return_value.load_source = AsyncMock() + await phylm.load_source("tmdb") + + assert phylm.tmdb == mock_tmdb.return_value + mock_tmdb.assert_called_once_with( + raw_title="foo", movie_id="abc", raw_year=None + ) + + @pytest.mark.parametrize("source_class", ("Rt", "Mtc", "Imdb", "Tmdb")) async def test_source_already_loaded(self, source_class: str) -> None: """ Given phylm instance with a source already loaded, @@ -193,7 +244,7 @@ async def test_source_already_loaded(self, source_class: str) -> None: assert mock_source.call_count == 1 - @pytest.mark.parametrize("source_class", ("Rt", "Mtc")) + @pytest.mark.parametrize("source_class", ("Rt", "Mtc", "Tmdb")) async def test_with_given_session(self, source_class: str) -> None: """ Given phylm instance, diff --git a/tests/unit/tools/test_tools.py b/tests/unit/tools/test_tools.py index f682c92..7bd2c01 100644 --- a/tests/unit/tools/test_tools.py +++ b/tests/unit/tools/test_tools.py @@ -52,6 +52,7 @@ def test_results(self, mock_ia: MagicMock, the_matrix: List[Movie]) -> None: class TestSearchTmdbMovies: """Tests for the `search_tmdb_movies` method.""" + @patch.dict(os.environ, {"TMDB_API_KEY": ""}, clear=True) def test_no_api_key(self) -> None: """ Given no api key, @@ -61,69 +62,34 @@ def test_no_api_key(self) -> None: with pytest.raises(NoTMDbApiKeyError): search_tmdb_movies(query="The Matrix") - @patch(f"{TOOLS_MODULE_PATH}.TmdbClient", autospec=True) - def test_with_api_key_as_arg(self, mock_tmdb_client_class: MagicMock) -> None: + @patch(f"{TOOLS_MODULE_PATH}.initialize_tmdb_client", autospec=True) + def test_with_api_key_as_arg(self, mock_initialize_tmdb_client: MagicMock) -> None: """ Given an api_key supplied as an arg, When the `search_tmdb_movies` function is invoked, Then the api_key is used in the client """ api_key = "nice_key" - mock_tmdb_client = mock_tmdb_client_class.return_value + mock_tmdb_client = mock_initialize_tmdb_client.return_value mock_tmdb_client.search_movies.return_value = [{"title": "The Matrix"}] results = search_tmdb_movies(query="The Matrix", api_key=api_key) assert results == [{"title": "The Matrix"}] - mock_tmdb_client_class.assert_called_once_with(api_key=api_key) + mock_initialize_tmdb_client.assert_called_once_with(api_key=api_key) mock_tmdb_client.search_movies.assert_called_once_with( query="The Matrix", region="us" ) - @patch(f"{TOOLS_MODULE_PATH}.TmdbClient", autospec=True) - @patch.dict(os.environ, {"TMDB_API_KEY": "nice_key"}, clear=True) - def test_with_api_key_as_env_var(self, mock_tmdb_client_class: MagicMock) -> None: - """ - Given an api_key supplied as an env var, - When the `search_tmdb_movies` function is invoked, - Then the api_key is used in the client - """ - mock_tmdb_client = mock_tmdb_client_class.return_value - mock_tmdb_client.search_movies.return_value = [{"title": "The Matrix"}] - - results = search_tmdb_movies(query="The Matrix") - - assert results == [{"title": "The Matrix"}] - mock_tmdb_client_class.assert_called_once_with(api_key="nice_key") - - @patch(f"{TOOLS_MODULE_PATH}.TmdbClient", autospec=True) - @patch.dict(os.environ, {"TMDB_API_KEY": "nice_key"}, clear=True) - def test_with_api_key_arg_preferred( - self, mock_tmdb_client_class: MagicMock - ) -> None: - """ - Given an api_key supplied as an env var and one as an arg, - When the `search_tmdb_movies` function is invoked, - Then the api_key as an arg is used in the client - """ - api_key = "better_key" - mock_tmdb_client = mock_tmdb_client_class.return_value - mock_tmdb_client.search_movies.return_value = [{"title": "The Matrix"}] - - results = search_tmdb_movies(query="The Matrix", api_key=api_key) - - assert results == [{"title": "The Matrix"}] - mock_tmdb_client_class.assert_called_once_with(api_key=api_key) - - @patch(f"{TOOLS_MODULE_PATH}.TmdbClient", autospec=True) - def test_different_region(self, mock_tmdb_client_class: MagicMock) -> None: + @patch(f"{TOOLS_MODULE_PATH}.initialize_tmdb_client", autospec=True) + def test_different_region(self, mock_initialize_tmdb_client: MagicMock) -> None: """ Given a region supplied as an arg, When the `search_tmdb_movies` function is invoked, Then the region is passed to the `search_movies` method """ api_key = "nice_key" - mock_tmdb_client = mock_tmdb_client_class.return_value + mock_tmdb_client = mock_initialize_tmdb_client.return_value mock_tmdb_client.search_movies.return_value = [{"title": "The Matrix"}] results = search_tmdb_movies(query="The Matrix", api_key=api_key, region="gb") @@ -137,7 +103,7 @@ def test_different_region(self, mock_tmdb_client_class: MagicMock) -> None: class TestGetStreamingProviders: """Tests for the `get_streaming_providers` method.""" - @patch(f"{TOOLS_MODULE_PATH}.TmdbClient", autospec=True) + @patch(f"{TOOLS_MODULE_PATH}.initialize_tmdb_client", autospec=True) def test_success( self, mock_initialize_client: MagicMock,