diff --git a/.travis.yml b/.travis.yml index c4a27f7..00adc4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ services: - docker python: - "3.6" -before_install: - - travis_retry docker pull existdb/existdb:4.7.1 - - docker run -d -p 8080:8080 -p 8443:8443 --name exist-4.7.1 existdb/existdb:4.7.1 install: - travis_retry pip install poetry - travis_retry poetry install diff --git a/README.rst b/README.rst index f1facf2..61612d5 100644 --- a/README.rst +++ b/README.rst @@ -22,29 +22,40 @@ It supports basic CRUD operations and uses `delb `_ pip install snakesist +``snakesist`` allows you to access individual documents from the database with a ``delb.Document``, either by simply using a URL -Usage example -------------- +.. code-block:: python + + >>> from delb import Document + + >>> manifest = Document("existdb://admin:@localhost:8080/exist/db/manifestos/dada_manifest.xml") + >>> [header.full_text for header in manifest.xpath("//head")] + ["Hugo Ball", "Das erste dadaistische Manifest"] + +or by instantiating a database client which you can subsequently reuse .. code-block:: python - from snakesist import ExistClient + >>> from snakesist import ExistClient - db = ExistClient() + >>> my_local_db = ExistClient(host="localhost", port=8080, user="admin", password="", root_collection="/db/manifestos") + >>> dada_manifest = Document("dada_manifest.xml", existdb_client=my_local_db) + >>> [header.full_text for header in dada_manifest.xpath("//head")] + ["Hugo Ball", "Das erste dadaistische Manifest"] + >>> communist_manifest = Document("communist_manifest.xml", existdb_client=my_local_db) + >>> communist_manifest.xpath("//head").first.full_text + "Manifest der Kommunistischen Partei" - db.root_collection = '/db/foo/bar' - # the client will only query from this point downwards - names = db.retrieve_resources('//*:persName') - # note the namespace wildcard in the XPath expression +and not only for accessing individual documents, but also for querying data across multiple documents + +.. code-block:: python + + >>> all_headers = my_local_db.xpath("//*:head") + >>> [header.full_text for header in all_headers] + ["Hugo Ball", "Das erste dadaistische Manifest", "Manifest der Kommunistischen Partei", "I. Bourgeois und Proletarier.", "II. Proletarier und Kommunisten", "III. Sozialistische und kommunistische Literatur", "IV. Stellung der Kommunisten zu den verschiedenen oppositionellen Parteien"] - # append 'Python' to all names which are 'Monty' and delete the rest - for name in names: - if name.node.full_text == 'Monty': - name.node.append_child(' Python') - name.update_push() - else: - name.delete() +You can of course also modify and store documents back into the database or create new ones and store them. Your eXist instance @@ -57,4 +68,6 @@ for database queries. This means that allowing database queries using the backend. eXist allows this by default, so if you haven't configured your instance otherwise, don't worry about it. -``snakesist`` is tested with eXist 4.7.1 and is not compatible yet with eXist 5.0.0. +Please note that ``snakesist`` is tested with eXist 4.7.1 and is not compatible yet +with version 5. The bug preventing ``snakesist`` to be compatible with the newest major eXist +version will be fixed with the release of eXist 5.3.0. diff --git a/docs/api_doc.rst b/docs/api_doc.rst index 4c94abd..045c386 100644 --- a/docs/api_doc.rst +++ b/docs/api_doc.rst @@ -4,7 +4,6 @@ API Documentation Database Client --------------- -.. automodule:: snakesist .. autoclass:: snakesist.ExistClient Resources @@ -12,4 +11,6 @@ Resources .. autoclass:: snakesist.delb_plugins.ExistDBExtension +.. autofunction:: snakesist.delb_plugins.existdb_loader + .. autoclass:: snakesist.NodeResource diff --git a/docs/conf.py b/docs/conf.py index d4ecf91..2c4002f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,6 +51,7 @@ intersphinx_mapping = { 'delb': ('https://delb.readthedocs.io/en/latest/', None), + 'cpython': ('https://docs.python.org', None) } diff --git a/poetry.lock b/poetry.lock index d745fd6..a1ebb4e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,7 +12,7 @@ description = "Atomic file writes." name = "atomicwrites" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.3.0" +version = "1.4.0" [[package]] category = "dev" @@ -20,12 +20,13 @@ description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.1.0" +version = "19.3.0" [package.extras] -dev = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface", "sphinx", "pre-commit"] +azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] +dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] docs = ["sphinx", "zope.interface"] -tests = ["coverage", "hypothesis", "pympler", "pytest", "six", "zope.interface"] +tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] category = "dev" @@ -33,7 +34,7 @@ description = "Internationalization utilities" name = "babel" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.7.0" +version = "2.8.0" [package.dependencies] pytz = ">=2015.7" @@ -43,8 +44,8 @@ category = "dev" description = "Modern password hashing for your software and your servers" name = "bcrypt" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.1.7" +python-versions = ">=3.6" +version = "3.2.0" [package.dependencies] cffi = ">=1.1" @@ -52,6 +53,7 @@ six = ">=1.4.1" [package.extras] tests = ["pytest (>=3.2.1,<3.3.0 || >3.3.0)"] +typecheck = ["mypy"] [[package]] category = "dev" @@ -67,7 +69,7 @@ description = "Python package for providing Mozilla's CA Bundle." name = "certifi" optional = false python-versions = "*" -version = "2019.9.11" +version = "2020.6.20" [[package]] category = "dev" @@ -75,7 +77,7 @@ description = "Foreign Function Interface for Python calling C code." name = "cffi" optional = false python-versions = "*" -version = "1.14.0" +version = "1.14.2" [package.dependencies] pycparser = "*" @@ -94,8 +96,8 @@ description = "Cross-platform colored terminal text." marker = "python_version >= \"3.5\" and sys_platform == \"win32\" or sys_platform == \"win32\"" name = "colorama" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.4.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.4.3" [[package]] category = "dev" @@ -103,17 +105,17 @@ description = "cryptography is a package which provides cryptographic recipes an name = "cryptography" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "2.9.2" +version = "3.1" [package.dependencies] cffi = ">=1.8,<1.11.3 || >1.11.3" six = ">=1.4.1" [package.extras] -docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0)", "sphinx-rtd-theme"] +docs = ["sphinx (>=1.6.5,<1.8.0 || >1.8.0,<3.1.0 || >3.1.0,<3.1.1 || >3.1.1)", "sphinx-rtd-theme"] docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -idna = ["idna (>=2.1)"] -pep8test = ["flake8", "flake8-import-order", "pep8-naming"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +ssh = ["bcrypt (>=3.1.5)"] test = ["pytest (>=3.6.0,<3.9.0 || >3.9.0,<3.9.1 || >3.9.1,<3.9.2 || >3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,<3.79.2 || >3.79.2)"] [[package]] @@ -130,16 +132,34 @@ description = "A library that provides an ergonomic model for XML encoded text d name = "delb" optional = false python-versions = ">=3.6,<4.0" -version = "0.2b3" +version = "0.2" [package.dependencies] cssselect = "*" +fastcache = ">=1.1.0,<2.0.0" lxml = "*" setuptools = "*" +[package.dependencies.requests] +optional = true +version = ">=2.21,<3.0" + [package.extras] https-loader = ["requests (>=2.21,<3.0)"] +[[package]] +category = "dev" +description = "A package with spare non-sense plugins for delb as developer's reference." +name = "delb-reference-plugins" +optional = false +python-versions = ">=3.6,<4.0" +version = "0.2" + +[package.dependencies] +[package.dependencies.delb] +extras = ["https-loader"] +version = "0.2" + [[package]] category = "dev" description = "Distro - an OS platform information API" @@ -154,9 +174,10 @@ description = "A Python library for the Docker Engine API." name = "docker" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "4.2.1" +version = "4.3.1" [package.dependencies] +pywin32 = "227" requests = ">=2.14.2,<2.18.0 || >2.18.0" six = ">=1.4.0" websocket-client = ">=0.32.0" @@ -165,10 +186,6 @@ websocket-client = ">=0.32.0" optional = true version = ">=2.4.2" -[package.dependencies.pypiwin32] -python = ">=3.6" -version = "223" - [package.extras] ssh = ["paramiko (>=2.4.2)"] tls = ["pyOpenSSL (>=17.5.0)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] @@ -178,8 +195,8 @@ category = "dev" description = "Multi-container orchestration for Docker" name = "docker-compose" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.26.0" +python-versions = ">=3.4" +version = "1.27.2" [package.dependencies] PyYAML = ">=3.10,<6" @@ -191,17 +208,16 @@ docopt = ">=0.6.1,<1" jsonschema = ">=2.5.1,<4" python-dotenv = ">=0.13.0,<1" requests = ">=2.20.0,<3" -six = ">=1.3.0,<2" texttable = ">=0.9.0,<2" websocket-client = ">=0.32.0,<1" [package.dependencies.docker] extras = ["ssh"] -version = ">=3.7.0,<5" +version = ">=4.3.1,<5" [package.extras] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2)"] -tests = ["ddt (>=1.2.2,<2)", "mock (>=1.0.1,<4)", "pytest (<6)"] +tests = ["ddt (>=1.2.2,<2)", "pytest (<6)"] [[package]] category = "dev" @@ -227,8 +243,16 @@ category = "dev" description = "Docutils -- Python Documentation Utilities" name = "docutils" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "0.15.2" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "0.16" + +[[package]] +category = "main" +description = "C implementation of Python 3 functools.lru_cache" +name = "fastcache" +optional = false +python-versions = "*" +version = "1.1.0" [[package]] category = "main" @@ -236,7 +260,7 @@ description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.8" +version = "2.10" [[package]] category = "dev" @@ -244,7 +268,7 @@ description = "Getting image size from png/jpeg/jpeg2000/gif file" name = "imagesize" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.1.0" +version = "1.2.0" [[package]] category = "dev" @@ -252,23 +276,23 @@ description = "Read metadata from Python packages" marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\"" name = "importlib-metadata" optional = false -python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3" -version = "0.23" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +version = "1.7.0" [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] -testing = ["packaging", "importlib-resources"] +testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] category = "dev" -description = "A small but fast and easy to use stand-alone template engine written in pure python." +description = "A very fast and expressive template engine." name = "jinja2" optional = false -python-versions = "*" -version = "2.10.1" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +version = "2.11.2" [package.dependencies] MarkupSafe = ">=0.23" @@ -304,7 +328,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li name = "lxml" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" -version = "4.4.1" +version = "4.5.2" [package.extras] cssselect = ["cssselect (>=0.7)"] @@ -325,8 +349,8 @@ category = "dev" description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false -python-versions = ">=3.4" -version = "7.2.0" +python-versions = ">=3.5" +version = "8.5.0" [[package]] category = "dev" @@ -335,7 +359,7 @@ marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_versi name = "mypy" optional = false python-versions = ">=3.5" -version = "0.770" +version = "0.782" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -348,7 +372,7 @@ dmypy = ["psutil (>=4.0)"] [[package]] category = "dev" description = "Experimental type system extensions for programs checked with the mypy typechecker." -marker = "python_version >= \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\")" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\"" name = "mypy-extensions" optional = false python-versions = "*" @@ -360,7 +384,7 @@ description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "19.2" +version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" @@ -372,7 +396,7 @@ description = "SSH2 protocol library" name = "paramiko" optional = false python-versions = "*" -version = "2.7.1" +version = "2.7.2" [package.dependencies] bcrypt = ">=3.1.3" @@ -391,7 +415,7 @@ description = "plugin and hook calling mechanisms for python" name = "pluggy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.0" +version = "0.13.1" [package.dependencies] [package.dependencies.importlib-metadata] @@ -407,7 +431,7 @@ description = "library with cross-python path, ini-parsing, io, code, log facili name = "py" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.8.0" +version = "1.9.0" [[package]] category = "dev" @@ -422,8 +446,8 @@ category = "dev" description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.4.2" +python-versions = ">=3.5" +version = "2.7.0" [[package]] category = "dev" @@ -447,30 +471,15 @@ description = "Python parsing module" name = "pyparsing" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.2" - -[[package]] -category = "dev" -description = "" -marker = "sys_platform == \"win32\" and python_version >= \"3.6\"" -name = "pypiwin32" -optional = false -python-versions = "*" -version = "223" - -[package.dependencies] -pywin32 = ">=223" +version = "2.4.7" [[package]] category = "dev" description = "Persistent/Functional/Immutable data structures" name = "pyrsistent" optional = false -python-versions = "*" -version = "0.16.0" - -[package.dependencies] -six = "*" +python-versions = ">=3.5" +version = "0.17.3" [[package]] category = "dev" @@ -528,7 +537,7 @@ description = "Add .env support to your django/flask apps in development and dep name = "python-dotenv" optional = false python-versions = "*" -version = "0.13.0" +version = "0.14.0" [package.extras] cli = ["click (>=5.0)"] @@ -539,16 +548,16 @@ description = "World timezone definitions, modern and historical" name = "pytz" optional = false python-versions = "*" -version = "2019.2" +version = "2020.1" [[package]] category = "dev" description = "Python for Window Extensions" -marker = "sys_platform == \"win32\" and python_version >= \"3.6\"" +marker = "sys_platform == \"win32\"" name = "pywin32" optional = false python-versions = "*" -version = "228" +version = "227" [[package]] category = "dev" @@ -564,16 +573,16 @@ description = "Python HTTP for Humans." name = "requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.22.0" +version = "2.24.0" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<3.1.0" -idna = ">=2.5,<2.9" +chardet = ">=3.0.2,<4" +idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"] +security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] [[package]] @@ -581,8 +590,8 @@ category = "dev" description = "Python 2 and 3 compatibility utilities" name = "six" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*" -version = "1.12.0" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +version = "1.15.0" [[package]] category = "dev" @@ -590,7 +599,7 @@ description = "This package provides 26 stemmers for 25 languages generated from name = "snowballstemmer" optional = false python-versions = "*" -version = "1.9.1" +version = "2.0.0" [[package]] category = "dev" @@ -598,13 +607,13 @@ description = "Python documentation generator" name = "sphinx" optional = false python-versions = ">=3.5" -version = "2.2.0" +version = "3.2.1" [package.dependencies] Jinja2 = ">=2.3" Pygments = ">=2.0" alabaster = ">=0.7,<0.8" -babel = ">=1.3,<2.0 || >2.0" +babel = ">=1.3" colorama = ">=0.3.5" docutils = ">=0.12" imagesize = "*" @@ -621,7 +630,8 @@ sphinxcontrib-serializinghtml = "*" [package.extras] docs = ["sphinxcontrib-websupport"] -test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.720)", "docutils-stubs"] +lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-stubs"] +test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"] [[package]] category = "dev" @@ -636,36 +646,39 @@ sphinx = "*" [[package]] category = "dev" -description = "" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" name = "sphinxcontrib-applehelp" optional = false -python-versions = "*" -version = "1.0.1" +python-versions = ">=3.5" +version = "1.0.2" [package.extras] -test = ["pytest", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." name = "sphinxcontrib-devhelp" optional = false -python-versions = "*" -version = "1.0.1" +python-versions = ">=3.5" +version = "1.0.2" [package.extras] -test = ["pytest", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" name = "sphinxcontrib-htmlhelp" optional = false -python-versions = "*" -version = "1.0.2" +python-versions = ">=3.5" +version = "1.0.3" [package.extras] -test = ["pytest", "flake8", "mypy", "html5lib"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] [[package]] category = "dev" @@ -680,25 +693,27 @@ test = ["pytest", "flake8", "mypy"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." name = "sphinxcontrib-qthelp" optional = false -python-versions = "*" -version = "1.0.2" +python-versions = ">=3.5" +version = "1.0.3" [package.extras] -test = ["pytest", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" -description = "" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." name = "sphinxcontrib-serializinghtml" optional = false -python-versions = "*" -version = "1.1.3" +python-versions = ">=3.5" +version = "1.1.4" [package.extras] -test = ["pytest", "flake8", "mypy"] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] [[package]] category = "dev" @@ -706,49 +721,37 @@ description = "module for creating simple ASCII tables" name = "texttable" optional = false python-versions = "*" -version = "1.6.2" +version = "1.6.3" [[package]] category = "dev" description = "a fork of Python 2 and 3 ast modules with type comment support" -marker = "python_version >= \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\")" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\"" name = "typed-ast" optional = false python-versions = "*" -version = "1.4.0" - -[[package]] -category = "dev" -description = "Type Hints for Python" -marker = "python_version >= \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\") and (python_version >= \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\"))" -name = "typing" -optional = false -python-versions = "*" -version = "3.7.4.1" +version = "1.4.1" [[package]] category = "dev" description = "Backported and Experimental Type Hints for Python 3.5+" -marker = "python_version >= \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\")" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version >= \"3.8\"" name = "typing-extensions" optional = false python-versions = "*" -version = "3.7.4" - -[package.dependencies] -typing = ">=3.7.4" +version = "3.7.4.3" [[package]] category = "main" description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4" -version = "1.25.6" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +version = "1.25.10" [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] [[package]] @@ -765,21 +768,19 @@ six = "*" [[package]] category = "dev" description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\" or python_version >= \"3.5\" and python_version < \"3.8\" and (python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\")" +marker = "python_version >= \"3.5\" and python_version < \"3.8\" or python_version < \"3.8\"" name = "zipp" optional = false -python-versions = ">=2.7" -version = "0.6.0" - -[package.dependencies] -more-itertools = "*" +python-versions = ">=3.6" +version = "3.1.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pathlib2", "contextlib2", "unittest2"] +testing = ["jaraco.itertools", "func-timeout"] [metadata] -content-hash = "432c670fee1cca8a941dc6a42d143d1442e10285c25f5b0a273fcc10dc5b4854" +content-hash = "aa26444b237803d7fc1b3272c208a7ae6e2fe96c9e8b618f11a6cdc508958ed2" +lock-version = "1.0" python-versions = "^3.6" [metadata.files] @@ -788,123 +789,119 @@ alabaster = [ {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] atomicwrites = [ - {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, - {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, ] attrs = [ - {file = "attrs-19.1.0-py2.py3-none-any.whl", hash = "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79"}, - {file = "attrs-19.1.0.tar.gz", hash = "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"}, + {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, + {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, ] babel = [ - {file = "Babel-2.7.0-py2.py3-none-any.whl", hash = "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab"}, - {file = "Babel-2.7.0.tar.gz", hash = "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28"}, + {file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"}, + {file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"}, ] bcrypt = [ - {file = "bcrypt-3.1.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7"}, - {file = "bcrypt-3.1.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31"}, - {file = "bcrypt-3.1.7-cp27-cp27m-win32.whl", hash = "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161"}, - {file = "bcrypt-3.1.7-cp27-cp27m-win_amd64.whl", hash = "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e"}, - {file = "bcrypt-3.1.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0"}, - {file = "bcrypt-3.1.7-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052"}, - {file = "bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105"}, - {file = "bcrypt-3.1.7-cp34-cp34m-win32.whl", hash = "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de"}, - {file = "bcrypt-3.1.7-cp34-cp34m-win_amd64.whl", hash = "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133"}, - {file = "bcrypt-3.1.7-cp35-cp35m-win32.whl", hash = "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5"}, - {file = "bcrypt-3.1.7-cp35-cp35m-win_amd64.whl", hash = "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09"}, - {file = "bcrypt-3.1.7-cp36-cp36m-win32.whl", hash = "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c"}, - {file = "bcrypt-3.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89"}, - {file = "bcrypt-3.1.7-cp37-cp37m-win32.whl", hash = "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294"}, - {file = "bcrypt-3.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"}, - {file = "bcrypt-3.1.7-cp38-cp38-win32.whl", hash = "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1"}, - {file = "bcrypt-3.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752"}, - {file = "bcrypt-3.1.7.tar.gz", hash = "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42"}, + {file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"}, + {file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"}, + {file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"}, + {file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"}, + {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] cached-property = [ {file = "cached-property-1.5.1.tar.gz", hash = "sha256:9217a59f14a5682da7c4b8829deadbfc194ac22e9908ccf7c8820234e80a1504"}, {file = "cached_property-1.5.1-py2.py3-none-any.whl", hash = "sha256:3a026f1a54135677e7da5ce819b0c690f156f37976f3e30c5430740725203d7f"}, ] certifi = [ - {file = "certifi-2019.9.11-py2.py3-none-any.whl", hash = "sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef"}, - {file = "certifi-2019.9.11.tar.gz", hash = "sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50"}, + {file = "certifi-2020.6.20-py2.py3-none-any.whl", hash = "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"}, + {file = "certifi-2020.6.20.tar.gz", hash = "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3"}, ] cffi = [ - {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, - {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, - {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, - {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, - {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, - {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, - {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, - {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, - {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, - {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, - {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, - {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, - {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, - {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, - {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, - {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, - {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, - {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, - {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, - {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, - {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, - {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, + {file = "cffi-1.14.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82"}, + {file = "cffi-1.14.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4"}, + {file = "cffi-1.14.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e"}, + {file = "cffi-1.14.2-cp27-cp27m-win32.whl", hash = "sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c"}, + {file = "cffi-1.14.2-cp27-cp27m-win_amd64.whl", hash = "sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1"}, + {file = "cffi-1.14.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7"}, + {file = "cffi-1.14.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c"}, + {file = "cffi-1.14.2-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731"}, + {file = "cffi-1.14.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0"}, + {file = "cffi-1.14.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e"}, + {file = "cffi-1.14.2-cp35-cp35m-win32.whl", hash = "sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487"}, + {file = "cffi-1.14.2-cp35-cp35m-win_amd64.whl", hash = "sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad"}, + {file = "cffi-1.14.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2"}, + {file = "cffi-1.14.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123"}, + {file = "cffi-1.14.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1"}, + {file = "cffi-1.14.2-cp36-cp36m-win32.whl", hash = "sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281"}, + {file = "cffi-1.14.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4"}, + {file = "cffi-1.14.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798"}, + {file = "cffi-1.14.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4"}, + {file = "cffi-1.14.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f"}, + {file = "cffi-1.14.2-cp37-cp37m-win32.whl", hash = "sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650"}, + {file = "cffi-1.14.2-cp37-cp37m-win_amd64.whl", hash = "sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15"}, + {file = "cffi-1.14.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa"}, + {file = "cffi-1.14.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c"}, + {file = "cffi-1.14.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75"}, + {file = "cffi-1.14.2-cp38-cp38-win32.whl", hash = "sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e"}, + {file = "cffi-1.14.2-cp38-cp38-win_amd64.whl", hash = "sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c"}, + {file = "cffi-1.14.2.tar.gz", hash = "sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, ] colorama = [ - {file = "colorama-0.4.1-py2.py3-none-any.whl", hash = "sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"}, - {file = "colorama-0.4.1.tar.gz", hash = "sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d"}, + {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, + {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] cryptography = [ - {file = "cryptography-2.9.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e"}, - {file = "cryptography-2.9.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b"}, - {file = "cryptography-2.9.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365"}, - {file = "cryptography-2.9.2-cp27-cp27m-win32.whl", hash = "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0"}, - {file = "cryptography-2.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55"}, - {file = "cryptography-2.9.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270"}, - {file = "cryptography-2.9.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf"}, - {file = "cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d"}, - {file = "cryptography-2.9.2-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785"}, - {file = "cryptography-2.9.2-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b"}, - {file = "cryptography-2.9.2-cp35-cp35m-win32.whl", hash = "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae"}, - {file = "cryptography-2.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b"}, - {file = "cryptography-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6"}, - {file = "cryptography-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3"}, - {file = "cryptography-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b"}, - {file = "cryptography-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e"}, - {file = "cryptography-2.9.2-cp38-cp38-win32.whl", hash = "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0"}, - {file = "cryptography-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5"}, - {file = "cryptography-2.9.2.tar.gz", hash = "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229"}, + {file = "cryptography-3.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:969ae512a250f869c1738ca63be843488ff5cc031987d302c1f59c7dbe1b225f"}, + {file = "cryptography-3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b45ab1c6ece7c471f01c56f5d19818ca797c34541f0b2351635a5c9fe09ac2e0"}, + {file = "cryptography-3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:247df238bc05c7d2e934a761243bfdc67db03f339948b1e2e80c75d41fc7cc36"}, + {file = "cryptography-3.1-cp27-cp27m-win32.whl", hash = "sha256:10c9775a3f31610cf6b694d1fe598f2183441de81cedcf1814451ae53d71b13a"}, + {file = "cryptography-3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:9f734423eb9c2ea85000aa2476e0d7a58e021bc34f0a373ac52a5454cd52f791"}, + {file = "cryptography-3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e7563eb7bc5c7e75a213281715155248cceba88b11cb4b22957ad45b85903761"}, + {file = "cryptography-3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:94191501e4b4009642be21dde2a78bd3c2701a81ee57d3d3d02f1d99f8b64a9e"}, + {file = "cryptography-3.1-cp35-abi3-macosx_10_10_x86_64.whl", hash = "sha256:dc3f437ca6353979aace181f1b790f0fc79e446235b14306241633ab7d61b8f8"}, + {file = "cryptography-3.1-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:725875681afe50b41aee7fdd629cedbc4720bab350142b12c55c0a4d17c7416c"}, + {file = "cryptography-3.1-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:321761d55fb7cb256b771ee4ed78e69486a7336be9143b90c52be59d7657f50f"}, + {file = "cryptography-3.1-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:2a27615c965173c4c88f2961cf18115c08fedfb8bdc121347f26e8458dc6d237"}, + {file = "cryptography-3.1-cp35-cp35m-win32.whl", hash = "sha256:e7dad66a9e5684a40f270bd4aee1906878193ae50a4831922e454a2a457f1716"}, + {file = "cryptography-3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4005b38cd86fc51c955db40b0f0e52ff65340874495af72efabb1bb8ca881695"}, + {file = "cryptography-3.1-cp36-abi3-win32.whl", hash = "sha256:cc6096c86ec0de26e2263c228fb25ee01c3ff1346d3cfc219d67d49f303585af"}, + {file = "cryptography-3.1-cp36-abi3-win_amd64.whl", hash = "sha256:2e26223ac636ca216e855748e7d435a1bf846809ed12ed898179587d0cf74618"}, + {file = "cryptography-3.1-cp36-cp36m-win32.whl", hash = "sha256:7a63e97355f3cd77c94bd98c59cb85fe0efd76ea7ef904c9b0316b5bbfde6ed1"}, + {file = "cryptography-3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:4b9e96543d0784acebb70991ebc2dbd99aa287f6217546bb993df22dd361d41c"}, + {file = "cryptography-3.1-cp37-cp37m-win32.whl", hash = "sha256:eb80a288e3cfc08f679f95da72d2ef90cb74f6d8a8ba69d2f215c5e110b2ca32"}, + {file = "cryptography-3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:180c9f855a8ea280e72a5d61cf05681b230c2dce804c48e9b2983f491ecc44ed"}, + {file = "cryptography-3.1-cp38-cp38-win32.whl", hash = "sha256:fa7fbcc40e2210aca26c7ac8a39467eae444d90a2c346cbcffd9133a166bcc67"}, + {file = "cryptography-3.1-cp38-cp38-win_amd64.whl", hash = "sha256:548b0818e88792318dc137d8b1ec82a0ab0af96c7f0603a00bb94f896fbf5e10"}, + {file = "cryptography-3.1.tar.gz", hash = "sha256:26409a473cc6278e4c90f782cd5968ebad04d3911ed1c402fc86908c17633e08"}, ] cssselect = [ {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"}, {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"}, ] delb = [ - {file = "delb-0.2b3-py3-none-any.whl", hash = "sha256:c44fafb860f64da45131cec73158fbfaa75e27dcc34cdee96550bb5e94b3660c"}, - {file = "delb-0.2b3.tar.gz", hash = "sha256:7bf03ab3ac9d3eff03c7a8501de867d3543013f04c2b7b9ece966a2a5d144bf1"}, + {file = "delb-0.2-py3-none-any.whl", hash = "sha256:e6b478d5007c1498fab1c4675f5255722133ca60b5295702c527a3c1dfb8fbce"}, + {file = "delb-0.2.tar.gz", hash = "sha256:099e0ed0ab7f879cc1c6ecccd0ff99e7728113835d3a881b9aeb38c83f076e66"}, +] +delb-reference-plugins = [ + {file = "delb-reference-plugins-0.2.tar.gz", hash = "sha256:5a7cf7fec7f14b72ad4ac9d5973794d2bcd77dee2458ea5c55aa87a3850b3991"}, + {file = "delb_reference_plugins-0.2-py3-none-any.whl", hash = "sha256:974e1bc5712eb20e731b36d6437dd9579e3ddb5b5892c7ff67a317b0c2b8a636"}, ] distro = [ {file = "distro-1.5.0-py2.py3-none-any.whl", hash = "sha256:df74eed763e18d10d0da624258524ae80486432cd17392d9c3d96f5e83cd2799"}, {file = "distro-1.5.0.tar.gz", hash = "sha256:0e58756ae38fbd8fc3020d54badb8eae17c5b9dcbed388b17bb55b8a5928df92"}, ] docker = [ - {file = "docker-4.2.1-py2.py3-none-any.whl", hash = "sha256:672f51aead26d90d1cfce84a87e6f71fca401bbc2a6287be18603583620a28ba"}, - {file = "docker-4.2.1.tar.gz", hash = "sha256:380a20d38fbfaa872e96ee4d0d23ad9beb0f9ed57ff1c30653cbeb0c9c0964f2"}, + {file = "docker-4.3.1-py2.py3-none-any.whl", hash = "sha256:13966471e8bc23b36bfb3a6fb4ab75043a5ef1dac86516274777576bed3b9828"}, + {file = "docker-4.3.1.tar.gz", hash = "sha256:bad94b8dd001a8a4af19ce4becc17f41b09f228173ffe6a4e0355389eef142f2"}, ] docker-compose = [ - {file = "docker-compose-1.26.0.tar.gz", hash = "sha256:7e836102d139aca667d6af53f0f4d942c9459ec24d6dd4f0203d74359b0fd311"}, - {file = "docker_compose-1.26.0-py2.py3-none-any.whl", hash = "sha256:82ddf7b3908635426e7b0f552ad9926ad6448be8c0fdb768505819763a5eb2e6"}, + {file = "docker-compose-1.27.2.tar.gz", hash = "sha256:4675242b1bc3fc5b3a8ae367d34012e250ba413fd2815e365541104086488971"}, + {file = "docker_compose-1.27.2-py2.py3-none-any.whl", hash = "sha256:bc5430f5ba1eaa951382d077938551667c7570ba710c06bac3672a40a60f60e2"}, ] dockerpty = [ {file = "dockerpty-0.4.1.tar.gz", hash = "sha256:69a9d69d573a0daa31bcd1c0774eeed5c15c295fe719c61aca550ed1393156ce"}, @@ -913,57 +910,60 @@ docopt = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] docutils = [ - {file = "docutils-0.15.2-py2-none-any.whl", hash = "sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827"}, - {file = "docutils-0.15.2-py3-none-any.whl", hash = "sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0"}, - {file = "docutils-0.15.2.tar.gz", hash = "sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"}, + {file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"}, + {file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"}, +] +fastcache = [ + {file = "fastcache-1.1.0.tar.gz", hash = "sha256:6de1b16e70335b7bde266707eb401a3aaec220fb66c5d13b02abf0eab8be782b"}, ] idna = [ - {file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"}, - {file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"}, + {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, + {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, ] imagesize = [ - {file = "imagesize-1.1.0-py2.py3-none-any.whl", hash = "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8"}, - {file = "imagesize-1.1.0.tar.gz", hash = "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"}, + {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, + {file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, ] importlib-metadata = [ - {file = "importlib_metadata-0.23-py2.py3-none-any.whl", hash = "sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af"}, - {file = "importlib_metadata-0.23.tar.gz", hash = "sha256:aa18d7378b00b40847790e7c27e11673d7fed219354109d0e7b9e5b25dc3ad26"}, + {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, + {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, ] jinja2 = [ - {file = "Jinja2-2.10.1-py2.py3-none-any.whl", hash = "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"}, - {file = "Jinja2-2.10.1.tar.gz", hash = "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013"}, + {file = "Jinja2-2.11.2-py2.py3-none-any.whl", hash = "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035"}, + {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] jsonschema = [ {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, ] lxml = [ - {file = "lxml-4.4.1-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1f4f214337f6ee5825bf90a65d04d70aab05526c08191ab888cb5149501923c5"}, - {file = "lxml-4.4.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e9c028b5897901361d81a4718d1db217b716424a0283afe9d6735fe0caf70f79"}, - {file = "lxml-4.4.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:02ca7bf899da57084041bb0f6095333e4d239948ad3169443f454add9f4e9cb4"}, - {file = "lxml-4.4.1-cp27-cp27m-win32.whl", hash = "sha256:4dffd405390a45ecb95ab5ab1c1b847553c18b0ef8ed01e10c1c8b1a76452916"}, - {file = "lxml-4.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:c27eaed872185f047bb7f7da2d21a7d8913457678c9a100a50db6da890bc28b9"}, - {file = "lxml-4.4.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:a63b4fd3e2cabdcc9d918ed280bdde3e8e9641e04f3c59a2a3109644a07b9832"}, - {file = "lxml-4.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:fe489d486cd00b739be826e8c1be188ddb74c7a1ca784d93d06fda882a6a1681"}, - {file = "lxml-4.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:726c17f3e0d7a7200718c9a890ccfeab391c9133e363a577a44717c85c71db27"}, - {file = "lxml-4.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17e3950add54c882e032527795c625929613adbd2ce5162b94667334458b5a36"}, - {file = "lxml-4.4.1-cp35-cp35m-win32.whl", hash = "sha256:d11874b3c33ee441059464711cd365b89fa1a9cf19ae75b0c189b01fbf735b84"}, - {file = "lxml-4.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:be78485e5d5f3684e875dab60f40cddace2f5b2a8f7fede412358ab3214c3a6f"}, - {file = "lxml-4.4.1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:891fe897b49abb7db470c55664b198b1095e4943b9f82b7dcab317a19116cd38"}, - {file = "lxml-4.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:17cae1730a782858a6e2758fd20dd0ef7567916c47757b694a06ffafdec20046"}, - {file = "lxml-4.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b0b84408d4eabc6de9dd1e1e0bc63e7731e890c0b378a62443e5741cfd0ae90a"}, - {file = "lxml-4.4.1-cp36-cp36m-win32.whl", hash = "sha256:796685d3969815a633827c818863ee199440696b0961e200b011d79b9394bbe7"}, - {file = "lxml-4.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a471628e20f03dcdfde00770eeaf9c77811f0c331c8805219ca7b87ac17576c5"}, - {file = "lxml-4.4.1-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:760c12276fee05c36f95f8040180abc7fbebb9e5011447a97cdc289b5d6ab6fc"}, - {file = "lxml-4.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0a920ff98cf1aac310470c644bc23b326402d3ef667ddafecb024e1713d485f1"}, - {file = "lxml-4.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6b899931a5648862c7b88c795eddff7588fb585e81cecce20f8d9da16eff96e0"}, - {file = "lxml-4.4.1-cp37-cp37m-win32.whl", hash = "sha256:096b82c5e0ea27ce9138bcbb205313343ee66a6e132f25c5ed67e2c8d960a1bc"}, - {file = "lxml-4.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2e8f77db25b0a96af679e64ff9bf9dddb27d379c9900c3272f3041c4d1327c9d"}, - {file = "lxml-4.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ae88588d687bd476be588010cbbe551e9c2872b816f2da8f01f6f1fda74e1ef0"}, - {file = "lxml-4.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1409b14bf83a7d729f92e2a7fbfe7ec929d4883ca071b06e95c539ceedb6497c"}, - {file = "lxml-4.4.1-cp38-cp38-win32.whl", hash = "sha256:c7fccd08b14aa437fe096c71c645c0f9be0655a9b1a4b7cffc77bcb23b3d61d2"}, - {file = "lxml-4.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:9277562f175d2334744ad297568677056861070399cec56ff06abbe2564d1232"}, - {file = "lxml-4.4.1.tar.gz", hash = "sha256:c81cb40bff373ab7a7446d6bbca0190bccc5be3448b47b51d729e37799bb5692"}, + {file = "lxml-4.5.2-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:74f48ec98430e06c1fa8949b49ebdd8d27ceb9df8d3d1c92e1fdc2773f003f20"}, + {file = "lxml-4.5.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e70d4e467e243455492f5de463b72151cc400710ac03a0678206a5f27e79ddef"}, + {file = "lxml-4.5.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:7ad7906e098ccd30d8f7068030a0b16668ab8aa5cda6fcd5146d8d20cbaa71b5"}, + {file = "lxml-4.5.2-cp27-cp27m-win32.whl", hash = "sha256:92282c83547a9add85ad658143c76a64a8d339028926d7dc1998ca029c88ea6a"}, + {file = "lxml-4.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:05a444b207901a68a6526948c7cc8f9fe6d6f24c70781488e32fd74ff5996e3f"}, + {file = "lxml-4.5.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:94150231f1e90c9595ccc80d7d2006c61f90a5995db82bccbca7944fd457f0f6"}, + {file = "lxml-4.5.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bea760a63ce9bba566c23f726d72b3c0250e2fa2569909e2d83cda1534c79443"}, + {file = "lxml-4.5.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c3f511a3c58676147c277eff0224c061dd5a6a8e1373572ac817ac6324f1b1e0"}, + {file = "lxml-4.5.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:59daa84aef650b11bccd18f99f64bfe44b9f14a08a28259959d33676554065a1"}, + {file = "lxml-4.5.2-cp35-cp35m-win32.whl", hash = "sha256:9dc9006dcc47e00a8a6a029eb035c8f696ad38e40a27d073a003d7d1443f5d88"}, + {file = "lxml-4.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:08fc93257dcfe9542c0a6883a25ba4971d78297f63d7a5a26ffa34861ca78730"}, + {file = "lxml-4.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:121b665b04083a1e85ff1f5243d4a93aa1aaba281bc12ea334d5a187278ceaf1"}, + {file = "lxml-4.5.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5591c4164755778e29e69b86e425880f852464a21c7bb53c7ea453bbe2633bbe"}, + {file = "lxml-4.5.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc411ad324a4486b142c41d9b2b6a722c534096963688d879ea6fa8a35028258"}, + {file = "lxml-4.5.2-cp36-cp36m-win32.whl", hash = "sha256:786aad2aa20de3dbff21aab86b2fb6a7be68064cbbc0219bde414d3a30aa47ae"}, + {file = "lxml-4.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e1cacf4796b20865789083252186ce9dc6cc59eca0c2e79cca332bdff24ac481"}, + {file = "lxml-4.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:80a38b188d20c0524fe8959c8ce770a8fdf0e617c6912d23fc97c68301bb9aba"}, + {file = "lxml-4.5.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ecc930ae559ea8a43377e8b60ca6f8d61ac532fc57efb915d899de4a67928efd"}, + {file = "lxml-4.5.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a76979f728dd845655026ab991df25d26379a1a8fc1e9e68e25c7eda43004bed"}, + {file = "lxml-4.5.2-cp37-cp37m-win32.whl", hash = "sha256:5a9c8d11aa2c8f8b6043d845927a51eb9102eb558e3f936df494e96393f5fd3e"}, + {file = "lxml-4.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:4b4a111bcf4b9c948e020fd207f915c24a6de3f1adc7682a2d92660eb4e84f1a"}, + {file = "lxml-4.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5dd20538a60c4cc9a077d3b715bb42307239fcd25ef1ca7286775f95e9e9a46d"}, + {file = "lxml-4.5.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:2b30aa2bcff8e958cd85d907d5109820b01ac511eae5b460803430a7404e34d7"}, + {file = "lxml-4.5.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:aa8eba3db3d8761db161003e2d0586608092e217151d7458206e243be5a43843"}, + {file = "lxml-4.5.2-cp38-cp38-win32.whl", hash = "sha256:107781b213cf7201ec3806555657ccda67b1fccc4261fb889ef7fc56976db81f"}, + {file = "lxml-4.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:f161af26f596131b63b236372e4ce40f3167c1b5b5d459b29d2514bd8c9dc9ee"}, + {file = "lxml-4.5.2.tar.gz", hash = "sha256:cdc13a1682b2a6241080745b1953719e7fe0850b40a5c71ca574f090a1391df6"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -1001,52 +1001,52 @@ markupsafe = [ {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, ] more-itertools = [ - {file = "more-itertools-7.2.0.tar.gz", hash = "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832"}, - {file = "more_itertools-7.2.0-py3-none-any.whl", hash = "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"}, + {file = "more-itertools-8.5.0.tar.gz", hash = "sha256:6f83822ae94818eae2612063a5101a7311e68ae8002005b5e05f03fd74a86a20"}, + {file = "more_itertools-8.5.0-py3-none-any.whl", hash = "sha256:9b30f12df9393f0d28af9210ff8efe48d10c94f73e5daf886f10c4b0b0b4f03c"}, ] mypy = [ - {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"}, - {file = "mypy-0.770-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754"}, - {file = "mypy-0.770-cp35-cp35m-win_amd64.whl", hash = "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65"}, - {file = "mypy-0.770-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce"}, - {file = "mypy-0.770-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761"}, - {file = "mypy-0.770-cp36-cp36m-win_amd64.whl", hash = "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2"}, - {file = "mypy-0.770-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8"}, - {file = "mypy-0.770-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913"}, - {file = "mypy-0.770-cp37-cp37m-win_amd64.whl", hash = "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9"}, - {file = "mypy-0.770-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1"}, - {file = "mypy-0.770-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27"}, - {file = "mypy-0.770-cp38-cp38-win_amd64.whl", hash = "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"}, - {file = "mypy-0.770-py3-none-any.whl", hash = "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164"}, - {file = "mypy-0.770.tar.gz", hash = "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae"}, + {file = "mypy-0.782-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c"}, + {file = "mypy-0.782-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e"}, + {file = "mypy-0.782-cp35-cp35m-win_amd64.whl", hash = "sha256:c05b9e4fb1d8a41d41dec8786c94f3b95d3c5f528298d769eb8e73d293abc48d"}, + {file = "mypy-0.782-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:6731603dfe0ce4352c555c6284c6db0dc935b685e9ce2e4cf220abe1e14386fd"}, + {file = "mypy-0.782-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:f05644db6779387ccdb468cc47a44b4356fc2ffa9287135d05b70a98dc83b89a"}, + {file = "mypy-0.782-cp36-cp36m-win_amd64.whl", hash = "sha256:b7fbfabdbcc78c4f6fc4712544b9b0d6bf171069c6e0e3cb82440dd10ced3406"}, + {file = "mypy-0.782-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:3fdda71c067d3ddfb21da4b80e2686b71e9e5c72cca65fa216d207a358827f86"}, + {file = "mypy-0.782-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7df6eddb6054d21ca4d3c6249cae5578cb4602951fd2b6ee2f5510ffb098707"}, + {file = "mypy-0.782-cp37-cp37m-win_amd64.whl", hash = "sha256:a4a2cbcfc4cbf45cd126f531dedda8485671545b43107ded25ce952aac6fb308"}, + {file = "mypy-0.782-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6bb93479caa6619d21d6e7160c552c1193f6952f0668cdda2f851156e85186fc"}, + {file = "mypy-0.782-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:81c7908b94239c4010e16642c9102bfc958ab14e36048fa77d0be3289dda76ea"}, + {file = "mypy-0.782-cp38-cp38-win_amd64.whl", hash = "sha256:5dd13ff1f2a97f94540fd37a49e5d255950ebcdf446fb597463a40d0df3fac8b"}, + {file = "mypy-0.782-py3-none-any.whl", hash = "sha256:e0b61738ab504e656d1fe4ff0c0601387a5489ca122d55390ade31f9ca0e252d"}, + {file = "mypy-0.782.tar.gz", hash = "sha256:eff7d4a85e9eea55afa34888dfeaccde99e7520b51f867ac28a48492c0b1130c"}, ] 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"}, ] packaging = [ - {file = "packaging-19.2-py2.py3-none-any.whl", hash = "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108"}, - {file = "packaging-19.2.tar.gz", hash = "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47"}, + {file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"}, + {file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"}, ] paramiko = [ - {file = "paramiko-2.7.1-py2.py3-none-any.whl", hash = "sha256:9c980875fa4d2cb751604664e9a2d0f69096643f5be4db1b99599fe114a97b2f"}, - {file = "paramiko-2.7.1.tar.gz", hash = "sha256:920492895db8013f6cc0179293147f830b8c7b21fdfc839b6bad760c27459d9f"}, + {file = "paramiko-2.7.2-py2.py3-none-any.whl", hash = "sha256:4f3e316fef2ac628b05097a637af35685183111d4bc1b5979bd397c2ab7b5898"}, + {file = "paramiko-2.7.2.tar.gz", hash = "sha256:7f36f4ba2c0d81d219f4595e35f70d56cc94f9ac40a6acdf51d6ca210ce65035"}, ] pluggy = [ - {file = "pluggy-0.13.0-py2.py3-none-any.whl", hash = "sha256:0db4b7601aae1d35b4a033282da476845aa19185c1e6964b25cf324b5e4ec3e6"}, - {file = "pluggy-0.13.0.tar.gz", hash = "sha256:fa5fa1622fa6dd5c030e9cad086fa19ef6a0cf6d7a2d12318e10cb49d6d68f34"}, + {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, + {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] py = [ - {file = "py-1.8.0-py2.py3-none-any.whl", hash = "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa"}, - {file = "py-1.8.0.tar.gz", hash = "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"}, + {file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"}, + {file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"}, ] pycparser = [ {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, ] pygments = [ - {file = "Pygments-2.4.2-py2.py3-none-any.whl", hash = "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127"}, - {file = "Pygments-2.4.2.tar.gz", hash = "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"}, + {file = "Pygments-2.7.0-py3-none-any.whl", hash = "sha256:2df50d16b45b977217e02cba6c8422aaddb859f3d0570a88e09b00eafae89c6e"}, + {file = "Pygments-2.7.0.tar.gz", hash = "sha256:2594e8fdb06fef91552f86f4fd3a244d148ab24b66042036e64f29a291515048"}, ] pynacl = [ {file = "PyNaCl-1.4.0-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff"}, @@ -1067,15 +1067,11 @@ pynacl = [ {file = "PyNaCl-1.4.0.tar.gz", hash = "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505"}, ] pyparsing = [ - {file = "pyparsing-2.4.2-py2.py3-none-any.whl", hash = "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4"}, - {file = "pyparsing-2.4.2.tar.gz", hash = "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80"}, -] -pypiwin32 = [ - {file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"}, - {file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"}, + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] pyrsistent = [ - {file = "pyrsistent-0.16.0.tar.gz", hash = "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3"}, + {file = "pyrsistent-0.17.3.tar.gz", hash = "sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"}, ] pytest = [ {file = "pytest-3.10.1-py2.py3-none-any.whl", hash = "sha256:3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec"}, @@ -1090,26 +1086,26 @@ pytest-mypy = [ {file = "pytest_mypy-0.4.2-py3-none-any.whl", hash = "sha256:3b7b56912d55439d5f447cc609f91caac7f74f0f1c89f1379d04f06bac777c32"}, ] python-dotenv = [ - {file = "python-dotenv-0.13.0.tar.gz", hash = "sha256:3b9909bc96b0edc6b01586e1eed05e71174ef4e04c71da5786370cebea53ad74"}, - {file = "python_dotenv-0.13.0-py2.py3-none-any.whl", hash = "sha256:25c0ff1a3e12f4bde8d592cc254ab075cfe734fc5dd989036716fd17ee7e5ec7"}, + {file = "python-dotenv-0.14.0.tar.gz", hash = "sha256:8c10c99a1b25d9a68058a1ad6f90381a62ba68230ca93966882a4dbc3bc9c33d"}, + {file = "python_dotenv-0.14.0-py2.py3-none-any.whl", hash = "sha256:c10863aee750ad720f4f43436565e4c1698798d763b63234fb5021b6c616e423"}, ] pytz = [ - {file = "pytz-2019.2-py2.py3-none-any.whl", hash = "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"}, - {file = "pytz-2019.2.tar.gz", hash = "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32"}, + {file = "pytz-2020.1-py2.py3-none-any.whl", hash = "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed"}, + {file = "pytz-2020.1.tar.gz", hash = "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"}, ] pywin32 = [ - {file = "pywin32-228-cp27-cp27m-win32.whl", hash = "sha256:37dc9935f6a383cc744315ae0c2882ba1768d9b06700a70f35dc1ce73cd4ba9c"}, - {file = "pywin32-228-cp27-cp27m-win_amd64.whl", hash = "sha256:11cb6610efc2f078c9e6d8f5d0f957620c333f4b23466931a247fb945ed35e89"}, - {file = "pywin32-228-cp35-cp35m-win32.whl", hash = "sha256:1f45db18af5d36195447b2cffacd182fe2d296849ba0aecdab24d3852fbf3f80"}, - {file = "pywin32-228-cp35-cp35m-win_amd64.whl", hash = "sha256:6e38c44097a834a4707c1b63efa9c2435f5a42afabff634a17f563bc478dfcc8"}, - {file = "pywin32-228-cp36-cp36m-win32.whl", hash = "sha256:ec16d44b49b5f34e99eb97cf270806fdc560dff6f84d281eb2fcb89a014a56a9"}, - {file = "pywin32-228-cp36-cp36m-win_amd64.whl", hash = "sha256:a60d795c6590a5b6baeacd16c583d91cce8038f959bd80c53bd9a68f40130f2d"}, - {file = "pywin32-228-cp37-cp37m-win32.whl", hash = "sha256:af40887b6fc200eafe4d7742c48417529a8702dcc1a60bf89eee152d1d11209f"}, - {file = "pywin32-228-cp37-cp37m-win_amd64.whl", hash = "sha256:00eaf43dbd05ba6a9b0080c77e161e0b7a601f9a3f660727a952e40140537de7"}, - {file = "pywin32-228-cp38-cp38-win32.whl", hash = "sha256:fa6ba028909cfc64ce9e24bcf22f588b14871980d9787f1e2002c99af8f1850c"}, - {file = "pywin32-228-cp38-cp38-win_amd64.whl", hash = "sha256:9b3466083f8271e1a5eb0329f4e0d61925d46b40b195a33413e0905dccb285e8"}, - {file = "pywin32-228-cp39-cp39-win32.whl", hash = "sha256:ed74b72d8059a6606f64842e7917aeee99159ebd6b8d6261c518d002837be298"}, - {file = "pywin32-228-cp39-cp39-win_amd64.whl", hash = "sha256:8319bafdcd90b7202c50d6014efdfe4fde9311b3ff15fd6f893a45c0868de203"}, + {file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"}, + {file = "pywin32-227-cp27-cp27m-win_amd64.whl", hash = "sha256:4cdad3e84191194ea6d0dd1b1b9bdda574ff563177d2adf2b4efec2a244fa116"}, + {file = "pywin32-227-cp35-cp35m-win32.whl", hash = "sha256:f4c5be1a293bae0076d93c88f37ee8da68136744588bc5e2be2f299a34ceb7aa"}, + {file = "pywin32-227-cp35-cp35m-win_amd64.whl", hash = "sha256:a929a4af626e530383a579431b70e512e736e9588106715215bf685a3ea508d4"}, + {file = "pywin32-227-cp36-cp36m-win32.whl", hash = "sha256:300a2db938e98c3e7e2093e4491439e62287d0d493fe07cce110db070b54c0be"}, + {file = "pywin32-227-cp36-cp36m-win_amd64.whl", hash = "sha256:9b31e009564fb95db160f154e2aa195ed66bcc4c058ed72850d047141b36f3a2"}, + {file = "pywin32-227-cp37-cp37m-win32.whl", hash = "sha256:47a3c7551376a865dd8d095a98deba954a98f326c6fe3c72d8726ca6e6b15507"}, + {file = "pywin32-227-cp37-cp37m-win_amd64.whl", hash = "sha256:31f88a89139cb2adc40f8f0e65ee56a8c585f629974f9e07622ba80199057511"}, + {file = "pywin32-227-cp38-cp38-win32.whl", hash = "sha256:7f18199fbf29ca99dff10e1f09451582ae9e372a892ff03a28528a24d55875bc"}, + {file = "pywin32-227-cp38-cp38-win_amd64.whl", hash = "sha256:7c1ae32c489dc012930787f06244426f8356e129184a02c25aef163917ce158e"}, + {file = "pywin32-227-cp39-cp39-win32.whl", hash = "sha256:c054c52ba46e7eb6b7d7dfae4dbd987a1bb48ee86debe3f245a2884ece46e295"}, + {file = "pywin32-227-cp39-cp39-win_amd64.whl", hash = "sha256:f27cec5e7f588c3d1051651830ecc00294f90728d19c3bf6916e6dba93ea357c"}, ] pyyaml = [ {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, @@ -1125,93 +1121,90 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] requests = [ - {file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"}, - {file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"}, + {file = "requests-2.24.0-py2.py3-none-any.whl", hash = "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"}, + {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, ] six = [ - {file = "six-1.12.0-py2.py3-none-any.whl", hash = "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c"}, - {file = "six-1.12.0.tar.gz", hash = "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"}, + {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, + {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, ] snowballstemmer = [ - {file = "snowballstemmer-1.9.1.tar.gz", hash = "sha256:713e53b79cbcf97bc5245a06080a33d54a77e7cce2f789c835a143bcdb5c033e"}, + {file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"}, + {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, ] sphinx = [ - {file = "Sphinx-2.2.0-py3-none-any.whl", hash = "sha256:839a3ed6f6b092bb60f492024489cc9e6991360fb9f52ed6361acd510d261069"}, - {file = "Sphinx-2.2.0.tar.gz", hash = "sha256:0d586b0f8c2fc3cc6559c5e8fd6124628110514fda0e5d7c82e682d749d2e845"}, + {file = "Sphinx-3.2.1-py3-none-any.whl", hash = "sha256:ce6fd7ff5b215af39e2fcd44d4a321f6694b4530b6f2b2109b64d120773faea0"}, + {file = "Sphinx-3.2.1.tar.gz", hash = "sha256:321d6d9b16fa381a5306e5a0b76cd48ffbc588e6340059a729c6fdd66087e0e8"}, ] sphinx-rtd-theme = [ {file = "sphinx_rtd_theme-0.4.3-py2.py3-none-any.whl", hash = "sha256:00cf895504a7895ee433807c62094cf1e95f065843bf3acd17037c3e9a2becd4"}, {file = "sphinx_rtd_theme-0.4.3.tar.gz", hash = "sha256:728607e34d60456d736cc7991fd236afb828b21b82f956c5ea75f94c8414040a"}, ] sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"}, - {file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"}, + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, ] sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"}, - {file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"}, + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, ] sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"}, - {file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"}, + {file = "sphinxcontrib-htmlhelp-1.0.3.tar.gz", hash = "sha256:e8f5bb7e31b2dbb25b9cc435c8ab7a79787ebf7f906155729338f3156d93659b"}, + {file = "sphinxcontrib_htmlhelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:3c0bc24a2c41e340ac37c85ced6dafc879ab485c095b1d65d2461ac2f7cca86f"}, ] sphinxcontrib-jsmath = [ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, ] sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"}, - {file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"}, + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, ] sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"}, - {file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"}, + {file = "sphinxcontrib-serializinghtml-1.1.4.tar.gz", hash = "sha256:eaa0eccc86e982a9b939b2b82d12cc5d013385ba5eadcc7e4fed23f4405f77bc"}, + {file = "sphinxcontrib_serializinghtml-1.1.4-py2.py3-none-any.whl", hash = "sha256:f242a81d423f59617a8e5cf16f5d4d74e28ee9a66f9e5b637a18082991db5a9a"}, ] texttable = [ - {file = "texttable-1.6.2-py2.py3-none-any.whl", hash = "sha256:7dc282a5b22564fe0fdc1c771382d5dd9a54742047c61558e071c8cd595add86"}, - {file = "texttable-1.6.2.tar.gz", hash = "sha256:eff3703781fbc7750125f50e10f001195174f13825a92a45e9403037d539b4f4"}, + {file = "texttable-1.6.3-py2.py3-none-any.whl", hash = "sha256:f802f2ef8459058736264210f716c757cbf85007a30886d8541aa8c3404f1dda"}, + {file = "texttable-1.6.3.tar.gz", hash = "sha256:ce0faf21aa77d806bbff22b107cc22cce68dc9438f97a2df32c93e9afa4ce436"}, ] typed-ast = [ - {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e"}, - {file = "typed_ast-1.4.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b"}, - {file = "typed_ast-1.4.0-cp35-cp35m-win32.whl", hash = "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4"}, - {file = "typed_ast-1.4.0-cp35-cp35m-win_amd64.whl", hash = "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"}, - {file = "typed_ast-1.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631"}, - {file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233"}, - {file = "typed_ast-1.4.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1"}, - {file = "typed_ast-1.4.0-cp36-cp36m-win32.whl", hash = "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a"}, - {file = "typed_ast-1.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c"}, - {file = "typed_ast-1.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a"}, - {file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e"}, - {file = "typed_ast-1.4.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d"}, - {file = "typed_ast-1.4.0-cp37-cp37m-win32.whl", hash = "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36"}, - {file = "typed_ast-1.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0"}, - {file = "typed_ast-1.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66"}, - {file = "typed_ast-1.4.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2"}, - {file = "typed_ast-1.4.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47"}, - {file = "typed_ast-1.4.0-cp38-cp38-win32.whl", hash = "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161"}, - {file = "typed_ast-1.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e"}, - {file = "typed_ast-1.4.0.tar.gz", hash = "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34"}, -] -typing = [ - {file = "typing-3.7.4.1-py2-none-any.whl", hash = "sha256:c8cabb5ab8945cd2f54917be357d134db9cc1eb039e59d1606dc1e60cb1d9d36"}, - {file = "typing-3.7.4.1-py3-none-any.whl", hash = "sha256:f38d83c5a7a7086543a0f649564d661859c5146a85775ab90c0d2f93ffaa9714"}, - {file = "typing-3.7.4.1.tar.gz", hash = "sha256:91dfe6f3f706ee8cc32d38edbbf304e9b7583fb37108fef38229617f8b3eba23"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, + {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, + {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, + {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, + {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, + {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, + {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, + {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, + {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, + {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, + {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, + {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, + {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, + {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, + {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4-py2-none-any.whl", hash = "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87"}, - {file = "typing_extensions-3.7.4-py3-none-any.whl", hash = "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed"}, - {file = "typing_extensions-3.7.4.tar.gz", hash = "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95"}, + {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, + {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, + {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, ] urllib3 = [ - {file = "urllib3-1.25.6-py2.py3-none-any.whl", hash = "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398"}, - {file = "urllib3-1.25.6.tar.gz", hash = "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"}, + {file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"}, + {file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"}, ] websocket-client = [ {file = "websocket_client-0.57.0-py2.py3-none-any.whl", hash = "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549"}, {file = "websocket_client-0.57.0.tar.gz", hash = "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010"}, ] zipp = [ - {file = "zipp-0.6.0-py2.py3-none-any.whl", hash = "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335"}, - {file = "zipp-0.6.0.tar.gz", hash = "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e"}, + {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, + {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, ] diff --git a/pyproject.toml b/pyproject.toml index 41cc9c0..0a8894c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "snakesist" -version = "0.1.1" +version = "0.2.0" description = "A Python database interface for eXist-db" license = "MIT" authors = ["Theodor Costea ", "Frank Sachsenheim "] @@ -20,8 +20,7 @@ snakesist = "snakesist.delb_plugins" [tool.poetry.dependencies] python = "^3.6" -delb = "^0.2b3" -requests = "^2.22" +delb = { version="^0.2", extras=["https-loader"] } [tool.poetry.dev-dependencies] pytest = "^3.0" @@ -30,6 +29,7 @@ sphinx = "*" sphinx_rtd_theme = "^0.4.3" pytest-docker = "^0.7.2" docker-compose = "^1.26.0" +delb-reference-plugins = "^0.2" [build-system] requires = ["poetry>=0.12"] diff --git a/snakesist/__init__.py b/snakesist/__init__.py index a533988..a3ef6c0 100644 --- a/snakesist/__init__.py +++ b/snakesist/__init__.py @@ -1,4 +1,4 @@ from pkg_resources import get_distribution -from .exist_client import ExistClient, DocumentResource, NodeResource +from snakesist.exist_client import ExistClient, NodeResource __version__ = get_distribution("snakesist").version diff --git a/snakesist/delb_plugins.py b/snakesist/delb_plugins.py index 06f4933..94e29aa 100644 --- a/snakesist/delb_plugins.py +++ b/snakesist/delb_plugins.py @@ -1,86 +1,237 @@ +import re +from functools import wraps +from pathlib import PurePosixPath from types import SimpleNamespace from typing import Any, Dict +from urllib.parse import urlparse +from warnings import warn -from delb.plugins import plugin_manager, DocumentExtensionHooks -from delb.typing import LoaderResult +import requests +from _delb.plugins import plugin_manager, DocumentExtensionHooks +from _delb.plugins.core_loaders import ftp_http_loader +from _delb.plugins.https_loader import https_loader +from _delb.typing import LoaderResult + +from snakesist.exceptions import ( + SnakesistConfigError, + SnakesistWriteError, + SnakesistReadError, + SnakesistNotFound, +) +from snakesist.exist_client import _mangle_path, _validate_filename, ExistClient + + +is_existdb_url = re.compile(r"^existdb(\+https?)?://.+").match @plugin_manager.register_loader() def existdb_loader(source: Any, config: SimpleNamespace) -> LoaderResult: - raise NotImplemented + """ + This loader loads a document from a eXist-db instance. There are two ways to + retrieve a particular document. + One is to specify an URL with the ``existdb://`` scheme, which can optionally be + extended with the transport protocol: ``existdb+http://`` or ``existdb+https://``. -@plugin_manager.register_document_extension -class ExistDBExtension(DocumentExtensionHooks): - """ - This class provides extensions to :class:`delb.Document` in order to interact - with a eXist-db instance. + The overall pattern of the URLs is: + ``existdb[+http[s]]://[[]:[]@][:][/]/`` - Documents can be loaded from an eXist-db instance by either specifying an URL with - the ``existdb://`` scheme or by specifying a path made up of a collection path and - a filename and passing a :class:`snakesist.ExistClient` instance with the - ``existdb_client`` keyword. These two initializations would yield the same - :class:`delb.Document`: + For example: - .. code-block:: + .. code-block:: python - import delb - import snakesist + document = delb.Document("existdb://example.org/exist/corpus/document.xml") - # with an URL - document = delb.Document( - "existdb://example.exist-host.org/exist/collection/document.xml" - ) + Note that omitted ports would default to ``80`` or ``443`` respectively, depending + on the used transport protocol. Which in turn is probed for if not specified, + preferring encrypted connections. + + The other way is to pass a configured client as ``existdb_client`` keyword and a + path as string or :class:`pathlib.PurePosixPath` that points to the document within + the client's configured :attr:`snakesist.ExistClient.root_collection`, hence this + would be an equivalent to the example above, assuming that ``https`` is available on + the addressed host: + + .. code-block:: python - # with a client client = snakesist.ExistClient( - host="example.exist-host.org", - prefix="exist", + transport="https", + host="example.org", + port=443, + user="", + root_collection="/corpus", + ) + document = delb.Document("document.xml", existdb_client=client) + + In both cases the document instance will have a configured ``config`` namespace + ``existdb`` with the property ``client`` which is a :class:`snakesist.ExistClient` + instance. + + Further interaction with the database is facilitated with the + :class:`ExistDBExtension` that extends the :class:`delb.Document` class. + """ + if isinstance(source, str) and is_existdb_url(source): + return load_from_url(source, config) + elif hasattr(config, "existdb"): + return load_from_path(source, config) + else: + return ( + "The input value is neither a existdb URL nor is a existdb client " + "configured." ) - document = delb.Document("/collection/document.xml", existdb_client=client) + + +def load_from_url(source: Any, config: SimpleNamespace) -> LoaderResult: + if hasattr(config, "existdb"): + warn("The current existdb configuration is replaced.") + config.existdb = SimpleNamespace(client=ExistClient.from_url(source)) + path = PurePosixPath(urlparse(source).path).relative_to( + PurePosixPath(f"/{config.existdb.client.prefix}") + ) + result = load_from_path(path, config) + if isinstance(result, tuple): + config.source_url = source + return result + + +def load_from_path(source: Any, config: SimpleNamespace) -> LoaderResult: + try: + if isinstance(source, str): + source = _mangle_path(source) + if not isinstance(source, PurePosixPath): + raise TypeError + except Exception: + return "The input value is not a proper path." + else: + path = source + + client: ExistClient = config.existdb.client + url = f"{client.root_collection_url}/{path}" + + try: + if client.transport == "https": + result = https_loader(url, config) + else: # http + result = ftp_http_loader(url, config) + except requests.HTTPError as e: + if e.response.status_code == 404: + raise SnakesistNotFound(f"Document '{path}' not found.") + raise SnakesistReadError("Could not read from database.") from e + + config.__dict__.pop("source_url", None) + config.existdb.collection = path.parent + config.existdb.filename = path.name + return result + + +def ensure_configured_client(method): + @wraps(method) + def wrapper(self, *args, **kwargs): + if not isinstance(self.config.existdb.client, ExistClient): + raise SnakesistConfigError( + f"The document {self!r} has no configured eXist-db client." + ) + return method(self, *args, **kwargs) + + return wrapper + + +@plugin_manager.register_document_extension +class ExistDBExtension(DocumentExtensionHooks): + """ + This class provides extensions to :class:`delb.Document` in order to interact + with a eXist-db instance. + + See :func:`existdb_loader` on retrieving documents from an eXist-db instance. """ + # for mypy: + config: SimpleNamespace + def _init_config(self, config_args: Dict[str, Any]): + client = config_args.pop("existdb_client", None) + if not (client is None or isinstance(client, ExistClient)): + raise ValueError("Invalid object passed as existdb_client.") + self.config.existdb = SimpleNamespace( + client=client, collection=PurePosixPath("."), filename="" + ) super()._init_config(config_args) @property - def existdb_collection(self): + def existdb_collection(self) -> str: """ The collection within an eXist-db instance where the document was fetched from. This property can be changed to designate another location to store to. """ - raise NotImplemented + return f"/{self.config.existdb.collection}" @existdb_collection.setter - def existdb_collection(self, collection: str): - raise NotImplemented + def existdb_collection(self, path: str): + self.config.existdb.collection = _mangle_path(path) + @ensure_configured_client def existdb_delete(self): """ Deletes the document that currently resides at the location which is made up of - the current ``existdb_collection`` and ``existdb_filename`` in the associated - eXist-db instance. + the current :attr:`ExistDBExtension.existdb_collection` and + :attr:`ExistDBExtension.existdb_filename` in the associated eXist-db instance. """ - raise NotImplemented + self.config.existdb.client.delete_document( + f"{self.existdb_collection}/{self.existdb_filename}" + ) @property - def existdb_filename(self): + def existdb_filename(self) -> str: """ The filename within the eXist-db instance and collection where the document was fetched from. This property can be changed to designate another location to store to. """ - raise NotImplemented + return self.config.existdb.filename @existdb_filename.setter def existdb_filename(self, filename: str): - raise NotImplemented - - def existdb_store(self, collection: str = None, filename: str = None): + _validate_filename(filename) + self.config.existdb.filename = filename + + @ensure_configured_client + def existdb_store( + self, + collection: str = None, + filename: str = None, + replace_existing: bool = False, + ): """ Stores the current state of the document in the associated eXist-db instance. :param collection: An alternate collection to save into. :param filename: An alternate name to store the document. + :param replace_existing: Allows to overwrite existing documents. """ - raise NotImplemented + + client = self.config.existdb.client + if collection is None: + collection = self.existdb_collection + else: + collection = str(_mangle_path(collection)) + if filename is None: + filename = self.existdb_filename + else: + _validate_filename(filename) + + url = f"{client.root_collection_url}/{collection}/{filename}" + + if not replace_existing and requests.head(url).status_code == 200: + raise SnakesistWriteError( + "Document already exists. Overwriting must be explicitly allowed." + ) + + response = requests.put( + url, headers={"Content-Type": "application/xml"}, data=str(self).encode(), + ) + if not response.status_code == 201: + raise SnakesistWriteError(f"Unexpected response: {response}") + try: + response.raise_for_status() + except Exception as e: + raise SnakesistWriteError("Unhandled error while storing.") from e diff --git a/snakesist/exceptions.py b/snakesist/exceptions.py new file mode 100644 index 0000000..f9cda45 --- /dev/null +++ b/snakesist/exceptions.py @@ -0,0 +1,28 @@ +class SnakesistError(Exception): + """Snakesist base exception class""" + + pass + + +class SnakesistConfigError(SnakesistError): + """Raised if the database connection is improperly configured""" + + pass + + +class SnakesistReadError(SnakesistError): + """Raised if a writing operation fails""" + + pass + + +class SnakesistNotFound(SnakesistReadError): + """Raised if a database resource is not found""" + + pass + + +class SnakesistWriteError(SnakesistError): + """Raised if a reading operation fails""" + + pass diff --git a/snakesist/exist_client.py b/snakesist/exist_client.py index b06e4ab..6846d0d 100644 --- a/snakesist/exist_client.py +++ b/snakesist/exist_client.py @@ -3,15 +3,21 @@ :synopsis: A module containing basic tools for connecting to eXist. """ -from abc import ABC, abstractmethod -from typing import List, Optional, NamedTuple -from urllib.parse import urljoin -from uuid import uuid4 +import re +from pathlib import PurePosixPath +from typing import List, NamedTuple, Optional, Tuple +from urllib.parse import urlparse import requests -from delb.nodes import NodeBase, TagNode +from _delb.nodes import NodeBase, TagNode from lxml import cssselect, etree # type: ignore -from requests.auth import HTTPBasicAuth + +from snakesist.exceptions import ( + SnakesistConfigError, + SnakesistNotFound, + SnakesistReadError, + SnakesistWriteError, +) class QueryResultItem(NamedTuple): @@ -27,7 +33,7 @@ class ConnectionProps(NamedTuple): password: str host: str port: int - prefix: str + prefix: PurePosixPath DEFAULT_TRANSPORT = "http" @@ -36,24 +42,42 @@ class ConnectionProps(NamedTuple): DEFAULT_USER = "admin" DEFAULT_PASSWORD = "" DEFAULT_PARSER = etree.XMLParser(recover=True) +EXISTDB_NAMESPACE = "http://exist.sourceforge.net/NS/exist" +TRANSPORT_PROTOCOLS = {"https": 443, "http": 80} # the order matters! XML_NAMESPACE = "https://snakesist.readthedocs.io/" fetch_resource_paths = cssselect.CSSSelector( - "x|result x|value", namespaces={"x": "http://exist.sourceforge.net/NS/exist"} + "x|result x|value", namespaces={"x": EXISTDB_NAMESPACE} ) fetch_snakesist_results = cssselect.CSSSelector( "x|result", namespaces={"x": XML_NAMESPACE} ) +content_type_is_xml = re.compile(r"^(application|text)/xml(;.+)?").match + + +def _mangle_path(path: str) -> PurePosixPath: + return PurePosixPath(path.lstrip("/")) -class Resource(ABC): +def _validate_filename(filename: str): + as_path = PurePosixPath(filename) + if str(as_path) != as_path.name: + raise ValueError(f"Invalid filename: '{filename}'") + + +class NodeResource: """ - A representation of an eXist resource (documents, nodes etc.). + A representation of an XML node in a eXist-db resource. Each Resource object must be coupled to an :class:`ExistClient`. - Resources are identified by IDs: Some resources (documents) just have - an absolute resource ID, while others (nodes) require an additional node ID. + Resources are identified by an absolute resource ID that points to the containing + document and a node ID within that document. + + :param exist_client: The client to which the resource is coupled. + :query_result: A tuple containing the absolute resource ID, node ID + and the node of the resource. + """ def __init__( @@ -61,11 +85,6 @@ def __init__( exist_client: "ExistClient", query_result: Optional[QueryResultItem] = None, ): - """ - :param exist_client: The client to which the resource is coupled. - :query_result: A tuple containing the absolute resource ID, node ID - and the node of the resource. - """ self.node: Optional[NodeBase] self._exist_client = exist_client @@ -93,19 +112,21 @@ def update_pull(self): abs_resource_id=self._abs_resource_id, node_id=self._node_id ) - @abstractmethod def update_push(self): - """ - Write the resource object to the database. - """ - pass + """ Writes the node to the database. """ + self._exist_client.update_node( + data=str(self.node), + abs_resource_id=self._abs_resource_id, + node_id=self._node_id, + ) - @abstractmethod def delete(self): - """ - Remove the node from the database. - """ - pass + """ Deletes the node from the database. """ + self._exist_client.delete_node( + abs_resource_id=self._abs_resource_id, node_id=self._node_id + ) + self._node_id = "" + self._abs_resource_id = "" @property def abs_resource_id(self): @@ -129,43 +150,6 @@ def document_path(self): return self._document_path -class DocumentResource(Resource): - """ - A representation of an eXist document node - """ - - def delete(self): - self._exist_client.delete_document(document_path=self.document_path) - self._node_id = "" - self._abs_resource_id = "" - self._document_path = "" - - def update_push(self): - self._exist_client.update_document( - data=str(self.node), document_path=self.document_path, - ) - - -class NodeResource(Resource): - """ - A representation of an eXist node at the sub-document level - """ - - def delete(self): - self._exist_client.delete_node( - abs_resource_id=self._abs_resource_id, node_id=self._node_id - ) - self._node_id = "" - self._abs_resource_id = "" - - def update_push(self): - self._exist_client.update_node( - data=str(self.node), - abs_resource_id=self._abs_resource_id, - node_id=self._node_id, - ) - - class ExistClient: """ An eXist-db client object representing a database instance. @@ -195,77 +179,124 @@ def __init__( root_collection: str = "/", parser: etree.XMLParser = DEFAULT_PARSER, ): + _prefix = _mangle_path(prefix) self.__connection_props = ConnectionProps( transport=transport, user=user, password=password, host=host, port=port, - prefix=prefix, + prefix=_prefix, ) - self.__base_url = f"{transport}://{user}:{password}@{host}:{port}/{prefix}" + self.__base_url = f"{transport}://{user}:{password}@{host}:{port}/{_prefix}" self.parser = parser - self._root_collection = root_collection + self.root_collection = root_collection - @staticmethod - def _join_paths(*args): - return "/".join(s.strip("/") for s in args) - - def _get_request( - self, url: str, query: Optional[str] = None, wrap: bool = True - ) -> bytes: - if query: - params = { - "_howmany": "0", - "_indent": "no", - "_wrap": "yes" if wrap else "no", - "_query": query, - } - else: - params = {} + @classmethod + def from_url(cls, url: str, parser=DEFAULT_PARSER) -> "ExistClient": + """ + Returns a client instance from the given URL. Path parts that point to something + beyond the database instance's path prefix are ignored. + """ + parsed_url = urlparse(url) - response = requests.get( - url, - headers={"Content-Type": "application/xml"}, - auth=HTTPBasicAuth(self.user, self.password), - params=params, - ) + if parsed_url.scheme == "existdb": + transport = None + elif parsed_url.scheme.startswith("existdb+"): + transport = parsed_url.scheme.split("+")[1] + if transport not in TRANSPORT_PROTOCOLS: + raise SnakesistConfigError( + f"Invalid transport '{transport}' for existdb." + ) + else: + raise SnakesistConfigError( + f"Invalid URL scheme '{parsed_url.scheme}' for existdb." + ) - response.raise_for_status() + user = parsed_url.username or "" + password = parsed_url.password or "" + host = parsed_url.hostname + port = parsed_url.port - return response.content + if not isinstance(host, str) and host: + raise SnakesistConfigError(f"Invalid host in URL: {host}") - def _put_request(self, url: str, data: str) -> bytes: - response = requests.put( - url, - headers={"Content-Type": "application/xml"}, - auth=HTTPBasicAuth(self.user, self.password), - data=data.encode("utf-8"), + if transport is None: + probe_result = cls._probe_transport_and_port( + f"{user}:{password}@{host}", port + ) + if probe_result is None: + raise SnakesistConfigError( + f"Couldn't figure out how to talk to {host}." + ) + transport, port = probe_result + elif port is None: + port = TRANSPORT_PROTOCOLS[transport] + + prefix = cls._probe_instance_prefix( + f"{transport}://{user}:{password}@{host}:{port}", parsed_url.path ) + if prefix is None: + raise SnakesistConfigError( + "Couldn't determine the location of the 'rest' interface." + ) - if response.status_code != requests.codes.ok: - response.raise_for_status() - - return response.content - - def _delete_request(self, url: str) -> None: - response = requests.delete( - url, - headers={"Content-Type": "application/xml"}, - auth=HTTPBasicAuth(self.user, self.password), + return cls( + transport=transport, + host=host, + port=port, + user=user, + password=password, + prefix=prefix, + parser=parser, ) - if response.status_code != requests.codes.ok: - response.raise_for_status() + @staticmethod + def _probe_transport_and_port( + host: str, port: Optional[int] + ) -> Optional[Tuple[str, int]]: + for transport, default_port in TRANSPORT_PROTOCOLS.items(): + _port = port or default_port + try: + requests.head(f"{transport}://{host}:{_port}/") + except requests.exceptions.ConnectionError: + pass + else: + return transport, _port + return None @staticmethod - def _parse_item(node: etree._Element) -> QueryResultItem: - return QueryResultItem( - node.attrib["absid"], - node.attrib["nodeid"], - node.attrib["path"], - TagNode(node[0], {}), - ) + def _probe_instance_prefix(base: str, path: str) -> Optional[str]: + path_parts = PurePosixPath(path).parts[1:] + encountered_collection = False + + # looks for longest path as different instances could have overlapping prefixes + # will return false results if a path contained a part named "rest" + for i in range(len(path_parts), 0, -1): + response = requests.get(f"{base}/{'/'.join(path_parts[:i])}/rest/") + + if response.status_code == 401: + raise SnakesistConfigError("Failed authentication.") + + if not content_type_is_xml(response.headers.get("Content-Type", "")): + if encountered_collection: + break + else: + continue + + qualified_name = etree.QName(etree.fromstring(response.content)) + if ( + qualified_name.namespace == EXISTDB_NAMESPACE + and qualified_name.localname == "result" + ): + encountered_collection = True + elif encountered_collection: + break + + if encountered_collection: + return "/".join(path_parts[:i]) + else: + return None @property def base_url(self) -> str: @@ -274,6 +305,13 @@ def base_url(self) -> str: """ return self.__base_url + @property + def transport(self): + """ + The used transport protocol + """ + return self.__connection_props.transport + @property def host(self): """ @@ -307,70 +345,75 @@ def prefix(self): """ The URL prefix of the database """ - return self.__connection_props.prefix + return str(self.__connection_props.prefix) @property def root_collection(self) -> str: """ The configured root collection for database queries. """ - return self._root_collection + return str(self.__root_collection) @root_collection.setter - def root_collection(self, collection: str): + def root_collection(self, path: str): """ Set the path to the root collection for database queries (e. g. '/db/foo/bar/'). """ - self._root_collection = collection + self.__root_collection = _mangle_path(path) @property - def root_collection_url(self): + def root_collection_url(self) -> str: """ The URL pointing to the configured root collection. """ - data_path = self._join_paths("/rest/", self.root_collection) - url = urljoin(self.base_url, data_path) - return url + result = f"{self.__base_url}/rest/{self.__root_collection}" + if result.endswith("/."): + return result[:-2] + else: + return result def query(self, query_expression: str) -> etree._Element: """ - Make a database query using XQuery + Make a database query using XQuery. The configured root collection + will be the starting point of the query. :param query_expression: XQuery expression :return: The query result as a ``delb.Document`` object. """ - response_string = self._get_request( - self.root_collection_url, query=query_expression - ) - return etree.fromstring(response_string, parser=self.parser) - def create_resource(self, document_path: str, node: str): - """ - Write a new document node to the database. + params = { + "_howmany": "0", + "_indent": "no", + "_wrap": "yes", + "_query": query_expression, + } - :param document_path: Path to collection where document will be stored, - relative to the configured root collection - :param node: XML string - """ - path = self._join_paths(self.root_collection, document_path) - self.query( - f"let $collection-check := if (not(xmldb:collection-available('{path}'))) " - f"then (xmldb:create-collection('/', '{path}')) else () " - f"return xmldb:store('/{path}', '{uuid4().hex}', {node})" + response = requests.get( + self.root_collection_url, + headers={"Content-Type": "application/xml"}, + params=params, ) - def retrieve_resources(self, xpath: str) -> List[Resource]: + try: + response.raise_for_status() + except Exception as e: + raise SnakesistReadError("Unhandled query error.") from e + + return etree.fromstring(response.content, parser=self.parser) + + def xpath(self, expression: str) -> List[NodeResource]: """ Retrieve a set of resources from the database using - an XPath expression. + an XPath expression. The configured root collection + will be the starting point of the query. - :param xpath: XPath expression (whatever version your eXist - instance supports via its RESTful API) + :param expression: XPath expression (whatever version your eXist + instance supports via its RESTful API) :return: The query results as a list of :class:`Resource` objects. """ results_node = self.query( - f"for $node in {xpath} " + f"for $node in {expression} " f"return List[Resource]: ) resources = [] for item in fetch_snakesist_results(results_node): - query_result = self._parse_item(item) - resources.append( - DocumentResource(exist_client=self, query_result=query_result) - if query_result.node_id == "1" - else NodeResource(exist_client=self, query_result=query_result) + query_result = QueryResultItem( + item.attrib["absid"], + item.attrib["nodeid"], + item.attrib["path"], + TagNode(item[0], {}), ) + resources.append(NodeResource(exist_client=self, query_result=query_result)) return resources - def retrieve_resource(self, abs_resource_id: str, node_id: str = "") -> Resource: + # TODO? rename + def retrieve_resource(self, abs_resource_id: str, node_id: str) -> NodeResource: """ Retrieve a single resource by its internal database IDs. @@ -401,19 +446,14 @@ def retrieve_resource(self, abs_resource_id: str, node_id: str = "") -> Resource f"return util:collection-name($node) || '/' || util:document-name($node)" ) )[0].text - assert isinstance(path, str), path - if node_id: - query = ( - "util:node-by-id(util:get-resource-by-absolute-id({abs_resource_id}), " - "'{node_id}')" - ) - else: - query = f"util:get-resource-by-absolute-id({abs_resource_id})" - queried_node = self.query(query)[0] - assert isinstance(queried_node, etree._Element) + query = ( + "util:node-by-id(util:get-resource-by-absolute-id({abs_resource_id}), " + "'{node_id}')" + ) + queried_node: etree._Element = self.query(query)[0] - return DocumentResource( + return NodeResource( self, QueryResultItem(abs_resource_id, node_id, path, TagNode(queried_node, {})), ) @@ -431,16 +471,6 @@ def update_node(self, data: str, abs_resource_id: str, node_id: str) -> None: f"with {data}" ) - def update_document(self, data: str, document_path: str) -> None: - """ - Replace a document root node with an updated version. - - :param data: A well-formed XML string containing the node to replace the old one with. - :param document_path: The path pointing to the document (relative to the REST endpoint, e. g. '/db/foo/bar') - """ - url = self._join_paths(self.base_url, "rest", document_path) - self._put_request(url, data) - def delete_node(self, abs_resource_id: str, node_id: str = "") -> None: """ Remove a node from the database. @@ -456,9 +486,17 @@ def delete_node(self, abs_resource_id: str, node_id: str = "") -> None: def delete_document(self, document_path: str) -> None: """ - Remove a document from a database. + Removes a document from the associated database. - :param document_path: The path pointing to the document (relative to the REST endpoint, e. g. '/db/foo/bar') + :param document_path: The path pointing to the document within the root + collection. """ - url = self._join_paths(self.base_url, "rest", document_path) - self._delete_request(url) + response = requests.delete( + f"{self.root_collection_url}/{_mangle_path(document_path)}" + ) + if response.status_code == 404: + raise SnakesistNotFound(f"Document '{document_path}' not found.") + try: + response.raise_for_status() + except Exception as e: + raise SnakesistWriteError("Unhandled error while deleting.") from e diff --git a/tests/conftest.py b/tests/conftest.py index a8a6f18..d7e8410 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,9 +8,7 @@ @fixture def rest_base_url(test_client): - return ( - f"{test_client.base_url}rest{test_client.root_collection}?_wrap=no&_indent=no" - ) + return f"{test_client.root_collection_url}?_wrap=no&_indent=no" def existdb_is_responsive(url): @@ -24,10 +22,14 @@ def existdb_is_responsive(url): @fixture -def db(docker_ip, docker_services): +def db(docker_ip, docker_services, monkeypatch): """ Database setup and teardown """ + monkeypatch.setenv( + "REQUESTS_CA_BUNDLE", + str(Path(__file__).resolve().parent / "db_fixture" / "nginx" / "cert.pem"), + ) base_url = f"http://{docker_ip}:{docker_services.port_for('existdb', 8080)}" docker_services.wait_until_responsive( timeout=30.0, pause=0.1, check=lambda: existdb_is_responsive(base_url) @@ -36,7 +38,7 @@ def db(docker_ip, docker_services): @fixture(scope="session") -def docker_compose_file(pytestconfig): +def docker_compose_file(): return str(Path(__file__).parent.resolve() / "db_fixture" / "docker-compose.yml") diff --git a/tests/db_fixture/docker-compose.yml b/tests/db_fixture/docker-compose.yml index de59247..ee0f7e3 100644 --- a/tests/db_fixture/docker-compose.yml +++ b/tests/db_fixture/docker-compose.yml @@ -4,5 +4,16 @@ services: existdb: build: . ports: - - "8080" + - "8080:8080" + proxy: + image: nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/cert.pem:/etc/ssl/certs/localhost.pem:ro + - ./nginx/key.pem:/etc/ssl/private/localhost.pem:ro + depends_on: + - existdb diff --git a/tests/db_fixture/nginx/cert.pem b/tests/db_fixture/nginx/cert.pem new file mode 100644 index 0000000..b584ec7 --- /dev/null +++ b/tests/db_fixture/nginx/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHzCCAgegAwIBAgIUC8ZBdErvp7ZK5+VQxMKzzAOaBRQwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMDkxMzExMjE1NloXDTMwMDkx +MzExMjE1NlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAuPS6MoZTyFWOkws8eKchOO8k+A7Aytosc/0aP9+j9Bmw +6FNQL29VDT4m9hImd4hHVEZDXDlajjfGVSnZlBr+9PK3SqF15F51qm5NHV/Yrsnn +xA/J8ZD8lVbdOcvaCLbVlfXOmpgQ3xrnHU4rDIj4a1TmM+YndBE++FUB3wAJJVc+ +o8KdKR+BcZKzLhcgST0uZKdqGedBZajSnFc/GeyUL3hewcEAjb64cSKJLtd9p/6o +WDo7zJBZTAIQuxV3zQwPwldBEExnuS8iBbIJMQtvrOtFRM0o3BWwCEV5AqmOLynE +M3D5Tn+QgnSrV2RPCRJXFQSgiHCF8P9/LfXAzFj23wIDAQABo2kwZzAdBgNVHQ4E +FgQUW8PnT2cxPzNljB3nhlNI5f3V9qMwHwYDVR0jBBgwFoAUW8PnT2cxPzNljB3n +hlNI5f3V9qMwDwYDVR0TAQH/BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3Qw +DQYJKoZIhvcNAQELBQADggEBAK0GUSU+14WLruc20ti4Qp8zSwdQAGP2ZUuIgXpT +7B0sADCBxUthwdCT5G6c6HE99iga/Cp0zBMENWTXYA7kQ1DVIqQrhus5BxVe3dG1 +qULwHKgbuR8BTy2dnkS5qjZ7hPTPPIEXBt3EO9Fu49V/cq4ZmlcO/yyozi1aJyEg +oD2JyRnfupfJdKAaJgzfx5ST2Lk2H0k/IkVCu5+AwdYxxmQ9TNVUps4cqv/k/2wV +ufg7JDHAaOL0JMCLNRvyh2EVofEEzgUbCAM9ITp5Gxktk9A8G7ZAGbn0JvuVBNjT +m7zX2AzQIbKVE2zgA8GtIUWlrChyvcr4BjY3lpwBpSJwR3o= +-----END CERTIFICATE----- diff --git a/tests/db_fixture/nginx/key.pem b/tests/db_fixture/nginx/key.pem new file mode 100644 index 0000000..58973cc --- /dev/null +++ b/tests/db_fixture/nginx/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC49LoyhlPIVY6T +Czx4pyE47yT4DsDK2ixz/Ro/36P0GbDoU1Avb1UNPib2EiZ3iEdURkNcOVqON8ZV +KdmUGv708rdKoXXkXnWqbk0dX9iuyefED8nxkPyVVt05y9oIttWV9c6amBDfGucd +TisMiPhrVOYz5id0ET74VQHfAAklVz6jwp0pH4FxkrMuFyBJPS5kp2oZ50FlqNKc +Vz8Z7JQveF7BwQCNvrhxIoku132n/qhYOjvMkFlMAhC7FXfNDA/CV0EQTGe5LyIF +sgkxC2+s60VEzSjcFbAIRXkCqY4vKcQzcPlOf5CCdKtXZE8JElcVBKCIcIXw/38t +9cDMWPbfAgMBAAECggEAFi9cqjTMleoVSPP/E+XQKVkeERcL7wkObfc9D85OhzgR +rTxZEOeExyreV4PD03CoRaaL6+MBRq/P/WUgsRAv5se6TpxhXLwr1v6WB2D4x7Z2 +M2FFctFWSr95l7Yo+ASeR/eNCaQV2f0LHcvDhCRlz9IGLtF7iAONjxH7QHlrn2vM +Y5AYBHONtoad0BlbX7OFeDDSfg4gZ6XZj0BuF/HWcWLfkUte2T9PYHtZTxKc6abb +4slCpCnlej5CObCxmTsb0h+L4IiD2+p5V9sKm0QXsmyPc04Klw2gO43QNI53KhIE +Ud8oxzm9BHdnglpg2zQzEfmRacx7O4n6nTD8HKlPgQKBgQDerlUxpZH0aJyIJjW5 +z3hHnM12UIr6r5I19x4RBzDeeL1JoYpHc1Z/hZXfCSFssNV1xoq2AQ5MpJBgX3xt +U/BccPJ7N6+9M1JKQh65jiQh121YA6CxBRdu8KRNBRChOoU4Dglj1uTS/AtSy/dO +wSds4gEjA2+QRyRXGSgIafLiHwKBgQDUoVv6dcqjmb0RtC5F18HutBtjKmtGKj4K +2ElznyB0tIGKSs/XyuikQ40gm6rMiTni5F2StbQ2ZiApTxhdKQF7uHPZznBMdXBK +35oihcyqmODl/y3niZwXb0uYv5pNbkUulo4nOCY+k6btvJI/5UYfcFB2moh/Yjyn +V834jPLTQQKBgF799V/RZprhiWzXB/I02/WIoWlIYJEiFngo682NyL4OuYjQfYGP +ZSXPIajcZ0LLiLPON1v+xU/Sn7hM43Jr7Uwx7m67yZ8nZoJBBIRZa3Z4RK/YFFOP +fXiY9S3Z7EcrBKtt6XmKsWCDnnz6f4w7/4qGOr/p9kyQ3KK/G4HDWJY3AoGAflQU +Cp1bNJINs+wCJfrHPOQC9YrpSzmjvNu94BTlupDX+eryy3nBeH6t5Xa3JmhNXRFi +ytxr4noQl5pnlknbmL8BDaHKNJL9kW6B7Arki2LjE6Llig/HDmFQX8NOkLx4G/LV +KFYC2uDsp4iGt7GnVNxs4dp0SIJ8GMxhEk/joUECgYAyns9C0pmR6bEHom4HP0K4 +pmLNLaFm902uiUub6YKgaVprR56Tna6pC2r7/L5GjVG9LIgUhenVSfIAwLvjD7li +KJrlj7GimrFm3b97Lmc8BHJqtbOVhySuUtbR/jUJqmsIi5QQ+5VfzGobJj50f8HD +pLEuzsJ7GKGfIGrNj/281Q== +-----END PRIVATE KEY----- diff --git a/tests/db_fixture/nginx/nginx.conf b/tests/db_fixture/nginx/nginx.conf new file mode 100644 index 0000000..9ac5ded --- /dev/null +++ b/tests/db_fixture/nginx/nginx.conf @@ -0,0 +1,37 @@ +user nginx; +worker_processes 1; + +error_log /dev/stderr warn; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /dev/stdout main; + sendfile on; + keepalive_timeout 65; + + server { + listen 80; + listen 443 ssl; + server_name localhost; + ssl_certificate /etc/ssl/certs/localhost.pem; + ssl_certificate_key /etc/ssl/private/localhost.pem; + + location / { + proxy_pass http://existdb:8080/; + } + + } +} diff --git a/tests/test_delb_plugin.py b/tests/test_delb_plugin.py index 508e924..47febdb 100644 --- a/tests/test_delb_plugin.py +++ b/tests/test_delb_plugin.py @@ -2,15 +2,16 @@ from delb import Document, FailedDocumentLoading from snakesist import ExistClient +from snakesist.exceptions import SnakesistWriteError -@pytest.mark.usefixtures("db") -def test_delete_document(): - url = "existdb://admin:@localhost:8080/exist/db/apps/test-data/dada_manifest.xml" - document = Document(url) +def test_delete_document(test_client): + filename = "delete_document.xml" + document = Document("", existdb_client=test_client) + document.existdb_store(filename=filename) document.existdb_delete() with pytest.raises(FailedDocumentLoading): - Document(url) + Document(f"existdb://admin:@localhost:8080/exist/db/tests/{filename}") @pytest.mark.usefixtures("db") @@ -19,28 +20,22 @@ def test_load_document_from_url(): document = Document(url) assert document.source_url == url - assert isinstance(document.config.existdb.abs_id, str) assert isinstance(document.config.existdb.client, ExistClient) - assert document.existdb_collection == "/db/apps/test-data/" + assert document.existdb_collection == "/db/apps/test-data" assert document.existdb_filename == "dada_manifest.xml" def test_load_document_with_client(test_client): - document = Document( - "/db/apps/test-data/dada_manifest.xml", existdb_client=test_client - ) + test_client.root_collection = "/db/apps/" + document = Document("/test-data/dada_manifest.xml", existdb_client=test_client) - assert ( - document.source_url - == "existdb://admin:@localhost:8080/exist/db/apps/test-data/dada_manifest.xml" - ) - assert isinstance(document.config.existdb.abs_id, str) assert document.config.existdb.client is test_client - assert document.existdb_collection == "/db/apps/test-data/" + assert document.existdb_collection == "/test-data" assert document.existdb_filename == "dada_manifest.xml" def test_store_document(test_client): + test_client.root_collection = "/db/apps/" document = Document("", existdb_client=test_client,) document.existdb_store(collection="/test_collection/", filename="new_document.xml") @@ -51,9 +46,26 @@ def test_store_document(test_client): document.existdb_store( collection="/another/collection/", filename="another_name.xml", ) - assert document.existdb_collection == "/test_collection/" - assert document.existdb_filename == "test_document.xml" + assert document.existdb_collection == "/db/apps/test-data" + assert document.existdb_filename == "dada_manifest.xml" document.existdb_collection = "/another/collection/" document.existdb_filename = "another_name.xml" + with pytest.raises(SnakesistWriteError): + document.existdb_store() + + test_client.root_collection = "/" + document.existdb_collection = collection = "/db/apps/test_collection/" + document.existdb_filename = filename = "new_document.xml" document.existdb_store(replace_existing=True) + assert ( + Document(f"{collection}{filename}", existdb_client=test_client).root.local_name + != "test" + ) + + +@pytest.mark.usefixtures("db") +def test_with_other_extension(): + document = Document("existdb://localhost/exist/db/apps/test-data/dada_manifest.xml") + assert document.tei_header.title == "Das erste dadaistische Manifest" + assert document.tei_header.authors == ["Ball, Hugo"] diff --git a/tests/test_exist_client.py b/tests/test_exist_client.py index a8251f8..f6547e7 100644 --- a/tests/test_exist_client.py +++ b/tests/test_exist_client.py @@ -1,54 +1,17 @@ import pytest # type: ignore import requests -from requests.exceptions import HTTPError +from delb import Document, FailedDocumentLoading -def test_exist_client_create_resource_wellformed(rest_base_url, test_client): - new_node = 'wow a document node' - test_client.create_resource("/foo", new_node) - response = requests.get(f"{rest_base_url}&_query=//example[@id='t1']") - node = response.content.decode() - assert node == new_node - - -def test_exist_client_create_resource_malformed(test_client): - new_node = 'tags do not match' - with pytest.raises(HTTPError): - test_client.create_resource("/foo", new_node) - - -def test_exist_client_update_document(rest_base_url, test_client): - new_node = 'wow a document node' - updated_node = 'wow a NEW document node' - test_client.create_resource("/foo", new_node) - xq = "let $node := //example[@id='t2'] return util:collection-name($node) || '/' || util:document-name($node)" - path = requests.get(f"{rest_base_url}&_query={xq}").content.decode() - test_client.update_document(updated_node, path) - response = requests.get(f"{rest_base_url}&_query=//example[@id='t2']") - node = response.content.decode() - assert node == updated_node - - -def test_exist_client_update_node(rest_base_url, test_client): - new_node = 'i am a child' - updated_node = "child indeed" - test_client.create_resource("/foo", new_node) - xq = "let $node := //subnode return util:absolute-resource-id($node)" - abs_res_id = requests.get(f"{rest_base_url}&_query={xq}").content.decode() - xq = "let $node := //subnode return util:node-id($node)" - node_id = requests.get(f"{rest_base_url}&_query={xq}").content.decode() - test_client.update_node(updated_node, abs_res_id, node_id) - response = requests.get(f"{rest_base_url}&_query=//subnode") - node = response.content.decode() - assert node == updated_node +from snakesist import ExistClient def test_exist_client_delete_node(rest_base_url, test_client): - new_node = ( - 'i stay and i am to be deleted' - ) - remaining_node = 'i stay' - test_client.create_resource("/foo", new_node) + Document( + 'i stay and i am to be deleted', + existdb_client=test_client, + ).existdb_store(filename="foo.xml") + xq = "let $node := //deletee return util:absolute-resource-id($node)" abs_res_id = requests.get(f"{rest_base_url}&_query={xq}").content.decode() xq = "let $node := //deletee return util:node-id($node)" @@ -56,36 +19,59 @@ def test_exist_client_delete_node(rest_base_url, test_client): test_client.delete_node(abs_res_id, node_id) response = requests.get(f"{rest_base_url}&_query=//example[@id='t4']") node = response.content.decode() - assert node == remaining_node + assert node == 'i stay' def test_exist_client_delete_document(rest_base_url, test_client): - new_node = 'i am to be deleted' - test_client.create_resource("/foo", new_node) - xq = "let $node := //example[@id='t5'] return util:collection-name($node) || '/' || util:document-name($node)" - path = requests.get(f"{rest_base_url}&_query={xq}").content.decode() - test_client.delete_document(path) - response = requests.get(f"{rest_base_url}&_query=//example[@id='t5']") - node = response.content.decode() - assert node == "" - - -def test_exist_client_retrieve_resource(rest_base_url, test_client): - new_node = 'retrieve me!' - test_client.create_resource("/foo", new_node) - xq = "let $node := //example[@id='t6'] return util:absolute-resource-id($node)" - abs_id = requests.get(f"{rest_base_url}&_query={xq}").content.decode() - retrieved_node = test_client.retrieve_resource(abs_resource_id=abs_id) - assert new_node == str(retrieved_node) - + Document( + 'i am to be deleted', existdb_client=test_client + ).existdb_store(collection="/bar", filename="foo.xml") + test_client.delete_document("/bar/foo.xml") + with pytest.raises(FailedDocumentLoading): + Document("/bar/foo.xml", existdb_client=test_client) + + +def test_exist_client_xpath(test_client): + paragraph_1 = "

retrieve me first!

" + paragraph_2 = "

retrieve me too!

" + Document( + f'{paragraph_1}', existdb_client=test_client + ).existdb_store(filename="document_1.xml") + Document(paragraph_2, existdb_client=test_client).existdb_store( + filename="document_2.xml" + ) -def test_exist_client_retrieve_resources(test_client): - node_resource = "

retrieve me first!

" - node_resource_doc = f'{node_resource}' - test_client.create_resource("/foo", node_resource_doc) - document_resource = "

retrieve me too!

" - test_client.create_resource("/foo", document_resource) - retrieved_nodes = test_client.retrieve_resources("//p") + retrieved_nodes = test_client.xpath("//p") retrieved_nodes_str = [str(node) for node in retrieved_nodes] - assert node_resource in retrieved_nodes_str - assert document_resource in retrieved_nodes_str + assert paragraph_1 in retrieved_nodes_str + assert paragraph_2 in retrieved_nodes_str + + +@pytest.mark.usefixtures("db") +@pytest.mark.parametrize( + "url, properties", + ( + ("existdb://localhost/exist", ("https", "", "", "localhost", 443, "exist")), + ( + "existdb+https://localhost/exist", + ("https", "", "", "localhost", 443, "exist"), + ), + ("existdb+http://localhost/exist", ("http", "", "", "localhost", 80, "exist")), + ( + "existdb+http://localhost:8080/exist", + ("http", "", "", "localhost", 8080, "exist"), + ), + ( + "existdb://admin:@localhost/exist", + ("https", "admin", "", "localhost", 443, "exist"), + ), + ), +) +def test_url_parsing(url, properties): + client = ExistClient.from_url(url) + assert client.transport == properties[0] + assert client.user == properties[1] + assert client.password == properties[2] + assert client.host == properties[3] + assert client.port == properties[4] + assert client.prefix == properties[5]