Changeset - a84a39f18784
[Not reviewed]
default
0 4 1
Dennis Fink - 3 years ago 2022-03-21 17:39:50
dennis.fink@c3l.lu
Add books
5 files changed with 338 insertions and 12 deletions:
0 comments (0 inline, 0 general)
poetry.lock
Show inline comments
 
[[package]]
 
name = "appdirs"
 
version = "1.4.4"
 
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 
category = "main"
 
optional = false
 
python-versions = "*"
 

	
 
[[package]]
 
name = "attrs"
 
version = "21.4.0"
 
description = "Classes Without Boilerplate"
 
category = "main"
 
optional = false
 
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 

	
 
[package.extras]
 
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
 
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
 
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
 
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
 

	
 
[[package]]
 
name = "beautifulsoup4"
 
version = "4.10.0"
 
description = "Screen-scraping library"
 
category = "main"
 
optional = false
 
python-versions = ">3.0.0"
 

	
 
[package.dependencies]
 
soupsieve = ">1.2"
 

	
 
[package.extras]
 
html5lib = ["html5lib"]
 
lxml = ["lxml"]
 

	
 
[[package]]
 
name = "black"
 
version = "22.1.0"
 
description = "The uncompromising code formatter."
 
category = "dev"
 
optional = false
 
python-versions = ">=3.6.2"
 

	
 
[package.dependencies]
 
click = ">=8.0.0"
 
mypy-extensions = ">=0.4.3"
 
pathspec = ">=0.9.0"
 
platformdirs = ">=2"
 
tomli = ">=1.1.0"
 

	
 
[package.extras]
 
colorama = ["colorama (>=0.4.3)"]
 
d = ["aiohttp (>=3.7.4)"]
 
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
 
uvloop = ["uvloop (>=0.15.2)"]
 

	
 
[[package]]
 
name = "cattrs"
 
version = "1.10.0"
 
description = "Composable complex class support for attrs and dataclasses."
 
category = "main"
 
optional = false
 
python-versions = ">=3.7,<4.0"
 

	
 
[package.dependencies]
 
attrs = ">=20"
 

	
 
[[package]]
 
name = "certifi"
 
version = "2021.10.8"
 
description = "Python package for providing Mozilla's CA Bundle."
 
category = "main"
 
optional = false
 
python-versions = "*"
 

	
 
[[package]]
 
name = "charset-normalizer"
 
version = "2.0.11"
 
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 
category = "main"
 
optional = false
 
python-versions = ">=3.5.0"
 

	
 
[package.extras]
 
unicode_backport = ["unicodedata2"]
 

	
 
[[package]]
 
name = "click"
 
version = "8.0.3"
 
description = "Composable command line interface toolkit"
 
category = "main"
 
optional = false
 
python-versions = ">=3.6"
 

	
 
[package.dependencies]
 
colorama = {version = "*", markers = "platform_system == \"Windows\""}
 

	
 
[[package]]
 
name = "colorama"
 
version = "0.4.4"
 
description = "Cross-platform colored terminal text."
 
category = "main"
 
optional = false
 
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 

	
 
[[package]]
 
name = "commonmark"
 
version = "0.9.1"
 
description = "Python parser for the CommonMark Markdown spec"
 
category = "main"
 
optional = false
 
python-versions = "*"
 

	
 
[package.extras]
 
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
 

	
 
[[package]]
 
name = "idna"
 
version = "3.3"
 
description = "Internationalized Domain Names in Applications (IDNA)"
 
category = "main"
 
optional = false
 
python-versions = ">=3.5"
 

	
 
[[package]]
 
name = "isbnlib"
 
version = "3.10.10"
 
description = "Extract, clean, transform, hyphenate and metadata for ISBNs (International Standard Book Number)."
 
category = "main"
 
optional = false
 
python-versions = "*"
 

	
 
[[package]]
 
name = "isbnlib-worldcat2"
 
version = "0.1.2"
 
description = "An isbnlib plugin for the WorldCat service (https://www.worldcat.org/)."
 
category = "main"
 
optional = false
 
python-versions = "*"
 

	
 
[package.dependencies]
 
beautifulsoup4 = ">=4.7.1"
 
isbnlib = ">=3.9.1"
 
pycountry = ">=1.12.8"
 

	
 
[[package]]
 
name = "isort"
 
version = "5.10.1"
 
description = "A Python utility / library to sort Python imports."
 
category = "dev"
 
optional = false
 
python-versions = ">=3.6.1,<4.0"
 

	
 
[package.extras]
 
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
 
requirements_deprecated_finder = ["pipreqs", "pip-api"]
 
colors = ["colorama (>=0.4.3,<0.5.0)"]
 
plugins = ["setuptools"]
 

	
 
[[package]]
 
name = "mypy"
 
version = "0.931"
 
description = "Optional static typing for Python"
 
category = "dev"
 
optional = false
 
python-versions = ">=3.6"
 

	
 
[package.dependencies]
 
mypy-extensions = ">=0.4.3"
 
tomli = ">=1.1.0"
 
typing-extensions = ">=3.10"
 

	
 
[package.extras]
 
dmypy = ["psutil (>=4.0)"]
 
python2 = ["typed-ast (>=1.4.0,<2)"]
 

	
 
[[package]]
 
name = "mypy-extensions"
 
version = "0.4.3"
 
description = "Experimental type system extensions for programs checked with the mypy typechecker."
 
category = "dev"
 
optional = false
 
python-versions = "*"
 

	
 
[[package]]
 
name = "pathspec"
 
version = "0.9.0"
 
description = "Utility library for gitignore style pattern matching of file paths."
 
category = "dev"
 
optional = false
 
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
 

	
 
[[package]]
 
name = "platformdirs"
 
version = "2.4.1"
 
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 
category = "dev"
 
optional = false
 
python-versions = ">=3.7"
 

	
 
[package.extras]
 
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
 
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
 

	
 
[[package]]
 
name = "pycountry"
 
version = "22.3.5"
 
description = "ISO country, subdivision, language, currency and script definitions and their translations"
 
category = "main"
 
optional = false
 
python-versions = ">=3.6, <4"
 

	
 
[[package]]
 
name = "pygments"
 
version = "2.11.2"
 
description = "Pygments is a syntax highlighting package written in Python."
 
category = "main"
 
optional = false
 
python-versions = ">=3.5"
 

	
 
[[package]]
 
name = "requests"
 
version = "2.27.1"
 
description = "Python HTTP for Humans."
 
category = "main"
 
optional = false
 
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 

	
 
[package.dependencies]
 
certifi = ">=2017.4.17"
 
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
 
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
 
urllib3 = ">=1.21.1,<1.27"
 

	
 
[package.extras]
 
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
 
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
 

	
 
[[package]]
 
name = "requests-cache"
 
version = "0.9.3"
 
description = "A transparent persistent cache for the requests library"
 
category = "main"
 
optional = false
 
python-versions = ">=3.7,<4.0"
 

	
 
[package.dependencies]
 
appdirs = ">=1.4.4,<2.0.0"
 
attrs = ">=21.2,<22.0"
 
cattrs = ">=1.8,<2.0"
 
requests = ">=2.22,<3.0"
 
url-normalize = ">=1.4,<2.0"
 
urllib3 = ">=1.25.5,<2.0.0"
 

	
 
[package.extras]
 
dynamodb = ["boto3 (>=1.15,<2.0)", "botocore (>=1.18,<2.0)"]
 
all = ["boto3 (>=1.15,<2.0)", "botocore (>=1.18,<2.0)", "pymongo (>=3,<5)", "redis (>=3,<5)", "itsdangerous (>=2.0,<3.0)", "pyyaml (>=5.4)", "ujson (>=4.0)"]
 
mongodb = ["pymongo (>=3,<5)"]
 
redis = ["redis (>=3,<5)"]
 
bson = ["bson (>=0.5)"]
 
security = ["itsdangerous (>=2.0,<3.0)"]
 
yaml = ["pyyaml (>=5.4)"]
 
json = ["ujson (>=4.0)"]
 
docs = ["furo (>=2021.9.8)", "linkify-it-py (>=1.0.1,<2.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx (==4.3.0)", "sphinx-autodoc-typehints (>=1.11,<2.0)", "sphinx-automodapi (>=0.13,<0.15)", "sphinx-copybutton (>=0.3,<0.5)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinx-notfound-page", "sphinx-panels (>=0.6,<0.7)", "sphinxcontrib-apidoc (>=0.3,<0.4)"]
 

	
 
[[package]]
 
name = "rich"
 
version = "11.2.0"
 
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 
category = "main"
 
optional = false
 
python-versions = ">=3.6.2,<4.0.0"
 

	
 
[package.dependencies]
 
colorama = ">=0.4.0,<0.5.0"
 
commonmark = ">=0.9.0,<0.10.0"
 
pygments = ">=2.6.0,<3.0.0"
 

	
 
[package.extras]
 
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"]
 

	
 
[[package]]
 
name = "six"
 
version = "1.16.0"
 
description = "Python 2 and 3 compatibility utilities"
 
category = "main"
 
optional = false
 
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 

	
 
[[package]]
 
name = "soupsieve"
 
version = "2.3.1"
 
description = "A modern CSS selector implementation for Beautiful Soup."
 
category = "main"
 
optional = false
 
python-versions = ">=3.6"
 

	
 
[[package]]
 
name = "tomli"
 
version = "2.0.0"
 
description = "A lil' TOML parser"
 
category = "dev"
 
optional = false
 
python-versions = ">=3.7"
 

	
 
[[package]]
 
name = "typing-extensions"
 
version = "4.0.1"
 
description = "Backported and Experimental Type Hints for Python 3.6+"
 
category = "dev"
 
optional = false
 
python-versions = ">=3.6"
 

	
 
[[package]]
 
name = "url-normalize"
 
version = "1.4.3"
 
description = "URL normalization for Python"
 
category = "main"
 
optional = false
 
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
 

	
 
[package.dependencies]
 
six = "*"
 

	
 
[[package]]
 
name = "urllib3"
 
version = "1.26.8"
 
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.*, <4"
 

	
 
[package.extras]
 
brotli = ["brotlipy (>=0.6.0)"]
 
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
 
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 

	
 
[[package]]
 
name = "vermin"
 
version = "1.3.3"
 
description = "Concurrently detect the minimum Python versions needed to run code"
 
category = "dev"
 
optional = false
 
python-versions = ">=2.7"
 

	
 
[metadata]
 
lock-version = "1.1"
 
python-versions = "^3.10"
 
content-hash = "e73abda5748e52a1074f136fa035a8e427d57dd92ba4983c81141974a97dc25e"
 
content-hash = "505b76babe9d3e271acda50f879b483fe1e75bbc34e79d498f83efe2f02db178"
 

	
 
[metadata.files]
 
appdirs = [
 
    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
 
    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
 
]
 
attrs = [
 
    {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
 
    {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
 
]
 
beautifulsoup4 = [
 
    {file = "beautifulsoup4-4.10.0-py3-none-any.whl", hash = "sha256:9a315ce70049920ea4572a4055bc4bd700c940521d36fc858205ad4fcde149bf"},
 
    {file = "beautifulsoup4-4.10.0.tar.gz", hash = "sha256:c23ad23c521d818955a4151a67d81580319d4bf548d3d49f4223ae041ff98891"},
 
]
 
black = [
 
    {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"},
 
    {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"},
 
    {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"},
 
    {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"},
 
    {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"},
 
    {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"},
 
    {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"},
 
    {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"},
 
    {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"},
 
    {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"},
 
    {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"},
 
    {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"},
 
    {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"},
 
    {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"},
 
    {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"},
 
    {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"},
 
    {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"},
 
    {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"},
 
    {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"},
 
    {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"},
 
    {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"},
 
    {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"},
 
    {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"},
 
]
 
cattrs = [
 
    {file = "cattrs-1.10.0-py3-none-any.whl", hash = "sha256:35dd9063244263e63bd0bd24ea61e3015b00272cead084b2c40d788b0f857c46"},
 
    {file = "cattrs-1.10.0.tar.gz", hash = "sha256:211800f725cdecedcbcf4c753bbd22d248312b37d130f06045434acb7d9b34e1"},
 
]
 
certifi = [
 
    {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
 
    {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
 
]
 
charset-normalizer = [
 
    {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"},
 
    {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"},
 
]
 
click = [
 
    {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
 
    {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
 
]
 
colorama = [
 
    {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
 
    {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
 
]
 
commonmark = [
 
    {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
 
    {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
 
]
 
idna = [
 
    {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
 
    {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
 
]
 
isbnlib = [
 
    {file = "isbnlib-3.10.10-py2.py3-none-any.whl", hash = "sha256:623a09329e8ec7049edf15dd412db042bf4f8236a428bf7a22d84a125584f52d"},
 
    {file = "isbnlib-3.10.10.tar.gz", hash = "sha256:c9e6c1dcaa9dff195429373cf2beb3117f30b3fca43d7db5aec5a2d1f6f59784"},
 
]
 
isbnlib-worldcat2 = [
 
    {file = "isbnlib-worldcat2-0.1.2.tar.gz", hash = "sha256:fd05266a6d58ecb13bea8b9c69e3c4d6871708aa38f79e869a31ce909943ef14"},
 
    {file = "isbnlib_worldcat2-0.1.2-py3-none-any.whl", hash = "sha256:296a4a5d46eb4eab201b5a218a03354d6208ab52d62a563ae39e940c71c1ced7"},
 
]
 
isort = [
 
    {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
 
    {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
 
]
 
mypy = [
 
    {file = "mypy-0.931-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c5b42d0815e15518b1f0990cff7a705805961613e701db60387e6fb663fe78a"},
 
    {file = "mypy-0.931-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c89702cac5b302f0c5d33b172d2b55b5df2bede3344a2fbed99ff96bddb2cf00"},
 
    {file = "mypy-0.931-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:300717a07ad09525401a508ef5d105e6b56646f7942eb92715a1c8d610149714"},
 
    {file = "mypy-0.931-cp310-cp310-win_amd64.whl", hash = "sha256:7b3f6f557ba4afc7f2ce6d3215d5db279bcf120b3cfd0add20a5d4f4abdae5bc"},
 
    {file = "mypy-0.931-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:1bf752559797c897cdd2c65f7b60c2b6969ffe458417b8d947b8340cc9cec08d"},
 
    {file = "mypy-0.931-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4365c60266b95a3f216a3047f1d8e3f895da6c7402e9e1ddfab96393122cc58d"},
 
    {file = "mypy-0.931-cp36-cp36m-win_amd64.whl", hash = "sha256:1b65714dc296a7991000b6ee59a35b3f550e0073411ac9d3202f6516621ba66c"},
 
    {file = "mypy-0.931-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e839191b8da5b4e5d805f940537efcaa13ea5dd98418f06dc585d2891d228cf0"},
 
    {file = "mypy-0.931-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50c7346a46dc76a4ed88f3277d4959de8a2bd0a0fa47fa87a4cde36fe247ac05"},
 
    {file = "mypy-0.931-cp37-cp37m-win_amd64.whl", hash = "sha256:d8f1ff62f7a879c9fe5917b3f9eb93a79b78aad47b533911b853a757223f72e7"},
 
    {file = "mypy-0.931-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f9fe20d0872b26c4bba1c1be02c5340de1019530302cf2dcc85c7f9fc3252ae0"},
 
    {file = "mypy-0.931-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1b06268df7eb53a8feea99cbfff77a6e2b205e70bf31743e786678ef87ee8069"},
 
    {file = "mypy-0.931-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8c11003aaeaf7cc2d0f1bc101c1cc9454ec4cc9cb825aef3cafff8a5fdf4c799"},
 
    {file = "mypy-0.931-cp38-cp38-win_amd64.whl", hash = "sha256:d9d2b84b2007cea426e327d2483238f040c49405a6bf4074f605f0156c91a47a"},
 
    {file = "mypy-0.931-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ff3bf387c14c805ab1388185dd22d6b210824e164d4bb324b195ff34e322d166"},
 
    {file = "mypy-0.931-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b56154f8c09427bae082b32275a21f500b24d93c88d69a5e82f3978018a0266"},
 
    {file = "mypy-0.931-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ca7f8c4b1584d63c9a0f827c37ba7a47226c19a23a753d52e5b5eddb201afcd"},
 
    {file = "mypy-0.931-cp39-cp39-win_amd64.whl", hash = "sha256:74f7eccbfd436abe9c352ad9fb65872cc0f1f0a868e9d9c44db0893440f0c697"},
 
    {file = "mypy-0.931-py3-none-any.whl", hash = "sha256:1171f2e0859cfff2d366da2c7092b06130f232c636a3f7301e3feb8b41f6377d"},
 
    {file = "mypy-0.931.tar.gz", hash = "sha256:0038b21890867793581e4cb0d810829f5fd4441aa75796b53033af3aa30430ce"},
 
]
 
mypy-extensions = [
 
    {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
 
    {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 
]
 
pathspec = [
 
    {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
 
    {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
 
]
 
platformdirs = [
 
    {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
 
    {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
 
]
 
pycountry = [
 
    {file = "pycountry-22.3.5.tar.gz", hash = "sha256:b2163a246c585894d808f18783e19137cb70a0c18fb36748dc01fc6f109c1646"},
 
]
 
pygments = [
 
    {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"},
 
    {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"},
 
]
 
requests = [
 
    {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
 
    {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
 
]
 
requests-cache = [
 
    {file = "requests-cache-0.9.3.tar.gz", hash = "sha256:b32f8afba2439e1b3e12cba511c8f579271eff827f063210d62f9efa5bed6564"},
 
    {file = "requests_cache-0.9.3-py3-none-any.whl", hash = "sha256:d8b32405b2725906aa09810f4796e54cc03029de269381b404c426bae927bada"},
 
]
 
rich = [
 
    {file = "rich-11.2.0-py3-none-any.whl", hash = "sha256:d5f49ad91fb343efcae45a2b2df04a9755e863e50413623ab8c9e74f05aee52b"},
 
    {file = "rich-11.2.0.tar.gz", hash = "sha256:1a6266a5738115017bb64a66c59c717e7aa047b3ae49a011ede4abdeffc6536e"},
 
]
 
six = [
 
    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
 
    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 
]
 
soupsieve = [
 
    {file = "soupsieve-2.3.1-py3-none-any.whl", hash = "sha256:1a3cca2617c6b38c0343ed661b1fa5de5637f257d4fe22bd9f1338010a1efefb"},
 
    {file = "soupsieve-2.3.1.tar.gz", hash = "sha256:b8d49b1cd4f037c7082a9683dfa1801aa2597fb11c3a1155b7a5b94829b4f1f9"},
 
]
 
tomli = [
 
    {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"},
 
    {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"},
 
]
 
typing-extensions = [
 
    {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
 
    {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"},
 
]
 
url-normalize = [
 
    {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"},
 
    {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"},
 
]
 
urllib3 = [
 
    {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
 
    {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
 
]
 
vermin = [
 
    {file = "vermin-1.3.3-py2.py3-none-any.whl", hash = "sha256:c5bd8bf2026d87563332293a0479606954bd973d8cf72ab370e1164a019c0524"},
 
]
pyproject.toml
Show inline comments
 
[tool.poetry]
 
name = "stockcli"
 
version = "0.0.1"
 
description = ""
 
authors = ["Dennis Fink <dennis.fink@c3l.lu>"]
 

	
 
[tool.poetry.dependencies]
 
python = "^3.10"
 
requests = "^2.27.1"
 
click = "^8.0.3"
 
rich = "^11.2.0"
 
isbnlib = "^3.10.10"
 
isbnlib-worldcat2 = "^0.1.2"
 
requests-cache = "^0.9.3"
 

	
 
[tool.poetry.dev-dependencies]
 
mypy = "^0.931"
 
black = "^22.1.0"
 
isort = "^5.10.1"
 
vermin = "^1.3.3"
 

	
 
[build-system]
 
requires = ["poetry-core>=1.0.0"]
 
build-backend = "poetry.core.masonry.api"
stockcli/books.py
Show inline comments
 
new file 100644
 
import logging
 

	
 
import isbnlib
 
from rich.panel import Panel
 
from rich.table import Table
 

	
 
from . import utils
 
from .console import DEFAULT_PADDING, console, int_prompt, prompt
 
from .style import GreenBoldText
 

	
 

	
 
def add_book_by_barcode(barcode: str) -> None:
 

	
 
    canonical_isbn = isbnlib.canonical(barcode)
 
    if not (isbnlib.is_isbn10(canonical_isbn) or isbnlib.is_isbn13(canonical_isbn)):
 
        logging.error(f"{barcode} is not a valid ISBN!")
 
        error_console.print(f"{barcode} is not a valid ISBN!")
 
        return
 

	
 
    userentity = utils.get_request("objects/userentities?query[]=name=books")
 
    userentity_id = userentity[0]["id"]
 

	
 
    all_books = utils.get_request(
 
        f"objects/userobjects?query[]=userentity_id={userentity_id}"
 
    )
 

	
 
    for book in all_books:
 
        book_metadata = utils.get_request(
 
            f"userfields/userentity-books/{book['id']}", cached=True
 
        )
 
        if isbnlib.canonical(book_metadata["isbn"]) == canonical_isbn:
 

	
 
            book_metadata = utils.get_request(
 
                f"userfields/userentity-books/{book['id']}", cached=False
 
            )
 

	
 
            grid = Table.grid(padding=DEFAULT_PADDING)
 
            grid.add_column(justify="right", no_wrap=True)
 
            grid.add_column(justify="left", style="cyan", no_wrap=True)
 
            grid.add_row(GreenBoldText("Title:"), book_metadata["title"])
 
            grid.add_row(GreenBoldText("Amount:"), book_metadata["amount"])
 
            console.print(
 
                Panel(grid, title="[green bold]Book already found[/green bold]")
 
            )
 

	
 
            add_to_amount = bool(
 
                int_prompt.ask("Add? (Enter 0 to abort)", choices=["0", "1"], default=0)
 
            )
 
            if not add_to_amount:
 
                logging.debug("User aborted task!")
 
                return
 
            else:
 
                book_metadata["amount"] = str(int(book_metadata["amount"]) + 1)
 
                response = utils.put_request(
 
                    f"userfields/userentity-books/{book['id']}",
 
                    book_metadata,
 
                    cached=True,
 
                )
 
                console.print("Successfully updated!")
 
                return
 

	
 
    metadata = isbnlib.meta(canonical_isbn, "worldcat")
 

	
 
    grid = Table.grid(padding=DEFAULT_PADDING)
 
    grid.add_column(justify="right", no_wrap=True)
 
    grid.add_column(justify="left", style="cyan", no_wrap=True)
 
    grid.add_row(GreenBoldText("Title:"), metadata["Title"])
 
    grid.add_row(GreenBoldText("Author(s)"), ", ".join(metadata["Authors"]))
 
    console.print(Panel(grid, title="[green bold]Book Info[/green bold]"))
 

	
 
    ok = bool(
 
        int_prompt.ask(
 
            "Is the metadata correct? (Enter 0 to abort)", choices=["0", "1"], default=0
 
        )
 
    )
 
    if not ok:
 
        logging.debug("User aborted task!")
 
        return
 
    new_book_id = utils.post_request(
 
        "objects/userobjects", {"userentity_id": userentity_id}
 
    )["created_object_id"]
 

	
 
    response = utils.put_request(
 
        f"userfields/userentity-books/{new_book_id}",
 
        {
 
            "title": metadata["Title"],
 
            "isbn": isbnlib.mask(canonical_isbn),
 
            "authors": "\n".join(metadata["Authors"]),
 
            "amount": "1",
 
            "categories": None,
 
        },
 
    )
 
    console.print("Successfully added!")
 
    return
 

	
 

	
 
def update_isbn(barcode: str) -> None:
 
    userentity = utils.get_request("objects/userentities?query[]=name=books")
 
    userentity_id = userentity[0]["id"]
 

	
 
    all_books = utils.get_request(
 
        f"objects/userobjects?query[]=userentity_id={userentity_id}"
 
    )
 

	
 
    for book in all_books:
 
        book_metadata = utils.get_request(
 
            f"userfields/userentity-books/{book['id']}", cached=False
 
        )
 
        console.print("Handling")
 
        console.print(book_metadata)
 
        try:
 
            book_metadata["isbn"] = isbnlib.mask(
 
                isbnlib.canonical(book_metadata["isbn"])
 
            )
 
            response = utils.put_request(
 
                f"userfields/userentity-books/{book['id']}",
 
                book_metadata,
 
            )
 
        except:
 
            continue
stockcli/cli.py
Show inline comments
 
import json
 
import logging
 
import logging.config
 
from datetime import timedelta
 
from operator import itemgetter
 

	
 
import click
 
import requests
 
import requests_cache
 
from rich.panel import Panel
 
from rich.table import Table
 

	
 
from .books import add_book_by_barcode, update_isbn
 
from .console import DEFAULT_PADDING, console, int_prompt, prompt
 
from .stock import (
 
    add_by_barcode,
 
    get_info_by_barcode,
 
    transfer_by_barcode,
 
    update_by_barcode,
 
)
 
from .utils import prepare_barcode
 

	
 
TASK_MAP = {
 
    "1": ("Transfer stock", transfer_by_barcode),
 
    "2": ("Add stock", add_by_barcode),
 
    "3": ("Update stock", update_by_barcode),
 
    "4": ("Get product info", get_info_by_barcode),
 
    "5": ("Add book", add_book_by_barcode),
 
}
 

	
 

	
 
@click.command(context_settings={"help_option_names": ("-h", "--help", "-?")})
 
@click.option(
 
    "-c",
 
    "--config",
 
    "configfile",
 
    default="/etc/stockcli.json",
 
    type=click.File("r"),
 
    help="Config file to load",
 
)
 
@click.pass_context
 
def stockcli(ctx: click.Context, configfile: click.File) -> None:
 

	
 
    config = json.load(configfile)  # type: ignore
 
    logging.config.dictConfig(config["logging"])
 

	
 
    ctx.ensure_object(dict)
 
    ctx.obj["request_session"] = requests.Session()
 
    ctx.obj["request_session"].headers.update(
 
        {
 
            "accept": "application/json",
 
            "GROCY-API-KEY": config["grocy"]["apikey"],
 
        }
 
    )
 
    ctx.obj["cached_session"] = requests_cache.CachedSession(
 
        "stockcli",
 
        ignored_parameters=["GROCY-API-KEY"],
 
        expire_after=timedelta(days=1),
 
        cache_control=True,
 
        allowable_methods=["GET", "POST"],
 
        allowable_codes=[200, 400],
 
        match_headers=True,
 
        stale_if_error=True,
 
    )
 
    ctx.obj["cached_session"].headers.update(
 
        {
 
            "accept": "application/json",
 
            "GROCY-API-KEY": config["grocy"]["apikey"],
 
        }
 
    )
 
    ctx.obj["base_url"] = f"{config['grocy']['url']}/api/"
 

	
 
    menu = Table.grid(padding=DEFAULT_PADDING)
 
    menu.add_column(justify="left", style="green", no_wrap=True)
 
    menu.add_column(justify="left", style="cyan", no_wrap=True)
 

	
 
    for task_id, task in sorted(TASK_MAP.items(), key=itemgetter(0)):
 
        menu.add_row(task_id, task[0])
 

	
 
    while True:
 
        click.clear()
 
        console.print(Panel(menu, title="[green bold]Menu[/green bold]"))
 
        choice = prompt.ask(
 
            "Enter a number to select a task", choices=list(TASK_MAP.keys())
 
        )
 
        logging.debug(f"User selected task: {choice}")
 
        selected_task = TASK_MAP[choice][1]
 

	
 
        rerun = True
 
        while rerun:
 
            barcode = prompt.ask("Please scan the barcode")
 
            barcode = prepare_barcode(barcode)
 

	
 
            try:
 
                selected_task(barcode)
 
            except (
 
                requests.Timeout,
 
                requests.ConnectionError,
 
                requests.HTTPError,
 
                requests.TooManyRedirects,
 
            ):
 
                rerun = False
 
                console.input("Continue")
 
            else:
 
                rerun = bool(
 
                    int_prompt.ask(
 
                        "Do the same with another product",
 
                        choices=["0", "1"],
 
                        default=0,
 
                    )
 
                )
 

	
 

	
 
if __name__ == "__main__":
 
    stockcli()
stockcli/utils.py
Show inline comments
 
import logging
 
import string
 
from operator import attrgetter
 
from typing import Any, Dict, Optional
 

	
 
import click
 
import requests
 

	
 
from .console import error_console
 

	
 
UNALLOWED_CHARACTERS = str.maketrans(dict((c, None) for c in string.whitespace))
 

	
 

	
 
def make_request(method: str, url_path: str, data: Optional[Any] = None) -> Any:
 
def make_request(
 
    method: str, url_path: str, data: Optional[Any] = None, *, cached: bool = False
 
) -> Any:
 
    obj = click.get_current_context().obj
 
    session = obj["request_session"]
 

	
 
    if cached:
 
        session = obj["cached_session"]
 
    else:
 
        session = obj["request_session"]
 
    base_url = obj["base_url"]
 
    requested_url = base_url + url_path
 

	
 
    method_function = attrgetter(method)
 

	
 
    try:
 
        if data is not None:
 
            logging.debug(
 
                f"Making {method.upper()} request: {requested_url} with {data}"
 
            )
 
            response = method_function(session)(requested_url, json=data)
 
        else:
 
            logging.debug(f"Making {method.upper()} request: {requested_url}")
 
            response = method_function(session)(requested_url)
 
        response.raise_for_status()
 
    except requests.Timeout:
 
        logging.error(f"The connection to {requested_url} timed out!")
 
        error_console.print("Connection timed out!")
 
        raise
 
    except requests.ConnectionError:
 
        logging.error(f"Couldn't establish a connection to {requested_url}!")
 
        error_console.print("Couldn't establish a connection!")
 
        raise
 
    except requests.HTTPError:
 
        logging.error(f"{requested_url} sent back an HTTPError")
 
        error_console.print("Got the following error:")
 
        error_message = response.json()["error_message"]
 
        logging.error(error_message)
 
        error_console.print(error_message)
 
        raise
 
    except requests.TooManyRedirects:
 
        logging.error(f"{requested_url} had too many redirects!")
 
        error_console.print("Too many redirects!")
 
        raise
 
    else:
 
        return response.json()
 

	
 

	
 
def get_request(url_path: str) -> Any:
 
    return make_request("get", url_path)
 
        try:
 
            return response.json()
 
        except requests.JSONDecodeError:
 
            return response
 

	
 

	
 
def post_request(url_path: str, data: Dict[str, Any]) -> Any:
 
    return make_request("post", url_path, data)
 
def get_request(url_path: str, *, cached: bool = False) -> Any:
 
    return make_request("get", url_path, cached=cached)
 

	
 

	
 
def put_request(url_path: str, data: Dict[str, Any]) -> Any:
 
    return make_request("put", url_path, data)
 
def post_request(url_path: str, data: Dict[str, Any], *, cached: bool = False) -> Any:
 
    return make_request("post", url_path, data, cached=cached)
 

	
 

	
 
def put_request(url_path: str, data: Dict[str, Any], *, cached: bool = False) -> Any:
 
    return make_request("put", url_path, data, cached=cached)
 

	
 

	
 
def prepare_barcode(barcode: str) -> str:
 
    return barcode.translate(UNALLOWED_CHARACTERS)
0 comments (0 inline, 0 general)