diff --git a/diffsync/__init__.py b/diffsync/__init__.py index 15c3c01e..b82b015b 100644 --- a/diffsync/__init__.py +++ b/diffsync/__init__.py @@ -329,7 +329,7 @@ def add_child(self, child: "DiffSyncModel"): attr_name = self._children[child_type] childs = getattr(self, attr_name) if child.get_unique_id() in childs: - raise ObjectAlreadyExists(f"Already storing a {child_type} with unique_id {child.get_unique_id()}") + raise ObjectAlreadyExists(f"Already storing a {child_type} with unique_id {child.get_unique_id()}", child) childs.append(child.get_unique_id()) def remove_child(self, child: "DiffSyncModel"): @@ -648,13 +648,17 @@ def add(self, obj: DiffSyncModel): obj (DiffSyncModel): Object to store Raises: - ObjectAlreadyExists: if an object with the same uid is already present + ObjectAlreadyExists: if a different object with the same uid is already present. """ modelname = obj.get_type() uid = obj.get_unique_id() - if uid in self._data[modelname]: - raise ObjectAlreadyExists(f"Object {uid} already present") + existing_obj = self._data[modelname].get(uid) + if existing_obj: + if existing_obj is not obj: + raise ObjectAlreadyExists(f"Object {uid} already present", obj) + # Return so we don't have to change anything on the existing object and underlying data + return if not obj.diffsync: obj.diffsync = self @@ -692,6 +696,59 @@ def remove(self, obj: DiffSyncModel, remove_children: bool = False): # Since this is "cleanup" code, log an error and continue, instead of letting the exception raise self._log.error(f"Unable to remove child {child_id} of {modelname} {uid} - not found!") + def get_or_instantiate( + self, model: Type[DiffSyncModel], ids: Dict, attrs: Dict = None + ) -> Tuple[DiffSyncModel, bool]: + """Attempt to get the object with provided identifiers or instantiate it with provided identifiers and attrs. + + Args: + model (DiffSyncModel): The DiffSyncModel to get or create. + ids (Mapping): Identifiers for the DiffSyncModel to get or create with. + attrs (Mapping, optional): Attributes when creating an object if it doesn't exist. Defaults to None. + + Returns: + Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. + """ + created = False + try: + obj = self.get(model, ids) + except ObjectNotFound: + if not attrs: + attrs = {} + obj = model(**ids, **attrs) + # Add the object to diffsync adapter + self.add(obj) + created = True + + return obj, created + + def update_or_instantiate(self, model: Type[DiffSyncModel], ids: Dict, attrs: Dict) -> Tuple[DiffSyncModel, bool]: + """Attempt to update an existing object with provided ids/attrs or instantiate it with provided identifiers and attrs. + + Args: + model (DiffSyncModel): The DiffSyncModel to get or create. + ids (Dict): Identifiers for the DiffSyncModel to get or create with. + attrs (Dict): Attributes when creating/updating an object if it doesn't exist. Pass in empty dict, if no specific attrs. + + Returns: + Tuple[DiffSyncModel, bool]: Provides the existing or new object and whether it was created or not. + """ + created = False + try: + obj = self.get(model, ids) + except ObjectNotFound: + obj = model(**ids, **attrs) + # Add the object to diffsync adapter + self.add(obj) + created = True + + # Update existing obj with attrs + for attr, value in attrs.items(): + if getattr(obj, attr) != value: + setattr(obj, attr, value) + + return obj, created + # DiffSyncModel references DiffSync and DiffSync references DiffSyncModel. Break the typing loop: DiffSyncModel.update_forward_refs() diff --git a/diffsync/diff.py b/diffsync/diff.py index 6a9f46ff..412924ea 100644 --- a/diffsync/diff.py +++ b/diffsync/diff.py @@ -55,7 +55,7 @@ def add(self, element: "DiffElement"): """ # Note that element.name is usually a DiffSyncModel.shortname() -- i.e., NOT guaranteed globally unique!! if element.name in self.children[element.type]: - raise ObjectAlreadyExists(f"Already storing a {element.type} named {element.name}") + raise ObjectAlreadyExists(f"Already storing a {element.type} named {element.name}", element) self.children[element.type][element.name] = element diff --git a/diffsync/exceptions.py b/diffsync/exceptions.py index ff130791..809954e0 100644 --- a/diffsync/exceptions.py +++ b/diffsync/exceptions.py @@ -39,6 +39,11 @@ class ObjectStoreException(Exception): class ObjectAlreadyExists(ObjectStoreException): """Exception raised when trying to store a DiffSyncModel or DiffElement that is already being stored.""" + def __init__(self, message, existing_object, *args, **kwargs): + """Add existing_object to the exception to provide user with existing object.""" + self.existing_object = existing_object + super().__init__(message, existing_object, *args, **kwargs) + class ObjectNotFound(ObjectStoreException): """Exception raised when trying to access a DiffSyncModel that isn't in storage.""" diff --git a/diffsync/helpers.py b/diffsync/helpers.py index 29470c7c..1960fb0c 100644 --- a/diffsync/helpers.py +++ b/diffsync/helpers.py @@ -85,7 +85,8 @@ def calculate_diffs(self) -> Diff: for obj_type in intersection(self.dst_diffsync.top_level, self.src_diffsync.top_level): diff_elements = self.diff_object_list( - src=self.src_diffsync.get_all(obj_type), dst=self.dst_diffsync.get_all(obj_type), + src=self.src_diffsync.get_all(obj_type), + dst=self.dst_diffsync.get_all(obj_type), ) for diff_element in diff_elements: @@ -220,7 +221,10 @@ def diff_object_pair( return diff_element def diff_child_objects( - self, diff_element: DiffElement, src_obj: Optional["DiffSyncModel"], dst_obj: Optional["DiffSyncModel"], + self, + diff_element: DiffElement, + src_obj: Optional["DiffSyncModel"], + dst_obj: Optional["DiffSyncModel"], ): """For all children of the given DiffSyncModel pair, diff recursively, adding diffs to the given diff_element. diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst index 4fe1395e..4bd1043c 100644 --- a/docs/source/examples/index.rst +++ b/docs/source/examples/index.rst @@ -6,4 +6,5 @@ For each example, the complete source code is `available in Github + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from models import Site, Device, Interface +from diffsync import DiffSync + +BACKEND_DATA_A = [ + { + "name": "nyc-spine1", + "role": "spine", + "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, + "site": "nyc", + }, + { + "name": "nyc-spine2", + "role": "spine", + "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, + "site": "nyc", + }, + { + "name": "sfo-spine1", + "role": "spine", + "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, + "site": "sfo", + }, + { + "name": "sfo-spine2", + "role": "spine", + "interfaces": {"eth0": "TBD", "eth1": "ddd", "eth2": "Interface 2"}, + "site": "sfo", + }, +] +BACKEND_DATA_B = [ + { + "name": "atl-spine1", + "role": "spine", + "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, + "site": "atl", + }, + { + "name": "atl-spine2", + "role": "spine", + "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, + "site": "atl", + }, + { + "name": "nyc-spine1", + "role": "spine", + "interfaces": {"eth0": "Interface 0/0", "eth1": "Interface 1"}, + "site": "nyc", + }, + { + "name": "nyc-spine2", + "role": "spine", + "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, + "site": "nyc", + }, + {"name": "sfo-spine1", "role": "leaf", "interfaces": {"eth0": "Interface 0", "eth1": "Interface 1"}, "site": "sfo"}, + {"name": "sfo-spine2", "role": "spine", "interfaces": {"eth0": "TBD", "eth1": "ddd"}, "site": "sfo"}, +] + + +class BackendA(DiffSync): + """Example of a DiffSync adapter implementation.""" + + site = Site + device = Device + interface = Interface + + top_level = ["device"] + + type = "Backend A" + + def load(self): + """Initialize the BackendA Object by loading some site, device and interfaces from DATA.""" + for device_data in BACKEND_DATA_A: + device, instantiated = self.get_or_instantiate( + self.device, {"name": device_data["name"]}, {"role": device_data["role"]} + ) + + site, instantiated = self.get_or_instantiate(self.site, {"name": device_data["site"]}) + if instantiated: + device.add_child(site) + + for intf_name, desc in device_data["interfaces"].items(): + intf, instantiated = self.update_or_instantiate( + self.interface, {"name": intf_name, "device_name": device_data["name"]}, {"description": desc} + ) + if instantiated: + device.add_child(intf) + + +class BackendB(DiffSync): + """Example of a DiffSync adapter implementation.""" + + site = Site + device = Device + interface = Interface + + top_level = ["device"] + + type = "Backend B" + + def load(self): + """Initialize the BackendB Object by loading some site, device and interfaces from DATA.""" + for device_data in BACKEND_DATA_B: + device, instantiated = self.get_or_instantiate( + self.device, {"name": device_data["name"]}, {"role": device_data["role"]} + ) + + site, instantiated = self.get_or_instantiate(self.site, {"name": device_data["site"]}) + if instantiated: + device.add_child(site) + + for intf_name, desc in device_data["interfaces"].items(): + intf, instantiated = self.get_or_instantiate( + self.interface, {"name": intf_name, "device_name": device_data["name"]}, {"description": desc} + ) + if instantiated: + device.add_child(intf) diff --git a/examples/04-get-update-instantiate/main.py b/examples/04-get-update-instantiate/main.py new file mode 100755 index 00000000..d96afe30 --- /dev/null +++ b/examples/04-get-update-instantiate/main.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +"""Main executable for DiffSync "04-get-update-instantiate". + +Copyright (c) 2021 Network To Code, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import argparse +import pprint + +from backends import BackendA +from backends import BackendB + +from diffsync.logging import enable_console_logging + + +def main(): + """Demonstrate DiffSync behavior using the example backends provided.""" + parser = argparse.ArgumentParser("example4") + parser.add_argument("--verbosity", "-v", default=0, action="count") + args = parser.parse_args() + enable_console_logging(verbosity=args.verbosity) + + print("Initializing and loading Backend A...") + backend_a = BackendA(name="Backend-A") + backend_a.load() + print(backend_a.str()) + + print("Initializing and loading Backend B...") + backend_b = BackendB(name="Backend-B") + backend_b.load() + print(backend_b.str()) + + print("Getting diffs from Backend A to Backend B...") + diff_a_b = backend_a.diff_to(backend_b) + print(diff_a_b.str()) + + print("Diffs can also be represented as a dictionary...") + pprint.pprint(diff_a_b.dict(), width=120) + + print("Syncing changes from Backend A to Backend B...") + backend_a.sync_to(backend_b) + print("Getting updated diffs from Backend A to Backend B...") + print(backend_a.diff_to(backend_b).str()) + + +if __name__ == "__main__": + main() diff --git a/examples/04-get-update-instantiate/models.py b/examples/04-get-update-instantiate/models.py new file mode 100644 index 00000000..105dd5ce --- /dev/null +++ b/examples/04-get-update-instantiate/models.py @@ -0,0 +1,58 @@ +"""Example models. + +Copyright (c) 2021 Network To Code, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" +from typing import List, Optional +from diffsync import DiffSyncModel + + +class Site(DiffSyncModel): + """Example model of a geographic Site.""" + + _modelname = "site" + _identifiers = ("name",) + _shortname = () + _attributes = () + + name: str + + +class Device(DiffSyncModel): + """Example model of a network Device.""" + + _modelname = "device" + _identifiers = ("name",) + _attributes = () + _children = {"interface": "interfaces", "site": "sites"} + + name: str + site_name: Optional[str] # note that this attribute is NOT included in _attributes + role: Optional[str] # note that this attribute is NOT included in _attributes + interfaces: List = list() + sites: List = list() + + +class Interface(DiffSyncModel): + """Example model of a network Interface.""" + + _modelname = "interface" + _identifiers = ("device_name", "name") + _shortname = ("name",) + _attributes = ("description",) + + name: str + device_name: str + + description: Optional[str] diff --git a/poetry.lock b/poetry.lock index 3d5d6eaa..a45745fd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,15 +2,7 @@ name = "alabaster" version = "0.7.12" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "appdirs" -version = "1.4.4" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -54,7 +46,7 @@ tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (> name = "babel" version = "2.9.1" description = "Internationalization utilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -78,29 +70,38 @@ stevedore = ">=1.20.0" [[package]] name = "black" -version = "19.10b0" +version = "21.10b0" description = "The uncompromising code formatter." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.6.2" [package.dependencies] -appdirs = "*" -attrs = ">=18.1.0" -click = ">=6.5" -pathspec = ">=0.6,<1" -regex = "*" -toml = ">=0.9.4" -typed-ast = ">=1.4.0" +click = ">=7.1.2" +dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""} +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +regex = ">=2020.1.8" +tomli = ">=0.2.6,<2.0.0" +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""} +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = "!=3.10.0.1", markers = "python_version >= \"3.10\""}, +] [package.extras] -d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" version = "2021.5.30" description = "Python package for providing Mozilla's CA Bundle." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -108,7 +109,7 @@ python-versions = "*" name = "chardet" version = "4.0.0" description = "Universal encoding detector for Python 2 and 3" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -154,7 +155,7 @@ python-versions = ">=3.6, <3.7" name = "docutils" version = "0.16" description = "Docutils -- Python Documentation Utilities" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -198,7 +199,7 @@ gitdb = ">=4.0.1,<5" name = "idna" version = "2.10" description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -206,7 +207,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "imagesize" version = "1.2.0" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -258,7 +259,7 @@ colors = ["colorama (>=0.4.3,<0.5.0)"] name = "jinja2" version = "3.0.1" description = "A very fast and expressive template engine." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -280,7 +281,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "m2r2" version = "0.2.7" description = "Markdown and reStructuredText in a single file." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -292,7 +293,7 @@ mistune = "*" name = "markupsafe" version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -308,7 +309,7 @@ python-versions = "*" name = "mistune" version = "0.8.4" description = "The fastest markdown parser in pure Python" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -340,7 +341,7 @@ python-versions = "*" name = "packaging" version = "20.7" description = "Core utilities for Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" @@ -349,11 +350,11 @@ pyparsing = ">=2.0.2" [[package]] name = "pathspec" -version = "0.8.1" +version = "0.9.0" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [[package]] name = "pbr" @@ -363,6 +364,18 @@ category = "dev" optional = false python-versions = ">=2.6" +[[package]] +name = "platformdirs" +version = "2.4.0" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + [[package]] name = "pluggy" version = "0.13.1" @@ -432,7 +445,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "pygments" version = "2.9.0" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -455,7 +468,7 @@ toml = ">=0.7.1" name = "pyparsing" version = "2.4.7" description = "Python parsing module" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" @@ -513,7 +526,7 @@ structlog = "*" name = "pytz" version = "2021.1" description = "World timezone definitions, modern and historical" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -537,7 +550,7 @@ python-versions = "*" name = "requests" version = "2.25.1" description = "Python HTTP for Humans." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" @@ -571,7 +584,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "snowballstemmer" version = "2.0.0" description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." -category = "dev" +category = "main" optional = false python-versions = "*" @@ -579,7 +592,7 @@ python-versions = "*" name = "sphinx" version = "4.0.2" description = "Python documentation generator" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -610,7 +623,7 @@ test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] name = "sphinx-rtd-theme" version = "0.5.2" description = "Read the Docs theme for Sphinx" -category = "dev" +category = "main" optional = false python-versions = "*" @@ -625,7 +638,7 @@ dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] name = "sphinxcontrib-applehelp" version = "1.0.2" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -637,7 +650,7 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -649,7 +662,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.0" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -661,7 +674,7 @@ test = ["pytest", "html5lib"] name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -672,7 +685,7 @@ test = ["pytest", "flake8", "mypy"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -684,7 +697,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" +category = "main" optional = false python-versions = ">=3.5" @@ -725,13 +738,21 @@ tests = ["coverage", "freezegun (>=0.2.8)", "pretend", "pytest (>=3.3.0)", "simp name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "tomli" +version = "1.2.2" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.6" + [[package]] name = "typed-ast" -version = "1.4.1" +version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "dev" optional = false @@ -745,11 +766,19 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "typing-extensions" +version = "3.10.0.2" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "dev" +optional = false +python-versions = "*" + [[package]] name = "urllib3" version = "1.26.5" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" @@ -790,20 +819,19 @@ python-versions = ">=3.6" docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +[extras] +docs = ["sphinx", "m2r2", "toml", "sphinx-rtd-theme", "pydantic", "structlog", "colorama", "dataclasses"] + [metadata] lock-version = "1.1" -python-versions = "^3.6" -content-hash = "479de3df9b86abb75fc2136a6ecbaf7f02a5ccd62f35ffee6a99b36f998f3126" +python-versions = "^3.6.2" +content-hash = "01b806daea7ea0f931916666391b6b83cf7558f75ccce9fc140dbe9a2c15073f" [metadata.files] alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -appdirs = [ - {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, - {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, -] astroid = [ {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"}, {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"}, @@ -825,8 +853,8 @@ bandit = [ {file = "bandit-1.6.3.tar.gz", hash = "sha256:d02dfe250f4aa2d166c127ad81d192579e2bfcdb8501717c0e2005e35a6bcf60"}, ] black = [ - {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, - {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, + {file = "black-21.10b0-py3-none-any.whl", hash = "sha256:6eb7448da9143ee65b856a5f3676b7dda98ad9abe0f87fce8c59291f15e82a5b"}, + {file = "black-21.10b0.tar.gz", hash = "sha256:a9952229092e325fe5f3dae56d81f639b23f7131eb840781947e4b2886030f33"}, ] certifi = [ {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"}, @@ -957,12 +985,28 @@ m2r2 = [ {file = "m2r2-0.2.7.tar.gz", hash = "sha256:fbf72ade9f648d41658f97c5267687431612451f36b65761a138fbc276294df5"}, ] markupsafe = [ + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"}, + {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"}, + {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"}, {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"}, @@ -971,14 +1015,27 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"}, + {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"}, {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"}, {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"}, {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"}, + {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"}, {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"}, {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"}, @@ -988,6 +1045,12 @@ markupsafe = [ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"}, {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"}, + {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"}, {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"}, {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"}, @@ -1025,13 +1088,17 @@ packaging = [ {file = "packaging-20.7.tar.gz", hash = "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236"}, ] pathspec = [ - {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, - {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, ] pbr = [ {file = "pbr-5.5.1-py2.py3-none-any.whl", hash = "sha256:b236cde0ac9a6aedd5e3c34517b423cd4fd97ef723849da6b0d2231142d89c00"}, {file = "pbr-5.5.1.tar.gz", hash = "sha256:5fad80b613c402d5b7df7bd84812548b2a61e9977387a80a5fc5c396492b13c9"}, ] +platformdirs = [ + {file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"}, + {file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"}, +] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, @@ -1222,42 +1289,49 @@ toml = [ {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] +tomli = [ + {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, + {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, +] typed-ast = [ - {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-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, - {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-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, - {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-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, - {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-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, - {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, - {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, - {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, - {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ {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"}, + {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, + {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, + {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, ] urllib3 = [ {file = "urllib3-1.26.5-py2.py3-none-any.whl", hash = "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c"}, diff --git a/pyproject.toml b/pyproject.toml index 34b19e6d..e1d88aa0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.6" +python = "^3.6.2" pydantic = "^1.7.4,!=1.8,!=1.8.1" structlog = "^20.1.0" colorama = {version = "^0.4.3", optional = true} @@ -33,7 +33,7 @@ toml = {version = "^0.10.2", optional = true} [tool.poetry.dev-dependencies] pytest = "^6.1.0" pyyaml = "^5.3" -black = "^19.10b0" +black = "^21.10b0" pylint = "^2.4.4" pydocstyle = "^5.0.2" yamllint = "^1.20.0" diff --git a/tests/unit/test_diffsync.py b/tests/unit/test_diffsync.py index 0c85dd83..4d32f98d 100644 --- a/tests/unit/test_diffsync.py +++ b/tests/unit/test_diffsync.py @@ -22,7 +22,7 @@ from diffsync import DiffSync, DiffSyncModel, DiffSyncFlags, DiffSyncModelFlags from diffsync.exceptions import ObjectAlreadyExists, ObjectNotFound, ObjectCrudException -from .conftest import Site, Device, Interface, TrackedDiff, BackendA +from .conftest import Site, Device, Interface, TrackedDiff, BackendA, PersonA def test_diffsync_default_name_type(generic_diffsync): @@ -81,11 +81,125 @@ def test_diffsync_get_by_uids_with_no_data(generic_diffsync): generic_diffsync.get_by_uids(["any", "another"], DiffSyncModel) -def test_diffsync_add(generic_diffsync, generic_diffsync_model): +def test_diffsync_add_no_raises_existing_same_object(generic_diffsync): + person = PersonA(name="Mikhail Yohman") + + modelname = person.get_type() + uid = person.get_unique_id() + + # First attempt at adding object + generic_diffsync.add(person) + assert modelname in generic_diffsync._data # pylint: disable=protected-access + assert uid in generic_diffsync._data[modelname] # pylint: disable=protected-access + assert person == generic_diffsync._data[modelname][uid] # pylint: disable=protected-access + + # Attempt to add again and make sure it doesn't raise an exception + generic_diffsync.add(person) + assert person is generic_diffsync._data[modelname][uid] # pylint: disable=protected-access + assert person is generic_diffsync.get(PersonA, "Mikhail Yohman") + + +def test_diffsync_add_raises_already_exists_with_updated_object(generic_diffsync): + intf = Interface(device_name="device1", name="eth0") # A DiffSync can store arbitrary DiffSyncModel objects, even if it doesn't know about them at definition time. - generic_diffsync.add(generic_diffsync_model) - with pytest.raises(ObjectAlreadyExists): - generic_diffsync.add(generic_diffsync_model) + generic_diffsync.add(intf) + # Create new interface with same identifiers so it's technically the same object, but set additional attribute + new_intf = Interface(device_name="device1", name="eth0", interface_type="1000base-t") + with pytest.raises(ObjectAlreadyExists) as error: + generic_diffsync.add(new_intf) + error_model = error.value.existing_object + assert isinstance(error_model, DiffSyncModel) + assert new_intf is error_model + + +def test_diffsync_get_or_instantiate_create_non_existent_object(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + + # Assert that the object does not currently exist. + with pytest.raises(ObjectNotFound): + generic_diffsync.get(Interface, intf_identifiers) + + obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers) + assert created + assert obj is generic_diffsync.get(Interface, intf_identifiers) + + +def test_diffsync_get_or_instantiate_retrieve_existing_object(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + intf = Interface(**intf_identifiers) + generic_diffsync.add(intf) + + obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers) + assert obj is intf + assert not created + + +def test_diffsync_get_or_instantiate_retrieve_existing_object_w_attrs(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} + intf = Interface(**intf_identifiers) + generic_diffsync.add(intf) + + obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers, intf_attrs) + assert obj is intf + assert not created + assert obj.interface_type == "ethernet" + assert obj.description is None + + +def test_diffsync_get_or_instantiate_retrieve_create_non_existent_w_attrs(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} + + obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers, intf_attrs) + assert created + assert obj.interface_type == "1000base-t" + assert obj.description == "Testing" + assert obj is generic_diffsync.get(Interface, intf_identifiers) + + +def test_diffsync_get_or_instantiate_retrieve_existing_object_wo_attrs(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + intf = Interface(**intf_identifiers) + generic_diffsync.add(intf) + + obj, created = generic_diffsync.get_or_instantiate(Interface, intf_identifiers) + assert obj is intf + assert not created + assert obj.interface_type == "ethernet" + assert obj.description is None + + +def test_diffsync_update_or_instantiate_retrieve_existing_object_w_updated_attrs(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} + intf = Interface(**intf_identifiers) + generic_diffsync.add(intf) + + obj, created = generic_diffsync.update_or_instantiate(Interface, intf_identifiers, intf_attrs) + assert obj is intf + assert not created + assert obj.interface_type == "1000base-t" + assert obj.description == "Testing" + + +def test_diffsync_update_or_instantiate_create_object(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + + obj, created = generic_diffsync.update_or_instantiate(Interface, intf_identifiers, {}) + assert created + assert obj.interface_type == "ethernet" + assert obj.description is None + + +def test_diffsync_update_or_instantiate_create_object_w_attrs(generic_diffsync): + intf_identifiers = {"device_name": "device1", "name": "eth1"} + intf_attrs = {"interface_type": "1000base-t", "description": "Testing"} + + obj, created = generic_diffsync.update_or_instantiate(Interface, intf_identifiers, intf_attrs) + assert created + assert obj.interface_type == "1000base-t" + assert obj.description == "Testing" def test_diffsync_get_with_generic_model(generic_diffsync, generic_diffsync_model): @@ -550,8 +664,6 @@ def test_diffsync_add_get_remove_with_subclass_and_data(backend_a): site_rdu_a = backend_a.get(Site, "rdu") site_atl_a = Site(name="atl") backend_a.add(site_atl_a) - with pytest.raises(ObjectAlreadyExists): - backend_a.add(site_atl_a) assert backend_a.get(Site, "atl") == site_atl_a assert list(backend_a.get_all("site")) == [site_nyc_a, site_sfo_a, site_rdu_a, site_atl_a] diff --git a/tests/unit/test_diffsync_model.py b/tests/unit/test_diffsync_model.py index 56e5d4a4..7670ff2a 100644 --- a/tests/unit/test_diffsync_model.py +++ b/tests/unit/test_diffsync_model.py @@ -126,8 +126,11 @@ def test_diffsync_model_subclass_add_remove(make_site, make_device, make_interfa assert site1.devices == ["device1"] with pytest.raises(ObjectStoreWrongType): site1.add_child(device1_eth0) - with pytest.raises(ObjectAlreadyExists): + with pytest.raises(ObjectAlreadyExists) as error: site1.add_child(device1) + error_model = error.value.args[1] + assert isinstance(error_model, DiffSyncModel) + assert error_model is device1 site1.remove_child(device1) assert site1.devices == [] @@ -141,8 +144,11 @@ def test_diffsync_model_subclass_add_remove(make_site, make_device, make_interfa assert device1.interfaces == ["device1__eth0"] with pytest.raises(ObjectStoreWrongType): device1.add_child(site1) - with pytest.raises(ObjectAlreadyExists): + with pytest.raises(ObjectAlreadyExists) as error: device1.add_child(device1_eth0) + error_model = error.value.args[1] + assert isinstance(error_model, DiffSyncModel) + assert error_model is device1_eth0 device1.remove_child(device1_eth0) assert device1.interfaces == [] @@ -217,7 +223,9 @@ def test_diffsync_model_subclass_crud(generic_diffsync): assert device1.role == "spine" device1_eth0 = Interface.create( - generic_diffsync, {"name": "eth0", "device_name": "device1"}, {"description": "some description"}, + generic_diffsync, + {"name": "eth0", "device_name": "device1"}, + {"description": "some description"}, ) assert isinstance(device1_eth0, Interface) assert device1_eth0.diffsync == generic_diffsync diff --git a/tests/unit/test_examples.py b/tests/unit/test_examples.py index 78542054..41119488 100644 --- a/tests/unit/test_examples.py +++ b/tests/unit/test_examples.py @@ -35,3 +35,11 @@ def test_example_2(): example2_main = join(example2_dir, "main.py") # Run it and make sure it doesn't raise an exception or otherwise exit with a non-zero code. subprocess.run(example2_main, cwd=example2_dir, check=True) + + +def test_example_4(): + """Test that the "example4" script runs successfully.""" + example4_dir = join(EXAMPLES, "04-get-update-instantiate") + example4_main = join(example4_dir, "main.py") + # Run it and make sure it doesn't raise an exception or otherwise exit with a non-zero code. + subprocess.run(example4_main, cwd=example4_dir, check=True)