From 42a6cff13925bb51739e348420328a6906c0d98e Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Sat, 8 Jan 2022 16:53:04 +0200 Subject: [PATCH 001/112] Add TOTP field for the Field class With Connect 1.4.0 we introduced the capability to get the totp value through Connect. Now the SDK supports that as well. --- src/onepasswordconnectsdk/models/field.py | 33 ++++++++++++++++++++--- src/tests/test_client_items.py | 11 ++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 8be07e2..5a39b02 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -38,7 +38,8 @@ class Field(object): 'label': 'str', 'value': 'str', 'generate': 'bool', - 'entropy': 'float' + 'entropy': 'float', + 'totp': 'str' } attribute_map = { @@ -49,10 +50,11 @@ class Field(object): 'label': 'label', 'value': 'value', 'generate': 'generate', - 'entropy': 'entropy' + 'entropy': 'entropy', + 'totp': 'str' } - def __init__(self, id=None, section=None, type='STRING', purpose=None, label=None, value=None, generate=False, entropy=None): # noqa: E501 + def __init__(self, id=None, section=None, type='STRING', purpose=None, label=None, value=None, generate=False, entropy=None, totp=None): # noqa: E501 self._id = None self._section = None self._type = None @@ -62,6 +64,7 @@ def __init__(self, id=None, section=None, type='STRING', purpose=None, label=Non self._generate = None self._entropy = None self.discriminator = None + self.totp = None self.id = id if section is not None: @@ -78,6 +81,8 @@ def __init__(self, id=None, section=None, type='STRING', purpose=None, label=Non self.generate = generate if entropy is not None: self.entropy = entropy + if totp is not None: + self.totp = totp @property def id(self): @@ -264,6 +269,28 @@ def entropy(self, entropy): """ self._entropy = entropy + + @property + def totp(self): + """Gets the TOTP. # noqa: E501 + + For fields of type 'OTP' this is the one-time password code # noqa: E501 + + :return: The TOTP of the Field. # noqa: E501 + :rtype: str + """ + + @totp.setter + def totp(self, totp): + """Sets the totp of this Field. + + For fields of type 'OTP' this is the one-time password code # noqa: E501 + + :param totp: The TOTP of this Field. # noqa: E501 + :type: str + """ + + self._totp = totp def to_dict(self): """Returns the model properties as a dict""" diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index fcdb2cf..2c97e22 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -269,6 +269,7 @@ def compare_fields(expected_field, returned_field): assert expected_field["purpose"] == returned_field.purpose assert expected_field["section"]["id"] == returned_field.section.id assert expected_field["type"] == returned_field.type + assert expected_field["totp"] == returned_field.totp def compare_sections(expected_section, returned_section): @@ -331,6 +332,16 @@ def get_item(): "type": "STRING", "label": "something", "value": "test" + }, + { + "id": "TOTP_acf2fgvsa312c9sd4vs8jhkli", + "section": { + "id": "Section_47DC4DDBF26640AB8B8618DA36D5A492" + }, + "type": "OTP", + "label": "one-time password", + "value": "otpauth://totp=testop?secret=test", + "totp": "134253" } ], "lastEditedBy": "DOIHOHSV2NHK5HMSOLCWJUXFDM", From e98b4e67e3f5c3b66b44664cd3c1f3cb3e1a4789 Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Sat, 8 Jan 2022 17:08:18 +0200 Subject: [PATCH 002/112] Fix totp property and tests --- src/onepasswordconnectsdk/models/field.py | 5 +++-- src/tests/test_client_items.py | 18 +++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 5a39b02..3c3bb36 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -51,7 +51,7 @@ class Field(object): 'value': 'value', 'generate': 'generate', 'entropy': 'entropy', - 'totp': 'str' + 'totp': 'totp' } def __init__(self, id=None, section=None, type='STRING', purpose=None, label=None, value=None, generate=False, entropy=None, totp=None): # noqa: E501 @@ -63,8 +63,8 @@ def __init__(self, id=None, section=None, type='STRING', purpose=None, label=Non self._value = None self._generate = None self._entropy = None + self._totp = None self.discriminator = None - self.totp = None self.id = id if section is not None: @@ -279,6 +279,7 @@ def totp(self): :return: The TOTP of the Field. # noqa: E501 :rtype: str """ + return self._totp @totp.setter def totp(self, totp): diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index 2c97e22..0a494eb 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -263,18 +263,18 @@ def compare_items(expected_item, returned_item): def compare_fields(expected_field, returned_field): - assert expected_field["id"] == returned_field.id - assert expected_field["label"] == returned_field.label - assert expected_field["value"] == returned_field.value - assert expected_field["purpose"] == returned_field.purpose - assert expected_field["section"]["id"] == returned_field.section.id - assert expected_field["type"] == returned_field.type - assert expected_field["totp"] == returned_field.totp + assert expected_field.get("id") == returned_field.id + assert expected_field.get("label") == returned_field.label + assert expected_field.get("value") == returned_field.value + assert expected_field.get("purpose") == returned_field.purpose + assert expected_field.get("section").get("id") == returned_field.section.id + assert expected_field.get("type") == returned_field.type + assert expected_field.get("totp") == returned_field.totp def compare_sections(expected_section, returned_section): - assert expected_section["id"] == returned_section.id - assert expected_section["label"] == returned_section.label + assert expected_section.get("id") == returned_section.id + assert expected_section.get("label") == returned_section.label def get_items(): From 5e7f5cd75d33994eb92d27bcf0c622edb97b54af Mon Sep 17 00:00:00 2001 From: Rick van Galen <1130569+DCKcode@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:19:31 -0400 Subject: [PATCH 003/112] Address injection vector in GitHub workflow --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed5978a..c65c9e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,10 @@ jobs: password: ${{ secrets.PYPI_API_TOKEN }} - name: Create Release tag id: create-tag + env: + PR_REF: ${{ github.event.pull_request.head.ref }} run: | - release_tag=$(echo ${{ github.event.pull_request.head.ref }} | cut -d "/" -f2) + release_tag=$(echo "$PR_REF" | cut -d "/" -f2) echo "::set-output name=release-tag::$release_tag" - name: Make new release uses: ncipollo/release-action@v1 From 188dcfa0b6f1429062851c226454c64cabf05bf4 Mon Sep 17 00:00:00 2001 From: Rick van Galen <1130569+DCKcode@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:39:59 -0400 Subject: [PATCH 004/112] Escape GitHub ref on PR workflow --- .github/workflows/release-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index e4340c6..01d655a 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -46,7 +46,7 @@ jobs: - name: Parse release version id: get_version - run: echo "::set-output name=version::$(echo $GITHUB_REF | sed 's|^refs/heads/release/v?*||g')" + run: echo "::set-output name=version::$(echo "${GITHUB_REF}" | sed 's|^refs/heads/release/v?*||g')" - name: Prepare Pull Request id: prep_pr From 4fb0e3031f33303be66f5a57cd328fae699db2b7 Mon Sep 17 00:00:00 2001 From: Rick van Galen <1130569+DCKcode@users.noreply.github.com> Date: Mon, 4 Apr 2022 17:59:36 -0400 Subject: [PATCH 005/112] Correct nested echo syntax --- .github/workflows/release-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 01d655a..73a5930 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -46,7 +46,7 @@ jobs: - name: Parse release version id: get_version - run: echo "::set-output name=version::$(echo "${GITHUB_REF}" | sed 's|^refs/heads/release/v?*||g')" + run: echo "::set-output name=version::`echo "${GITHUB_REF}" | sed 's|^refs/heads/release/v?*||g'`" - name: Prepare Pull Request id: prep_pr From 33776587786b8d53fbc7232f84a5e06c42e091c2 Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Tue, 7 Jun 2022 14:13:42 +0200 Subject: [PATCH 006/112] Make the release pipeline build the wheel as well --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c65c9e5..2e7d38f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: pip install poetry poetry install - name: Build the package - run: poetry build --format sdist + run: poetry build - name: Publish to PyPi uses: pypa/gh-action-pypi-publish@release/v1 with: From 93cc1907bda0732bcaa6404d9b1be5a483c911d3 Mon Sep 17 00:00:00 2001 From: Tim Bishop Date: Sun, 24 Jul 2022 19:17:29 +0100 Subject: [PATCH 007/112] Add GeneratorRecipe. --- example/main.py | 14 +- src/onepasswordconnectsdk/models/__init__.py | 1 + src/onepasswordconnectsdk/models/field.py | 28 ++- .../models/generator_recipe.py | 165 ++++++++++++++++++ 4 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/onepasswordconnectsdk/models/generator_recipe.py diff --git a/example/main.py b/example/main.py index 1bc64dd..7c4ab26 100644 --- a/example/main.py +++ b/example/main.py @@ -3,7 +3,7 @@ import time import onepasswordconnectsdk -from onepasswordconnectsdk.models import (ItemVault, Field) +from onepasswordconnectsdk.models import (ItemVault, Field, GeneratorRecipe) op_connect_token = os.environ["OP_CONNECT_TOKEN"] default_vault = os.environ["OP_VAULT"] @@ -21,7 +21,17 @@ title="Secret String", category="LOGIN", tags=["1password-connect"], - fields=[Field(value=secret_string)]) + fields=[ + Field( + value=secret_string + ), + Field( + purpose="PASSWORD", + generate=True, + recipe=GeneratorRecipe(length=10, character_sets=['LETTERS']) + ) + ] +) print(steps.steps["step2"]) # ADD THE ITEM TO THE 1P VAULT diff --git a/src/onepasswordconnectsdk/models/__init__.py b/src/onepasswordconnectsdk/models/__init__.py index 60cf4d3..15b7d55 100644 --- a/src/onepasswordconnectsdk/models/__init__.py +++ b/src/onepasswordconnectsdk/models/__init__.py @@ -12,6 +12,7 @@ from onepasswordconnectsdk.models.field import Field from onepasswordconnectsdk.models.field_section import FieldSection from onepasswordconnectsdk.models.file import File +from onepasswordconnectsdk.models.generator_recipe import GeneratorRecipe from onepasswordconnectsdk.models.section import Section from onepasswordconnectsdk.models.summary_item import SummaryItem from onepasswordconnectsdk.models.item_urls import ItemUrls diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 3c3bb36..c78ef3a 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -38,6 +38,7 @@ class Field(object): 'label': 'str', 'value': 'str', 'generate': 'bool', + 'recipe': 'GeneratorRecipe', 'entropy': 'float', 'totp': 'str' } @@ -50,11 +51,12 @@ class Field(object): 'label': 'label', 'value': 'value', 'generate': 'generate', + 'recipe': 'recipe', 'entropy': 'entropy', 'totp': 'totp' } - def __init__(self, id=None, section=None, type='STRING', purpose=None, label=None, value=None, generate=False, entropy=None, totp=None): # noqa: E501 + def __init__(self, id=None, section=None, type='STRING', purpose=None, label=None, value=None, generate=False, recipe=None, entropy=None, totp=None): # noqa: E501 self._id = None self._section = None self._type = None @@ -62,6 +64,7 @@ def __init__(self, id=None, section=None, type='STRING', purpose=None, label=Non self._label = None self._value = None self._generate = None + self._recipe = None self._entropy = None self._totp = None self.discriminator = None @@ -79,6 +82,8 @@ def __init__(self, id=None, section=None, type='STRING', purpose=None, label=Non self.value = value if generate is not None: self.generate = generate + if recipe is not None: + self.recipe = recipe if entropy is not None: self.entropy = entropy if totp is not None: @@ -247,6 +252,27 @@ def generate(self, generate): self._generate = generate + @property + def recipe(self): + """Gets the recipe of this Field. # noqa: E501 + + + :return: The recipe of this Field. # noqa: E501 + :rtype: GeneratorRecipe + """ + return self._recipe + + @recipe.setter + def recipe(self, recipe): + """Sets the recipe of this Field. + + + :param recipe: The recipe of this Field. # noqa: E501 + :type: GeneratorRecipe + """ + + self._recipe = recipe + @property def entropy(self): """Gets the entropy of this Field. # noqa: E501 diff --git a/src/onepasswordconnectsdk/models/generator_recipe.py b/src/onepasswordconnectsdk/models/generator_recipe.py new file mode 100644 index 0000000..d156a6e --- /dev/null +++ b/src/onepasswordconnectsdk/models/generator_recipe.py @@ -0,0 +1,165 @@ +# coding: utf-8 + +""" + 1Password Connect + + REST API interface for 1Password Connect. # noqa: E501 + + The version of the OpenAPI document: 1.3.0 + Contact: support@1password.com + Generated by: https://openapi-generator.tech +""" + + +try: + from inspect import getfullargspec +except ImportError: + from inspect import getargspec as getfullargspec +import pprint +import re # noqa: F401 +import six + +class GeneratorRecipe(object): + """NOTE: This class is auto generated by OpenAPI Generator. + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + """ + Attributes: + openapi_types (dict): The key is attribute name + and the value is attribute type. + attribute_map (dict): The key is attribute name + and the value is json key in definition. + """ + openapi_types = { + 'length': 'int', + 'character_sets': 'list[str]' + } + + attribute_map = { + 'length': 'length', + 'character_sets': 'characterSets' + } + + def __init__(self, length=32, character_sets=None, local_vars_configuration=None): # noqa: E501 + """GeneratorRecipe - a model defined in OpenAPI""" # noqa: E501 + + self._length = None + self._character_sets = None + self.discriminator = None + + if length is not None: + self.length = length + if character_sets is not None: + self.character_sets = character_sets + + @property + def length(self): + """Gets the length of this GeneratorRecipe. # noqa: E501 + + Length of the generated value # noqa: E501 + + :return: The length of this GeneratorRecipe. # noqa: E501 + :rtype: int + """ + return self._length + + @length.setter + def length(self, length): + """Sets the length of this GeneratorRecipe. + + Length of the generated value # noqa: E501 + + :param length: The length of this GeneratorRecipe. # noqa: E501 + :type length: int + """ + if (length is not None and length > 64): # noqa: E501 + raise ValueError("Invalid value for `length`, must be a value less than or equal to `64`") # noqa: E501 + if (length is not None and length < 1): # noqa: E501 + raise ValueError("Invalid value for `length`, must be a value greater than or equal to `1`") # noqa: E501 + + self._length = length + + @property + def character_sets(self): + """Gets the character_sets of this GeneratorRecipe. # noqa: E501 + + + :return: The character_sets of this GeneratorRecipe. # noqa: E501 + :rtype: list[str] + """ + return self._character_sets + + @character_sets.setter + def character_sets(self, character_sets): + """Sets the character_sets of this GeneratorRecipe. + + + :param character_sets: The character_sets of this GeneratorRecipe. # noqa: E501 + :type character_sets: list[str] + """ + allowed_values = ["LETTERS", "DIGITS", "SYMBOLS"] # noqa: E501 + if (not set(character_sets).issubset(set(allowed_values))): # noqa: E501 + raise ValueError( + "Invalid values for `character_sets` [{0}], must be a subset of [{1}]" # noqa: E501 + .format(", ".join(map(str, set(character_sets) - set(allowed_values))), # noqa: E501 + ", ".join(map(str, allowed_values))) + ) + + self._character_sets = character_sets + + def to_dict(self, serialize=False): + """Returns the model properties as a dict""" + result = {} + + def convert(x): + if hasattr(x, "to_dict"): + args = getfullargspec(x.to_dict).args + if len(args) == 1: + return x.to_dict() + else: + return x.to_dict(serialize) + else: + return x + + for attr, _ in six.iteritems(self.openapi_types): + value = getattr(self, attr) + attr = self.attribute_map.get(attr, attr) if serialize else attr + if isinstance(value, list): + result[attr] = list(map( + lambda x: convert(x), + value + )) + elif isinstance(value, dict): + result[attr] = dict(map( + lambda item: (item[0], convert(item[1])), + value.items() + )) + else: + result[attr] = convert(value) + + return result + + def to_str(self): + """Returns the string representation of the model""" + return pprint.pformat(self.to_dict()) + + def __repr__(self): + """For `print` and `pprint`""" + return self.to_str() + + def __eq__(self, other): + """Returns true if both objects are equal""" + if not isinstance(other, GeneratorRecipe): + return False + + return self.to_dict() == other.to_dict() + + def __ne__(self, other): + """Returns true if both objects are not equal""" + if not isinstance(other, GeneratorRecipe): + return True + + return self.to_dict() != other.to_dict() From 3199c1c7bf8f62b9b6a97a71a76a62c1da5f80bd Mon Sep 17 00:00:00 2001 From: Justin Tether Date: Fri, 28 Oct 2022 11:44:36 -0600 Subject: [PATCH 008/112] fix: correctly handle section objects with None type labels --- src/onepasswordconnectsdk/config.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 535faa8..7398232 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -193,17 +193,20 @@ def _set_values_for_item( section_id = field.section.id except AttributeError: section_id = None - - if field.label == path_parts[1] and ( - section_id is None or section_id == sections[path_parts[0]] - ): - value_found = True - - if config_object: - setattr(config_object, parsed_field.name, field.value) - else: - config_dict[parsed_field.name] = field.value - break + + if field.label == path_parts[1]: + if ( + section_id is None + or (section_id == sections.get(path_parts[0])) + or path_parts[0] in sections.values() + ): + value_found = True + + if config_object: + setattr(config_object, parsed_field.name, field.value) + else: + config_dict[parsed_field.name] = field.value + break if not value_found: raise UnknownSectionAndFieldTag( f"There is no section {path_parts[0]} \ @@ -214,6 +217,7 @@ def _set_values_for_item( def _convert_sections_to_dict(sections: List[Section]): if not sections: return {} + section_dict = {section.label: section.id for section in sections} return section_dict From 430f6d42d5c43f63f9c0095cb506436799422b9e Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Tue, 22 Nov 2022 12:19:39 +0000 Subject: [PATCH 009/112] [bug] Allow field type to be a reference --- src/onepasswordconnectsdk/models/field.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 3c3bb36..c9d0453 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -144,7 +144,7 @@ def type(self, type): :param type: The type of this Field. # noqa: E501 :type: str """ - allowed_values = ["STRING", "EMAIL", "CONCEALED", "URL", "OTP", "DATE", "MONTH_YEAR", "MENU"] # noqa: E501 + allowed_values = ["STRING", "EMAIL", "CONCEALED", "URL", "OTP", "DATE", "MONTH_YEAR", "MENU", "REFERENCE"] # noqa: E501 if type not in allowed_values: # noqa: E501 raise ValueError( "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 From 5c4e305ce116f26da3a54e4b5712343678dadb06 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Fri, 2 Dec 2022 17:05:36 +0100 Subject: [PATCH 010/112] Remove hard-coded checks for item category and field type Because these checks are also applied during deserialization of Connect's JSON response, they can cause the SDK to return an error if a new category or field type is added in Connect. --- src/onepasswordconnectsdk/models/field.py | 7 ------- src/onepasswordconnectsdk/models/item.py | 7 ------- src/onepasswordconnectsdk/models/summary_item.py | 7 ------- 3 files changed, 21 deletions(-) diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 3c3bb36..518834b 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -144,13 +144,6 @@ def type(self, type): :param type: The type of this Field. # noqa: E501 :type: str """ - allowed_values = ["STRING", "EMAIL", "CONCEALED", "URL", "OTP", "DATE", "MONTH_YEAR", "MENU"] # noqa: E501 - if type not in allowed_values: # noqa: E501 - raise ValueError( - "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 - .format(type, allowed_values) - ) - self._type = type @property diff --git a/src/onepasswordconnectsdk/models/item.py b/src/onepasswordconnectsdk/models/item.py index ede5942..b078f7d 100644 --- a/src/onepasswordconnectsdk/models/item.py +++ b/src/onepasswordconnectsdk/models/item.py @@ -190,13 +190,6 @@ def category(self, category): :param category: The category of this Item. # noqa: E501 :type: str """ - allowed_values = ["LOGIN", "PASSWORD", "SERVER", "DATABASE", "CREDIT_CARD", "MEMBERSHIP", "PASSPORT", "SOFTWARE_LICENSE", "OUTDOOR_LICENSE", "SECURE_NOTE", "WIRELESS_ROUTER", "BANK_ACCOUNT", "DRIVER_LICENSE", "IDENTITY", "REWARD_PROGRAM", "DOCUMENT", "EMAIL_ACCOUNT", "SOCIAL_SECURITY_NUMBER", "API_CREDENTIAL", "CUSTOM"] # noqa: E501 - if category not in allowed_values: # noqa: E501 - raise ValueError( - "Invalid value for `category` ({0}), must be one of {1}" # noqa: E501 - .format(category, allowed_values) - ) - self._category = category @property diff --git a/src/onepasswordconnectsdk/models/summary_item.py b/src/onepasswordconnectsdk/models/summary_item.py index 109f1cf..cb65cb2 100644 --- a/src/onepasswordconnectsdk/models/summary_item.py +++ b/src/onepasswordconnectsdk/models/summary_item.py @@ -182,13 +182,6 @@ def category(self, category): :param category: The category of this Item. # noqa: E501 :type: str """ - allowed_values = ["LOGIN", "PASSWORD", "SERVER", "DATABASE", "CREDIT_CARD", "MEMBERSHIP", "PASSPORT", "SOFTWARE_LICENSE", "OUTDOOR_LICENSE", "SECURE_NOTE", "WIRELESS_ROUTER", "BANK_ACCOUNT", "DRIVER_LICENSE", "IDENTITY", "REWARD_PROGRAM", "DOCUMENT", "EMAIL_ACCOUNT", "SOCIAL_SECURITY_NUMBER", "API_CREDENTIAL", "CUSTOM"] # noqa: E501 - if category not in allowed_values: # noqa: E501 - raise ValueError( - "Invalid value for `category` ({0}), must be one of {1}" # noqa: E501 - .format(category, allowed_values) - ) - self._category = category @property From f9ca76bb46f428e9110238c5919478cde1f6bc0c Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Mon, 5 Dec 2022 11:26:30 +0100 Subject: [PATCH 011/112] Prepare release v1.3.0 --- CHANGELOG.md | 26 +++++++++++++++++++------- pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 228ac15..e4c41e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,30 @@ -[//]: # "START/LATEST" - +[//]: # (START/LATEST) # Latest ## Features - -- A user-friendly description of a new feature. {issue-number} + * A user-friendly description of a new feature. {issue-number} ## Fixes - -- A user-friendly description of a fix. {issue-number} + * A user-friendly description of a fix. {issue-number} ## Security + * A user-friendly description of a security fix. {issue-number} + +--- + +[//]: # "START/v1.3.0" + +# v1.3.0 + +## Features + +- The TOTP code of a TOTP field can now be accessed using the `.totp` property of a field. {#33} +- Release of the SDK also includes `wheel` format. {#45} + +## Fixes -- A user-friendly description of a security fix. {issue-number} +- Sections without a label can now be correctly accessed. {#49} +- Retrieving an item no longer returns "Invalid value for `type`" or "Invalid value for `category`" when retrieving an item with a field type or item category that is not defined in the SDK. {#52,#54} --- diff --git a/pyproject.toml b/pyproject.toml index b0fb9b7..4b3e748 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "1.2.0" +version = "1.3.0" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From e39d027972732022bed458424f65dc781fad0d48 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Tue, 6 Dec 2022 15:06:06 +0100 Subject: [PATCH 012/112] Fix naming of OTP field --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4c41e5..33ced3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ ## Features -- The TOTP code of a TOTP field can now be accessed using the `.totp` property of a field. {#33} +- The TOTP code of a OTP field can now be accessed using the `.totp` property of a field. {#33} - Release of the SDK also includes `wheel` format. {#45} ## Fixes From 39b9cfe680da1348b0b8c3cee2c420c653dfb887 Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Tue, 6 Dec 2022 15:07:35 +0100 Subject: [PATCH 013/112] Remove internal change from changelog All past releases already have a wheel. The only thing that changed is that it's now added automatically. --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ced3e..286142f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ ## Features - The TOTP code of a OTP field can now be accessed using the `.totp` property of a field. {#33} -- Release of the SDK also includes `wheel` format. {#45} ## Fixes From fb5f44049fcee40f7217fc217a4e3ebf8756f715 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Dec 2022 09:33:29 +0000 Subject: [PATCH 014/112] Bump certifi from 2020.11.8 to 2022.12.7 Bumps [certifi](https://github.com/certifi/python-certifi) from 2020.11.8 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2020.11.08...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/poetry.lock b/poetry.lock index d5dcdda..79cee9e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -15,18 +15,18 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] docs = ["furo", "sphinx", "zope.interface"] tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] name = "certifi" -version = "2020.11.8" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6" [[package]] name = "chardet" @@ -78,8 +78,8 @@ python-versions = ">=3.6" zipp = ">=0.5" [package.extras] -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)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "pytest (>=3.5,!=3.7.3)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8", "pytest-mypy"] [[package]] name = "iniconfig" @@ -150,7 +150,7 @@ py = ">=1.8.2" toml = "*" [package.extras] -checkqa_mypy = ["mypy (==0.780)"] +checkqa-mypy = ["mypy (==0.780)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] @@ -166,7 +166,7 @@ coverage = {version = ">=5.2.1", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "python-dateutil" @@ -194,7 +194,7 @@ idna = ">=2.5,<3" urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] +security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] @@ -231,7 +231,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" [package.extras] brotli = ["brotlipy (>=0.6.0)"] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] @@ -243,8 +243,8 @@ optional = false python-versions = ">=3.6" [package.extras] -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"] +docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["func-timeout", "jaraco.itertools", "jaraco.test (>=3.2.0)", "pytest (>=3.5,!=3.7.3)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8", "pytest-mypy"] [metadata] lock-version = "1.1" @@ -261,8 +261,8 @@ attrs = [ {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, ] certifi = [ - {file = "certifi-2020.11.8-py2.py3-none-any.whl", hash = "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd"}, - {file = "certifi-2020.11.8.tar.gz", hash = "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] chardet = [ {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, @@ -313,9 +313,6 @@ coverage = [ {file = "coverage-6.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e14bceb1f3ae8a14374be2b2d7bc12a59226872285f91d66d301e5f41705d4d6"}, {file = "coverage-6.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0147f7833c41927d84f5af9219d9b32f875c0689e5e74ac8ca3cb61e73a698f9"}, {file = "coverage-6.1.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b1d0a1bce919de0dd8da5cff4e616b2d9e6ebf3bd1410ff645318c3dd615010a"}, - {file = "coverage-6.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ae6de0e41f44794e68d23644636544ed8003ce24845f213b24de097cbf44997f"}, - {file = "coverage-6.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2797ed7a7e883b9ab76e8e778bb4c859fc2037d6fd0644d8675e64d58d1653"}, - {file = "coverage-6.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c40966b683d92869b72ea3c11fd6b99a091fd30e12652727eca117273fc97366"}, {file = "coverage-6.1.1-cp39-cp39-win32.whl", hash = "sha256:a11a2c019324fc111485e79d55907e7289e53d0031275a6c8daed30690bc50c0"}, {file = "coverage-6.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d8b453764b9b26b0dd2afb83086a7c3f9379134e340288d2a52f8a91592394b"}, {file = "coverage-6.1.1-pp36-none-any.whl", hash = "sha256:3b270c6b48d3ff5a35deb3648028ba2643ad8434b07836782b1139cf9c66313f"}, From 3a3ddb63c9d795fc6aba3e39166cd10e638e0a82 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Tue, 27 Dec 2022 15:31:03 +0100 Subject: [PATCH 015/112] Remove six package from dependencies Signed-off-by: volodymyrZotov --- poetry.lock | 346 +++++++++--------- pyproject.toml | 5 +- src/onepasswordconnectsdk/client.py | 14 +- src/onepasswordconnectsdk/models/error.py | 4 +- src/onepasswordconnectsdk/models/field.py | 4 +- .../models/field_section.py | 4 +- src/onepasswordconnectsdk/models/file.py | 4 +- src/onepasswordconnectsdk/models/item.py | 4 +- .../models/item_details.py | 4 +- src/onepasswordconnectsdk/models/item_urls.py | 4 +- .../models/item_vault.py | 4 +- src/onepasswordconnectsdk/models/section.py | 4 +- .../models/summary_item.py | 3 +- src/onepasswordconnectsdk/models/vault.py | 4 +- 14 files changed, 185 insertions(+), 223 deletions(-) diff --git a/poetry.lock b/poetry.lock index 79cee9e..a81aa0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,24 +1,17 @@ -[[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - [[package]] name = "attrs" -version = "20.3.0" +version = "22.2.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pympler", "pytest (>=4.3.0)", "six", "sphinx", "zope.interface"] -docs = ["furo", "sphinx", "zope.interface"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] [[package]] name = "certifi" @@ -29,57 +22,73 @@ optional = false python-versions = ">=3.6" [[package]] -name = "chardet" -version = "3.0.4" -description = "Universal encoding detector for Python 2 and 3" +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.6" description = "Cross-platform colored terminal text." 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.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" -version = "6.1.1" +version = "7.0.1" description = "Code coverage measurement for Python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] toml = ["tomli"] +[[package]] +name = "exceptiongroup" +version = "1.1.0" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "idna" -version = "2.10" +version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "3.1.1" +version = "5.2.0" description = "Read metadata from Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "pytest (>=3.5,!=3.7.3)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8", "pytest-mypy"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -91,71 +100,51 @@ python-versions = "*" [[package]] name = "packaging" -version = "20.7" +version = "22.0" description = "Core utilities for Python packages" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[package.dependencies] -pyparsing = ">=2.0.2" +python-versions = ">=3.7" [[package]] name = "pluggy" -version = "0.13.1" +version = "1.0.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.6" [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] - -[[package]] -name = "py" -version = "1.10.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "6.1.2" +version = "7.2.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" [package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=17.4.0" +attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" -pluggy = ">=0.12,<1.0" -py = ">=1.8.2" -toml = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -checkqa-mypy = ["mypy (==0.780)"] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "pytest-cov" -version = "3.0.0" +version = "4.0.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false @@ -170,7 +159,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dateutil" -version = "2.8.1" +version = "2.8.2" description = "Extensions to the standard Python datetime module" category = "main" optional = false @@ -181,206 +170,203 @@ six = ">=1.5" [[package]] name = "requests" -version = "2.25.0" +version = "2.28.1" description = "Python HTTP for Humans." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7, <4" [package.dependencies] certifi = ">=2017.4.17" -chardet = ">=3.0.2,<4" -idna = ">=2.5,<3" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" urllib3 = ">=1.21.1,<1.27" [package.extras] -security = ["cryptography (>=1.3.4)", "pyOpenSSL (>=0.14)"] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "six" -version = "1.15.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.7" [[package]] -name = "tomli" -version = "1.2.2" -description = "A lil' TOML parser" +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [[package]] name = "urllib3" -version = "1.26.7" +version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [package.extras] -brotli = ["brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "zipp" -version = "3.4.0" +version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "jaraco.test (>=3.2.0)", "pytest (>=3.5,!=3.7.3)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8", "pytest-mypy"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "8059de2d8d207fe0a668fb358a951cde011defd526fd486f713c71c17f7fab22" +content-hash = "cac4a5815b13269da64f86e0b0481742124eba1200928ffb3fdd605c12d5c3c8" [metadata.files] -atomicwrites = [ - {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-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, - {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, ] certifi = [ {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] -chardet = [ - {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, - {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, ] colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ - {file = "coverage-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42a1fb5dee3355df90b635906bb99126faa7936d87dfc97eacc5293397618cb7"}, - {file = "coverage-6.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a00284dbfb53b42e35c7dd99fc0e26ef89b4a34efff68078ed29d03ccb28402a"}, - {file = "coverage-6.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:51a441011a30d693e71dea198b2a6f53ba029afc39f8e2aeb5b77245c1b282ef"}, - {file = "coverage-6.1.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e76f017b6d4140a038c5ff12be1581183d7874e41f1c0af58ecf07748d36a336"}, - {file = "coverage-6.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7833c872718dc913f18e51ee97ea0dece61d9930893a58b20b3daf09bb1af6b6"}, - {file = "coverage-6.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8186b5a4730c896cbe1e4b645bdc524e62d874351ae50e1db7c3e9f5dc81dc26"}, - {file = "coverage-6.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbca34dca5a2d60f81326d908d77313816fad23d11b6069031a3d6b8c97a54f9"}, - {file = "coverage-6.1.1-cp310-cp310-win32.whl", hash = "sha256:72bf437d54186d104388cbae73c9f2b0f8a3e11b6e8d7deb593bd14625c96026"}, - {file = "coverage-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:994ce5a7b3d20981b81d83618aa4882f955bfa573efdbef033d5632b58597ba9"}, - {file = "coverage-6.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ab6a0fe4c96f8058d41948ddf134420d3ef8c42d5508b5a341a440cce7a37a1d"}, - {file = "coverage-6.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10ab138b153e4cc408b43792cb7f518f9ee02f4ff55cd1ab67ad6fd7e9905c7e"}, - {file = "coverage-6.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7e083d32965d2eb6638a77e65b622be32a094fdc0250f28ce6039b0732fbcaa8"}, - {file = "coverage-6.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:359a32515e94e398a5c0fa057e5887a42e647a9502d8e41165cf5cb8d3d1ca67"}, - {file = "coverage-6.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:bf656cd74ff7b4ed7006cdb2a6728150aaad69c7242b42a2a532f77b63ea233f"}, - {file = "coverage-6.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:dc5023be1c2a8b0a0ab5e31389e62c28b2453eb31dd069f4b8d1a0f9814d951a"}, - {file = "coverage-6.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:557594a50bfe3fb0b1b57460f6789affe8850ad19c1acf2d14a3e12b2757d489"}, - {file = "coverage-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:9eb0a1923354e0fdd1c8a6f53f5db2e6180d670e2b587914bf2e79fa8acfd003"}, - {file = "coverage-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:04a92a6cf9afd99f9979c61348ec79725a9f9342fb45e63c889e33c04610d97b"}, - {file = "coverage-6.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:479228e1b798d3c246ac89b09897ee706c51b3e5f8f8d778067f38db73ccc717"}, - {file = "coverage-6.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78287731e3601ea5ce9d6468c82d88a12ef8fe625d6b7bdec9b45d96c1ad6533"}, - {file = "coverage-6.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c95257aa2ccf75d3d91d772060538d5fea7f625e48157f8ca44594f94d41cb33"}, - {file = "coverage-6.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9ad5895938a894c368d49d8470fe9f519909e5ebc6b8f8ea5190bd0df6aa4271"}, - {file = "coverage-6.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:326d944aad0189603733d646e8d4a7d952f7145684da973c463ec2eefe1387c2"}, - {file = "coverage-6.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e7d5606b9240ed4def9cbdf35be4308047d11e858b9c88a6c26974758d6225ce"}, - {file = "coverage-6.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:572f917267f363101eec375c109c9c1118037c7cc98041440b5eabda3185ac7b"}, - {file = "coverage-6.1.1-cp37-cp37m-win32.whl", hash = "sha256:35cd2230e1ed76df7d0081a997f0fe705be1f7d8696264eb508076e0d0b5a685"}, - {file = "coverage-6.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:65ad3ff837c89a229d626b8004f0ee32110f9bfdb6a88b76a80df36ccc60d926"}, - {file = "coverage-6.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:977ce557d79577a3dd510844904d5d968bfef9489f512be65e2882e1c6eed7d8"}, - {file = "coverage-6.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62512c0ec5d307f56d86504c58eace11c1bc2afcdf44e3ff20de8ca427ca1d0e"}, - {file = "coverage-6.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2e5b9c17a56b8bf0c0a9477fcd30d357deb486e4e1b389ed154f608f18556c8a"}, - {file = "coverage-6.1.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:666c6b32b69e56221ad1551d377f718ed00e6167c7a1b9257f780b105a101271"}, - {file = "coverage-6.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fb2fa2f6506c03c48ca42e3fe5a692d7470d290c047ee6de7c0f3e5fa7639ac9"}, - {file = "coverage-6.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f0f80e323a17af63eac6a9db0c9188c10f1fd815c3ab299727150cc0eb92c7a4"}, - {file = "coverage-6.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:738e823a746841248b56f0f3bd6abf3b73af191d1fd65e4c723b9c456216f0ad"}, - {file = "coverage-6.1.1-cp38-cp38-win32.whl", hash = "sha256:8605add58e6a960729aa40c0fd9a20a55909dd9b586d3e8104cc7f45869e4c6b"}, - {file = "coverage-6.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:6e994003e719458420e14ffb43c08f4c14990e20d9e077cb5cad7a3e419bbb54"}, - {file = "coverage-6.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e3c4f5211394cd0bf6874ac5d29684a495f9c374919833dcfff0bd6d37f96201"}, - {file = "coverage-6.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e14bceb1f3ae8a14374be2b2d7bc12a59226872285f91d66d301e5f41705d4d6"}, - {file = "coverage-6.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0147f7833c41927d84f5af9219d9b32f875c0689e5e74ac8ca3cb61e73a698f9"}, - {file = "coverage-6.1.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b1d0a1bce919de0dd8da5cff4e616b2d9e6ebf3bd1410ff645318c3dd615010a"}, - {file = "coverage-6.1.1-cp39-cp39-win32.whl", hash = "sha256:a11a2c019324fc111485e79d55907e7289e53d0031275a6c8daed30690bc50c0"}, - {file = "coverage-6.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4d8b453764b9b26b0dd2afb83086a7c3f9379134e340288d2a52f8a91592394b"}, - {file = "coverage-6.1.1-pp36-none-any.whl", hash = "sha256:3b270c6b48d3ff5a35deb3648028ba2643ad8434b07836782b1139cf9c66313f"}, - {file = "coverage-6.1.1-pp37-none-any.whl", hash = "sha256:ffa8fee2b1b9e60b531c4c27cf528d6b5d5da46b1730db1f4d6eee56ff282e07"}, - {file = "coverage-6.1.1-pp38-none-any.whl", hash = "sha256:4cd919057636f63ab299ccb86ea0e78b87812400c76abab245ca385f17d19fb5"}, - {file = "coverage-6.1.1.tar.gz", hash = "sha256:b8e4f15b672c9156c1154249a9c5746e86ac9ae9edc3799ee3afebc323d9d9e0"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, + {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, + {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, + {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, + {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, + {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, + {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, + {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, + {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, + {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, + {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, + {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, + {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, + {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, ] idna = [ - {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, - {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] importlib-metadata = [ - {file = "importlib_metadata-3.1.1-py3-none-any.whl", hash = "sha256:6112e21359ef8f344e7178aa5b72dc6e62b38b0d008e6d3cb212c5b84df72013"}, - {file = "importlib_metadata-3.1.1.tar.gz", hash = "sha256:b0c2d3b226157ae4517d9625decf63591461c66b3a808c2666d538946519d170"}, + {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, + {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] packaging = [ - {file = "packaging-20.7-py2.py3-none-any.whl", hash = "sha256:eb41423378682dadb7166144a4926e443093863024de508ca5c9737d6bc08376"}, - {file = "packaging-20.7.tar.gz", hash = "sha256:05af3bb85d320377db281cf254ab050e1a7ebcbf5410685a9a407e18a1f81236"}, + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, ] pluggy = [ - {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.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, -] -pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] pytest = [ - {file = "pytest-6.1.2-py3-none-any.whl", hash = "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe"}, - {file = "pytest-6.1.2.tar.gz", hash = "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"}, + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] pytest-cov = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, ] python-dateutil = [ - {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, - {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] requests = [ - {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, - {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, ] six = [ - {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, - {file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] tomli = [ - {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, - {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] urllib3 = [ - {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, - {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, ] zipp = [ - {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, - {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, ] diff --git a/pyproject.toml b/pyproject.toml index 4b3e748..7f2595e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,12 +14,11 @@ repository = "https://github.com/1Password/connect-sdk-python" [tool.poetry.dependencies] python = "^3.7" requests = "^2.24.0" -six = "^1.10" python-dateutil = "^2.8.1" [tool.poetry.dev-dependencies] -pytest = "^6.0" -pytest-cov = "^3.0.0" +pytest = "^7.2.0" +pytest-cov = "^4.0.0" [build-system] requires = ["poetry>=0.12"] diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 15d4b85..2950b80 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -3,7 +3,6 @@ import json import os import re -import six import requests import datetime from requests.exceptions import HTTPError @@ -16,10 +15,9 @@ class Client: - PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types + PRIMITIVE_TYPES = (float, bool, bytes, str, int) NATIVE_TYPES_MAPPING = { "int": int, - "long": int if six.PY3 else long, # type: ignore # noqa: F821 "float": float, "str": str, "bool": bool, @@ -443,13 +441,13 @@ def sanitize_for_serialization(self, obj): # model definition for request. obj_dict = { obj.attribute_map[attr]: getattr(obj, attr) - for attr, _ in six.iteritems(obj.openapi_types) + for attr in obj.openapi_types.keys() if getattr(obj, attr) is not None } return { key: self.sanitize_for_serialization(val) - for key, val in six.iteritems(obj_dict) + for key, val in obj_dict.items() } def __deserialize(self, data, klass): @@ -471,7 +469,7 @@ def __deserialize(self, data, klass): if klass.startswith("dict("): sub_kls = re.match(r"dict\(([^,]*), (.*)\)", klass).group(2) return { - k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data) # noqa: E501 + k: self.__deserialize(v, sub_kls) for k, v in data.items() # noqa: E501 } # convert str to class @@ -502,7 +500,7 @@ def __deserialize_primitive(self, data, klass): try: return klass(data) except UnicodeEncodeError: - return six.text_type(data) + return str(data) except TypeError: return data @@ -570,7 +568,7 @@ def __deserialize_model(self, data, klass): and klass.openapi_types is not None and isinstance(data, (list, dict)) ): - for attr, attr_type in six.iteritems(klass.openapi_types): + for attr, attr_type in klass.openapi_types.items(): if klass.attribute_map[attr] in data: value = data[klass.attribute_map[attr]] kwargs[attr] = self.__deserialize(value, attr_type) diff --git a/src/onepasswordconnectsdk/models/error.py b/src/onepasswordconnectsdk/models/error.py index b1fc6ae..ab8855a 100644 --- a/src/onepasswordconnectsdk/models/error.py +++ b/src/onepasswordconnectsdk/models/error.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class Error(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -94,7 +92,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list( diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 518834b..546fcd2 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class Field(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -290,7 +288,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/field_section.py b/src/onepasswordconnectsdk/models/field_section.py index 7ae2768..d042405 100644 --- a/src/onepasswordconnectsdk/models/field_section.py +++ b/src/onepasswordconnectsdk/models/field_section.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class FieldSection(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -70,7 +68,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/file.py b/src/onepasswordconnectsdk/models/file.py index 3c43115..1cf3e30 100644 --- a/src/onepasswordconnectsdk/models/file.py +++ b/src/onepasswordconnectsdk/models/file.py @@ -1,8 +1,6 @@ import pprint import re # noqa: F401 -import six - class File(object): openapi_types = { @@ -71,7 +69,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/item.py b/src/onepasswordconnectsdk/models/item.py index b078f7d..0cf9dff 100644 --- a/src/onepasswordconnectsdk/models/item.py +++ b/src/onepasswordconnectsdk/models/item.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class Item(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -406,7 +404,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/item_details.py b/src/onepasswordconnectsdk/models/item_details.py index 72fc277..34cdf84 100644 --- a/src/onepasswordconnectsdk/models/item_details.py +++ b/src/onepasswordconnectsdk/models/item_details.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class ItemDetails(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -96,7 +94,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/item_urls.py b/src/onepasswordconnectsdk/models/item_urls.py index d529b7b..0f8df8b 100644 --- a/src/onepasswordconnectsdk/models/item_urls.py +++ b/src/onepasswordconnectsdk/models/item_urls.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class ItemUrls(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -96,7 +94,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/item_vault.py b/src/onepasswordconnectsdk/models/item_vault.py index 609c57e..51eb52a 100644 --- a/src/onepasswordconnectsdk/models/item_vault.py +++ b/src/onepasswordconnectsdk/models/item_vault.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class ItemVault(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -70,7 +68,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/section.py b/src/onepasswordconnectsdk/models/section.py index 33f1ff0..9318f22 100644 --- a/src/onepasswordconnectsdk/models/section.py +++ b/src/onepasswordconnectsdk/models/section.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class Section(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -95,7 +93,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/summary_item.py b/src/onepasswordconnectsdk/models/summary_item.py index cb65cb2..76a4ac2 100644 --- a/src/onepasswordconnectsdk/models/summary_item.py +++ b/src/onepasswordconnectsdk/models/summary_item.py @@ -13,7 +13,6 @@ import pprint import re # noqa: F401 -import six from onepasswordconnectsdk.models import Item @@ -356,7 +355,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( diff --git a/src/onepasswordconnectsdk/models/vault.py b/src/onepasswordconnectsdk/models/vault.py index 255e58f..1282b37 100644 --- a/src/onepasswordconnectsdk/models/vault.py +++ b/src/onepasswordconnectsdk/models/vault.py @@ -13,8 +13,6 @@ import pprint import re # noqa: F401 -import six - class Vault(object): """NOTE: This class is auto generated by OpenAPI Generator. @@ -291,7 +289,7 @@ def to_dict(self): """Returns the model properties as a dict""" result = {} - for attr, _ in six.iteritems(self.openapi_types): + for attr in self.openapi_types.keys(): value = getattr(self, attr) if isinstance(value, list): result[attr] = list(map( From 5ee681b2d87eeb241d0188f76755dca85c276f9d Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Thu, 5 Jan 2023 13:58:39 +0100 Subject: [PATCH 016/112] Use httpx library instead of requests Signed-off-by: volodymyrZotov --- poetry.lock | 153 +++++++++++++++++++--------- pyproject.toml | 2 +- src/onepasswordconnectsdk/client.py | 60 +++++------ 3 files changed, 138 insertions(+), 77 deletions(-) diff --git a/poetry.lock b/poetry.lock index a81aa0d..396bc83 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,3 +1,21 @@ +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + [[package]] name = "attrs" version = "22.2.0" @@ -21,17 +39,6 @@ category = "main" optional = false python-versions = ">=3.6" -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - [[package]] name = "colorama" version = "0.4.6" @@ -65,6 +72,55 @@ python-versions = ">=3.7" [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "httpcore" +version = "0.16.3" +description = "A minimal low-level HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.0,<5.0" +certifi = "*" +h11 = ">=0.13,<0.15" +sniffio = ">=1.0.0,<2.0.0" + +[package.extras] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + +[[package]] +name = "httpx" +version = "0.23.3" +description = "The next generation HTTP client." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = "*" +httpcore = ">=0.15.0,<0.17.0" +rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} +sniffio = "*" + +[package.extras] +brotli = ["brotli", "brotlicffi"] +cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +http2 = ["h2 (>=3,<5)"] +socks = ["socksio (>=1.0.0,<2.0.0)"] + [[package]] name = "idna" version = "3.4" @@ -169,22 +225,18 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" six = ">=1.5" [[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." +name = "rfc3986" +version = "1.5.0" +description = "Validating URI References per RFC 3986" category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = "*" [package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} [package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +idna2008 = ["idna"] [[package]] name = "six" @@ -194,6 +246,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.7" + [[package]] name = "tomli" version = "2.0.1" @@ -206,22 +266,9 @@ python-versions = ">=3.7" name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.13" -description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +python-versions = ">=3.7" [[package]] name = "zipp" @@ -238,9 +285,13 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "cac4a5815b13269da64f86e0b0481742124eba1200928ffb3fdd605c12d5c3c8" +content-hash = "56df1ba62090d75443508ab810b7cf8f235361c6725e52f0ecb060e6ee6fc22d" [metadata.files] +anyio = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] attrs = [ {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, @@ -249,10 +300,6 @@ certifi = [ {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] colorama = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -314,6 +361,18 @@ exceptiongroup = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, ] +h11 = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] +httpcore = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] +httpx = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] idna = [ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, @@ -346,14 +405,18 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +rfc3986 = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sniffio = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] tomli = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, @@ -362,10 +425,6 @@ typing-extensions = [ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] -urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, -] zipp = [ {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, diff --git a/pyproject.toml b/pyproject.toml index 7f2595e..b2d40c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,8 +13,8 @@ repository = "https://github.com/1Password/connect-sdk-python" [tool.poetry.dependencies] python = "^3.7" -requests = "^2.24.0" python-dateutil = "^2.8.1" +httpx = "^0.23.3" [tool.poetry.dev-dependencies] pytest = "^7.2.0" diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 2950b80..f7ee7d7 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -1,11 +1,11 @@ """Python Client for connecting to 1Password Connect""" +import httpx +from httpx import HTTPError from dateutil.parser import parse import json import os import re -import requests import datetime -from requests.exceptions import HTTPError import onepasswordconnectsdk from onepasswordconnectsdk.models import Item, ItemVault from onepasswordconnectsdk.models.constants import CONNECT_HOST_ENV_VARIABLE @@ -28,28 +28,12 @@ class Client: """Python Client Class""" - def __init__(self, url: str, token: str): + def __init__(self, session): """Initialize client""" - self.url = url - self.token = token - self.session = self.create_session() + self.session = session - def create_session(self): - session = requests.Session() - session.headers.update(self.build_headers()) - return session - - def build_headers(self): - """Builds the headers needed to make a request to the server - - Returns: - dict: The 1Password Connect API request headers - """ - - headers = {} - headers["Authorization"] = f"Bearer {self.token}" - headers["Content-Type"] = "application/json" - return headers + def __del__(self): + self.session.close() def get_file(self, file_id: str, item_id: str, vault_id: str): url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" @@ -250,7 +234,7 @@ def create_item(self, vault_id: str, item: Item): url = f"/v1/vaults/{vault_id}/items" - response: requests.Response = self.build_request("POST", url, item) + response = self.build_request("POST", url, item) try: response.raise_for_status() except HTTPError: @@ -279,7 +263,7 @@ def update_item(self, item_uuid: str, vault_id: str, item: Item): item.id = item_uuid item.vault = ItemVault(id=vault_id) - response: requests.Response = self.build_request("PUT", url, item) + response = self.build_request("PUT", url, item) try: response.raise_for_status() except HTTPError: @@ -380,13 +364,12 @@ def build_request(self, method: str, path: str, body=None): Returns: Response object: The request response """ - url = f"{self.url}{path}" if body: serialized_body = json.dumps(self.sanitize_for_serialization(body)) - response = self.session.request(method, url, data=serialized_body) + response = self.session.request(method, path, data=serialized_body) else: - response = self.session.request(method, url) + response = self.session.request(method, path) return response def deserialize(self, response, response_type): @@ -591,7 +574,20 @@ def _is_valid_UUID(self, uuid): return True -def new_client(url: str, token: str): +class AsyncClient: + pass + + +def build_headers(token: str): + """Builds the headers needed to make a request to the server + + Returns: + dict: The 1Password Connect API request headers + """ + return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + +def new_client(url: str, token: str, is_async: bool = False): """Builds a new client for interacting with 1Password Connect Parameters: url: The url of the 1Password Connect API @@ -600,7 +596,13 @@ def new_client(url: str, token: str): Returns: Client: The 1Password Connect client """ - return Client(url=url, token=token) + headers = build_headers(token) + if is_async: + session = httpx.AsyncClient(base_url=url, headers=headers) + return AsyncClient(session=session) + + session = httpx.Client(base_url=url, headers=headers) + return Client(session=session) def new_client_from_environment(url: str = None): From f5d3cafe8c69f6ebe3f0dff894d9974aa8437782 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Thu, 5 Jan 2023 16:53:26 +0100 Subject: [PATCH 017/112] Extract serialization logic to standalone class Signed-off-by: volodymyrZotov --- src/onepasswordconnectsdk/client.py | 284 +++--------------------- src/onepasswordconnectsdk/errors.py | 22 ++ src/onepasswordconnectsdk/serializer.py | 211 ++++++++++++++++++ src/onepasswordconnectsdk/utils.py | 11 + 4 files changed, 271 insertions(+), 257 deletions(-) create mode 100644 src/onepasswordconnectsdk/errors.py create mode 100644 src/onepasswordconnectsdk/serializer.py create mode 100644 src/onepasswordconnectsdk/utils.py diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index f7ee7d7..9b8e288 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -1,36 +1,30 @@ """Python Client for connecting to 1Password Connect""" import httpx from httpx import HTTPError -from dateutil.parser import parse import json import os -import re -import datetime -import onepasswordconnectsdk + +from onepasswordconnectsdk.serializer import Serializer +from onepasswordconnectsdk.utils import is_valid_uuid +from onepasswordconnectsdk.errors import ( + FailedToRetrieveItemException, + FailedToRetrieveVaultException, + EnvironmentHostNotSetException, + EnvironmentTokenNotSetException, +) from onepasswordconnectsdk.models import Item, ItemVault from onepasswordconnectsdk.models.constants import CONNECT_HOST_ENV_VARIABLE ENV_SERVICE_ACCOUNT_JWT_VARIABLE = "OP_CONNECT_TOKEN" -UUIDLength = 26 class Client: - PRIMITIVE_TYPES = (float, bool, bytes, str, int) - NATIVE_TYPES_MAPPING = { - "int": int, - "float": float, - "str": str, - "bool": bool, - "date": datetime.date, - "datetime": datetime.datetime, - "object": object, - } - """Python Client Class""" - def __init__(self, session): + def __init__(self, session, serializer): """Initialize client""" self.session = session + self.serializer = serializer def __del__(self): self.session.close() @@ -45,7 +39,7 @@ def get_file(self, file_id: str, item_id: str, vault_id: str): f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "File") + return self.serializer.deserialize(response.content, "File") def get_files(self, item_id: str, vault_id: str): url = f"/v1/vaults/{vault_id}/items/{item_id}/files" @@ -58,7 +52,7 @@ def get_files(self, item_id: str, vault_id: str): f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "list[File]") + return self.serializer.deserialize(response.content, "list[File]") def get_file_content(self, file_id: str, item_id: str, vault_id: str): url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" @@ -99,10 +93,10 @@ def get_item(self, item: str, vault: str): """ vault_id = vault - if not self._is_valid_UUID(vault): + if not is_valid_uuid(vault): vault_id = self.get_vault_by_title(vault).id - if self._is_valid_UUID(item): + if is_valid_uuid(item): return self.get_item_by_id(item, vault_id) else: return self.get_item_by_title(item, vault_id) @@ -131,7 +125,7 @@ def get_item_by_id(self, item_id: str, vault_id: str): f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "Item") + return self.serializer.deserialize(response.content, "Item") def get_item_by_title(self, title: str, vault_id: str): """Get a specific item by title @@ -165,7 +159,7 @@ def get_item_by_title(self, title: str, vault_id: str): title {title}" ) - item_summary = self.deserialize(response.content, "list[SummaryItem]")[0] + item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] return self.get_item_by_id(item_summary.id, vault_id) def get_items(self, vault_id: str): @@ -192,7 +186,7 @@ def get_items(self, vault_id: str): for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "list[SummaryItem]") + return self.serializer.deserialize(response.content, "list[SummaryItem]") def delete_item(self, item_id: str, vault_id: str): """Deletes a specified item from a specified vault @@ -242,7 +236,7 @@ def create_item(self, vault_id: str, item: Item): f"Unable to post item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "Item") + return self.serializer.deserialize(response.content, "Item") def update_item(self, item_uuid: str, vault_id: str, item: Item): """Update the specified item at the specified vault. @@ -271,7 +265,7 @@ def update_item(self, item_uuid: str, vault_id: str, item: Item): f"Unable to post item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "Item") + return self.serializer.deserialize(response.content, "Item") def get_vault(self, vault_id: str): """Returns the vault with the given vault_id @@ -296,7 +290,7 @@ def get_vault(self, vault_id: str): for {url} with message {response.json().get('message')}" ) - return self.deserialize(response.content, "Vault") + return self.serializer.deserialize(response.content, "Vault") def get_vault_by_title(self, name: str): """Returns the vault with the given name @@ -329,7 +323,7 @@ def get_vault_by_title(self, name: str): name {name}" ) - return self.deserialize(response.content, "list[Vault]")[0] + return self.serializer.deserialize(response.content, "list[Vault]")[0] def get_vaults(self): """Returns all vaults for service account set in client @@ -352,7 +346,7 @@ def get_vaults(self): for {url} with message {response.json().get('message')}" ) - return self.deserialize(response.content, "list[Vault]") + return self.serializer.deserialize(response.content, "list[Vault]") def build_request(self, method: str, path: str, body=None): """Builds a http request @@ -366,213 +360,12 @@ def build_request(self, method: str, path: str, body=None): """ if body: - serialized_body = json.dumps(self.sanitize_for_serialization(body)) + serialized_body = json.dumps(self.serializer.sanitize_for_serialization(body)) response = self.session.request(method, path, data=serialized_body) else: response = self.session.request(method, path) return response - def deserialize(self, response, response_type): - """Deserializes response into an object. - - :param response: RESTResponse object to be deserialized. - :param response_type: class literal for - deserialized object, or string of class name. - - :return: deserialized object. - """ - # fetch data from response object - try: - data = json.loads(response) - except ValueError: - data = response - - return self.__deserialize(data, response_type) - - def sanitize_for_serialization(self, obj): - """Builds a JSON POST object. - - If obj is None, return None. - If obj is str, int, long, float, bool, return directly. - If obj is datetime.datetime, datetime.date convert to string - in iso8601 format. - If obj is list, sanitize each element in the list. - If obj is dict, return the dict. - If obj is OpenAPI model, return the properties dict. - - :param obj: The data to serialize. - :return: The serialized form of data. - """ - if obj is None: - return None - elif isinstance(obj, self.PRIMITIVE_TYPES): - return obj - elif isinstance(obj, list): - return [self.sanitize_for_serialization(sub_obj) for sub_obj in obj] # noqa: E501 - elif isinstance(obj, tuple): - return tuple(self.sanitize_for_serialization(sub_obj) for sub_obj in obj) # noqa: E501 - elif isinstance(obj, (datetime.datetime, datetime.date)): - return obj.isoformat() - - if isinstance(obj, dict): - obj_dict = obj - else: - # Convert model obj to dict except - # attributes `openapi_types`, `attribute_map` - # and attributes which value is not None. - # Convert attribute name to json key in - # model definition for request. - obj_dict = { - obj.attribute_map[attr]: getattr(obj, attr) - for attr in obj.openapi_types.keys() - if getattr(obj, attr) is not None - } - - return { - key: self.sanitize_for_serialization(val) - for key, val in obj_dict.items() - } - - def __deserialize(self, data, klass): - """Deserializes dict, list, str into an object. - - :param data: dict, list or str. - :param klass: class literal, or string of class name. - - :return: object. - """ - if data is None: - return None - - if type(klass) == str: - if klass.startswith("list["): - sub_kls = re.match(r"list\[(.*)\]", klass).group(1) - return [self.__deserialize(sub_data, sub_kls) for sub_data in data] # noqa: E501 - - if klass.startswith("dict("): - sub_kls = re.match(r"dict\(([^,]*), (.*)\)", klass).group(2) - return { - k: self.__deserialize(v, sub_kls) for k, v in data.items() # noqa: E501 - } - - # convert str to class - if klass in self.NATIVE_TYPES_MAPPING: - klass = self.NATIVE_TYPES_MAPPING[klass] - else: - klass = getattr(onepasswordconnectsdk.models, klass) - - if klass in self.PRIMITIVE_TYPES: - return self.__deserialize_primitive(data, klass) - elif klass == object: - return self.__deserialize_object(data) - elif klass == datetime.date: - return self.__deserialize_date(data) - elif klass == datetime.datetime: - return self.__deserialize_datetime(data) - else: - return self.__deserialize_model(data, klass) - - def __deserialize_primitive(self, data, klass): - """Deserializes string to primitive type. - - :param data: str. - :param klass: class literal. - - :return: int, long, float, str, bool. - """ - try: - return klass(data) - except UnicodeEncodeError: - return str(data) - except TypeError: - return data - - def __deserialize_object(self, value): - """Return an original value. - - :return: object. - """ - return value - - def __deserialize_date(self, string): - """Deserializes string to date. - - :param string: str. - :return: date. - """ - try: - return parse(string).date() - except ImportError: - return string - except ValueError: - raise FailedToDeserializeException( - f'Failed to parse `{0}`\ - as date object".format(string)' - ) - - def __deserialize_datetime(self, string): - """Deserializes string to datetime. - - The string should be in iso8601 datetime format. - - :param string: str. - :return: datetime. - """ - try: - return parse(string) - except ImportError: - return string - except ValueError: - raise FailedToDeserializeException( - f'Failed to parse `{0}`\ - as date object".format(string)' - ) - - def __deserialize_model(self, data, klass): - """Deserializes list or dict to model. - - :param data: dict, list. - :param klass: class literal. - :return: model object. - """ - has_discriminator = False - if ( - hasattr(klass, "get_real_child_model") - and klass.discriminator_value_class_map - ): - has_discriminator = True - - if not klass.openapi_types and has_discriminator is False: - return data - - kwargs = {} - if ( - data is not None - and klass.openapi_types is not None - and isinstance(data, (list, dict)) - ): - for attr, attr_type in klass.openapi_types.items(): - if klass.attribute_map[attr] in data: - value = data[klass.attribute_map[attr]] - kwargs[attr] = self.__deserialize(value, attr_type) - - instance = klass(**kwargs) - - if has_discriminator: - klass_name = instance.get_real_child_model(data) - if klass_name: - instance = self.__deserialize(data, klass_name) - return instance - - def _is_valid_UUID(self, uuid): - if len(uuid) is not UUIDLength: - return False - for c in uuid: - valid = (c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') - if valid is False: - return False - return True - class AsyncClient: pass @@ -597,12 +390,13 @@ def new_client(url: str, token: str, is_async: bool = False): Client: The 1Password Connect client """ headers = build_headers(token) + serializer = Serializer() if is_async: session = httpx.AsyncClient(base_url=url, headers=headers) - return AsyncClient(session=session) + return AsyncClient(session=session, serializer=serializer) session = httpx.Client(base_url=url, headers=headers) - return Client(session=session) + return Client(session=session, serializer=serializer) def new_client_from_environment(url: str = None): @@ -632,27 +426,3 @@ def new_client_from_environment(url: str = None): ) return Client(url=url, token=token) - - -class OnePasswordConnectSDKError(RuntimeError): - pass - - -class EnvironmentTokenNotSetException(OnePasswordConnectSDKError, TypeError): - pass - - -class EnvironmentHostNotSetException(OnePasswordConnectSDKError, TypeError): - pass - - -class FailedToRetrieveItemException(OnePasswordConnectSDKError): - pass - - -class FailedToRetrieveVaultException(OnePasswordConnectSDKError): - pass - - -class FailedToDeserializeException(OnePasswordConnectSDKError, TypeError): - pass diff --git a/src/onepasswordconnectsdk/errors.py b/src/onepasswordconnectsdk/errors.py new file mode 100644 index 0000000..1a24bb4 --- /dev/null +++ b/src/onepasswordconnectsdk/errors.py @@ -0,0 +1,22 @@ +class OnePasswordConnectSDKError(RuntimeError): + pass + + +class EnvironmentTokenNotSetException(OnePasswordConnectSDKError, TypeError): + pass + + +class EnvironmentHostNotSetException(OnePasswordConnectSDKError, TypeError): + pass + + +class FailedToRetrieveItemException(OnePasswordConnectSDKError): + pass + + +class FailedToRetrieveVaultException(OnePasswordConnectSDKError): + pass + + +class FailedToDeserializeException(OnePasswordConnectSDKError, TypeError): + pass \ No newline at end of file diff --git a/src/onepasswordconnectsdk/serializer.py b/src/onepasswordconnectsdk/serializer.py new file mode 100644 index 0000000..db07568 --- /dev/null +++ b/src/onepasswordconnectsdk/serializer.py @@ -0,0 +1,211 @@ +from dateutil.parser import parse +import json +import re +import datetime +import onepasswordconnectsdk +from onepasswordconnectsdk.errors import FailedToDeserializeException + + +class Serializer: + PRIMITIVE_TYPES = (float, bool, bytes, str, int) + NATIVE_TYPES_MAPPING = { + "int": int, + "float": float, + "str": str, + "bool": bool, + "date": datetime.date, + "datetime": datetime.datetime, + "object": object, + } + + def deserialize(self, response, response_type): + """Deserializes response into an object. + + :param response: RESTResponse object to be deserialized. + :param response_type: class literal for + deserialized object, or string of class name. + + :return: deserialized object. + """ + # fetch data from response object + try: + data = json.loads(response) + except ValueError: + data = response + + return self.__deserialize(data, response_type) + + def sanitize_for_serialization(self, obj): + """Builds a JSON POST object. + + If obj is None, return None. + If obj is str, int, long, float, bool, return directly. + If obj is datetime.datetime, datetime.date convert to string + in iso8601 format. + If obj is list, sanitize each element in the list. + If obj is dict, return the dict. + If obj is OpenAPI model, return the properties dict. + + :param obj: The data to serialize. + :return: The serialized form of data. + """ + if obj is None: + return None + elif isinstance(obj, self.PRIMITIVE_TYPES): + return obj + elif isinstance(obj, list): + return [self.sanitize_for_serialization(sub_obj) for sub_obj in obj] # noqa: E501 + elif isinstance(obj, tuple): + return tuple(self.sanitize_for_serialization(sub_obj) for sub_obj in obj) # noqa: E501 + elif isinstance(obj, (datetime.datetime, datetime.date)): + return obj.isoformat() + + if isinstance(obj, dict): + obj_dict = obj + else: + # Convert model obj to dict except + # attributes `openapi_types`, `attribute_map` + # and attributes which value is not None. + # Convert attribute name to json key in + # model definition for request. + obj_dict = { + obj.attribute_map[attr]: getattr(obj, attr) + for attr in obj.openapi_types.keys() + if getattr(obj, attr) is not None + } + + return { + key: self.sanitize_for_serialization(val) + for key, val in obj_dict.items() + } + + def __deserialize(self, data, klass): + """Deserializes dict, list, str into an object. + + :param data: dict, list or str. + :param klass: class literal, or string of class name. + + :return: object. + """ + if data is None: + return None + + if type(klass) == str: + if klass.startswith("list["): + sub_kls = re.match(r"list\[(.*)\]", klass).group(1) + return [self.__deserialize(sub_data, sub_kls) for sub_data in data] # noqa: E501 + + if klass.startswith("dict("): + sub_kls = re.match(r"dict\(([^,]*), (.*)\)", klass).group(2) + return { + k: self.__deserialize(v, sub_kls) for k, v in data.items() # noqa: E501 + } + + # convert str to class + if klass in self.NATIVE_TYPES_MAPPING: + klass = self.NATIVE_TYPES_MAPPING[klass] + else: + klass = getattr(onepasswordconnectsdk.models, klass) + + if klass in self.PRIMITIVE_TYPES: + return self.__deserialize_primitive(data, klass) + elif klass == object: + return self.__deserialize_object(data) + elif klass == datetime.date: + return self.__deserialize_date(data) + elif klass == datetime.datetime: + return self.__deserialize_datetime(data) + else: + return self.__deserialize_model(data, klass) + + def __deserialize_primitive(self, data, klass): + """Deserializes string to primitive type. + + :param data: str. + :param klass: class literal. + + :return: int, long, float, str, bool. + """ + try: + return klass(data) + except UnicodeEncodeError: + return str(data) + except TypeError: + return data + + def __deserialize_object(self, value): + """Return an original value. + + :return: object. + """ + return value + + def __deserialize_date(self, string): + """Deserializes string to date. + + :param string: str. + :return: date. + """ + try: + return parse(string).date() + except ImportError: + return string + except ValueError: + raise FailedToDeserializeException( + f'Failed to parse `{0}`\ + as date object".format(string)' + ) + + def __deserialize_datetime(self, string): + """Deserializes string to datetime. + + The string should be in iso8601 datetime format. + + :param string: str. + :return: datetime. + """ + try: + return parse(string) + except ImportError: + return string + except ValueError: + raise FailedToDeserializeException( + f'Failed to parse `{0}`\ + as date object".format(string)' + ) + + def __deserialize_model(self, data, klass): + """Deserializes list or dict to model. + + :param data: dict, list. + :param klass: class literal. + :return: model object. + """ + has_discriminator = False + if ( + hasattr(klass, "get_real_child_model") + and klass.discriminator_value_class_map + ): + has_discriminator = True + + if not klass.openapi_types and has_discriminator is False: + return data + + kwargs = {} + if ( + data is not None + and klass.openapi_types is not None + and isinstance(data, (list, dict)) + ): + for attr, attr_type in klass.openapi_types.items(): + if klass.attribute_map[attr] in data: + value = data[klass.attribute_map[attr]] + kwargs[attr] = self.__deserialize(value, attr_type) + + instance = klass(**kwargs) + + if has_discriminator: + klass_name = instance.get_real_child_model(data) + if klass_name: + instance = self.__deserialize(data, klass_name) + return instance diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py new file mode 100644 index 0000000..4a2c8e1 --- /dev/null +++ b/src/onepasswordconnectsdk/utils.py @@ -0,0 +1,11 @@ +UUIDLength = 26 + + +def is_valid_uuid(uuid): + if len(uuid) is not UUIDLength: + return False + for c in uuid: + valid = (c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') + if valid is False: + return False + return True From 7e286ee481e4649376b8b0df0dbba71a4921b821 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Fri, 6 Jan 2023 13:03:58 +0100 Subject: [PATCH 018/112] Add AsyncClient methods Signed-off-by: volodymyrZotov --- src/onepasswordconnectsdk/client.py | 344 +++++++++++++++++++++++++++- 1 file changed, 343 insertions(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 9b8e288..c66c536 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -368,7 +368,349 @@ def build_request(self, method: str, path: str, body=None): class AsyncClient: - pass + """Python Async Client Class""" + + def __init__(self, session, serializer): + """Initialize client""" + self.session = session + self.serializer = serializer + + async def get_file(self, file_id: str, item_id: str, vault_id: str): + url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "File") + + async def get_files(self, item_id: str, vault_id: str): + url = f"/v1/vaults/{vault_id}/items/{item_id}/files" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "list[File]") + + async def get_file_content(self, file_id: str, item_id: str, vault_id: str): + url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve items. Received {response.status_code} \ + for {url} with message: {response.json().get('message')}" + ) + return response.content + + async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): + file_object = await self.get_file(file_id, item_id, vault_id) + filename = file_object.name + content = await self.get_file_content(file_id, item_id, vault_id) + global_path = os.path.join(path, filename) + + file = open(global_path, "wb") + file.write(content) + file.close() + + async def get_item(self, item: str, vault: str): + """Get a specific item + + Args: + item (str): the id or title of the item to be fetched + vault (str): the id or name of the vault in which to get the item from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item object: The found item + """ + + vault_id = vault + if not is_valid_uuid(vault): + vault_id = self.get_vault_by_title(vault).id + + if is_valid_uuid(item): + return self.get_item_by_id(item, vault_id) + else: + return self.get_item_by_title(item, vault_id) + + async def get_item_by_id(self, item_id: str, vault_id: str): + """Get a specific item by uuid + + Args: + item_id (str): The id of the item to be fetched + vault_id (str): The id of the vault in which to get the item from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item object: The found item + """ + url = f"/v1/vaults/{vault_id}/items/{item_id}" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "Item") + + async def get_item_by_title(self, title: str, vault_id: str): + """Get a specific item by title + + Args: + title (str): The title of the item to be fetched + vault_id (str): The id of the vault in which to get the item from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item object: The found item + """ + filter_query = f'title eq "{title}"' + url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve items. Received {response.status_code} \ + for {url} with message: {response.json().get('message')}" + ) + + if len(response.json()) != 1: + raise FailedToRetrieveItemException( + f"Found {len(response.json())} items in vault {vault_id} with \ + title {title}" + ) + + item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] + return await self.get_item_by_id(item_summary.id, vault_id) + + async def get_items(self, vault_id: str): + """Returns a list of item summaries for the specified vault + + Args: + vault_id (str): The id of the vault in which to get the items from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + List[SummaryItem]: A list of summarized items + """ + url = f"/v1/vaults/{vault_id}/items" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve items. Received {response.status_code} \ + for {url} with message: {response.json().get('message')}" + ) + + return self.serializer.deserialize(response.content, "list[SummaryItem]") + + async def delete_item(self, item_id: str, vault_id: str): + """Deletes a specified item from a specified vault + + Args: + item_id (str): The id of the item in which to delete the item from + vault_id (str): The id of the vault in which to delete the item + from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + """ + url = f"/v1/vaults/{vault_id}/items/{item_id}" + + response = await self.build_request("DELETE", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to delete item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + + async def create_item(self, vault_id: str, item: Item): + """Creates an item at the specified vault + + Args: + vault_id (str): The id of the vault in which add the item to + item (Item): The item to create + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item: The created item + """ + + url = f"/v1/vaults/{vault_id}/items" + + response = await self.build_request("POST", url, item) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to post item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "Item") + + async def update_item(self, item_uuid: str, vault_id: str, item: Item): + """Update the specified item at the specified vault. + + Args: + item_uuid (str): The id of the item in which to update + vault_id (str): The id of the vault in which to update the item + item (Item): The updated item + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item: The updated item + """ + url = f"/v1/vaults/{vault_id}/items/{item_uuid}" + item.id = item_uuid + item.vault = ItemVault(id=vault_id) + + response = await self.build_request("PUT", url, item) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to post item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "Item") + + async def get_vault(self, vault_id: str): + """Returns the vault with the given vault_id + + Args: + vault_id (str): The id of the vault in which to fetch + + Raises: + FailedToRetrieveVaultException: Thrown when a HTTP error is + returned from the 1Password Connect API + + Returns: + Vault: The specified vault + """ + url = f"/v1/vaults/{vault_id}" + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveVaultException( + f"Unable to retrieve vault. Received {response.status_code} \ + for {url} with message {response.json().get('message')}" + ) + + return self.serializer.deserialize(response.content, "Vault") + + async def get_vault_by_title(self, name: str): + """Returns the vault with the given name + + Args: + name (str): The name of the vault in which to fetch + + Raises: + FailedToRetrieveVaultException: Thrown when a HTTP error is + returned from the 1Password Connect API + + Returns: + Vault: The specified vault + """ + filter_query = f'name eq "{name}"' + url = f"/v1/vaults?filter={filter_query}" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveVaultException( + f"Unable to retrieve vaults. Received {response.status_code} \ + for {url} with message {response.json().get('message')}" + ) + + if len(response.json()) != 1: + raise FailedToRetrieveItemException( + f"Found {len(response.json())} vaults with \ + name {name}" + ) + + return self.serializer.deserialize(response.content, "list[Vault]")[0] + + async def get_vaults(self): + """Returns all vaults for service account set in client + + Raises: + FailedToRetrieveVaultException: Thrown when a HTTP error is + returned from the 1Password Connect API + + Returns: + List[Vault]: All vaults for the service account in use + """ + url = "/v1/vaults" + response = await self.build_request("GET", url) + + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveVaultException( + f"Unable to retrieve vaults. Received {response.status_code} \ + for {url} with message {response.json().get('message')}" + ) + + return self.serializer.deserialize(response.content, "list[Vault]") + + def build_request(self, method: str, path: str, body=None): + """Builds a http request + Parameters: + method (str): The rest method to be used + path (str): The request path + body (str): The request body + + Returns: + Response object: The request response + """ + + if body: + serialized_body = json.dumps(self.serializer.sanitize_for_serialization(body)) + response = self.session.request(method, path, data=serialized_body) + else: + response = self.session.request(method, path) + return response def build_headers(token: str): From c073954b0f0bfbd71022ee13cb1e7141f4a9b0d5 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Mon, 9 Jan 2023 15:49:18 +0100 Subject: [PATCH 019/112] Make tests to use respx library Signed-off-by: volodymyrZotov --- poetry.lock | 127 ++++++++++-------- pyproject.toml | 1 + src/onepasswordconnectsdk/client.py | 4 +- src/tests/test_client_items.py | 192 +++++++++------------------- src/tests/test_client_vaults.py | 43 ++----- src/tests/test_config.py | 146 ++++++++++----------- 6 files changed, 217 insertions(+), 296 deletions(-) diff --git a/poetry.lock b/poetry.lock index 396bc83..0c37d5d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,7 +49,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 [[package]] name = "coverage" -version = "7.0.1" +version = "7.0.3" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -131,7 +131,7 @@ python-versions = ">=3.5" [[package]] name = "importlib-metadata" -version = "5.2.0" +version = "6.0.0" description = "Read metadata from Python packages" category = "dev" optional = false @@ -224,6 +224,17 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" [package.dependencies] six = ">=1.5" +[[package]] +name = "respx" +version = "0.20.1" +description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +httpx = ">=0.21.0" + [[package]] name = "rfc3986" version = "1.5.0" @@ -285,7 +296,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "56df1ba62090d75443508ab810b7cf8f235361c6725e52f0ecb060e6ee6fc22d" +content-hash = "ebabcc843f5c0064cfbcff7b8940a475b4b4fb48f10731c60f131b13f1122417" [metadata.files] anyio = [ @@ -305,57 +316,57 @@ colorama = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ - {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, - {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, - {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, - {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, - {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, - {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, - {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, - {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, - {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, - {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, - {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, - {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, - {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, - {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, - {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, - {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, - {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, - {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, - {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, - {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, - {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, + {file = "coverage-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f7c51b6074a8a3063c341953dffe48fd6674f8e4b1d3c8aa8a91f58d6e716a8"}, + {file = "coverage-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:628f47eaf66727fc986d3b190d6fa32f5e6b7754a243919d28bc0fd7974c449f"}, + {file = "coverage-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89d5abf86c104de808108a25d171ad646c07eda96ca76c8b237b94b9c71e518"}, + {file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75e43c6f4ea4d122dac389aabdf9d4f0e160770a75e63372f88005d90f5bcc80"}, + {file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49da0ff241827ebb52d5d6d5a36d33b455fa5e721d44689c95df99fd8db82437"}, + {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0bce4ad5bdd0b02e177a085d28d2cea5fc57bb4ba2cead395e763e34cf934eb1"}, + {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f79691335257d60951638dd43576b9bcd6f52baa5c1c2cd07a509bb003238372"}, + {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5722269ed05fbdb94eef431787c66b66260ff3125d1a9afcc00facff8c45adf9"}, + {file = "coverage-7.0.3-cp310-cp310-win32.whl", hash = "sha256:bdbda870e0fda7dd0fe7db7135ca226ec4c1ade8aa76e96614829b56ca491012"}, + {file = "coverage-7.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:e56fae4292e216b8deeee38ace84557b9fa85b52db005368a275427cdabb8192"}, + {file = "coverage-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b82343a5bc51627b9d606f0b6b6b9551db7b6311a5dd920fa52a94beae2e8959"}, + {file = "coverage-7.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd0a8aa431f9b7ad9eb8264f55ef83cbb254962af3775092fb6e93890dea9ca2"}, + {file = "coverage-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112cfead1bd22eada8a8db9ed387bd3e8be5528debc42b5d3c1f7da4ffaf9fb5"}, + {file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af87e906355fa42447be5c08c5d44e6e1c005bf142f303f726ddf5ed6e0c8a4d"}, + {file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f30090e22a301952c5abd0e493a1c8358b4f0b368b49fa3e4568ed3ed68b8d1f"}, + {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae871d09901911eedda1981ea6fd0f62a999107293cdc4c4fd612321c5b34745"}, + {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ed7c9debf7bfc63c9b9f8b595409237774ff4b061bf29fba6f53b287a2fdeab9"}, + {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13121fa22dcd2c7b19c5161e3fd725692448f05377b788da4502a383573227b3"}, + {file = "coverage-7.0.3-cp311-cp311-win32.whl", hash = "sha256:037b51ee86bc600f99b3b957c20a172431c35c2ef9c1ca34bc813ab5b51fd9f5"}, + {file = "coverage-7.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:25fde928306034e8deecd5fc91a07432dcc282c8acb76749581a28963c9f4f3f"}, + {file = "coverage-7.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e8b0642c38b3d3b3c01417643ccc645345b03c32a2e84ef93cdd6844d6fe530"}, + {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18b09811f849cc958d23f733a350a66b54a8de3fed1e6128ba55a5c97ffb6f65"}, + {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:349d0b545520e8516f7b4f12373afc705d17d901e1de6a37a20e4ec9332b61f7"}, + {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b38813eee5b4739f505d94247604c72eae626d5088a16dd77b08b8b1724ab3"}, + {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba9af1218fa01b1f11c72271bc7290b701d11ad4dbc2ae97c445ecacf6858dba"}, + {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c5648c7eec5cf1ba5db1cf2d6c10036a582d7f09e172990474a122e30c841361"}, + {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d0df04495b76a885bfef009f45eebe8fe2fbf815ad7a83dabcf5aced62f33162"}, + {file = "coverage-7.0.3-cp37-cp37m-win32.whl", hash = "sha256:af6cef3796b8068713a48dd67d258dc9a6e2ebc3bd4645bfac03a09672fa5d20"}, + {file = "coverage-7.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:62ef3800c4058844e2e3fa35faa9dd0ccde8a8aba6c763aae50342e00d4479d4"}, + {file = "coverage-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acef7f3a3825a2d218a03dd02f5f3cc7f27aa31d882dd780191d1ad101120d74"}, + {file = "coverage-7.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a530663a361eb27375cec28aea5cd282089b5e4b022ae451c4c3493b026a68a5"}, + {file = "coverage-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c58cd6bb46dcb922e0d5792850aab5964433d511b3a020867650f8d930dde4f4"}, + {file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f918e9ef4c98f477a5458238dde2a1643aed956c7213873ab6b6b82e32b8ef61"}, + {file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b865aa679bee7fbd1c55960940dbd3252621dd81468268786c67122bbd15343"}, + {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c5d9b480ebae60fc2cbc8d6865194136bc690538fa542ba58726433bed6e04cc"}, + {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:985ad2af5ec3dbb4fd75d5b0735752c527ad183455520055a08cf8d6794cabfc"}, + {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ca15308ef722f120967af7474ba6a453e0f5b6f331251e20b8145497cf1bc14a"}, + {file = "coverage-7.0.3-cp38-cp38-win32.whl", hash = "sha256:c1cee10662c25c94415bbb987f2ec0e6ba9e8fce786334b10be7e6a7ab958f69"}, + {file = "coverage-7.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:44d6a556de4418f1f3bfd57094b8c49f0408df5a433cf0d253eeb3075261c762"}, + {file = "coverage-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e6dcc70a25cb95df0ae33dfc701de9b09c37f7dd9f00394d684a5b57257f8246"}, + {file = "coverage-7.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf76d79dfaea802f0f28f50153ffbc1a74ae1ee73e480baeda410b4f3e7ab25f"}, + {file = "coverage-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88834e5d56d01c141c29deedacba5773fe0bed900b1edc957595a8a6c0da1c3c"}, + {file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef001a60e888f8741e42e5aa79ae55c91be73761e4df5e806efca1ddd62fd400"}, + {file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959dc506be74e4963bd2c42f7b87d8e4b289891201e19ec551e64c6aa5441f8"}, + {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b791beb17b32ac019a78cfbe6184f992b6273fdca31145b928ad2099435e2fcb"}, + {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b07651e3b9af8f1a092861d88b4c74d913634a7f1f2280fca0ad041ad84e9e96"}, + {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e46fa4168ccb7497c9be78627fcb147e06f474f846a10d55feeb5108a24ef0"}, + {file = "coverage-7.0.3-cp39-cp39-win32.whl", hash = "sha256:e3f1cd1cd65695b1540b3cf7828d05b3515974a9d7c7530f762ac40f58a18161"}, + {file = "coverage-7.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:d8249666c23683f74f8f93aeaa8794ac87cc61c40ff70374a825f3352a4371dc"}, + {file = "coverage-7.0.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:b1ffc8f58b81baed3f8962e28c30d99442079b82ce1ec836a1f67c0accad91c1"}, + {file = "coverage-7.0.3.tar.gz", hash = "sha256:d5be4e93acce64f516bf4fd239c0e6118fc913c93fa1a3f52d15bdcc60d97b2d"}, ] exceptiongroup = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, @@ -378,8 +389,8 @@ idna = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] importlib-metadata = [ - {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, - {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, + {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, + {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -405,6 +416,10 @@ python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] +respx = [ + {file = "respx-0.20.1-py2.py3-none-any.whl", hash = "sha256:372f06991c03d1f7f480a420a2199d01f1815b6ed5a802f4e4628043a93bd03e"}, + {file = "respx-0.20.1.tar.gz", hash = "sha256:cc47a86d7010806ab65abdcf3b634c56337a737bb5c4d74c19a0dfca83b3bc73"}, +] rfc3986 = [ {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, diff --git a/pyproject.toml b/pyproject.toml index b2d40c1..26b5027 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ httpx = "^0.23.3" [tool.poetry.dev-dependencies] pytest = "^7.2.0" pytest-cov = "^4.0.0" +respx = "^0.20.1" [build-system] requires = ["poetry>=0.12"] diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index c66c536..3bd75ae 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -16,6 +16,7 @@ from onepasswordconnectsdk.models.constants import CONNECT_HOST_ENV_VARIABLE ENV_SERVICE_ACCOUNT_JWT_VARIABLE = "OP_CONNECT_TOKEN" +ENV_IS_ASYNC_CLIENT = "OP_CONNECT_CLIENT_ASYNC" class Client: @@ -753,6 +754,7 @@ def new_client_from_environment(url: str = None): Client: The 1Password Connect client """ token = os.environ.get(ENV_SERVICE_ACCOUNT_JWT_VARIABLE) + is_async = os.environ.get(ENV_IS_ASYNC_CLIENT) == "True" if url is None: url = os.environ.get(CONNECT_HOST_ENV_VARIABLE) @@ -767,4 +769,4 @@ def new_client_from_environment(url: str = None): f"{ENV_SERVICE_ACCOUNT_JWT_VARIABLE} variable" ) - return Client(url=url, token=token) + return new_client(url, token, is_async) diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index 0a494eb..ec74a9a 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -1,203 +1,136 @@ -import json -from requests import Session, Response -from unittest.mock import patch +from httpx import Response from onepasswordconnectsdk import client, models VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" VAULT_TITLE = "VaultA" ITEM_ID = "wepiqdxdzncjtnvmv5fegud4qy" ITEM_TITLE = "Test Login" -HOST = "mock_host" +HOST = "https://mock_host" TOKEN = "jwt_token" SS_CLIENT = client.new_client(HOST, TOKEN) -@patch.object(Session, 'request') -def test_get_item_by_id(mock): +def test_get_item_by_id(respx_mock): expected_item = get_item() - expected_path = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_item).encode("utf8") - mock.return_value = response + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_item)) item = SS_CLIENT.get_item_by_id(ITEM_ID, VAULT_ID) compare_items(expected_item, item) - mock.assert_called_with("GET", expected_path) + assert mock.called -@patch.object(Session, 'request') -def test_get_item_by_title(mock): +def test_get_item_by_title(respx_mock): expected_item = get_item() - expected_path_item_title = f"{HOST}/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" - expected_path_item = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - - mock.return_value.ok = True - - response_item_summary = Response() - response_item_summary.status_code = 200 - response_item_summary._content = json.dumps(get_items()).encode("utf8") + expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - response_item = Response() - response_item.status_code = 200 - response_item._content = json.dumps(get_item()).encode("utf8") - - mock.side_effect = [response_item_summary, response_item] + items_summary_mock = respx_mock.get(expected_path_item_title).mock(return_value=Response(200, json=get_items())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) item = SS_CLIENT.get_item_by_title(ITEM_TITLE, VAULT_ID) compare_items(expected_item, item) - mock.assert_any_call("GET", expected_path_item_title) - mock.assert_called_with("GET", expected_path_item) + assert items_summary_mock.called + assert item_mock.called -@patch.object(Session, 'request') -def test_get_item_by_item_id_vault_id(mock): +def test_get_item_by_item_id_vault_id(respx_mock): expected_item = get_item() - expected_path = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_item).encode("utf8") - mock.return_value = response + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_item)) item = SS_CLIENT.get_item(ITEM_ID, VAULT_ID) compare_items(expected_item, item) - mock.assert_called_with("GET", expected_path) + assert mock.called -@patch.object(Session, 'request') -def test_get_item_by_item_id_vault_title(mock): +def test_get_item_by_item_id_vault_title(respx_mock): expected_item = get_item() - expected_path_vault_title = f"{HOST}/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" - expected_path_item = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - - mock.return_value.ok = True + expected_path_vault_title = f"/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - response_vault = Response() - response_vault.status_code = 200 - response_vault._content = json.dumps(get_vaults()).encode("utf8") - - response_item = Response() - response_item.status_code = 200 - response_item._content = json.dumps(expected_item).encode("utf8") - - mock.side_effect = [response_vault, response_item] + vaults_by_title_mock = respx_mock.get(expected_path_vault_title).mock( + return_value=Response(200, json=get_vaults())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) item = SS_CLIENT.get_item(ITEM_ID, VAULT_TITLE) compare_items(expected_item, item) - mock.assert_any_call("GET", expected_path_vault_title) - mock.assert_called_with("GET", expected_path_item) + assert vaults_by_title_mock.called + assert item_mock.called -@patch.object(Session, 'request') -def test_get_item_by_item_title_vault_id(mock): +def test_get_item_by_item_title_vault_id(respx_mock): expected_item = get_item() - expected_path_item_title = f"{HOST}/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" - expected_path_item = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - - mock.return_value.ok = True - - response_item_summary = Response() - response_item_summary.status_code = 200 - response_item_summary._content = json.dumps(get_items()).encode("utf8") - - response_item = Response() - response_item.status_code = 200 - response_item._content = json.dumps(get_item()).encode("utf8") + expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - mock.side_effect = [response_item_summary, response_item] + items_by_title_mock = respx_mock.get(expected_path_item_title).mock( + return_value=Response(200, json=get_items())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) item = SS_CLIENT.get_item(ITEM_TITLE, VAULT_ID) compare_items(expected_item, item) - mock.assert_any_call("GET", expected_path_item_title) - mock.assert_called_with("GET", expected_path_item) + assert items_by_title_mock.called + assert item_mock.called -@patch.object(Session, 'request') -def test_get_item_by_item_title_vault_title(mock): +def test_get_item_by_item_title_vault_title(respx_mock): expected_item = get_item() - expected_path_vault_title = f"{HOST}/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" - expected_path_item_title = f"{HOST}/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" - expected_path_item = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + expected_path_vault_title = f"/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" + expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - mock.return_value.ok = True - - response_vault = Response() - response_vault.status_code = 200 - response_vault._content = json.dumps(get_vaults()).encode("utf8") - - response_item_summary = Response() - response_item_summary.status_code = 200 - response_item_summary._content = json.dumps(get_items()).encode("utf8") - - response_item = Response() - response_item.status_code = 200 - response_item._content = json.dumps(get_item()).encode("utf8") - - mock.side_effect = [response_vault, response_item_summary, response_item] + vaults_by_title_mock = respx_mock.get(expected_path_vault_title).mock( + return_value=Response(200, json=get_vaults())) + items_by_title_mock = respx_mock.get(expected_path_item_title).mock( + return_value=Response(200, json=get_items())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) item = SS_CLIENT.get_item(ITEM_TITLE, VAULT_TITLE) compare_items(expected_item, item) - mock.assert_any_call("GET", expected_path_vault_title) - mock.assert_any_call("GET", expected_path_item_title) - mock.assert_called_with("GET", expected_path_item) + assert vaults_by_title_mock.called + assert items_by_title_mock.called + assert item_mock.called -@patch.object(Session, 'request') -def test_get_items(mock): +def test_get_items(respx_mock): expected_items = get_items() - expected_path = f"{HOST}/v1/vaults/{VAULT_ID}/items" + expected_path = f"/v1/vaults/{VAULT_ID}/items" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_items).encode("utf8") - mock.return_value = response + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_items)) items = SS_CLIENT.get_items(VAULT_ID) assert len(expected_items) == len(items) compare_summary_items(expected_items[0], items[0]) - mock.assert_called_with("GET", expected_path) + assert mock.called -@patch.object(Session, 'request') -def test_delete_item(mock): +def test_delete_item(respx_mock): expected_items = get_items() - expected_path = f"{HOST}/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_items).encode("utf8") - mock.return_value = response + mock = respx_mock.delete(expected_path).mock(return_value=Response(200, json=expected_items)) SS_CLIENT.delete_item(ITEM_ID, VAULT_ID) - mock.assert_called_with("DELETE", expected_path) - + assert mock.called -@patch.object(Session, 'request') -def test_create_item(mock): - mock.return_value.ok = True - mock.side_effect = create_item_side_effect +def test_create_item(respx_mock): item = generate_full_item() + mock = respx_mock.post(f"/v1/vaults/{item.vault.id}/items").mock(return_value=Response(201, json=item.to_dict())) - created_item = SS_CLIENT.create_item(VAULT_ID, item) + created_item = SS_CLIENT.create_item(item.vault.id, item) assert mock.called compare_full_items(item, created_item) -@patch.object(Session, 'request') -def test_update_item(mock): - mock.return_value.ok = True - mock.side_effect = create_item_side_effect - +def test_update_item(respx_mock): item = generate_full_item() + mock = respx_mock.put(f"/v1/vaults/{item.vault.id}/items/{item.id}").mock(return_value=Response(200, json=item.to_dict())) - updated_item = SS_CLIENT.update_item(ITEM_ID, VAULT_ID, item) + updated_item = SS_CLIENT.update_item(item.id, item.vault.id, item) assert mock.called compare_full_items(item, updated_item) @@ -220,13 +153,6 @@ def compare_full_items(expected_item, returned_item): compare_full_item_fields(expected_item.fields[i], returned_item.fields[i]) -def create_item_side_effect(method, url, data): - response = Response() - response.status_code = 200 - response._content = data - return response - - def compare_full_item_fields(expected_field, returned_field): assert expected_field.id == returned_field.id assert expected_field.label == returned_field.label diff --git a/src/tests/test_client_vaults.py b/src/tests/test_client_vaults.py index 92f5979..892f4db 100644 --- a/src/tests/test_client_vaults.py +++ b/src/tests/test_client_vaults.py @@ -1,61 +1,44 @@ -import json -from requests import Session, Response -from unittest.mock import patch +from httpx import Response from onepasswordconnectsdk import client VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" VAULT_NAME = "VaultA" -HOST = "mock_host" +HOST = "https://mock_host" TOKEN = "jwt_token" SS_CLIENT = client.new_client(HOST, TOKEN) -@patch.object(Session, 'request') -def test_get_vaults(mock): +def test_get_vaults(respx_mock): expected_vaults = list_vaults() - expected_path = f"{HOST}/v1/vaults" + expected_path = "/v1/vaults" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_vaults).encode("utf8") - mock.return_value = response + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vaults)) vaults = SS_CLIENT.get_vaults() compare_vaults(expected_vaults[0], vaults[0]) - mock.assert_called_with("GET", expected_path) + assert mock.called -@patch.object(Session, 'request') -def test_get_vault(mock): +def test_get_vault(respx_mock): expected_vault = get_vault() expected_path = f"{HOST}/v1/vaults/{VAULT_ID}" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_vault).encode("utf8") - mock.return_value = response + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vault)) vault = SS_CLIENT.get_vault(VAULT_ID) compare_vaults(expected_vault, vault) - mock.assert_called_with("GET", expected_path) + assert mock.called -@patch.object(Session, 'request') -def test_get_vault_by_title(mock): +def test_get_vault_by_title(respx_mock): expected_vaults = list_vaults() - expected_path = f"{HOST}/v1/vaults?filter=name eq \"{VAULT_NAME}\"" + expected_path = f"/v1/vaults?filter=name eq \"{VAULT_NAME}\"" - mock.return_value.ok = True - response = Response() - response.status_code = 200 - response._content = json.dumps(expected_vaults).encode("utf8") - mock.return_value = response + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vaults)) vault = SS_CLIENT.get_vault_by_title(VAULT_NAME) compare_vaults(expected_vaults[0], vault) - mock.assert_called_with("GET", expected_path) + assert mock.called def list_vaults(): diff --git a/src/tests/test_config.py b/src/tests/test_config.py index 2c15f13..64a0b61 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -1,13 +1,13 @@ -import json -from requests import Session, Response -from unittest.mock import patch +from httpx import Response import onepasswordconnectsdk from onepasswordconnectsdk import client VAULT_ID = "abcdefghijklmnopqrstuvwxyz" ITEM_NAME1 = "TEST USER" +ITEM_ID1 = "wepiqdxdzncjtnvmv5fegud4q1" ITEM_NAME2 = "Another User" -HOST = "mock_host" +ITEM_ID2 = "wepiqdxdzncjtnvmv5fegud4q2" +HOST = "https://mock_host" TOKEN = "jwt_token" SS_CLIENT = client.new_client(HOST, TOKEN) @@ -25,20 +25,29 @@ class Config: CONFIG_CLASS = Config() -@patch.object(Session, 'request') -def test_load(mock): - mock.return_value.ok = True - mock.side_effect = get_item_side_effect +def test_load(respx_mock): + mock_items_list1 = respx_mock.get(f"v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_NAME1}\"").mock( + return_value=Response(200, json=[item]) + ) + mock_item1 = respx_mock.get(f"v1/vaults/{VAULT_ID}/items/{ITEM_ID1}").mock(return_value=Response(200, json=item)) + mock_items_list2 = respx_mock.get(f"v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_NAME2}\"").mock( + return_value=Response(200, json=[item2]) + ) + mock_item2 = respx_mock.get(f"v1/vaults/{VAULT_ID}/items/{ITEM_ID2}").mock(return_value=Response(200, json=item2)) config_with_values = onepasswordconnectsdk.load(SS_CLIENT, CONFIG_CLASS) - assert mock.called + + assert mock_items_list1.called + assert mock_item1.called + assert mock_items_list2.called + assert mock_item2.called + assert config_with_values.username == USERNAME_VALUE assert config_with_values.password == PASSWORD_VALUE assert config_with_values.host == HOST_VALUE -@patch.object(Session, 'request') -def test_load_dict(mock): +def test_load_dict(respx_mock): config_dict = { "username": { "opitem": ITEM_NAME1, @@ -51,76 +60,61 @@ def test_load_dict(mock): "opvault": VAULT_ID } } - mock.return_value.ok = True - mock.side_effect = get_item_side_effect + + mock_item_list = respx_mock.get(f"v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_NAME1}\"").mock( + return_value=Response(200, json=[item])) + mock_item = respx_mock.get(f"v1/vaults/{VAULT_ID}/items/{ITEM_ID1}").mock(return_value=Response(200, json=item)) config_with_values = onepasswordconnectsdk.load_dict(SS_CLIENT, config_dict) - assert mock.called + + assert mock_item_list.called + assert mock_item.called assert config_with_values['username'] == USERNAME_VALUE assert config_with_values['password'] == PASSWORD_VALUE -def get_item_side_effect(method, url): - response = Response() - response.status_code = 200 - - item = { - "id": ITEM_NAME1, - "title": ITEM_NAME1, - "vault": { - "id": VAULT_ID - }, - "category": "LOGIN", - "sections": [ - { - "id": "section1", - "label": "section1" - } - ], - "fields": [ - { - "id": "password", - "label": "password", - "value": PASSWORD_VALUE, - "section": { - "id": "section1" - } - }, - { - "id": "716C5B0E95A84092B2FE2CC402E0DDDF", - "label": "username", - "value": USERNAME_VALUE +item = { + "id": ITEM_ID1, + "title": ITEM_NAME1, + "vault": { + "id": VAULT_ID + }, + "category": "LOGIN", + "sections": [ + { + "id": "section1", + "label": "section1" + } + ], + "fields": [ + { + "id": "password", + "label": "password", + "value": PASSWORD_VALUE, + "section": { + "id": "section1" } - ] - } - - item2 = { - "id": ITEM_NAME2, - "title": ITEM_NAME2, - "vault": { - "id": VAULT_ID }, - "category": "LOGIN", - "fields": [ - { - "id": "716C5B0E95A84092B2FE2CC402E0DDDF", - "label": "host", - "value": HOST_VALUE - } - ] - } - - if ITEM_NAME1 in url: - if "eq" in url: - item = [item] - else: - item = item - elif ITEM_NAME2 in url: - if "eq" in url: - item = [item2] - else: - item = item2 - - response._content = str.encode(json.dumps(item)) - - return response + { + "id": "716C5B0E95A84092B2FE2CC402E0DDDF", + "label": "username", + "value": USERNAME_VALUE + } + ] +} + +item2 = { + "id": ITEM_ID2, + "title": ITEM_NAME2, + "vault": { + "id": VAULT_ID + }, + "category": "LOGIN", + "fields": [ + { + "id": "716C5B0E95A84092B2FE2CC402E0DDDF", + "label": "host", + "value": HOST_VALUE + } + ] +} From 4eb89b33fc6ccb896a77c53bf77031fb667f4e08 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Mon, 9 Jan 2023 16:39:12 +0100 Subject: [PATCH 020/112] Cover AsyncClient with tests Signed-off-by: volodymyrZotov --- poetry.lock | 142 ++++++++++++++++------------ pyproject.toml | 3 +- src/onepasswordconnectsdk/client.py | 7 +- src/tests/test_client_items.py | 138 +++++++++++++++++++++++++++ src/tests/test_client_vaults.py | 40 +++++++- 5 files changed, 264 insertions(+), 66 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0c37d5d..af1e32f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -49,7 +49,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 [[package]] name = "coverage" -version = "7.0.3" +version = "7.0.4" description = "Code coverage measurement for Python" category = "dev" optional = false @@ -148,15 +148,15 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag [[package]] name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "packaging" -version = "22.0" +version = "23.0" description = "Core utilities for Python packages" category = "dev" optional = false @@ -198,6 +198,22 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "0.20.3" +description = "Pytest support for asyncio" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=6.1.0" +typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] + [[package]] name = "pytest-cov" version = "4.0.0" @@ -296,7 +312,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools" [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "ebabcc843f5c0064cfbcff7b8940a475b4b4fb48f10731c60f131b13f1122417" +content-hash = "5c06df5db167647617c8fb7afc9da451dcec9d10e24df37e6024124cccd6da60" [metadata.files] anyio = [ @@ -316,57 +332,57 @@ colorama = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ - {file = "coverage-7.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f7c51b6074a8a3063c341953dffe48fd6674f8e4b1d3c8aa8a91f58d6e716a8"}, - {file = "coverage-7.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:628f47eaf66727fc986d3b190d6fa32f5e6b7754a243919d28bc0fd7974c449f"}, - {file = "coverage-7.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89d5abf86c104de808108a25d171ad646c07eda96ca76c8b237b94b9c71e518"}, - {file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75e43c6f4ea4d122dac389aabdf9d4f0e160770a75e63372f88005d90f5bcc80"}, - {file = "coverage-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49da0ff241827ebb52d5d6d5a36d33b455fa5e721d44689c95df99fd8db82437"}, - {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0bce4ad5bdd0b02e177a085d28d2cea5fc57bb4ba2cead395e763e34cf934eb1"}, - {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f79691335257d60951638dd43576b9bcd6f52baa5c1c2cd07a509bb003238372"}, - {file = "coverage-7.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5722269ed05fbdb94eef431787c66b66260ff3125d1a9afcc00facff8c45adf9"}, - {file = "coverage-7.0.3-cp310-cp310-win32.whl", hash = "sha256:bdbda870e0fda7dd0fe7db7135ca226ec4c1ade8aa76e96614829b56ca491012"}, - {file = "coverage-7.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:e56fae4292e216b8deeee38ace84557b9fa85b52db005368a275427cdabb8192"}, - {file = "coverage-7.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b82343a5bc51627b9d606f0b6b6b9551db7b6311a5dd920fa52a94beae2e8959"}, - {file = "coverage-7.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fd0a8aa431f9b7ad9eb8264f55ef83cbb254962af3775092fb6e93890dea9ca2"}, - {file = "coverage-7.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:112cfead1bd22eada8a8db9ed387bd3e8be5528debc42b5d3c1f7da4ffaf9fb5"}, - {file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af87e906355fa42447be5c08c5d44e6e1c005bf142f303f726ddf5ed6e0c8a4d"}, - {file = "coverage-7.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f30090e22a301952c5abd0e493a1c8358b4f0b368b49fa3e4568ed3ed68b8d1f"}, - {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ae871d09901911eedda1981ea6fd0f62a999107293cdc4c4fd612321c5b34745"}, - {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ed7c9debf7bfc63c9b9f8b595409237774ff4b061bf29fba6f53b287a2fdeab9"}, - {file = "coverage-7.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:13121fa22dcd2c7b19c5161e3fd725692448f05377b788da4502a383573227b3"}, - {file = "coverage-7.0.3-cp311-cp311-win32.whl", hash = "sha256:037b51ee86bc600f99b3b957c20a172431c35c2ef9c1ca34bc813ab5b51fd9f5"}, - {file = "coverage-7.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:25fde928306034e8deecd5fc91a07432dcc282c8acb76749581a28963c9f4f3f"}, - {file = "coverage-7.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7e8b0642c38b3d3b3c01417643ccc645345b03c32a2e84ef93cdd6844d6fe530"}, - {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18b09811f849cc958d23f733a350a66b54a8de3fed1e6128ba55a5c97ffb6f65"}, - {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:349d0b545520e8516f7b4f12373afc705d17d901e1de6a37a20e4ec9332b61f7"}, - {file = "coverage-7.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b38813eee5b4739f505d94247604c72eae626d5088a16dd77b08b8b1724ab3"}, - {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba9af1218fa01b1f11c72271bc7290b701d11ad4dbc2ae97c445ecacf6858dba"}, - {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c5648c7eec5cf1ba5db1cf2d6c10036a582d7f09e172990474a122e30c841361"}, - {file = "coverage-7.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d0df04495b76a885bfef009f45eebe8fe2fbf815ad7a83dabcf5aced62f33162"}, - {file = "coverage-7.0.3-cp37-cp37m-win32.whl", hash = "sha256:af6cef3796b8068713a48dd67d258dc9a6e2ebc3bd4645bfac03a09672fa5d20"}, - {file = "coverage-7.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:62ef3800c4058844e2e3fa35faa9dd0ccde8a8aba6c763aae50342e00d4479d4"}, - {file = "coverage-7.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acef7f3a3825a2d218a03dd02f5f3cc7f27aa31d882dd780191d1ad101120d74"}, - {file = "coverage-7.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a530663a361eb27375cec28aea5cd282089b5e4b022ae451c4c3493b026a68a5"}, - {file = "coverage-7.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c58cd6bb46dcb922e0d5792850aab5964433d511b3a020867650f8d930dde4f4"}, - {file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f918e9ef4c98f477a5458238dde2a1643aed956c7213873ab6b6b82e32b8ef61"}, - {file = "coverage-7.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b865aa679bee7fbd1c55960940dbd3252621dd81468268786c67122bbd15343"}, - {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c5d9b480ebae60fc2cbc8d6865194136bc690538fa542ba58726433bed6e04cc"}, - {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:985ad2af5ec3dbb4fd75d5b0735752c527ad183455520055a08cf8d6794cabfc"}, - {file = "coverage-7.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ca15308ef722f120967af7474ba6a453e0f5b6f331251e20b8145497cf1bc14a"}, - {file = "coverage-7.0.3-cp38-cp38-win32.whl", hash = "sha256:c1cee10662c25c94415bbb987f2ec0e6ba9e8fce786334b10be7e6a7ab958f69"}, - {file = "coverage-7.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:44d6a556de4418f1f3bfd57094b8c49f0408df5a433cf0d253eeb3075261c762"}, - {file = "coverage-7.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e6dcc70a25cb95df0ae33dfc701de9b09c37f7dd9f00394d684a5b57257f8246"}, - {file = "coverage-7.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf76d79dfaea802f0f28f50153ffbc1a74ae1ee73e480baeda410b4f3e7ab25f"}, - {file = "coverage-7.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88834e5d56d01c141c29deedacba5773fe0bed900b1edc957595a8a6c0da1c3c"}, - {file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef001a60e888f8741e42e5aa79ae55c91be73761e4df5e806efca1ddd62fd400"}, - {file = "coverage-7.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4959dc506be74e4963bd2c42f7b87d8e4b289891201e19ec551e64c6aa5441f8"}, - {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b791beb17b32ac019a78cfbe6184f992b6273fdca31145b928ad2099435e2fcb"}, - {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b07651e3b9af8f1a092861d88b4c74d913634a7f1f2280fca0ad041ad84e9e96"}, - {file = "coverage-7.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55e46fa4168ccb7497c9be78627fcb147e06f474f846a10d55feeb5108a24ef0"}, - {file = "coverage-7.0.3-cp39-cp39-win32.whl", hash = "sha256:e3f1cd1cd65695b1540b3cf7828d05b3515974a9d7c7530f762ac40f58a18161"}, - {file = "coverage-7.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:d8249666c23683f74f8f93aeaa8794ac87cc61c40ff70374a825f3352a4371dc"}, - {file = "coverage-7.0.3-pp37.pp38.pp39-none-any.whl", hash = "sha256:b1ffc8f58b81baed3f8962e28c30d99442079b82ce1ec836a1f67c0accad91c1"}, - {file = "coverage-7.0.3.tar.gz", hash = "sha256:d5be4e93acce64f516bf4fd239c0e6118fc913c93fa1a3f52d15bdcc60d97b2d"}, + {file = "coverage-7.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:daf91db39324e9939a9db919ee4fb42a1a23634a056616dae891a030e89f87ba"}, + {file = "coverage-7.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55121fe140d7e42cb970999b93cf1c2b24484ce028b32bbd00238bb25c13e34a"}, + {file = "coverage-7.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c027fbb83a8c78a6e06a0302ea1799fdb70e5cda9845a5e000545b8e2b47ea39"}, + {file = "coverage-7.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf82db5b7f16b51ec32fe0bd2da0805b177c807aa8bfb478c7e6f893418c284"}, + {file = "coverage-7.0.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ba5cc54baf3c322c4388de2a43cc95f7809366f0600e743e5aae8ea9d1038b2"}, + {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:260854160083f8275a9d9d49a05ab0ffc7a1f08f2ccccbfaec94a18aae9f407c"}, + {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ea45f0dba5a993e93b158f1a9dcfff2770e3bcabf2b80dbe7aa15dce0bcb3bf3"}, + {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6abc91f6f8b3cc0ae1034e2c03f38769fba1952ab70d0b26953aa01691265c39"}, + {file = "coverage-7.0.4-cp310-cp310-win32.whl", hash = "sha256:053cdc47cae08257051d7e934a0de4d095b60eb8a3024fa9f1b2322fa1547137"}, + {file = "coverage-7.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:1e9e94f2612ee549a4b3ee79cbc61bceed77e69cf38cfa05858bae939a886d16"}, + {file = "coverage-7.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5caa9dd91dcc5f054350dc57a02e053d79633907b9ccffff999568d13dcd19f8"}, + {file = "coverage-7.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:efc200fa75d9634525b40babc7a16342bd21c101db1a58ef84dc14f4bf6ac0fd"}, + {file = "coverage-7.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1791e5f74c5b52f76e83fe9f4bb9571cf76d40ee0c51952ee1e4ee935b7e98b9"}, + {file = "coverage-7.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d9201cfa5a98652b9cef36ab202f17fe3ea83f497b4ba2a8ed39399dfb8fcd4"}, + {file = "coverage-7.0.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d8ef6865cb6834cab2b72fff20747a55c714b57b675f7e11c9624fe4f7cb45"}, + {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b84076e3de192fba0f95e279ac017b64c7c6ecd4f09f36f13420f5bed898a9c7"}, + {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:dcfbf8ffc046f20d75fd775a92c378f6fc7b9bded6c6f2ab88b6b9cb5805a184"}, + {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4665a714af31f160403c2e448fb2fef330719d2e04e836b08d60d612707c1041"}, + {file = "coverage-7.0.4-cp311-cp311-win32.whl", hash = "sha256:2e59aef3fba5758059208c9eff10ae7ded3629e797972746ec33b56844f69411"}, + {file = "coverage-7.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:2b854f7985b48122b6fe346631e86d67b63293f8255cb59a93d79e3d9f1574e3"}, + {file = "coverage-7.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e44b60b0b49aa85d548d392a2dca2c6a581cd4084e72e9e16bd58bd86ec20816"}, + {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2904d7a0388911c61e7e3beefe48c29dfccaba938fc1158f63190101a21e04c2"}, + {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc74b64bfa89e2f862ea45dd6ac1def371d7cc883b76680d20bdd61a6f3daa20"}, + {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06046f54e719da21c79f98ecc0962581d1aee0b3798dc6b12b1217da8bf93f4"}, + {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bc9c77004970a364a1e5454cf7cb884e4277592b959c287689b2a0fd027ef552"}, + {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0815a09b32384e8ff00a5939ec9cd10efce8742347e019c2daca1a32f5ac2aae"}, + {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a78a80d131c067d67d8a6f9bd3d3f7ea7eac82c1c7259f97d7ab73f723da9d55"}, + {file = "coverage-7.0.4-cp37-cp37m-win32.whl", hash = "sha256:2b5936b624fbe711ed02dfd86edd678822e5ee68da02b6d231e5c01090b64590"}, + {file = "coverage-7.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a63922765ee49d5b4c32afb2cd5516812c8665f3b78e64a0dd005bdfabf991b1"}, + {file = "coverage-7.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d68f2f7bddb3acdd3b36ef7f334b9d14f30b93e094f808fbbd8d288b8f9e2f9b"}, + {file = "coverage-7.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dafdba3b2b9010abab08cb8c0dc6549bfca6e1630fe14d47b01dca00d39e694"}, + {file = "coverage-7.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0322354757b47640535daabd2d56384ff3cad2896248fc84d328c5fad4922d5c"}, + {file = "coverage-7.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e8267466662aff93d66fa72b9591d02122dfc8a729b0a43dd70e0fb07ed9b37"}, + {file = "coverage-7.0.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f684d88eb4924ed0630cf488fd5606e334c6835594bb5fe36b50a509b10383ed"}, + {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:70c294bb15ba576fb96b580db35895bf03749d683df044212b74e938a7f6821f"}, + {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:34c0457e1ba450ae8b22dc8ea2fd36ada1010af61291e4c96963cd9d9633366f"}, + {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b75aff2c35ceaa299691e772f7bf7c8aeab25f46acea2be3dd04cccb914a9860"}, + {file = "coverage-7.0.4-cp38-cp38-win32.whl", hash = "sha256:6c5554d55668381e131577f20e8f620d4882b04ad558f7e7f3f1f55b3124c379"}, + {file = "coverage-7.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c82f34fafaf5bc05d222fcf84423d6e156432ca35ca78672d4affd0c09c6ef6c"}, + {file = "coverage-7.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8dfb5fed540f77e814bf4ec79619c241af6b4578fa1093c5e3389bbb7beab3f"}, + {file = "coverage-7.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee32a080bab779b71c4d09a3eb5254bfca43ee88828a683dab27dfe8f582516e"}, + {file = "coverage-7.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dfbee0bf0d633be3a2ab068f5a5731a70adf147d0ba17d9f9932b46c7c5782b"}, + {file = "coverage-7.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32dc010713455ac0fe2fddb0e48aa43875cc7eb7b09768df10bad8ce45f9c430"}, + {file = "coverage-7.0.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cb88a3019ad042eaa69fc7639ef077793fedbf313e89207aa82fefe92c97ebd"}, + {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:73bc6114aab7753ca784f87bcd3b7613bc797aa255b5bca45e5654070ae9acfb"}, + {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92f135d370fcd7a6fb9659fa2eb716dd2ca364719cbb1756f74d90a221bca1a7"}, + {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f3d485e6ec6e09857bf2115ece572d666b7c498377d4c70e66bb06c63ed177c2"}, + {file = "coverage-7.0.4-cp39-cp39-win32.whl", hash = "sha256:c58921fcd9914b56444292e7546fe183d079db99528142c809549ddeaeacd8e9"}, + {file = "coverage-7.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:f092d9f2ddaa30235d33335fbdb61eb8f3657af519ef5f9dd6bdae65272def11"}, + {file = "coverage-7.0.4-pp37.pp38.pp39-none-any.whl", hash = "sha256:cb8cfa3bf3a9f18211279458917fef5edeb5e1fdebe2ea8b11969ec2ebe48884"}, + {file = "coverage-7.0.4.tar.gz", hash = "sha256:f6c4ad409a0caf7e2e12e203348b1a9b19c514e7d078520973147bf2d3dcbc6f"}, ] exceptiongroup = [ {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, @@ -393,12 +409,12 @@ importlib-metadata = [ {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, ] iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] packaging = [ - {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, - {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, @@ -408,6 +424,10 @@ pytest = [ {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, ] +pytest-asyncio = [ + {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, + {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, +] pytest-cov = [ {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, diff --git a/pyproject.toml b/pyproject.toml index 26b5027..4b9d1c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,8 +16,9 @@ python = "^3.7" python-dateutil = "^2.8.1" httpx = "^0.23.3" -[tool.poetry.dev-dependencies] +[tool.poetry.group.dev.dependencies] pytest = "^7.2.0" +pytest-asyncio = "^0.20.3" pytest-cov = "^4.0.0" respx = "^0.20.1" diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 3bd75ae..5bf0263 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -441,12 +441,13 @@ async def get_item(self, item: str, vault: str): vault_id = vault if not is_valid_uuid(vault): - vault_id = self.get_vault_by_title(vault).id + vault = await self.get_vault_by_title(vault) + vault_id = vault.id if is_valid_uuid(item): - return self.get_item_by_id(item, vault_id) + return await self.get_item_by_id(item, vault_id) else: - return self.get_item_by_title(item, vault_id) + return await self.get_item_by_title(item, vault_id) async def get_item_by_id(self, item_id: str, vault_id: str): """Get a specific item by uuid diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index ec74a9a..1f5f74d 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -1,3 +1,4 @@ +import pytest from httpx import Response from onepasswordconnectsdk import client, models @@ -8,6 +9,7 @@ HOST = "https://mock_host" TOKEN = "jwt_token" SS_CLIENT = client.new_client(HOST, TOKEN) +SS_CLIENT_ASYNC = client.new_client(HOST, TOKEN, True) def test_get_item_by_id(respx_mock): @@ -21,6 +23,18 @@ def test_get_item_by_id(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_get_item_by_id_async(respx_mock): + expected_item = get_item() + expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_item)) + + item = await SS_CLIENT_ASYNC.get_item_by_id(ITEM_ID, VAULT_ID) + compare_items(expected_item, item) + assert mock.called + + def test_get_item_by_title(respx_mock): expected_item = get_item() expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" @@ -35,6 +49,21 @@ def test_get_item_by_title(respx_mock): assert item_mock.called +@pytest.mark.asyncio +async def test_get_item_by_title_async(respx_mock): + expected_item = get_item() + expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + items_summary_mock = respx_mock.get(expected_path_item_title).mock(return_value=Response(200, json=get_items())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) + + item = await SS_CLIENT_ASYNC.get_item_by_title(ITEM_TITLE, VAULT_ID) + compare_items(expected_item, item) + assert items_summary_mock.called + assert item_mock.called + + def test_get_item_by_item_id_vault_id(respx_mock): expected_item = get_item() expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" @@ -46,6 +75,18 @@ def test_get_item_by_item_id_vault_id(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_get_item_by_item_id_vault_id_async(respx_mock): + expected_item = get_item() + expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_item)) + + item = await SS_CLIENT_ASYNC.get_item(ITEM_ID, VAULT_ID) + compare_items(expected_item, item) + assert mock.called + + def test_get_item_by_item_id_vault_title(respx_mock): expected_item = get_item() expected_path_vault_title = f"/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" @@ -61,6 +102,22 @@ def test_get_item_by_item_id_vault_title(respx_mock): assert item_mock.called +@pytest.mark.asyncio +async def test_get_item_by_item_id_vault_title_async(respx_mock): + expected_item = get_item() + expected_path_vault_title = f"/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + vaults_by_title_mock = respx_mock.get(expected_path_vault_title).mock( + return_value=Response(200, json=get_vaults())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) + + item = await SS_CLIENT_ASYNC.get_item(ITEM_ID, VAULT_TITLE) + compare_items(expected_item, item) + assert vaults_by_title_mock.called + assert item_mock.called + + def test_get_item_by_item_title_vault_id(respx_mock): expected_item = get_item() expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" @@ -76,6 +133,22 @@ def test_get_item_by_item_title_vault_id(respx_mock): assert item_mock.called +@pytest.mark.asyncio +async def test_get_item_by_item_title_vault_id_async(respx_mock): + expected_item = get_item() + expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + items_by_title_mock = respx_mock.get(expected_path_item_title).mock( + return_value=Response(200, json=get_items())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) + + item = await SS_CLIENT_ASYNC.get_item(ITEM_TITLE, VAULT_ID) + compare_items(expected_item, item) + assert items_by_title_mock.called + assert item_mock.called + + def test_get_item_by_item_title_vault_title(respx_mock): expected_item = get_item() expected_path_vault_title = f"/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" @@ -95,6 +168,26 @@ def test_get_item_by_item_title_vault_title(respx_mock): assert item_mock.called +@pytest.mark.asyncio +async def test_get_item_by_item_title_vault_title_async(respx_mock): + expected_item = get_item() + expected_path_vault_title = f"/v1/vaults?filter=name eq \"{VAULT_TITLE}\"" + expected_path_item_title = f"/v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_TITLE}\"" + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + vaults_by_title_mock = respx_mock.get(expected_path_vault_title).mock( + return_value=Response(200, json=get_vaults())) + items_by_title_mock = respx_mock.get(expected_path_item_title).mock( + return_value=Response(200, json=get_items())) + item_mock = respx_mock.get(expected_path_item).mock(return_value=Response(200, json=expected_item)) + + item = await SS_CLIENT_ASYNC.get_item(ITEM_TITLE, VAULT_TITLE) + compare_items(expected_item, item) + assert vaults_by_title_mock.called + assert items_by_title_mock.called + assert item_mock.called + + def test_get_items(respx_mock): expected_items = get_items() expected_path = f"/v1/vaults/{VAULT_ID}/items" @@ -107,6 +200,19 @@ def test_get_items(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_get_items_async(respx_mock): + expected_items = get_items() + expected_path = f"/v1/vaults/{VAULT_ID}/items" + + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_items)) + + items = await SS_CLIENT_ASYNC.get_items(VAULT_ID) + assert len(expected_items) == len(items) + compare_summary_items(expected_items[0], items[0]) + assert mock.called + + def test_delete_item(respx_mock): expected_items = get_items() expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" @@ -117,6 +223,18 @@ def test_delete_item(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_delete_item_async(respx_mock): + expected_items = get_items() + expected_path = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + mock = respx_mock.delete(expected_path).mock(return_value=Response(200, json=expected_items)) + + await SS_CLIENT_ASYNC.delete_item(ITEM_ID, VAULT_ID) + assert mock.called + + + def test_create_item(respx_mock): item = generate_full_item() mock = respx_mock.post(f"/v1/vaults/{item.vault.id}/items").mock(return_value=Response(201, json=item.to_dict())) @@ -126,6 +244,16 @@ def test_create_item(respx_mock): compare_full_items(item, created_item) +@pytest.mark.asyncio +async def test_create_item_async(respx_mock): + item = generate_full_item() + mock = respx_mock.post(f"/v1/vaults/{item.vault.id}/items").mock(return_value=Response(201, json=item.to_dict())) + + created_item = await SS_CLIENT_ASYNC.create_item(item.vault.id, item) + assert mock.called + compare_full_items(item, created_item) + + def test_update_item(respx_mock): item = generate_full_item() mock = respx_mock.put(f"/v1/vaults/{item.vault.id}/items/{item.id}").mock(return_value=Response(200, json=item.to_dict())) @@ -135,6 +263,16 @@ def test_update_item(respx_mock): compare_full_items(item, updated_item) +@pytest.mark.asyncio +async def test_update_item_async(respx_mock): + item = generate_full_item() + mock = respx_mock.put(f"/v1/vaults/{item.vault.id}/items/{item.id}").mock(return_value=Response(200, json=item.to_dict())) + + updated_item = await SS_CLIENT_ASYNC.update_item(item.id, item.vault.id, item) + assert mock.called + compare_full_items(item, updated_item) + + def compare_full_items(expected_item, returned_item): assert expected_item.id == returned_item.id assert expected_item.title == returned_item.title diff --git a/src/tests/test_client_vaults.py b/src/tests/test_client_vaults.py index 892f4db..e896e3b 100644 --- a/src/tests/test_client_vaults.py +++ b/src/tests/test_client_vaults.py @@ -1,3 +1,4 @@ +import pytest from httpx import Response from onepasswordconnectsdk import client @@ -6,6 +7,7 @@ HOST = "https://mock_host" TOKEN = "jwt_token" SS_CLIENT = client.new_client(HOST, TOKEN) +SS_CLIENT_ASYNC = client.new_client(HOST, TOKEN, True) def test_get_vaults(respx_mock): @@ -19,9 +21,21 @@ def test_get_vaults(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_get_vaults_async(respx_mock): + expected_vaults = list_vaults() + expected_path = "/v1/vaults" + + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vaults)) + + vaults = await SS_CLIENT_ASYNC.get_vaults() + compare_vaults(expected_vaults[0], vaults[0]) + assert mock.called + + def test_get_vault(respx_mock): expected_vault = get_vault() - expected_path = f"{HOST}/v1/vaults/{VAULT_ID}" + expected_path = f"/v1/vaults/{VAULT_ID}" mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vault)) @@ -30,6 +44,18 @@ def test_get_vault(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_get_vault_async(respx_mock): + expected_vault = get_vault() + expected_path = f"/v1/vaults/{VAULT_ID}" + + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vault)) + + vault = await SS_CLIENT_ASYNC.get_vault(VAULT_ID) + compare_vaults(expected_vault, vault) + assert mock.called + + def test_get_vault_by_title(respx_mock): expected_vaults = list_vaults() expected_path = f"/v1/vaults?filter=name eq \"{VAULT_NAME}\"" @@ -41,6 +67,18 @@ def test_get_vault_by_title(respx_mock): assert mock.called +@pytest.mark.asyncio +async def test_get_vault_by_title(respx_mock): + expected_vaults = list_vaults() + expected_path = f"/v1/vaults?filter=name eq \"{VAULT_NAME}\"" + + mock = respx_mock.get(expected_path).mock(return_value=Response(200, json=expected_vaults)) + + vault = await SS_CLIENT_ASYNC.get_vault_by_title(VAULT_NAME) + compare_vaults(expected_vaults[0], vault) + assert mock.called + + def list_vaults(): return [ get_vault() From 68c04dab6e55abb57c5b781499b6f599aad35f89 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Fri, 27 Jan 2023 09:30:29 +0100 Subject: [PATCH 021/112] Update README with async/await feature Signed-off-by: volodymyrZotov --- README.md | 34 ++++++++++++++++++++++++----- src/onepasswordconnectsdk/client.py | 1 + 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4d2f888..27eba6d 100644 --- a/README.md +++ b/README.md @@ -40,13 +40,16 @@ import onepasswordconnectsdk - `http://localhost:8080` if the Connect server is running in Docker on the same host. - `http(s)://:8080` or `http(s)://:8080` if the Connect server is running on another host. - **OP_VAULT** - The default vault to fetch items from if not specified. +- **OP_CONNECT_CLIENT_ASYNC** - Whether to use async client or not. Possible values are: + - True - to use async client + - False - to use NOT async client (this is used by default) **Create a Client** There are two methods available for creating a client: -- `new_client_from_environment`: Builds a new client for interacting with 1Password Connect using the `OP_CONNECT_TOKEN` and `OP_CONNECT_HOST` *environment variables*. -- `new_client`: Builds a new client for interacting with 1Password Connect. Accepts the hostname of 1Password Connect and the API token generated for the application. +- `new_client_from_environment`: Builds a new client for interacting with 1Password Connect using the `OP_CONNECT_TOKEN`, `OP_CONNECT_HOST` and `OP_CONNECT_CLIENT_ASYNC` *environment variables*. +- `new_client`: Builds a new client for interacting with 1Password Connect. Accepts the hostname of 1Password Connect, the API token generated for the application, is_async flag to initialize async client. ```python from onepasswordconnectsdk.client import ( @@ -55,13 +58,14 @@ from onepasswordconnectsdk.client import ( new_client ) -# creating client using OP_CONNECT_TOKEN and OP_CONNECT_HOST environment variables +# creating client using OP_CONNECT_TOKEN, OP_CONNECT_HOST and OP_CONNECT_CLIENT_ASYNC environment variables client_from_env: Client = new_client_from_environment() -# creates a client by supplying hostname and 1Password Connect API token +# creates a client by supplying hostname, 1Password Connect API token, is_async flag client_from_token: Client = new_client( "{1Password_Connect_Host}", - "{1Password_Connect_API_Token}") + "{1Password_Connect_API_Token}", + True) ``` **Get Item** @@ -173,6 +177,26 @@ Returns the contents of a given file. client.download_file("{file_id}", "{item_id}", "{vault_id}", "{content_path}") ``` +**Async way** + +All the examples above can work in async way. Here is an example: +```python +import asyncio + +# initialize async client by passing is_async = True +client: Client = new_client( + "{1Password_Connect_Host}", + "{1Password_Connect_API_Token}", + True) + +async def main(): + vaults = await client.get_vaults() + item = await client.get_item("{item_id}", "{vault_id}") + # do something with vaults and item + +asyncio.run(main()) +``` + **Load Configuration** Users can create `classes` or `dicts` that describe fields they wish to get the values from in 1Password. Two convienience methods are provided that will handle the fetching of values for these fields: diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 5bf0263..7c5ef2f 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -729,6 +729,7 @@ def new_client(url: str, token: str, is_async: bool = False): Parameters: url: The url of the 1Password Connect API token: The 1Password Service Account token + is_async: Initialize async or regular client Returns: Client: The 1Password Connect client From d20201d6561d21ae6ec514bbabccb6118cd564d3 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Fri, 27 Jan 2023 09:50:10 +0100 Subject: [PATCH 022/112] Update README Signed-off-by: volodymyrZotov --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 27eba6d..8f2cebd 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ async def main(): vaults = await client.get_vaults() item = await client.get_item("{item_id}", "{vault_id}") # do something with vaults and item + await async_client.session.aclose() # close the client gracefully when you are done asyncio.run(main()) ``` From 0b99330f55d7fa1f13177fd3dca4e878166570e0 Mon Sep 17 00:00:00 2001 From: volodymyrZotov Date: Tue, 31 Jan 2023 16:31:45 -0600 Subject: [PATCH 023/112] Use content_path url to fetch the file content Signed-off-by: volodymyrZotov --- src/onepasswordconnectsdk/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 2950b80..13b57fe 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -76,8 +76,8 @@ def get_files(self, item_id: str, vault_id: str): ) return self.deserialize(response.content, "list[File]") - def get_file_content(self, file_id: str, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" + def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None): + url = content_path if content_path is not None else f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" response = self.build_request("GET", url) try: @@ -91,8 +91,8 @@ def get_file_content(self, file_id: str, item_id: str, vault_id: str): def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): file_object = self.get_file(file_id, item_id, vault_id) - filename = file_object.name - content = self.get_file_content(file_id, item_id, vault_id) + filename = file_object.name or "1password_item_file.txt" + content = self.get_file_content(file_id, item_id, vault_id, file_object.content_path) global_path = os.path.join(path, filename) file = open(global_path, "wb") From 703a491995b90d8e8f9c267bf16fc5692cedae16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Jun 2023 19:10:17 +0000 Subject: [PATCH 024/112] Bump requests from 2.28.1 to 2.31.0 Bumps [requests](https://github.com/psf/requests) from 2.28.1 to 2.31.0. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.28.1...v2.31.0) --- updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- poetry.lock | 292 ++++++++++++++++++++++++---------------------------- 1 file changed, 136 insertions(+), 156 deletions(-) diff --git a/poetry.lock b/poetry.lock index a81aa0d..8605218 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,15 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + [[package]] name = "attrs" version = "22.2.0" description = "Classes Without Boilerplate" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] [package.extras] cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] @@ -17,17 +22,23 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy name = "certifi" version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] [[package]] name = "charset-normalizer" version = "2.1.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] [package.extras] unicode-backport = ["unicodedata2"] @@ -36,17 +47,72 @@ unicode-backport = ["unicodedata2"] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coverage" version = "7.0.1" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, + {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, + {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, + {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, + {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, + {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, + {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, + {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, + {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, + {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, + {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, + {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, + {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, + {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, + {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, + {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, + {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, + {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, + {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, + {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, + {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, + {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, + {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, + {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, + {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, + {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, + {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, + {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, +] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} @@ -58,9 +124,12 @@ toml = ["tomli"] name = "exceptiongroup" version = "1.1.0" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, + {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, +] [package.extras] test = ["pytest (>=6)"] @@ -69,17 +138,23 @@ test = ["pytest (>=6)"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] name = "importlib-metadata" version = "5.2.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, + {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -94,25 +169,34 @@ testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packag name = "iniconfig" version = "1.1.1" description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = "*" +files = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] [[package]] name = "packaging" version = "22.0" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, +] [[package]] name = "pluggy" version = "1.0.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -125,9 +209,12 @@ testing = ["pytest", "pytest-benchmark"] name = "pytest" version = "7.2.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, +] [package.dependencies] attrs = ">=19.2.0" @@ -146,9 +233,12 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2. name = "pytest-cov" version = "4.0.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} @@ -161,26 +251,32 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] [package.dependencies] six = ">=1.5" [[package]] name = "requests" -version = "2.28.1" +version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false -python-versions = ">=3.7, <4" +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" +charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] @@ -190,33 +286,45 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] [[package]] name = "urllib3" version = "1.26.13" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] @@ -227,146 +335,18 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] name = "zipp" version = "3.11.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.7" content-hash = "cac4a5815b13269da64f86e0b0481742124eba1200928ffb3fdd605c12d5c3c8" - -[metadata.files] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ - {file = "coverage-7.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c"}, - {file = "coverage-7.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d"}, - {file = "coverage-7.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0"}, - {file = "coverage-7.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498"}, - {file = "coverage-7.0.1-cp310-cp310-win32.whl", hash = "sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a"}, - {file = "coverage-7.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a"}, - {file = "coverage-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c"}, - {file = "coverage-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d"}, - {file = "coverage-7.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56"}, - {file = "coverage-7.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d"}, - {file = "coverage-7.0.1-cp311-cp311-win32.whl", hash = "sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7"}, - {file = "coverage-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390"}, - {file = "coverage-7.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e"}, - {file = "coverage-7.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f"}, - {file = "coverage-7.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4"}, - {file = "coverage-7.0.1-cp37-cp37m-win32.whl", hash = "sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c"}, - {file = "coverage-7.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8"}, - {file = "coverage-7.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856"}, - {file = "coverage-7.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69"}, - {file = "coverage-7.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910"}, - {file = "coverage-7.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab"}, - {file = "coverage-7.0.1-cp38-cp38-win32.whl", hash = "sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62"}, - {file = "coverage-7.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60"}, - {file = "coverage-7.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d"}, - {file = "coverage-7.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475"}, - {file = "coverage-7.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1"}, - {file = "coverage-7.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"}, - {file = "coverage-7.0.1-cp39-cp39-win32.whl", hash = "sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c"}, - {file = "coverage-7.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8"}, - {file = "coverage-7.0.1-pp37.pp38.pp39-none-any.whl", hash = "sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89"}, - {file = "coverage-7.0.1.tar.gz", hash = "sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, - {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -packaging = [ - {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, - {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pytest = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, -] -pytest-cov = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] -urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, -] -zipp = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] From 5940698aa1213daf01ac44b0db9b71e16f0ea595 Mon Sep 17 00:00:00 2001 From: Eduard Filip Date: Fri, 14 Jul 2023 17:40:33 +0200 Subject: [PATCH 025/112] Enhance README and move usage content to USAGE.md (#74) This contains the following key changes: - README.md - Content is arranged based on the new template we use for the README. - Provide a Quickstart section with two main cases: read and write an item. - Detailed content regarding the SDK is moved to a new file: USAGE.md. - USAGE.md - Functions related to item and vault operations are presented in a friendlier way. - Code snippets are adjusted to match the latest version of the SDK, therefore they're more accurate. --- README.md | 272 +++++++++++++----------------------------------------- USAGE.md | 138 +++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 207 deletions(-) create mode 100644 USAGE.md diff --git a/README.md b/README.md index 4d2f888..0a8eb47 100644 --- a/README.md +++ b/README.md @@ -1,235 +1,93 @@ -# 1Password Connect Python SDK + + -[![Python](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9-blue)](https://www.python.org) -[![PyPI version](https://badge.fury.io/py/onepasswordconnectsdk.svg)](https://badge.fury.io/py/onepasswordconnectsdk) -![CI](https://github.com/1Password/connect-sdk-python/workflows/Test/badge.svg) -[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://en.wikipedia.org/wiki/MIT_License) -[![codecov](https://codecov.io/gh/1Password/connect-sdk-python/branch/main/graph/badge.svg?token=VBPCH0CU2E)](https://codecov.io/gh/1Password/connect-sdk-python) +
+

1Password Connect SDK for Python

+

Access your 1Password items in your Python applications through your self-hosted 1Password Connect server.

+ + Get started + +
-The 1Password Connect SDK provides access to 1Password via [1Password Connect](https://support.1password.com/secrets-automation/) hosted in your infrastructure. The library is intended to be used by Python applications to simplify accessing items in 1Password vaults. +--- -## Prerequisites +The 1Password Connect SDK provides access to 1Password via [1Password Connect](https://developer.1password.com/docs/connect) hosted in your infrastructure. The library is intended to be used by Python applications to simplify accessing items in 1Password vaults. -- [1Password Connect](https://support.1password.com/secrets-automation/#step-2-deploy-a-1password-connect-server) deployed in your infrastructure -## Installation +## 🪄 See it in action -To install the 1Password Connect Python SDK: -```bash -$ pip install onepasswordconnectsdk -``` +Check the [Python Connect SDK Example](example/README.md) to see an example of item manipulation using the SDK that you can execute on your machine. -To install a specific release of the 1Password Connect Python SDK: -```bash -$ pip install onepasswordconnectsdk==1.0.1 -``` +## ✨ Get started -## Usage +1. Install the 1Password Connect Python SDK: -**Import 1Password Connect Python SDK** + ```sh + pip install onepasswordconnectsdk + ``` -```python -import onepasswordconnectsdk -``` +2. Export the `OP_CONNECT_HOST` and `OP_CONNECT_TOKEN` environment variables: -**Environment Variables** + ```sh + export OP_CONNECT_HOST= && \ + export OP_CONNECT_TOKEN= + ``` -- **OP_CONNECT_TOKEN** – The token to be used to authenticate with the 1Password Connect API. -- **OP_CONNECT_HOST** - The hostname of the 1Password Connect API. - Possible values include: - - `http(s)://connect-api:8080` if the Connect server is running in the same Kubernetes cluster as your application. - - `http://localhost:8080` if the Connect server is running in Docker on the same host. - - `http(s)://:8080` or `http(s)://:8080` if the Connect server is running on another host. -- **OP_VAULT** - The default vault to fetch items from if not specified. +3. Use the SDK: -**Create a Client** + - Read a secret: -There are two methods available for creating a client: + ```python + from onepasswordconnectsdk.client import ( + Client, + new_client_from_env, + } -- `new_client_from_environment`: Builds a new client for interacting with 1Password Connect using the `OP_CONNECT_TOKEN` and `OP_CONNECT_HOST` *environment variables*. -- `new_client`: Builds a new client for interacting with 1Password Connect. Accepts the hostname of 1Password Connect and the API token generated for the application. + connect_client: Client = new_client_from_env() -```python -from onepasswordconnectsdk.client import ( - Client, - new_client_from_environment, - new_client -) + client.get_item("{item_id}", "{vault_id}") + ``` -# creating client using OP_CONNECT_TOKEN and OP_CONNECT_HOST environment variables -client_from_env: Client = new_client_from_environment() + - Write a secret: -# creates a client by supplying hostname and 1Password Connect API token -client_from_token: Client = new_client( - "{1Password_Connect_Host}", - "{1Password_Connect_API_Token}") -``` + ```python + from onepasswordconnectsdk.client import ( + Client, + new_client_from_env, + } -**Get Item** + from onepasswordconnectsdk.models import ( + Item, + ItemVault, + Field + ) -Get a specific item by item and vault ids: + connect_client: Client = new_client_from_env() -```python -client.get_item("{item_id}", "{vault_id}") -``` + # Example item creation. Create an item with your desired arguments. + item = Item( + vault=ItemVault(id=op_vault), + id="custom_id", + title="newtitle", + category="LOGIN", + tags=["1password-connect"], + fields=[Field(value="new_user", purpose="USERNAME")], + ) -**Get Item By Title** + new_item = connect_client.create_item(op_vault, item) + ``` -Get a specific item by item title and vault id: +For more examples of how to use the SDK, check out [USAGE.md](USAGE.md). -```python -client.get_item_by_title("{item_title}", "{vault_id}") -``` +## 💙 Community & Support -**Get All Items** +- File an [issue](https://github.com/1Password/connect-sdk-python/issues) for bugs and feature requests. +- Join the [Developer Slack workspace](https://join.slack.com/t/1password-devs/shared_invite/zt-1halo11ps-6o9pEv96xZ3LtX_VE0fJQA). +- Subscribe to the [Developer Newsletter](https://1password.com/dev-subscribe/). -Get a summarized list of all items for a specified vault: +## 🔐 Security -```python -client.get_items("{vault_id}") -``` +1Password requests you practice responsible disclosure if you discover a vulnerability. -**Delete Item** +Please file requests via [**BugCrowd**](https://bugcrowd.com/agilebits). -Delete an item by item and vault ids: - -```python -client.delete_item("{item_id}", "{vault_id}") -``` - -**Create Item** - -Create an item in a specified vault: - -```python -from onepasswordconnectsdk.models import (ItemVault, Field) - -# Example item creation. Create an item with your desired arguments. -item = onepasswordconnectsdk.models.Item(vault=ItemVault(id="av223f76ydutdngislnkbz6z5u"), - id="kp2td65r4wbuhocwhhijpdbfqq", - title="newtitle", - category="LOGIN", - tags=["1password-connect"], - fields=[Field(value="new_user", - purpose="USERNAME")], - ) -client.create_item("{vault_id}", item) -``` - -**Update Item** - -Update the item identified by the specified item and vault ids. The existing item will be overwritten with the newly supplied item. - -```python -from onepasswordconnectsdk.models import (ItemVault, Field) - -# Example item creation. Create an item with your desired arguments. -item = onepasswordconnectsdk.models.Item(vault=ItemVault(id="av223f76ydutdngislnkbz6z5u"), - id="kp2td65r4wbuhocwhhijpdbfqq", - title="newtitle", - category="LOGIN", - tags=["1password-connect"], - fields=[Field(value="new_user", - purpose="USERNAME")], - ) -client.update_item("{item_id}", "{vault_id}", item) -``` - -**Get Specific Vault** - -Get a vault by vault id: - -```python -client.get_vault("{vault_id}") -``` - -**Get Vaults** - -Retrieve all vaults available to the service account: - -```python -client.get_vaults() -``` - -**List Files** -List summary information on all files stored in a given item, including file ids. - -```python -client.get_files("{item_id}", "{vault_id}") -``` - -**Get File Details** - -Get details on a specific file. - -```python -client.get_file("{file_id}", "{item_id}", "{vault_id}") -``` - -**Download File** - -Returns the contents of a given file. - -```python -client.download_file("{file_id}", "{item_id}", "{vault_id}", "{content_path}") -``` - -**Load Configuration** - -Users can create `classes` or `dicts` that describe fields they wish to get the values from in 1Password. Two convienience methods are provided that will handle the fetching of values for these fields: - -- **load_dict**: Takes a dictionary with keys specifying the user desired naming scheme of the values to return. Each key's value is a dictionary that includes information on where to find the item field value in 1Password. This returns a dictionary of user specified keys with values retrieved from 1Password -- **load**: Takes an object with class attributes annotated with tags describing where to find desired fields in 1Password. Manipulates given object and fills attributes in with 1Password item field values. - -```python -# example dict configuration for onepasswordconnectsdk.load_dict(client, CONFIG) -CONFIG = { - "server": { - "opitem": "My database item", - "opfield": "specific_section.hostname", - "opvault": "some_vault_id", - }, - "database": { - "opitem": "My database item", - "opfield": ".database", - }, - "username": { - "opitem": "My database item", - "opfield": ".username", - }, - "password": { - "opitem": "My database item", - "opfield": ".password", - }, -} - -values_dict = onepasswordconnectsdk.load_dict(client, CONFIG) -``` - -```python -# example class configuration for onepasswordconnectsdk.load(client, CONFIG) -class Config: - server: 'opitem:"My database item" opvault:some_vault_id opfield:specific_section.hostname' = None - database: 'opitem:"My database item" opfield:.database' = None - username: 'opitem:"My database item" opfield:.username' = None - password: 'opitem:"My database item" opfield:.password' = None - -CONFIG = Config() - -values_object = onepasswordconnectsdk.load(client, CONFIG) -``` - -## Development - -**Testing** - -```bash -make test -``` - -## Security - -1Password requests you practice responsible disclosure if you discover a vulnerability. - -Please file requests via [**BugCrowd**](https://bugcrowd.com/agilebits). - -For information about security practices, please visit our [Security homepage](https://bugcrowd.com/agilebits). +For information about security practices, please visit the [1Password Bug Bounty Program](https://bugcrowd.com/agilebits). diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..d53fb58 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,138 @@ +# Usage + +## Creating a Connect API Client + +There are two methods available for creating a client: + +- `new_client_from_environment`: Builds a new client for interacting with 1Password Connect using the `OP_CONNECT_TOKEN` and `OP_CONNECT_HOST` environment variables. +- `new_client`: Builds a new client for interacting with 1Password Connect. Accepts the hostname of 1Password Connect and the API token generated for the application. + +```python +from onepasswordconnectsdk.client import ( + Client, + new_client_from_environment, + new_client +) + +# creating client using OP_CONNECT_TOKEN and OP_CONNECT_HOST environment variables +connect_client_from_env: Client = new_client_from_environment() + +# creates a client by supplying hostname and 1Password Connect API token +connect_client_from_token: Client = new_client( + "{1Password_Connect_Host}", + "{1Password_Connect_API_Token}") +``` + +## Environment Variables + +- **OP_CONNECT_TOKEN** – The token to be used to authenticate with the 1Password Connect API. +- **OP_CONNECT_HOST** - The hostname of the 1Password Connect API. + Possible values include: + - `http(s)://connect-api:8080` if the Connect server is running in the same Kubernetes cluster as your application. + - `http://localhost:8080` if the Connect server is running in Docker on the same host. + - `http(s)://:8080` or `http(s)://:8080` if the Connect server is running on another host. +- **OP_VAULT** - The default vault to fetch items from if not specified. + +## Working with Vaults + +```python +# Get a list of all vaults +vaults = connect_client.get_vaults() + +# Get a specific vault +vault = connect_client.get_vault("{vault_id}") +vault_by_title = connect_client.get_vault_by_title("{vault_title}") +``` + +## Working with Items + +```python +from onepasswordconnectsdk.models import (Item, ItemVault, Field) + +vault_id = "{vault_id}" + +# Get a list of all items in a vault +items = connect_client.get_items("{vault_id}") + +# Create an item +new_item = Item( + title="Example Login Item", + category="LOGIN", + tags=["1password-connect"], + fields=[Field(value="new_user", purpose="USERNAME")], +) + +created_item = connect_client.create_item(vault_id, new_item) + +# Get an item +item = connect_client.get_item("{item_id}", vault_id) +item_by_title = connect_client.get_item_by_title("{item_title}", vault_id) + +# Update an item +created_item.title = "New Item Title" +updated_item = connect_client.update_item(created_item.id, vault_id, created_item) + +# Delete an item +connect_client.delete_item(updated_item.id, vault_id) +``` + +### Working with Items that contain files + +```python +item_id = "{item_id}" +vault_id = "{vault_id}" + +# Get summary information on all files stored in a given item +files = connect_client.get_files(item_id, vault_id) + +# Get a file's contents +file = connect_client.get_file_content(files[0].id, item_id, vault_id) + +# Download a file's contents +connect_client.download_file(files[1].id, item_id, vault_id, "local/path/to/file") +``` + +## Load Configuration + +Users can create `classes` or `dicts` that describe fields they wish to get the values from in 1Password. Two convenience methods are provided that will handle the fetching of values for these fields: + +- **load_dict**: Takes a dictionary with keys specifying the user desired naming scheme of the values to return. Each key's value is a dictionary that includes information on where to find the item field value in 1Password. This returns a dictionary of user specified keys with values retrieved from 1Password. +- **load**: Takes an object with class attributes annotated with tags describing where to find desired fields in 1Password. Manipulates given object and fills attributes in with 1Password item field values. + +```python +# example dict configuration for onepasswordconnectsdk.load_dict(connect_client, CONFIG) +CONFIG = { + "server": { + "opitem": "My database item", + "opfield": "specific_section.hostname", + "opvault": "some_vault_id", + }, + "database": { + "opitem": "My database item", + "opfield": ".database", + }, + "username": { + "opitem": "My database item", + "opfield": ".username", + }, + "password": { + "opitem": "My database item", + "opfield": ".password", + }, +} + +values_dict = onepasswordconnectsdk.load_dict(connect_client, CONFIG) +``` + +```python +# example class configuration for onepasswordconnectsdk.load(connect_client, CONFIG) +class Config: + server: 'opitem:"My database item" opvault:some_vault_id opfield:specific_section.hostname' = None + database: 'opitem:"My database item" opfield:.database' = None + username: 'opitem:"My database item" opfield:.username' = None + password: 'opitem:"My database item" opfield:.password' = None + +CONFIG = Config() + +values_object = onepasswordconnectsdk.load(connect_client, CONFIG) +``` From ddd8396c0f0a1d3b74bf45c1135888477503db9a Mon Sep 17 00:00:00 2001 From: "Jamie (Bear) Murphy" <1613241+ITJamie@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:22:25 +0100 Subject: [PATCH 026/112] Update README.md - fix bracket typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a8eb47..a1834ad 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i from onepasswordconnectsdk.client import ( Client, new_client_from_env, - } + ) connect_client: Client = new_client_from_env() From 07de759af76bef7be89d7ab39757d29ec4aaa3ad Mon Sep 17 00:00:00 2001 From: "Jamie (Bear) Murphy" <1613241+ITJamie@users.noreply.github.com> Date: Mon, 31 Jul 2023 11:54:55 +0100 Subject: [PATCH 027/112] enable filter use on get_items --- src/onepasswordconnectsdk/client.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 13b57fe..81fb5cd 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -184,11 +184,12 @@ def get_item_by_title(self, title: str, vault_id: str): item_summary = self.deserialize(response.content, "list[SummaryItem]")[0] return self.get_item_by_id(item_summary.id, vault_id) - def get_items(self, vault_id: str): + def get_items(self, vault_id: str, filter_query=None): """Returns a list of item summaries for the specified vault Args: vault_id (str): The id of the vault in which to get the items from + filter_query (str): A optional query statement, eg `title eq foo.bar` Raises: FailedToRetrieveItemException: Thrown when a HTTP error is returned @@ -197,7 +198,10 @@ def get_items(self, vault_id: str): Returns: List[SummaryItem]: A list of summarized items """ - url = f"/v1/vaults/{vault_id}/items" + if filter_query: + url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" + else: + url = f"/v1/vaults/{vault_id}/items" response = self.build_request("GET", url) try: From dd541417cfd3b5280281bd934cebe094864b1602 Mon Sep 17 00:00:00 2001 From: "Jamie (Bear) Murphy" <1613241+ITJamie@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:47:33 +0100 Subject: [PATCH 028/112] Update client.py --- src/onepasswordconnectsdk/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 81fb5cd..694da33 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -189,7 +189,7 @@ def get_items(self, vault_id: str, filter_query=None): Args: vault_id (str): The id of the vault in which to get the items from - filter_query (str): A optional query statement, eg `title eq foo.bar` + filter_query (str): A optional query statement. `title eq foo.bar` Raises: FailedToRetrieveItemException: Thrown when a HTTP error is returned @@ -198,10 +198,10 @@ def get_items(self, vault_id: str, filter_query=None): Returns: List[SummaryItem]: A list of summarized items """ - if filter_query: - url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" - else: + if filter_query is None: url = f"/v1/vaults/{vault_id}/items" + else: + url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" response = self.build_request("GET", url) try: From d44f496be708a7167e8e8f76fb649d8975dd3bae Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 15 Aug 2023 10:53:46 -0500 Subject: [PATCH 029/112] Update README.md --- README.md | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8f2cebd..d8d5c72 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ import onepasswordconnectsdk - **OP_VAULT** - The default vault to fetch items from if not specified. - **OP_CONNECT_CLIENT_ASYNC** - Whether to use async client or not. Possible values are: - True - to use async client - - False - to use NOT async client (this is used by default) + - False - to use synchronous client (this is used by default) **Create a Client** @@ -61,7 +61,7 @@ from onepasswordconnectsdk.client import ( # creating client using OP_CONNECT_TOKEN, OP_CONNECT_HOST and OP_CONNECT_CLIENT_ASYNC environment variables client_from_env: Client = new_client_from_environment() -# creates a client by supplying hostname, 1Password Connect API token, is_async flag +# creates a client by supplying hostname, 1Password Connect API token and `is_async` flag client_from_token: Client = new_client( "{1Password_Connect_Host}", "{1Password_Connect_API_Token}", @@ -177,27 +177,6 @@ Returns the contents of a given file. client.download_file("{file_id}", "{item_id}", "{vault_id}", "{content_path}") ``` -**Async way** - -All the examples above can work in async way. Here is an example: -```python -import asyncio - -# initialize async client by passing is_async = True -client: Client = new_client( - "{1Password_Connect_Host}", - "{1Password_Connect_API_Token}", - True) - -async def main(): - vaults = await client.get_vaults() - item = await client.get_item("{item_id}", "{vault_id}") - # do something with vaults and item - await async_client.session.aclose() # close the client gracefully when you are done - -asyncio.run(main()) -``` - **Load Configuration** Users can create `classes` or `dicts` that describe fields they wish to get the values from in 1Password. Two convienience methods are provided that will handle the fetching of values for these fields: @@ -243,6 +222,27 @@ CONFIG = Config() values_object = onepasswordconnectsdk.load(client, CONFIG) ``` +## Async client + +All the examples above can work using an async client. +```python +import asyncio + +# initialize async client by passing is_async = True +async_client: Client = new_client( + "{1Password_Connect_Host}", + "{1Password_Connect_API_Token}", + True) + +async def main(): + vaults = await async_client.get_vaults() + item = await async_client.get_item("{item_id}", "{vault_id}") + # do something with vaults and item + await async_client.session.aclose() # close the client gracefully when you are done + +asyncio.run(main()) +``` + ## Development **Testing** From 39e0636ca8b66d85bc3efed98e7da06c468789f1 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 15 Aug 2023 11:10:19 -0500 Subject: [PATCH 030/112] Address PR comments --- src/onepasswordconnectsdk/errors.py | 2 +- src/tests/test_client_vaults.py | 2 +- src/tests/test_config.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/onepasswordconnectsdk/errors.py b/src/onepasswordconnectsdk/errors.py index 1a24bb4..add5db0 100644 --- a/src/onepasswordconnectsdk/errors.py +++ b/src/onepasswordconnectsdk/errors.py @@ -19,4 +19,4 @@ class FailedToRetrieveVaultException(OnePasswordConnectSDKError): class FailedToDeserializeException(OnePasswordConnectSDKError, TypeError): - pass \ No newline at end of file + pass diff --git a/src/tests/test_client_vaults.py b/src/tests/test_client_vaults.py index e896e3b..9a3eab3 100644 --- a/src/tests/test_client_vaults.py +++ b/src/tests/test_client_vaults.py @@ -68,7 +68,7 @@ def test_get_vault_by_title(respx_mock): @pytest.mark.asyncio -async def test_get_vault_by_title(respx_mock): +async def test_get_vault_by_title_async(respx_mock): expected_vaults = list_vaults() expected_path = f"/v1/vaults?filter=name eq \"{VAULT_NAME}\"" diff --git a/src/tests/test_config.py b/src/tests/test_config.py index 64a0b61..551cbcf 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -96,7 +96,7 @@ def test_load_dict(respx_mock): } }, { - "id": "716C5B0E95A84092B2FE2CC402E0DDDF", + "id": "username", "label": "username", "value": USERNAME_VALUE } From 93edd505fee9240aadaab1856827bea2f739f0e3 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 15 Aug 2023 11:35:14 -0500 Subject: [PATCH 031/112] Update poetry.lock --- poetry.lock | 372 +++++++++++++++++++++++++--------------------------- 1 file changed, 181 insertions(+), 191 deletions(-) diff --git a/poetry.lock b/poetry.lock index af1e32f..6ccac8c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,43 +1,39 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + [[package]] name = "anyio" -version = "3.6.2" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "main" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] [package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16,<0.22)"] - -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] [[package]] name = "colorama" @@ -46,14 +42,80 @@ description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] [[package]] name = "coverage" -version = "7.0.4" +version = "7.2.7" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] [package.dependencies] tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} @@ -63,11 +125,15 @@ toml = ["tomli"] [[package]] name = "exceptiongroup" -version = "1.1.0" +version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] [package.extras] test = ["pytest (>=6)"] @@ -79,6 +145,10 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.8\""} @@ -90,6 +160,10 @@ description = "A minimal low-level HTTP client." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, + {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, +] [package.dependencies] anyio = ">=3.0,<5.0" @@ -108,6 +182,10 @@ description = "The next generation HTTP client." category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, + {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, +] [package.dependencies] certifi = "*" @@ -128,14 +206,22 @@ description = "Internationalized Domain Names in Applications (IDNA)" category = "main" optional = false python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] [[package]] name = "importlib-metadata" -version = "6.0.0" +version = "6.7.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} @@ -144,7 +230,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "iniconfig" @@ -153,22 +239,34 @@ description = "brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] [[package]] name = "packaging" -version = "23.0" +version = "23.1" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] [[package]] name = "pluggy" -version = "1.0.0" +version = "1.2.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, + {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, +] [package.dependencies] importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -179,14 +277,17 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.2.0" +version = "7.4.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, + {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, +] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} @@ -196,7 +297,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" @@ -205,6 +306,10 @@ description = "Pytest support for asyncio" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, + {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, +] [package.dependencies] pytest = ">=6.1.0" @@ -216,11 +321,15 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy [[package]] name = "pytest-cov" -version = "4.0.0" +version = "4.1.0" description = "Pytest plugin for measuring coverage." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +files = [ + {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, + {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, +] [package.dependencies] coverage = {version = ">=5.2.1", extras = ["toml"]} @@ -236,17 +345,25 @@ description = "Extensions to the standard Python datetime module" category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] [package.dependencies] six = ">=1.5" [[package]] name = "respx" -version = "0.20.1" +version = "0.20.2" description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"}, + {file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"}, +] [package.dependencies] httpx = ">=0.21.0" @@ -258,6 +375,10 @@ description = "Validating URI References per RFC 3986" category = "main" optional = false python-versions = "*" +files = [ + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, +] [package.dependencies] idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} @@ -272,6 +393,10 @@ description = "Python 2 and 3 compatibility utilities" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] [[package]] name = "sniffio" @@ -280,6 +405,10 @@ description = "Sniff out which async library your code is running under" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] [[package]] name = "tomli" @@ -288,179 +417,40 @@ description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] [[package]] name = "typing-extensions" -version = "4.4.0" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] [[package]] name = "zipp" -version = "3.11.0" +version = "3.15.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] -lock-version = "1.1" +lock-version = "2.0" python-versions = "^3.7" content-hash = "5c06df5db167647617c8fb7afc9da451dcec9d10e24df37e6024124cccd6da60" - -[metadata.files] -anyio = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, -] -attrs = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -coverage = [ - {file = "coverage-7.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:daf91db39324e9939a9db919ee4fb42a1a23634a056616dae891a030e89f87ba"}, - {file = "coverage-7.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:55121fe140d7e42cb970999b93cf1c2b24484ce028b32bbd00238bb25c13e34a"}, - {file = "coverage-7.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c027fbb83a8c78a6e06a0302ea1799fdb70e5cda9845a5e000545b8e2b47ea39"}, - {file = "coverage-7.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf82db5b7f16b51ec32fe0bd2da0805b177c807aa8bfb478c7e6f893418c284"}, - {file = "coverage-7.0.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ba5cc54baf3c322c4388de2a43cc95f7809366f0600e743e5aae8ea9d1038b2"}, - {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:260854160083f8275a9d9d49a05ab0ffc7a1f08f2ccccbfaec94a18aae9f407c"}, - {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ea45f0dba5a993e93b158f1a9dcfff2770e3bcabf2b80dbe7aa15dce0bcb3bf3"}, - {file = "coverage-7.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6abc91f6f8b3cc0ae1034e2c03f38769fba1952ab70d0b26953aa01691265c39"}, - {file = "coverage-7.0.4-cp310-cp310-win32.whl", hash = "sha256:053cdc47cae08257051d7e934a0de4d095b60eb8a3024fa9f1b2322fa1547137"}, - {file = "coverage-7.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:1e9e94f2612ee549a4b3ee79cbc61bceed77e69cf38cfa05858bae939a886d16"}, - {file = "coverage-7.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5caa9dd91dcc5f054350dc57a02e053d79633907b9ccffff999568d13dcd19f8"}, - {file = "coverage-7.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:efc200fa75d9634525b40babc7a16342bd21c101db1a58ef84dc14f4bf6ac0fd"}, - {file = "coverage-7.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1791e5f74c5b52f76e83fe9f4bb9571cf76d40ee0c51952ee1e4ee935b7e98b9"}, - {file = "coverage-7.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d9201cfa5a98652b9cef36ab202f17fe3ea83f497b4ba2a8ed39399dfb8fcd4"}, - {file = "coverage-7.0.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22d8ef6865cb6834cab2b72fff20747a55c714b57b675f7e11c9624fe4f7cb45"}, - {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b84076e3de192fba0f95e279ac017b64c7c6ecd4f09f36f13420f5bed898a9c7"}, - {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:dcfbf8ffc046f20d75fd775a92c378f6fc7b9bded6c6f2ab88b6b9cb5805a184"}, - {file = "coverage-7.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4665a714af31f160403c2e448fb2fef330719d2e04e836b08d60d612707c1041"}, - {file = "coverage-7.0.4-cp311-cp311-win32.whl", hash = "sha256:2e59aef3fba5758059208c9eff10ae7ded3629e797972746ec33b56844f69411"}, - {file = "coverage-7.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:2b854f7985b48122b6fe346631e86d67b63293f8255cb59a93d79e3d9f1574e3"}, - {file = "coverage-7.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e44b60b0b49aa85d548d392a2dca2c6a581cd4084e72e9e16bd58bd86ec20816"}, - {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2904d7a0388911c61e7e3beefe48c29dfccaba938fc1158f63190101a21e04c2"}, - {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc74b64bfa89e2f862ea45dd6ac1def371d7cc883b76680d20bdd61a6f3daa20"}, - {file = "coverage-7.0.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06046f54e719da21c79f98ecc0962581d1aee0b3798dc6b12b1217da8bf93f4"}, - {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:bc9c77004970a364a1e5454cf7cb884e4277592b959c287689b2a0fd027ef552"}, - {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0815a09b32384e8ff00a5939ec9cd10efce8742347e019c2daca1a32f5ac2aae"}, - {file = "coverage-7.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a78a80d131c067d67d8a6f9bd3d3f7ea7eac82c1c7259f97d7ab73f723da9d55"}, - {file = "coverage-7.0.4-cp37-cp37m-win32.whl", hash = "sha256:2b5936b624fbe711ed02dfd86edd678822e5ee68da02b6d231e5c01090b64590"}, - {file = "coverage-7.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:a63922765ee49d5b4c32afb2cd5516812c8665f3b78e64a0dd005bdfabf991b1"}, - {file = "coverage-7.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d68f2f7bddb3acdd3b36ef7f334b9d14f30b93e094f808fbbd8d288b8f9e2f9b"}, - {file = "coverage-7.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dafdba3b2b9010abab08cb8c0dc6549bfca6e1630fe14d47b01dca00d39e694"}, - {file = "coverage-7.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0322354757b47640535daabd2d56384ff3cad2896248fc84d328c5fad4922d5c"}, - {file = "coverage-7.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e8267466662aff93d66fa72b9591d02122dfc8a729b0a43dd70e0fb07ed9b37"}, - {file = "coverage-7.0.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f684d88eb4924ed0630cf488fd5606e334c6835594bb5fe36b50a509b10383ed"}, - {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:70c294bb15ba576fb96b580db35895bf03749d683df044212b74e938a7f6821f"}, - {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:34c0457e1ba450ae8b22dc8ea2fd36ada1010af61291e4c96963cd9d9633366f"}, - {file = "coverage-7.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b75aff2c35ceaa299691e772f7bf7c8aeab25f46acea2be3dd04cccb914a9860"}, - {file = "coverage-7.0.4-cp38-cp38-win32.whl", hash = "sha256:6c5554d55668381e131577f20e8f620d4882b04ad558f7e7f3f1f55b3124c379"}, - {file = "coverage-7.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:c82f34fafaf5bc05d222fcf84423d6e156432ca35ca78672d4affd0c09c6ef6c"}, - {file = "coverage-7.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8dfb5fed540f77e814bf4ec79619c241af6b4578fa1093c5e3389bbb7beab3f"}, - {file = "coverage-7.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee32a080bab779b71c4d09a3eb5254bfca43ee88828a683dab27dfe8f582516e"}, - {file = "coverage-7.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dfbee0bf0d633be3a2ab068f5a5731a70adf147d0ba17d9f9932b46c7c5782b"}, - {file = "coverage-7.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32dc010713455ac0fe2fddb0e48aa43875cc7eb7b09768df10bad8ce45f9c430"}, - {file = "coverage-7.0.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cb88a3019ad042eaa69fc7639ef077793fedbf313e89207aa82fefe92c97ebd"}, - {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:73bc6114aab7753ca784f87bcd3b7613bc797aa255b5bca45e5654070ae9acfb"}, - {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:92f135d370fcd7a6fb9659fa2eb716dd2ca364719cbb1756f74d90a221bca1a7"}, - {file = "coverage-7.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f3d485e6ec6e09857bf2115ece572d666b7c498377d4c70e66bb06c63ed177c2"}, - {file = "coverage-7.0.4-cp39-cp39-win32.whl", hash = "sha256:c58921fcd9914b56444292e7546fe183d079db99528142c809549ddeaeacd8e9"}, - {file = "coverage-7.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:f092d9f2ddaa30235d33335fbdb61eb8f3657af519ef5f9dd6bdae65272def11"}, - {file = "coverage-7.0.4-pp37.pp38.pp39-none-any.whl", hash = "sha256:cb8cfa3bf3a9f18211279458917fef5edeb5e1fdebe2ea8b11969ec2ebe48884"}, - {file = "coverage-7.0.4.tar.gz", hash = "sha256:f6c4ad409a0caf7e2e12e203348b1a9b19c514e7d078520973147bf2d3dcbc6f"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] -h11 = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] -httpcore = [ - {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, - {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, -] -httpx = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -importlib-metadata = [ - {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, - {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, -] -iniconfig = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] -packaging = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pytest = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, - {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, -] -pytest-cov = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, -] -python-dateutil = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] -respx = [ - {file = "respx-0.20.1-py2.py3-none-any.whl", hash = "sha256:372f06991c03d1f7f480a420a2199d01f1815b6ed5a802f4e4628043a93bd03e"}, - {file = "respx-0.20.1.tar.gz", hash = "sha256:cc47a86d7010806ab65abdcf3b634c56337a737bb5c4d74c19a0dfca83b3bc73"}, -] -rfc3986 = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -sniffio = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] -zipp = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] From 28422ebb8fff8704025ffd6fff7c7ca47b31df88 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 15 Aug 2023 11:39:45 -0500 Subject: [PATCH 032/112] Update USAGE.md with Async client examples --- USAGE.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/USAGE.md b/USAGE.md index d53fb58..902b71b 100644 --- a/USAGE.md +++ b/USAGE.md @@ -21,6 +21,12 @@ connect_client_from_env: Client = new_client_from_environment() connect_client_from_token: Client = new_client( "{1Password_Connect_Host}", "{1Password_Connect_API_Token}") + +# creates async client +connect_async_client: Client = new_client( + "{1Password_Connect_Host}", + "{1Password_Connect_API_Token}", + True) ``` ## Environment Variables @@ -32,6 +38,10 @@ connect_client_from_token: Client = new_client( - `http://localhost:8080` if the Connect server is running in Docker on the same host. - `http(s)://:8080` or `http(s)://:8080` if the Connect server is running on another host. - **OP_VAULT** - The default vault to fetch items from if not specified. +- **OP_CONNECT_CLIENT_ASYNC** - Whether to use async client or not. Possible values are: + - True - to use async client + - False - to use synchronous client (this is used by default) + ## Working with Vaults @@ -136,3 +146,24 @@ CONFIG = Config() values_object = onepasswordconnectsdk.load(connect_client, CONFIG) ``` + +## Async client + +All the examples above can work using an async client. +```python +import asyncio + +# initialize async client by passing `is_async = True` +async_client: Client = new_client( + "{1Password_Connect_Host}", + "{1Password_Connect_API_Token}", + True) + +async def main(): + vaults = await async_client.get_vaults() + item = await async_client.get_item("{item_id}", "{vault_id}") + # do something with vaults and item + await async_client.session.aclose() # close the client gracefully when you are done + +asyncio.run(main()) +``` \ No newline at end of file From 1b453a52e10d8f33b94159a57fce69dd822a4fd5 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Wed, 16 Aug 2023 18:41:29 -0500 Subject: [PATCH 033/112] Gracefully close httpx client when delete AsyncClient instance --- src/onepasswordconnectsdk/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 28d6b85..ce6189b 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -376,6 +376,9 @@ def __init__(self, session, serializer): self.session = session self.serializer = serializer + def __del__(self): + self.session.aclose() + async def get_file(self, file_id: str, item_id: str, vault_id: str): url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" response = await self.build_request("GET", url) From 325f5d7bb86150429393abd40a5911265ab06dfe Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Thu, 17 Aug 2023 12:12:27 -0500 Subject: [PATCH 034/112] Bump 'certify' version to 2023.7.22 Version contains the patch to fix Removal of e-Tugra root certificate vulnerability https://github.com/1Password/connect-sdk-python/security/dependabot/6 --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8605218..60cfaeb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -20,13 +20,13 @@ tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] From a9d05e25f3106fd3a7cbbb3c2759f3ff93251d57 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 18 Aug 2023 15:38:22 -0500 Subject: [PATCH 035/112] Keep Client and AsyncClient backward compatibility --- src/onepasswordconnectsdk/async_client.py | 377 +++++++++++++++++++++ src/onepasswordconnectsdk/client.py | 389 ++-------------------- src/onepasswordconnectsdk/utils.py | 9 + 3 files changed, 406 insertions(+), 369 deletions(-) create mode 100644 src/onepasswordconnectsdk/async_client.py diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py new file mode 100644 index 0000000..a7eb76d --- /dev/null +++ b/src/onepasswordconnectsdk/async_client.py @@ -0,0 +1,377 @@ +"""Python AsyncClient for connecting to 1Password Connect""" +import httpx +from httpx import HTTPError +import json +import os + +from onepasswordconnectsdk.serializer import Serializer +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid +from onepasswordconnectsdk.errors import ( + FailedToRetrieveItemException, + FailedToRetrieveVaultException, +) +from onepasswordconnectsdk.models import Item, ItemVault + + +class AsyncClient: + """Python Async Client Class""" + + def __init__(self, url: str, token: str): + """Initialize async client""" + self.url = url + self.token = token + self.session = self.create_session(url, token) + self.serializer = Serializer() + + def create_session(self, url: str, token: str): + return httpx.AsyncClient(base_url=url, headers=self.build_headers(token)) + + def build_headers(self, token: str): + return build_headers(token) + + def __del__(self): + self.session.aclose() + + async def get_file(self, file_id: str, item_id: str, vault_id: str): + url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "File") + + async def get_files(self, item_id: str, vault_id: str): + url = f"/v1/vaults/{vault_id}/items/{item_id}/files" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "list[File]") + + async def get_file_content(self, file_id: str, item_id: str, vault_id: str): + url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve items. Received {response.status_code} \ + for {url} with message: {response.json().get('message')}" + ) + return response.content + + async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): + file_object = await self.get_file(file_id, item_id, vault_id) + filename = file_object.name + content = await self.get_file_content(file_id, item_id, vault_id) + global_path = os.path.join(path, filename) + + file = open(global_path, "wb") + file.write(content) + file.close() + + async def get_item(self, item: str, vault: str): + """Get a specific item + + Args: + item (str): the id or title of the item to be fetched + vault (str): the id or name of the vault in which to get the item from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item object: The found item + """ + + vault_id = vault + if not is_valid_uuid(vault): + vault = await self.get_vault_by_title(vault) + vault_id = vault.id + + if is_valid_uuid(item): + return await self.get_item_by_id(item, vault_id) + else: + return await self.get_item_by_title(item, vault_id) + + async def get_item_by_id(self, item_id: str, vault_id: str): + """Get a specific item by uuid + + Args: + item_id (str): The id of the item to be fetched + vault_id (str): The id of the vault in which to get the item from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item object: The found item + """ + url = f"/v1/vaults/{vault_id}/items/{item_id}" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "Item") + + async def get_item_by_title(self, title: str, vault_id: str): + """Get a specific item by title + + Args: + title (str): The title of the item to be fetched + vault_id (str): The id of the vault in which to get the item from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item object: The found item + """ + filter_query = f'title eq "{title}"' + url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve items. Received {response.status_code} \ + for {url} with message: {response.json().get('message')}" + ) + + if len(response.json()) != 1: + raise FailedToRetrieveItemException( + f"Found {len(response.json())} items in vault {vault_id} with \ + title {title}" + ) + + item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] + return await self.get_item_by_id(item_summary.id, vault_id) + + async def get_items(self, vault_id: str): + """Returns a list of item summaries for the specified vault + + Args: + vault_id (str): The id of the vault in which to get the items from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + List[SummaryItem]: A list of summarized items + """ + url = f"/v1/vaults/{vault_id}/items" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to retrieve items. Received {response.status_code} \ + for {url} with message: {response.json().get('message')}" + ) + + return self.serializer.deserialize(response.content, "list[SummaryItem]") + + async def delete_item(self, item_id: str, vault_id: str): + """Deletes a specified item from a specified vault + + Args: + item_id (str): The id of the item in which to delete the item from + vault_id (str): The id of the vault in which to delete the item + from + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + """ + url = f"/v1/vaults/{vault_id}/items/{item_id}" + + response = await self.build_request("DELETE", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to delete item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + + async def create_item(self, vault_id: str, item: Item): + """Creates an item at the specified vault + + Args: + vault_id (str): The id of the vault in which add the item to + item (Item): The item to create + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item: The created item + """ + + url = f"/v1/vaults/{vault_id}/items" + + response = await self.build_request("POST", url, item) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to post item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "Item") + + async def update_item(self, item_uuid: str, vault_id: str, item: Item): + """Update the specified item at the specified vault. + + Args: + item_uuid (str): The id of the item in which to update + vault_id (str): The id of the vault in which to update the item + item (Item): The updated item + + Raises: + FailedToRetrieveItemException: Thrown when a HTTP error is returned + from the 1Password Connect API + + Returns: + Item: The updated item + """ + url = f"/v1/vaults/{vault_id}/items/{item_uuid}" + item.id = item_uuid + item.vault = ItemVault(id=vault_id) + + response = await self.build_request("PUT", url, item) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveItemException( + f"Unable to post item. Received {response.status_code}\ + for {url} with message: {response.json().get('message')}" + ) + return self.serializer.deserialize(response.content, "Item") + + async def get_vault(self, vault_id: str): + """Returns the vault with the given vault_id + + Args: + vault_id (str): The id of the vault in which to fetch + + Raises: + FailedToRetrieveVaultException: Thrown when a HTTP error is + returned from the 1Password Connect API + + Returns: + Vault: The specified vault + """ + url = f"/v1/vaults/{vault_id}" + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveVaultException( + f"Unable to retrieve vault. Received {response.status_code} \ + for {url} with message {response.json().get('message')}" + ) + + return self.serializer.deserialize(response.content, "Vault") + + async def get_vault_by_title(self, name: str): + """Returns the vault with the given name + + Args: + name (str): The name of the vault in which to fetch + + Raises: + FailedToRetrieveVaultException: Thrown when a HTTP error is + returned from the 1Password Connect API + + Returns: + Vault: The specified vault + """ + filter_query = f'name eq "{name}"' + url = f"/v1/vaults?filter={filter_query}" + + response = await self.build_request("GET", url) + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveVaultException( + f"Unable to retrieve vaults. Received {response.status_code} \ + for {url} with message {response.json().get('message')}" + ) + + if len(response.json()) != 1: + raise FailedToRetrieveItemException( + f"Found {len(response.json())} vaults with \ + name {name}" + ) + + return self.serializer.deserialize(response.content, "list[Vault]")[0] + + async def get_vaults(self): + """Returns all vaults for service account set in client + + Raises: + FailedToRetrieveVaultException: Thrown when a HTTP error is + returned from the 1Password Connect API + + Returns: + List[Vault]: All vaults for the service account in use + """ + url = "/v1/vaults" + response = await self.build_request("GET", url) + + try: + response.raise_for_status() + except HTTPError: + raise FailedToRetrieveVaultException( + f"Unable to retrieve vaults. Received {response.status_code} \ + for {url} with message {response.json().get('message')}" + ) + + return self.serializer.deserialize(response.content, "list[Vault]") + + def build_request(self, method: str, path: str, body=None): + """Builds a http request + Parameters: + method (str): The rest method to be used + path (str): The request path + body (str): The request body + + Returns: + Response object: The request response + """ + + if body: + serialized_body = json.dumps(self.serializer.sanitize_for_serialization(body)) + response = self.session.request(method, path, data=serialized_body) + else: + response = self.session.request(method, path) + return response + + def deserialize(self, response, response_type): + return self.serializer.deserialize(response, response_type) + + def sanitize_for_serialization(self, obj): + return self.serializer.sanitize_for_serialization(obj) \ No newline at end of file diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index ce6189b..c86093c 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -4,8 +4,9 @@ import json import os +from onepasswordconnectsdk.async_client import AsyncClient from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import is_valid_uuid +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, @@ -22,10 +23,18 @@ class Client: """Python Client Class""" - def __init__(self, session, serializer): + def __init__(self, url: str, token: str): """Initialize client""" - self.session = session - self.serializer = serializer + self.url = url + self.token = token + self.session = self.create_session(url, token) + self.serializer = Serializer() + + def create_session(self, url: str, token: str): + return httpx.Client(base_url=url, headers=self.build_headers(token)) + + def build_headers(self, token: str): + return build_headers(token) def __del__(self): self.session.close() @@ -367,364 +376,11 @@ def build_request(self, method: str, path: str, body=None): response = self.session.request(method, path) return response + def deserialize(self, response, response_type): + return self.serializer.deserialize(response, response_type) -class AsyncClient: - """Python Async Client Class""" - - def __init__(self, session, serializer): - """Initialize client""" - self.session = session - self.serializer = serializer - - def __del__(self): - self.session.aclose() - - async def get_file(self, file_id: str, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to retrieve item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" - ) - return self.serializer.deserialize(response.content, "File") - - async def get_files(self, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files" - - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to retrieve item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" - ) - return self.serializer.deserialize(response.content, "list[File]") - - async def get_file_content(self, file_id: str, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" - - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to retrieve items. Received {response.status_code} \ - for {url} with message: {response.json().get('message')}" - ) - return response.content - - async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): - file_object = await self.get_file(file_id, item_id, vault_id) - filename = file_object.name - content = await self.get_file_content(file_id, item_id, vault_id) - global_path = os.path.join(path, filename) - - file = open(global_path, "wb") - file.write(content) - file.close() - - async def get_item(self, item: str, vault: str): - """Get a specific item - - Args: - item (str): the id or title of the item to be fetched - vault (str): the id or name of the vault in which to get the item from - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - - Returns: - Item object: The found item - """ - - vault_id = vault - if not is_valid_uuid(vault): - vault = await self.get_vault_by_title(vault) - vault_id = vault.id - - if is_valid_uuid(item): - return await self.get_item_by_id(item, vault_id) - else: - return await self.get_item_by_title(item, vault_id) - - async def get_item_by_id(self, item_id: str, vault_id: str): - """Get a specific item by uuid - - Args: - item_id (str): The id of the item to be fetched - vault_id (str): The id of the vault in which to get the item from - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - - Returns: - Item object: The found item - """ - url = f"/v1/vaults/{vault_id}/items/{item_id}" - - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to retrieve item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" - ) - return self.serializer.deserialize(response.content, "Item") - - async def get_item_by_title(self, title: str, vault_id: str): - """Get a specific item by title - - Args: - title (str): The title of the item to be fetched - vault_id (str): The id of the vault in which to get the item from - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - - Returns: - Item object: The found item - """ - filter_query = f'title eq "{title}"' - url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" - - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to retrieve items. Received {response.status_code} \ - for {url} with message: {response.json().get('message')}" - ) - - if len(response.json()) != 1: - raise FailedToRetrieveItemException( - f"Found {len(response.json())} items in vault {vault_id} with \ - title {title}" - ) - - item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] - return await self.get_item_by_id(item_summary.id, vault_id) - - async def get_items(self, vault_id: str): - """Returns a list of item summaries for the specified vault - - Args: - vault_id (str): The id of the vault in which to get the items from - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - - Returns: - List[SummaryItem]: A list of summarized items - """ - url = f"/v1/vaults/{vault_id}/items" - - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to retrieve items. Received {response.status_code} \ - for {url} with message: {response.json().get('message')}" - ) - - return self.serializer.deserialize(response.content, "list[SummaryItem]") - - async def delete_item(self, item_id: str, vault_id: str): - """Deletes a specified item from a specified vault - - Args: - item_id (str): The id of the item in which to delete the item from - vault_id (str): The id of the vault in which to delete the item - from - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - """ - url = f"/v1/vaults/{vault_id}/items/{item_id}" - - response = await self.build_request("DELETE", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to delete item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" - ) - - async def create_item(self, vault_id: str, item: Item): - """Creates an item at the specified vault - - Args: - vault_id (str): The id of the vault in which add the item to - item (Item): The item to create - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - - Returns: - Item: The created item - """ - - url = f"/v1/vaults/{vault_id}/items" - - response = await self.build_request("POST", url, item) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to post item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" - ) - return self.serializer.deserialize(response.content, "Item") - - async def update_item(self, item_uuid: str, vault_id: str, item: Item): - """Update the specified item at the specified vault. - - Args: - item_uuid (str): The id of the item in which to update - vault_id (str): The id of the vault in which to update the item - item (Item): The updated item - - Raises: - FailedToRetrieveItemException: Thrown when a HTTP error is returned - from the 1Password Connect API - - Returns: - Item: The updated item - """ - url = f"/v1/vaults/{vault_id}/items/{item_uuid}" - item.id = item_uuid - item.vault = ItemVault(id=vault_id) - - response = await self.build_request("PUT", url, item) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveItemException( - f"Unable to post item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" - ) - return self.serializer.deserialize(response.content, "Item") - - async def get_vault(self, vault_id: str): - """Returns the vault with the given vault_id - - Args: - vault_id (str): The id of the vault in which to fetch - - Raises: - FailedToRetrieveVaultException: Thrown when a HTTP error is - returned from the 1Password Connect API - - Returns: - Vault: The specified vault - """ - url = f"/v1/vaults/{vault_id}" - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveVaultException( - f"Unable to retrieve vault. Received {response.status_code} \ - for {url} with message {response.json().get('message')}" - ) - - return self.serializer.deserialize(response.content, "Vault") - - async def get_vault_by_title(self, name: str): - """Returns the vault with the given name - - Args: - name (str): The name of the vault in which to fetch - - Raises: - FailedToRetrieveVaultException: Thrown when a HTTP error is - returned from the 1Password Connect API - - Returns: - Vault: The specified vault - """ - filter_query = f'name eq "{name}"' - url = f"/v1/vaults?filter={filter_query}" - - response = await self.build_request("GET", url) - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveVaultException( - f"Unable to retrieve vaults. Received {response.status_code} \ - for {url} with message {response.json().get('message')}" - ) - - if len(response.json()) != 1: - raise FailedToRetrieveItemException( - f"Found {len(response.json())} vaults with \ - name {name}" - ) - - return self.serializer.deserialize(response.content, "list[Vault]")[0] - - async def get_vaults(self): - """Returns all vaults for service account set in client - - Raises: - FailedToRetrieveVaultException: Thrown when a HTTP error is - returned from the 1Password Connect API - - Returns: - List[Vault]: All vaults for the service account in use - """ - url = "/v1/vaults" - response = await self.build_request("GET", url) - - try: - response.raise_for_status() - except HTTPError: - raise FailedToRetrieveVaultException( - f"Unable to retrieve vaults. Received {response.status_code} \ - for {url} with message {response.json().get('message')}" - ) - - return self.serializer.deserialize(response.content, "list[Vault]") - - def build_request(self, method: str, path: str, body=None): - """Builds a http request - Parameters: - method (str): The rest method to be used - path (str): The request path - body (str): The request body - - Returns: - Response object: The request response - """ - - if body: - serialized_body = json.dumps(self.serializer.sanitize_for_serialization(body)) - response = self.session.request(method, path, data=serialized_body) - else: - response = self.session.request(method, path) - return response - - -def build_headers(token: str): - """Builds the headers needed to make a request to the server - - Returns: - dict: The 1Password Connect API request headers - """ - return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + def sanitize_for_serialization(self, obj): + return self.serializer.sanitize_for_serialization(obj) def new_client(url: str, token: str, is_async: bool = False): @@ -732,19 +388,14 @@ def new_client(url: str, token: str, is_async: bool = False): Parameters: url: The url of the 1Password Connect API token: The 1Password Service Account token - is_async: Initialize async or regular client + is_async: Initialize async or sync client Returns: Client: The 1Password Connect client """ - headers = build_headers(token) - serializer = Serializer() if is_async: - session = httpx.AsyncClient(base_url=url, headers=headers) - return AsyncClient(session=session, serializer=serializer) - - session = httpx.Client(base_url=url, headers=headers) - return Client(session=session, serializer=serializer) + return AsyncClient(url, token) + return Client(url, token) def new_client_from_environment(url: str = None): diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index 4a2c8e1..7d4a279 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -9,3 +9,12 @@ def is_valid_uuid(uuid): if valid is False: return False return True + + +def build_headers(token: str): + """Builds the headers needed to make a request to the server + + Returns: + dict: The 1Password Connect API request headers + """ + return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} From 8e99bc6c7a16935f7c1670f32678a6210614877a Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 18 Aug 2023 16:19:47 -0500 Subject: [PATCH 036/112] Add Connect.PathBuilder --- src/onepasswordconnectsdk/connect.py | 39 +++++++++++++++++++++++++++ src/tests/test_connect.py | 40 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/onepasswordconnectsdk/connect.py create mode 100644 src/tests/test_connect.py diff --git a/src/onepasswordconnectsdk/connect.py b/src/onepasswordconnectsdk/connect.py new file mode 100644 index 0000000..0d50552 --- /dev/null +++ b/src/onepasswordconnectsdk/connect.py @@ -0,0 +1,39 @@ +class PathBuilder: + def __init__(self, version: str = "/v1"): + self.path: str = version + + def build(self) -> str: + return self.path + + def vaults(self, uuid: str = None) -> 'PathBuilder': + self._append_path("vaults") + if uuid is not None: + self._append_path(uuid) + return self + + def items(self, uuid: str = None) -> 'PathBuilder': + self._append_path("items") + if uuid is not None: + self._append_path(uuid) + return self + + def files(self, uuid: str = None) -> 'PathBuilder': + self._append_path("files") + if uuid is not None: + self._append_path(uuid) + return self + + def content(self) -> 'PathBuilder': + self._append_path("content") + return self + + def query(self, key: str, value: str) -> 'PathBuilder': + key_value_pair = f"{key}={value}" + self._append_path(query=key_value_pair) + return self + + def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilder': + if path_chunk is not None: + self.path += f"/{path_chunk}" + if query is not None: + self.path += f"?{query}" diff --git a/src/tests/test_connect.py b/src/tests/test_connect.py new file mode 100644 index 0000000..d3624e8 --- /dev/null +++ b/src/tests/test_connect.py @@ -0,0 +1,40 @@ +from onepasswordconnectsdk.connect import PathBuilder + +VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" +ITEM_ID = "wepiqdxdzncjtnvmv5fegud4qy" +FILE_ID = "fileqdxczsc2tn32vsfegud123" + + +def test_all_vaults_path(): + path = PathBuilder().vaults().build() + assert path == "/v1/vaults" + + +def test_single_vault_path(): + path = PathBuilder().vaults(VAULT_ID).build() + assert path == f"/v1/vaults/{VAULT_ID}" + + +def test_all_items_path(): + path = PathBuilder().vaults(VAULT_ID).items().build() + assert path == f"/v1/vaults/{VAULT_ID}/items" + + +def test_filter_items_path(): + path = PathBuilder().vaults(VAULT_ID).items().query("filter", "title").build() + assert path == f"/v1/vaults/{VAULT_ID}/items?filter=title" + + +def test_single_item_path(): + path = PathBuilder().vaults(VAULT_ID).items(ITEM_ID).build() + assert path == f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + +def test_all_files_path(): + path = PathBuilder().vaults(VAULT_ID).items(ITEM_ID).files().build() + assert path == f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}/files" + + +def test_single_file_path(): + path = PathBuilder().vaults(VAULT_ID).items(ITEM_ID).files(FILE_ID).build() + assert path == f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}/files/{FILE_ID}" From f994624d5d1b5187b84b844bbf69bf8289a18f5e Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 18 Aug 2023 16:52:03 -0500 Subject: [PATCH 037/112] Use PathBuilder to construct the connect url path --- src/onepasswordconnectsdk/async_client.py | 38 ++++++++++------------- src/onepasswordconnectsdk/client.py | 37 +++++++++------------- 2 files changed, 31 insertions(+), 44 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index a7eb76d..cd51eb2 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -4,6 +4,7 @@ import json import os +from onepasswordconnectsdk.connect import PathBuilder from onepasswordconnectsdk.serializer import Serializer from onepasswordconnectsdk.utils import build_headers, is_valid_uuid from onepasswordconnectsdk.errors import ( @@ -33,7 +34,7 @@ def __del__(self): self.session.aclose() async def get_file(self, file_id: str, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" + url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -45,8 +46,7 @@ async def get_file(self, file_id: str, item_id: str, vault_id: str): return self.serializer.deserialize(response.content, "File") async def get_files(self, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files" - + url = PathBuilder().vaults(vault_id).items(item_id).files().build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -57,9 +57,10 @@ async def get_files(self, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "list[File]") - async def get_file_content(self, file_id: str, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" - + async def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None): + url = content_path + if content_path is None: + url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -73,7 +74,7 @@ async def get_file_content(self, file_id: str, item_id: str, vault_id: str): async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): file_object = await self.get_file(file_id, item_id, vault_id) filename = file_object.name - content = await self.get_file_content(file_id, item_id, vault_id) + content = await self.get_file_content(file_id, item_id, vault_id, file_object.content_path) global_path = os.path.join(path, filename) file = open(global_path, "wb") @@ -120,7 +121,6 @@ async def get_item_by_id(self, item_id: str, vault_id: str): Item object: The found item """ url = f"/v1/vaults/{vault_id}/items/{item_id}" - response = await self.build_request("GET", url) try: response.raise_for_status() @@ -146,8 +146,7 @@ async def get_item_by_title(self, title: str, vault_id: str): Item object: The found item """ filter_query = f'title eq "{title}"' - url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" - + url = PathBuilder().vaults(vault_id).items().query("filter", filter_query).build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -179,8 +178,7 @@ async def get_items(self, vault_id: str): Returns: List[SummaryItem]: A list of summarized items """ - url = f"/v1/vaults/{vault_id}/items" - + url = PathBuilder().vaults(vault_id).items().build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -204,8 +202,7 @@ async def delete_item(self, item_id: str, vault_id: str): FailedToRetrieveItemException: Thrown when a HTTP error is returned from the 1Password Connect API """ - url = f"/v1/vaults/{vault_id}/items/{item_id}" - + url = PathBuilder().vaults(vault_id).items(item_id).build() response = await self.build_request("DELETE", url) try: response.raise_for_status() @@ -230,8 +227,7 @@ async def create_item(self, vault_id: str, item: Item): Item: The created item """ - url = f"/v1/vaults/{vault_id}/items" - + url = PathBuilder().vaults(vault_id).items().build() response = await self.build_request("POST", url, item) try: response.raise_for_status() @@ -257,7 +253,7 @@ async def update_item(self, item_uuid: str, vault_id: str, item: Item): Returns: Item: The updated item """ - url = f"/v1/vaults/{vault_id}/items/{item_uuid}" + url = PathBuilder().vaults(vault_id).items(item_uuid).build() item.id = item_uuid item.vault = ItemVault(id=vault_id) @@ -284,7 +280,7 @@ async def get_vault(self, vault_id: str): Returns: Vault: The specified vault """ - url = f"/v1/vaults/{vault_id}" + url = PathBuilder().vaults(vault_id).build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -310,8 +306,7 @@ async def get_vault_by_title(self, name: str): Vault: The specified vault """ filter_query = f'name eq "{name}"' - url = f"/v1/vaults?filter={filter_query}" - + url = PathBuilder().vaults().query("filter", filter_query).build() response = await self.build_request("GET", url) try: response.raise_for_status() @@ -339,9 +334,8 @@ async def get_vaults(self): Returns: List[Vault]: All vaults for the service account in use """ - url = "/v1/vaults" + url = PathBuilder().vaults().build() response = await self.build_request("GET", url) - try: response.raise_for_status() except HTTPError: diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index c86093c..a8c3c59 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -5,6 +5,7 @@ import os from onepasswordconnectsdk.async_client import AsyncClient +from onepasswordconnectsdk.connect import PathBuilder from onepasswordconnectsdk.serializer import Serializer from onepasswordconnectsdk.utils import build_headers, is_valid_uuid from onepasswordconnectsdk.errors import ( @@ -40,7 +41,7 @@ def __del__(self): self.session.close() def get_file(self, file_id: str, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}" + url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -52,8 +53,7 @@ def get_file(self, file_id: str, item_id: str, vault_id: str): return self.serializer.deserialize(response.content, "File") def get_files(self, item_id: str, vault_id: str): - url = f"/v1/vaults/{vault_id}/items/{item_id}/files" - + url = PathBuilder().vaults(vault_id).items(item_id).files().build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -65,8 +65,9 @@ def get_files(self, item_id: str, vault_id: str): return self.serializer.deserialize(response.content, "list[File]") def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None): - url = content_path if content_path is not None else f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" - + url = content_path + if content_path is None: + url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -125,8 +126,7 @@ def get_item_by_id(self, item_id: str, vault_id: str): Returns: Item object: The found item """ - url = f"/v1/vaults/{vault_id}/items/{item_id}" - + url = PathBuilder().vaults(vault_id).items(item_id).build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -152,8 +152,7 @@ def get_item_by_title(self, title: str, vault_id: str): Item object: The found item """ filter_query = f'title eq "{title}"' - url = f"/v1/vaults/{vault_id}/items?filter={filter_query}" - + url = PathBuilder().vaults(vault_id).items().query("filter", filter_query).build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -185,8 +184,7 @@ def get_items(self, vault_id: str): Returns: List[SummaryItem]: A list of summarized items """ - url = f"/v1/vaults/{vault_id}/items" - + url = PathBuilder().vaults(vault_id).items().build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -204,14 +202,12 @@ def delete_item(self, item_id: str, vault_id: str): Args: item_id (str): The id of the item in which to delete the item from vault_id (str): The id of the vault in which to delete the item - from Raises: FailedToRetrieveItemException: Thrown when a HTTP error is returned from the 1Password Connect API """ - url = f"/v1/vaults/{vault_id}/items/{item_id}" - + url = PathBuilder().vaults(vault_id).items(item_id).build() response = self.build_request("DELETE", url) try: response.raise_for_status() @@ -236,8 +232,7 @@ def create_item(self, vault_id: str, item: Item): Item: The created item """ - url = f"/v1/vaults/{vault_id}/items" - + url = PathBuilder().vaults(vault_id).items().build() response = self.build_request("POST", url, item) try: response.raise_for_status() @@ -263,7 +258,7 @@ def update_item(self, item_uuid: str, vault_id: str, item: Item): Returns: Item: The updated item """ - url = f"/v1/vaults/{vault_id}/items/{item_uuid}" + url = PathBuilder().vaults(vault_id).items(item_uuid).build() item.id = item_uuid item.vault = ItemVault(id=vault_id) @@ -290,7 +285,7 @@ def get_vault(self, vault_id: str): Returns: Vault: The specified vault """ - url = f"/v1/vaults/{vault_id}" + url = PathBuilder().vaults(vault_id).build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -316,8 +311,7 @@ def get_vault_by_title(self, name: str): Vault: The specified vault """ filter_query = f'name eq "{name}"' - url = f"/v1/vaults?filter={filter_query}" - + url = PathBuilder().vaults().query("filter", filter_query).build() response = self.build_request("GET", url) try: response.raise_for_status() @@ -345,9 +339,8 @@ def get_vaults(self): Returns: List[Vault]: All vaults for the service account in use """ - url = "/v1/vaults" + url = PathBuilder().vaults().build() response = self.build_request("GET", url) - try: response.raise_for_status() except HTTPError: From feeeb87beb6781a1b968e7bb10506d2c3ba3b142 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 5 Sep 2023 18:15:14 -0500 Subject: [PATCH 038/112] Update filter_query comment --- src/onepasswordconnectsdk/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index dc0edb3..c54113e 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -176,7 +176,7 @@ def get_items(self, vault_id: str, filter_query: str = None): Args: vault_id (str): The id of the vault in which to get the items from - filter_query (str): A optional query statement. `title eq foo.bar` + filter_query (str): A optional query statement. `title eq "Example Item"` Raises: FailedToRetrieveItemException: Thrown when a HTTP error is returned From a7f5c06eef1aa804e43f59853dc77c00de521b4c Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 11:13:38 -0500 Subject: [PATCH 039/112] Move path builder class to utils.py --- src/onepasswordconnectsdk/async_client.py | 3 +- src/onepasswordconnectsdk/client.py | 3 +- src/onepasswordconnectsdk/connect.py | 39 --------------------- src/onepasswordconnectsdk/utils.py | 41 +++++++++++++++++++++++ src/tests/test_connect.py | 2 +- 5 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index cd51eb2..0776276 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -4,9 +4,8 @@ import json import os -from onepasswordconnectsdk.connect import PathBuilder from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import build_headers, is_valid_uuid +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index c54113e..4202ccf 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -5,9 +5,8 @@ import os from onepasswordconnectsdk.async_client import AsyncClient -from onepasswordconnectsdk.connect import PathBuilder from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import build_headers, is_valid_uuid +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, diff --git a/src/onepasswordconnectsdk/connect.py b/src/onepasswordconnectsdk/connect.py index 0d50552..e69de29 100644 --- a/src/onepasswordconnectsdk/connect.py +++ b/src/onepasswordconnectsdk/connect.py @@ -1,39 +0,0 @@ -class PathBuilder: - def __init__(self, version: str = "/v1"): - self.path: str = version - - def build(self) -> str: - return self.path - - def vaults(self, uuid: str = None) -> 'PathBuilder': - self._append_path("vaults") - if uuid is not None: - self._append_path(uuid) - return self - - def items(self, uuid: str = None) -> 'PathBuilder': - self._append_path("items") - if uuid is not None: - self._append_path(uuid) - return self - - def files(self, uuid: str = None) -> 'PathBuilder': - self._append_path("files") - if uuid is not None: - self._append_path(uuid) - return self - - def content(self) -> 'PathBuilder': - self._append_path("content") - return self - - def query(self, key: str, value: str) -> 'PathBuilder': - key_value_pair = f"{key}={value}" - self._append_path(query=key_value_pair) - return self - - def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilder': - if path_chunk is not None: - self.path += f"/{path_chunk}" - if query is not None: - self.path += f"?{query}" diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index 7d4a279..da35d50 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -18,3 +18,44 @@ def build_headers(token: str): dict: The 1Password Connect API request headers """ return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + + +class PathBuilder: + def __init__(self, version: str = "/v1"): + self.path: str = version + + def build(self) -> str: + return self.path + + def vaults(self, uuid: str = None) -> 'PathBuilder': + self._append_path("vaults") + if uuid is not None: + self._append_path(uuid) + return self + + def items(self, uuid: str = None) -> 'PathBuilder': + self._append_path("items") + if uuid is not None: + self._append_path(uuid) + return self + + def files(self, uuid: str = None) -> 'PathBuilder': + self._append_path("files") + if uuid is not None: + self._append_path(uuid) + return self + + def content(self) -> 'PathBuilder': + self._append_path("content") + return self + + def query(self, key: str, value: str) -> 'PathBuilder': + key_value_pair = f"{key}={value}" + self._append_path(query=key_value_pair) + return self + + def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilder': + if path_chunk is not None: + self.path += f"/{path_chunk}" + if query is not None: + self.path += f"?{query}" diff --git a/src/tests/test_connect.py b/src/tests/test_connect.py index d3624e8..8db556b 100644 --- a/src/tests/test_connect.py +++ b/src/tests/test_connect.py @@ -1,4 +1,4 @@ -from onepasswordconnectsdk.connect import PathBuilder +from onepasswordconnectsdk.utils import PathBuilder VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" ITEM_ID = "wepiqdxdzncjtnvmv5fegud4qy" From c79a37bdb8872680882289aef15af14355a9dfbd Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 11:18:50 -0500 Subject: [PATCH 040/112] Add test for file content path --- src/tests/{test_connect.py => test_utils.py} | 5 +++++ 1 file changed, 5 insertions(+) rename src/tests/{test_connect.py => test_utils.py} (85%) diff --git a/src/tests/test_connect.py b/src/tests/test_utils.py similarity index 85% rename from src/tests/test_connect.py rename to src/tests/test_utils.py index 8db556b..2ed0374 100644 --- a/src/tests/test_connect.py +++ b/src/tests/test_utils.py @@ -38,3 +38,8 @@ def test_all_files_path(): def test_single_file_path(): path = PathBuilder().vaults(VAULT_ID).items(ITEM_ID).files(FILE_ID).build() assert path == f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}/files/{FILE_ID}" + + +def test_file_conten_path(): + path = PathBuilder().vaults(VAULT_ID).items(ITEM_ID).files(FILE_ID).content().build() + assert path == f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}/files/{FILE_ID}/content" From a52019e0e3ee73e5337af82bae0ae4b92bff4c69 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 11:21:37 -0500 Subject: [PATCH 041/112] Use __aexit__ instead of __del__ to properly close async client --- src/onepasswordconnectsdk/async_client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 0776276..4441a79 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -29,8 +29,8 @@ def create_session(self, url: str, token: str): def build_headers(self, token: str): return build_headers(token) - def __del__(self): - self.session.aclose() + async def __aexit__(self): + await self.session.aclose() async def get_file(self, file_id: str, item_id: str, vault_id: str): url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).build() @@ -367,4 +367,4 @@ def deserialize(self, response, response_type): return self.serializer.deserialize(response, response_type) def sanitize_for_serialization(self, obj): - return self.serializer.sanitize_for_serialization(obj) \ No newline at end of file + return self.serializer.sanitize_for_serialization(obj) From 56a1c370efcbcf210f9f419a4a7ac22f072bfd7a Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 11:28:43 -0500 Subject: [PATCH 042/112] Use PathBuilder in get_item_by_id method --- src/onepasswordconnectsdk/async_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 4441a79..84aff20 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -119,7 +119,7 @@ async def get_item_by_id(self, item_id: str, vault_id: str): Returns: Item object: The found item """ - url = f"/v1/vaults/{vault_id}/items/{item_id}" + url = PathBuilder().vaults(vault_id).items(item_id).build() response = await self.build_request("GET", url) try: response.raise_for_status() From 818d055e5f15a55a00509dce22da9dcf45ae3ae7 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 11:30:31 -0500 Subject: [PATCH 043/112] Set default file name as "1password_item_file.txt" in download_file method --- src/onepasswordconnectsdk/async_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 84aff20..aca7324 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -72,7 +72,7 @@ async def get_file_content(self, file_id: str, item_id: str, vault_id: str, cont async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): file_object = await self.get_file(file_id, item_id, vault_id) - filename = file_object.name + filename = file_object.name or "1password_item_file.txt" content = await self.get_file_content(file_id, item_id, vault_id, file_object.content_path) global_path = os.path.join(path, filename) From 8ae9ffae375c9f287d9d6f96bc500eefad22dcb6 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 11:32:18 -0500 Subject: [PATCH 044/112] Add ability to filter in get_items method --- src/onepasswordconnectsdk/async_client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index aca7324..415924c 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -164,7 +164,7 @@ async def get_item_by_title(self, title: str, vault_id: str): item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] return await self.get_item_by_id(item_summary.id, vault_id) - async def get_items(self, vault_id: str): + async def get_items(self, vault_id: str, filter_query: str = None): """Returns a list of item summaries for the specified vault Args: @@ -177,7 +177,10 @@ async def get_items(self, vault_id: str): Returns: List[SummaryItem]: A list of summarized items """ - url = PathBuilder().vaults(vault_id).items().build() + if filter_query is None: + url = PathBuilder().vaults(vault_id).items().build() + else: + url = PathBuilder().vaults(vault_id).items().query("filter", filter_query).build() response = await self.build_request("GET", url) try: response.raise_for_status() From 7c832db4226e3412df155a1362890d7896d9d447 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 12:29:15 -0500 Subject: [PATCH 045/112] Bump project version to v1.4.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4b9d1c6..dd54dd1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "1.3.0" +version = "1.4.0" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From c511c4da6c6fa0eaaa85051e306caca06e8c5d20 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 8 Sep 2023 12:40:14 -0500 Subject: [PATCH 046/112] Update changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 286142f..71981bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,24 @@ --- +[//]: # "START/v1.4.0" + +# v1.4.0 + +## Features + +- Support async operations `(async/await)`. {#62} +- Enable filter usage on `get_items`. Credits to @ITJamie for the contribution! {#76} + +## Fixes + +- Drop support for python 2. {#61} +- 'download_file' function uses content path now {#65} +- Enhance README and move usage content to USAGE.md. {#74} +- Fix README typos. Credits to @ITJamie for the contribution! {#75} + +--- + [//]: # "START/v1.3.0" # v1.3.0 From a48295bf6b0ea86c3a9fe8b480addb1515916809 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 14:31:12 +0100 Subject: [PATCH 047/112] Upgrade Python syntax use with pyupgrade The removed lines no longer have an effect in a Python 3 codebase. --- src/onepasswordconnectsdk/__init__.py | 2 -- src/onepasswordconnectsdk/models/__init__.py | 5 ----- src/onepasswordconnectsdk/models/error.py | 4 +--- src/onepasswordconnectsdk/models/field.py | 6 ++---- src/onepasswordconnectsdk/models/field_section.py | 4 +--- src/onepasswordconnectsdk/models/file.py | 2 +- src/onepasswordconnectsdk/models/generator_recipe.py | 8 +++----- src/onepasswordconnectsdk/models/item.py | 4 +--- src/onepasswordconnectsdk/models/item_details.py | 4 +--- src/onepasswordconnectsdk/models/item_urls.py | 4 +--- src/onepasswordconnectsdk/models/item_vault.py | 4 +--- src/onepasswordconnectsdk/models/section.py | 4 +--- src/onepasswordconnectsdk/models/summary_item.py | 4 +--- src/onepasswordconnectsdk/models/vault.py | 6 ++---- 14 files changed, 16 insertions(+), 45 deletions(-) diff --git a/src/onepasswordconnectsdk/__init__.py b/src/onepasswordconnectsdk/__init__.py index ab7dcd4..376bcd8 100644 --- a/src/onepasswordconnectsdk/__init__.py +++ b/src/onepasswordconnectsdk/__init__.py @@ -1,5 +1,3 @@ -# coding: utf-8 - # flake8: noqa from onepasswordconnectsdk.config import load diff --git a/src/onepasswordconnectsdk/models/__init__.py b/src/onepasswordconnectsdk/models/__init__.py index 15b7d55..4727a26 100644 --- a/src/onepasswordconnectsdk/models/__init__.py +++ b/src/onepasswordconnectsdk/models/__init__.py @@ -1,10 +1,5 @@ -# coding: utf-8 - # flake8: noqa - -from __future__ import absolute_import - # import models into model package from onepasswordconnectsdk.models.error import Error from onepasswordconnectsdk.models.item import Item diff --git a/src/onepasswordconnectsdk/models/error.py b/src/onepasswordconnectsdk/models/error.py index ab8855a..92e31a7 100644 --- a/src/onepasswordconnectsdk/models/error.py +++ b/src/onepasswordconnectsdk/models/error.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class Error(object): +class Error: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index 5f1665e..c83482d 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class Field(object): +class Field: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -172,7 +170,7 @@ def purpose(self, purpose): allowed_values = ["", "USERNAME", "PASSWORD", "NOTES"] # noqa: E501 if purpose not in allowed_values: # noqa: E501 raise ValueError( - "Invalid value for `purpose` ({0}), must be one of {1}" # noqa: E501 + "Invalid value for `purpose` ({}), must be one of {}" # noqa: E501 .format(purpose, allowed_values) ) diff --git a/src/onepasswordconnectsdk/models/field_section.py b/src/onepasswordconnectsdk/models/field_section.py index d042405..313afd1 100644 --- a/src/onepasswordconnectsdk/models/field_section.py +++ b/src/onepasswordconnectsdk/models/field_section.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class FieldSection(object): +class FieldSection: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/file.py b/src/onepasswordconnectsdk/models/file.py index 1cf3e30..4124ad0 100644 --- a/src/onepasswordconnectsdk/models/file.py +++ b/src/onepasswordconnectsdk/models/file.py @@ -2,7 +2,7 @@ import re # noqa: F401 -class File(object): +class File: openapi_types = { 'id': 'str', 'name': 'str', diff --git a/src/onepasswordconnectsdk/models/generator_recipe.py b/src/onepasswordconnectsdk/models/generator_recipe.py index d156a6e..690a7a6 100644 --- a/src/onepasswordconnectsdk/models/generator_recipe.py +++ b/src/onepasswordconnectsdk/models/generator_recipe.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -19,7 +17,7 @@ import re # noqa: F401 import six -class GeneratorRecipe(object): +class GeneratorRecipe: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -103,7 +101,7 @@ def character_sets(self, character_sets): allowed_values = ["LETTERS", "DIGITS", "SYMBOLS"] # noqa: E501 if (not set(character_sets).issubset(set(allowed_values))): # noqa: E501 raise ValueError( - "Invalid values for `character_sets` [{0}], must be a subset of [{1}]" # noqa: E501 + "Invalid values for `character_sets` [{}], must be a subset of [{}]" # noqa: E501 .format(", ".join(map(str, set(character_sets) - set(allowed_values))), # noqa: E501 ", ".join(map(str, allowed_values))) ) @@ -124,7 +122,7 @@ def convert(x): else: return x - for attr, _ in six.iteritems(self.openapi_types): + for attr, _ in self.openapi_types.items(): value = getattr(self, attr) attr = self.attribute_map.get(attr, attr) if serialize else attr if isinstance(value, list): diff --git a/src/onepasswordconnectsdk/models/item.py b/src/onepasswordconnectsdk/models/item.py index 0cf9dff..8985cbb 100644 --- a/src/onepasswordconnectsdk/models/item.py +++ b/src/onepasswordconnectsdk/models/item.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class Item(object): +class Item: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/item_details.py b/src/onepasswordconnectsdk/models/item_details.py index 34cdf84..7844aa0 100644 --- a/src/onepasswordconnectsdk/models/item_details.py +++ b/src/onepasswordconnectsdk/models/item_details.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class ItemDetails(object): +class ItemDetails: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/item_urls.py b/src/onepasswordconnectsdk/models/item_urls.py index 0f8df8b..2a12fcf 100644 --- a/src/onepasswordconnectsdk/models/item_urls.py +++ b/src/onepasswordconnectsdk/models/item_urls.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class ItemUrls(object): +class ItemUrls: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/item_vault.py b/src/onepasswordconnectsdk/models/item_vault.py index 51eb52a..737eda9 100644 --- a/src/onepasswordconnectsdk/models/item_vault.py +++ b/src/onepasswordconnectsdk/models/item_vault.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class ItemVault(object): +class ItemVault: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/section.py b/src/onepasswordconnectsdk/models/section.py index 9318f22..8875cd7 100644 --- a/src/onepasswordconnectsdk/models/section.py +++ b/src/onepasswordconnectsdk/models/section.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class Section(object): +class Section: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/summary_item.py b/src/onepasswordconnectsdk/models/summary_item.py index 76a4ac2..bd9af8f 100644 --- a/src/onepasswordconnectsdk/models/summary_item.py +++ b/src/onepasswordconnectsdk/models/summary_item.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -17,7 +15,7 @@ from onepasswordconnectsdk.models import Item -class SummaryItem(object): +class SummaryItem: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech diff --git a/src/onepasswordconnectsdk/models/vault.py b/src/onepasswordconnectsdk/models/vault.py index 1282b37..2628eb9 100644 --- a/src/onepasswordconnectsdk/models/vault.py +++ b/src/onepasswordconnectsdk/models/vault.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """ 1Password Connect @@ -14,7 +12,7 @@ import re # noqa: F401 -class Vault(object): +class Vault: """NOTE: This class is auto generated by OpenAPI Generator. Ref: https://openapi-generator.tech @@ -237,7 +235,7 @@ def type(self, type): allowed_values = ["USER_CREATED", "PERSONAL", "EVERYONE", "TRANSFER"] # noqa: E501 if type not in allowed_values: # noqa: E501 raise ValueError( - "Invalid value for `type` ({0}), must be one of {1}" # noqa: E501 + "Invalid value for `type` ({}), must be one of {}" # noqa: E501 .format(type, allowed_values) ) From 7694371ef58803e5350ebb85440b33a19839217f Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 14:35:10 +0100 Subject: [PATCH 048/112] Remove unused imports - re is not used anywhere in the model generated code - inspect.getfullargspec is available from Python 3.4 onwards --- example/main.py | 2 +- src/onepasswordconnectsdk/models/error.py | 1 - src/onepasswordconnectsdk/models/field.py | 1 - src/onepasswordconnectsdk/models/field_section.py | 1 - src/onepasswordconnectsdk/models/file.py | 1 - src/onepasswordconnectsdk/models/generator_recipe.py | 7 +------ src/onepasswordconnectsdk/models/item.py | 1 - src/onepasswordconnectsdk/models/item_details.py | 1 - src/onepasswordconnectsdk/models/item_urls.py | 1 - src/onepasswordconnectsdk/models/item_vault.py | 1 - src/onepasswordconnectsdk/models/section.py | 1 - src/onepasswordconnectsdk/models/summary_item.py | 1 - src/onepasswordconnectsdk/models/vault.py | 1 - 13 files changed, 2 insertions(+), 18 deletions(-) diff --git a/example/main.py b/example/main.py index 7c4ab26..e2e54af 100644 --- a/example/main.py +++ b/example/main.py @@ -3,7 +3,7 @@ import time import onepasswordconnectsdk -from onepasswordconnectsdk.models import (ItemVault, Field, GeneratorRecipe) +from onepasswordconnectsdk.models import Field, GeneratorRecipe op_connect_token = os.environ["OP_CONNECT_TOKEN"] default_vault = os.environ["OP_VAULT"] diff --git a/src/onepasswordconnectsdk/models/error.py b/src/onepasswordconnectsdk/models/error.py index 92e31a7..eed602d 100644 --- a/src/onepasswordconnectsdk/models/error.py +++ b/src/onepasswordconnectsdk/models/error.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class Error: diff --git a/src/onepasswordconnectsdk/models/field.py b/src/onepasswordconnectsdk/models/field.py index c83482d..0b64346 100644 --- a/src/onepasswordconnectsdk/models/field.py +++ b/src/onepasswordconnectsdk/models/field.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class Field: diff --git a/src/onepasswordconnectsdk/models/field_section.py b/src/onepasswordconnectsdk/models/field_section.py index 313afd1..c8efaf6 100644 --- a/src/onepasswordconnectsdk/models/field_section.py +++ b/src/onepasswordconnectsdk/models/field_section.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class FieldSection: diff --git a/src/onepasswordconnectsdk/models/file.py b/src/onepasswordconnectsdk/models/file.py index 4124ad0..f326d0d 100644 --- a/src/onepasswordconnectsdk/models/file.py +++ b/src/onepasswordconnectsdk/models/file.py @@ -1,5 +1,4 @@ import pprint -import re # noqa: F401 class File: diff --git a/src/onepasswordconnectsdk/models/generator_recipe.py b/src/onepasswordconnectsdk/models/generator_recipe.py index 690a7a6..950e5df 100644 --- a/src/onepasswordconnectsdk/models/generator_recipe.py +++ b/src/onepasswordconnectsdk/models/generator_recipe.py @@ -9,13 +9,8 @@ """ -try: - from inspect import getfullargspec -except ImportError: - from inspect import getargspec as getfullargspec +from inspect import getfullargspec import pprint -import re # noqa: F401 -import six class GeneratorRecipe: """NOTE: This class is auto generated by OpenAPI Generator. diff --git a/src/onepasswordconnectsdk/models/item.py b/src/onepasswordconnectsdk/models/item.py index 8985cbb..b0ec3c6 100644 --- a/src/onepasswordconnectsdk/models/item.py +++ b/src/onepasswordconnectsdk/models/item.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class Item: diff --git a/src/onepasswordconnectsdk/models/item_details.py b/src/onepasswordconnectsdk/models/item_details.py index 7844aa0..2e97b46 100644 --- a/src/onepasswordconnectsdk/models/item_details.py +++ b/src/onepasswordconnectsdk/models/item_details.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class ItemDetails: diff --git a/src/onepasswordconnectsdk/models/item_urls.py b/src/onepasswordconnectsdk/models/item_urls.py index 2a12fcf..29bd6b7 100644 --- a/src/onepasswordconnectsdk/models/item_urls.py +++ b/src/onepasswordconnectsdk/models/item_urls.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class ItemUrls: diff --git a/src/onepasswordconnectsdk/models/item_vault.py b/src/onepasswordconnectsdk/models/item_vault.py index 737eda9..3f1d469 100644 --- a/src/onepasswordconnectsdk/models/item_vault.py +++ b/src/onepasswordconnectsdk/models/item_vault.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class ItemVault: diff --git a/src/onepasswordconnectsdk/models/section.py b/src/onepasswordconnectsdk/models/section.py index 8875cd7..d1b8794 100644 --- a/src/onepasswordconnectsdk/models/section.py +++ b/src/onepasswordconnectsdk/models/section.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class Section: diff --git a/src/onepasswordconnectsdk/models/summary_item.py b/src/onepasswordconnectsdk/models/summary_item.py index bd9af8f..793fc40 100644 --- a/src/onepasswordconnectsdk/models/summary_item.py +++ b/src/onepasswordconnectsdk/models/summary_item.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 from onepasswordconnectsdk.models import Item diff --git a/src/onepasswordconnectsdk/models/vault.py b/src/onepasswordconnectsdk/models/vault.py index 2628eb9..8c5fa63 100644 --- a/src/onepasswordconnectsdk/models/vault.py +++ b/src/onepasswordconnectsdk/models/vault.py @@ -9,7 +9,6 @@ import pprint -import re # noqa: F401 class Vault: From f7a1ef52b4446eb6fe26140334b6322d199de7d1 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 14:35:46 +0100 Subject: [PATCH 049/112] Use __all__ to explicitly mark imports as exported This removes the need to disable flake8 in these modules. --- src/onepasswordconnectsdk/__init__.py | 9 +++++++-- src/onepasswordconnectsdk/config.py | 1 - src/onepasswordconnectsdk/models/__init__.py | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/onepasswordconnectsdk/__init__.py b/src/onepasswordconnectsdk/__init__.py index 376bcd8..82f3764 100644 --- a/src/onepasswordconnectsdk/__init__.py +++ b/src/onepasswordconnectsdk/__init__.py @@ -1,6 +1,11 @@ -# flake8: noqa - from onepasswordconnectsdk.config import load from onepasswordconnectsdk.config import load_dict from onepasswordconnectsdk.client import new_client from onepasswordconnectsdk.client import new_client_from_environment + +__all__ = [ + "load", + "load_dict", + "new_client", + "new_client_from_environment" +] diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 7398232..4670b97 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -3,7 +3,6 @@ from typing import List, Dict from onepasswordconnectsdk.client import Client from onepasswordconnectsdk.models import ( - SummaryItem, Item, ParsedField, ParsedItem, diff --git a/src/onepasswordconnectsdk/models/__init__.py b/src/onepasswordconnectsdk/models/__init__.py index 4727a26..85e2deb 100644 --- a/src/onepasswordconnectsdk/models/__init__.py +++ b/src/onepasswordconnectsdk/models/__init__.py @@ -1,5 +1,3 @@ -# flake8: noqa - # import models into model package from onepasswordconnectsdk.models.error import Error from onepasswordconnectsdk.models.item import Item @@ -15,3 +13,20 @@ from onepasswordconnectsdk.models.parsed_field import ParsedField from onepasswordconnectsdk.models.parsed_item import ParsedItem from onepasswordconnectsdk.models.vault import Vault + +__all__ = [ + "Error", + "Field", + "FieldSection", + "File", + "GeneratorRecipe", + "Item", + "ItemDetails", + "ItemUrls", + "ItemVault", + "ParsedField", + "ParsedItem", + "Section", + "SummaryItem", + "Vault", +] \ No newline at end of file From 1d2cc70cb851f9ec098125e6bc0b0870c1099a67 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 14:36:33 +0100 Subject: [PATCH 050/112] Simplify loop over openapi types The value of each key-value pair is ignored, just loop over the keys. --- src/onepasswordconnectsdk/models/generator_recipe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/models/generator_recipe.py b/src/onepasswordconnectsdk/models/generator_recipe.py index 950e5df..1c19821 100644 --- a/src/onepasswordconnectsdk/models/generator_recipe.py +++ b/src/onepasswordconnectsdk/models/generator_recipe.py @@ -117,7 +117,7 @@ def convert(x): else: return x - for attr, _ in self.openapi_types.items(): + for attr in self.openapi_types: value = getattr(self, attr) attr = self.attribute_map.get(attr, attr) if serialize else attr if isinstance(value, list): From 385147eedfea237a9fd0f7f3acd36ea34328e755 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 17:01:32 +0100 Subject: [PATCH 051/112] Explicitly export client and models modules These modules are indirectly imported, but documentation shows these need to be explicitly available for consumers of this library. Without adding client and models to the exported names here, type checkers and linters will flag using `onepasswordconnectsdk.client` and `onepasswordconnectsdk.models` as invalid. --- src/onepasswordconnectsdk/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/onepasswordconnectsdk/__init__.py b/src/onepasswordconnectsdk/__init__.py index 82f3764..5a83802 100644 --- a/src/onepasswordconnectsdk/__init__.py +++ b/src/onepasswordconnectsdk/__init__.py @@ -1,11 +1,15 @@ +from onepasswordconnectsdk import client +from onepasswordconnectsdk import models from onepasswordconnectsdk.config import load from onepasswordconnectsdk.config import load_dict from onepasswordconnectsdk.client import new_client from onepasswordconnectsdk.client import new_client_from_environment __all__ = [ + "client", "load", "load_dict", + "models", "new_client", "new_client_from_environment" ] From 6c3faa020c2eaf8ee2a3689ee14abb257d3965b2 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 17:03:54 +0100 Subject: [PATCH 052/112] Simplify example by importing type --- example/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/main.py b/example/main.py index e2e54af..8be0e95 100644 --- a/example/main.py +++ b/example/main.py @@ -3,7 +3,7 @@ import time import onepasswordconnectsdk -from onepasswordconnectsdk.models import Field, GeneratorRecipe +from onepasswordconnectsdk.models import Field, GeneratorRecipe, Item op_connect_token = os.environ["OP_CONNECT_TOKEN"] default_vault = os.environ["OP_VAULT"] @@ -17,7 +17,7 @@ print(steps.steps["step1"]) # CREATE A NEW ITEM -username_item = onepasswordconnectsdk.models.Item( +username_item = Item( title="Secret String", category="LOGIN", tags=["1password-connect"], From ea8cea0ac17235f62a742f9d1be9f36535e30a15 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 19:20:43 +0100 Subject: [PATCH 053/112] Correct function name in documentation --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8c0b468..03b5a0b 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i ```python from onepasswordconnectsdk.client import ( Client, - new_client_from_env, + new_client_from_environment, ) - connect_client: Client = new_client_from_env() + connect_client: Client = new_client_from_environment() client.get_item("{item_id}", "{vault_id}") ``` @@ -52,7 +52,7 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i ```python from onepasswordconnectsdk.client import ( Client, - new_client_from_env, + new_client_from_environment, } from onepasswordconnectsdk.models import ( @@ -61,7 +61,7 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i Field ) - connect_client: Client = new_client_from_env() + connect_client: Client = new_client_from_environment() # Example item creation. Create an item with your desired arguments. item = Item( From df1942a787d2684554e257c8e9f0037ee8341694 Mon Sep 17 00:00:00 2001 From: William Park Date: Fri, 20 Oct 2023 16:16:26 -0700 Subject: [PATCH 054/112] Update example main.py to handle 'n' input --- example/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/main.py b/example/main.py index 7c4ab26..92466a9 100644 --- a/example/main.py +++ b/example/main.py @@ -48,12 +48,12 @@ print(steps.steps["confirmation"]) answer = input() -while answer != ('y' or 'n'): +while answer.lower() not in {'y', 'n'}: print(steps.steps["confirmation2"]) answer = input() # DELETE THE ITEM FROM THE VAULT -if answer == 'y': +if answer.lower() == 'y': client.delete_item(posted_item.id, default_vault) print(steps.steps["step5"]) From cca4dd575b24bcf4b824c6e6e27b53cd65af87a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Wed, 8 Mar 2023 18:00:10 +0100 Subject: [PATCH 055/112] Add return types to client functions. --- src/onepasswordconnectsdk/client.py | 43 +++++++++++++++-------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 4202ccf..bc381da 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -1,4 +1,5 @@ """Python Client for connecting to 1Password Connect""" +from typing import Dict, List, Union import httpx from httpx import HTTPError import json @@ -13,7 +14,7 @@ EnvironmentHostNotSetException, EnvironmentTokenNotSetException, ) -from onepasswordconnectsdk.models import Item, ItemVault +from onepasswordconnectsdk.models import File, Item, ItemVault, SummaryItem, Vault from onepasswordconnectsdk.models.constants import CONNECT_HOST_ENV_VARIABLE ENV_SERVICE_ACCOUNT_JWT_VARIABLE = "OP_CONNECT_TOKEN" @@ -23,23 +24,23 @@ class Client: """Python Client Class""" - def __init__(self, url: str, token: str): + def __init__(self, url: str, token: str) -> None: """Initialize client""" self.url = url self.token = token self.session = self.create_session(url, token) self.serializer = Serializer() - def create_session(self, url: str, token: str): + def create_session(self, url: str, token: str) -> httpx.Client: return httpx.Client(base_url=url, headers=self.build_headers(token)) - def build_headers(self, token: str): + def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) def __del__(self): self.session.close() - def get_file(self, file_id: str, item_id: str, vault_id: str): + def get_file(self, file_id: str, item_id: str, vault_id: str) -> File: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).build() response = self.build_request("GET", url) try: @@ -51,7 +52,7 @@ def get_file(self, file_id: str, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "File") - def get_files(self, item_id: str, vault_id: str): + def get_files(self, item_id: str, vault_id: str) -> List[File]: url = PathBuilder().vaults(vault_id).items(item_id).files().build() response = self.build_request("GET", url) try: @@ -63,7 +64,7 @@ def get_files(self, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "list[File]") - def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None): + def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> Union[bytes, str]: url = content_path if content_path is None: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() @@ -77,7 +78,7 @@ def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_pa ) return response.content - def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): + def download_file(self, file_id: str, item_id: str, vault_id: str, path: str) -> None: file_object = self.get_file(file_id, item_id, vault_id) filename = file_object.name or "1password_item_file.txt" content = self.get_file_content(file_id, item_id, vault_id, file_object.content_path) @@ -87,7 +88,7 @@ def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): file.write(content) file.close() - def get_item(self, item: str, vault: str): + def get_item(self, item: str, vault: str) -> Item: """Get a specific item Args: @@ -111,7 +112,7 @@ def get_item(self, item: str, vault: str): else: return self.get_item_by_title(item, vault_id) - def get_item_by_id(self, item_id: str, vault_id: str): + def get_item_by_id(self, item_id: str, vault_id: str) -> Item: """Get a specific item by uuid Args: @@ -136,7 +137,7 @@ def get_item_by_id(self, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "Item") - def get_item_by_title(self, title: str, vault_id: str): + def get_item_by_title(self, title: str, vault_id: str) -> Item: """Get a specific item by title Args: @@ -170,7 +171,7 @@ def get_item_by_title(self, title: str, vault_id: str): item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] return self.get_item_by_id(item_summary.id, vault_id) - def get_items(self, vault_id: str, filter_query: str = None): + def get_items(self, vault_id: str, filter_query: str = None) -> list[SummaryItem]: """Returns a list of item summaries for the specified vault Args: @@ -200,7 +201,7 @@ def get_items(self, vault_id: str, filter_query: str = None): return self.serializer.deserialize(response.content, "list[SummaryItem]") - def delete_item(self, item_id: str, vault_id: str): + def delete_item(self, item_id: str, vault_id: str) -> None: """Deletes a specified item from a specified vault Args: @@ -221,7 +222,7 @@ def delete_item(self, item_id: str, vault_id: str): for {url} with message: {response.json().get('message')}" ) - def create_item(self, vault_id: str, item: Item): + def create_item(self, vault_id: str, item: Item) -> Item: """Creates an item at the specified vault Args: @@ -247,7 +248,7 @@ def create_item(self, vault_id: str, item: Item): ) return self.serializer.deserialize(response.content, "Item") - def update_item(self, item_uuid: str, vault_id: str, item: Item): + def update_item(self, item_uuid: str, vault_id: str, item: Item) -> Item: """Update the specified item at the specified vault. Args: @@ -276,7 +277,7 @@ def update_item(self, item_uuid: str, vault_id: str, item: Item): ) return self.serializer.deserialize(response.content, "Item") - def get_vault(self, vault_id: str): + def get_vault(self, vault_id: str) -> Vault: """Returns the vault with the given vault_id Args: @@ -301,7 +302,7 @@ def get_vault(self, vault_id: str): return self.serializer.deserialize(response.content, "Vault") - def get_vault_by_title(self, name: str): + def get_vault_by_title(self, name: str) -> Vault: """Returns the vault with the given name Args: @@ -333,7 +334,7 @@ def get_vault_by_title(self, name: str): return self.serializer.deserialize(response.content, "list[Vault]")[0] - def get_vaults(self): + def get_vaults(self) -> list[Vault]: """Returns all vaults for service account set in client Raises: @@ -355,7 +356,7 @@ def get_vaults(self): return self.serializer.deserialize(response.content, "list[Vault]") - def build_request(self, method: str, path: str, body=None): + def build_request(self, method: str, path: str, body=None) -> httpx.Response: """Builds a http request Parameters: method (str): The rest method to be used @@ -380,7 +381,7 @@ def sanitize_for_serialization(self, obj): return self.serializer.sanitize_for_serialization(obj) -def new_client(url: str, token: str, is_async: bool = False): +def new_client(url: str, token: str, is_async: bool = False) -> Union[AsyncClient, Client]: """Builds a new client for interacting with 1Password Connect Parameters: url: The url of the 1Password Connect API @@ -395,7 +396,7 @@ def new_client(url: str, token: str, is_async: bool = False): return Client(url, token) -def new_client_from_environment(url: str = None): +def new_client_from_environment(url: str = None) -> Union[AsyncClient, Client]: """Builds a new client for interacting with 1Password Connect using the OP_TOKEN environment variable From 487401c9c6cafc0cb496f506da6323393687d2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Svae=20Slettvold?= Date: Sun, 8 Oct 2023 15:12:04 +0200 Subject: [PATCH 056/112] Deserialize requires string type. --- src/onepasswordconnectsdk/client.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index bc381da..51d09c2 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -50,7 +50,7 @@ def get_file(self, file_id: str, item_id: str, vault_id: str) -> File: f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "File") + return self.deserialize(response.content, "File") def get_files(self, item_id: str, vault_id: str) -> List[File]: url = PathBuilder().vaults(vault_id).items(item_id).files().build() @@ -62,7 +62,10 @@ def get_files(self, item_id: str, vault_id: str) -> List[File]: f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "list[File]") + return self.deserialize(response.content, "list[File]") + + def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> str: + url = content_path if content_path is not None else f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> Union[bytes, str]: url = content_path @@ -135,7 +138,7 @@ def get_item_by_id(self, item_id: str, vault_id: str) -> Item: f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "Item") + return self.deserialize(response.content, "Item") def get_item_by_title(self, title: str, vault_id: str) -> Item: """Get a specific item by title @@ -199,7 +202,7 @@ def get_items(self, vault_id: str, filter_query: str = None) -> list[SummaryItem for {url} with message: {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "list[SummaryItem]") + return self.deserialize(response.content, "list[SummaryItem]") def delete_item(self, item_id: str, vault_id: str) -> None: """Deletes a specified item from a specified vault @@ -246,7 +249,7 @@ def create_item(self, vault_id: str, item: Item) -> Item: f"Unable to post item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "Item") + return self.deserialize(response.content, "Item") def update_item(self, item_uuid: str, vault_id: str, item: Item) -> Item: """Update the specified item at the specified vault. @@ -275,7 +278,7 @@ def update_item(self, item_uuid: str, vault_id: str, item: Item) -> Item: f"Unable to post item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "Item") + return self.deserialize(response.content, "Item") def get_vault(self, vault_id: str) -> Vault: """Returns the vault with the given vault_id @@ -300,7 +303,7 @@ def get_vault(self, vault_id: str) -> Vault: for {url} with message {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "Vault") + return self.deserialize(response.content, "Vault") def get_vault_by_title(self, name: str) -> Vault: """Returns the vault with the given name @@ -354,7 +357,7 @@ def get_vaults(self) -> list[Vault]: for {url} with message {response.json().get('message')}" ) - return self.serializer.deserialize(response.content, "list[Vault]") + return self.deserialize(response.content, "list[Vault]") def build_request(self, method: str, path: str, body=None) -> httpx.Response: """Builds a http request From 9b7818d0ab3cfa74b63e795e37db9c31f1eae35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Svae=20Slettvold?= Date: Sun, 8 Oct 2023 16:36:36 +0200 Subject: [PATCH 057/112] Some new functions to add return types for. A few changed types. --- src/onepasswordconnectsdk/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 51d09c2..e64908c 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -37,7 +37,7 @@ def create_session(self, url: str, token: str) -> httpx.Client: def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) - def __del__(self): + def __del__(self) -> None: self.session.close() def get_file(self, file_id: str, item_id: str, vault_id: str) -> File: From c4bbcb6771168a0a2be05109ef3d2741365062b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Svae=20Slettvold?= Date: Sun, 8 Oct 2023 16:41:01 +0200 Subject: [PATCH 058/112] Clean up the file a bit, import all models from the models module. --- src/onepasswordconnectsdk/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index e64908c..7f83e94 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -117,7 +117,7 @@ def get_item(self, item: str, vault: str) -> Item: def get_item_by_id(self, item_id: str, vault_id: str) -> Item: """Get a specific item by uuid - + Args: item_id (str): The id of the item to be fetched vault_id (str): The id of the vault in which to get the item from @@ -142,7 +142,7 @@ def get_item_by_id(self, item_id: str, vault_id: str) -> Item: def get_item_by_title(self, title: str, vault_id: str) -> Item: """Get a specific item by title - + Args: title (str): The title of the item to be fetched vault_id (str): The id of the vault in which to get the item from @@ -307,10 +307,10 @@ def get_vault(self, vault_id: str) -> Vault: def get_vault_by_title(self, name: str) -> Vault: """Returns the vault with the given name - + Args: name (str): The name of the vault in which to fetch - + Raises: FailedToRetrieveVaultException: Thrown when a HTTP error is returned from the 1Password Connect API From 34bec97fa833d34b1156d2c4351808c5730b03f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Mon, 23 Oct 2023 19:53:30 +0200 Subject: [PATCH 059/112] After merge it can also return AsyncClient. Annotate init. Add bytes to get_file_content return types. --- src/onepasswordconnectsdk/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 7f83e94..97ba494 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -64,9 +64,6 @@ def get_files(self, item_id: str, vault_id: str) -> List[File]: ) return self.deserialize(response.content, "list[File]") - def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> str: - url = content_path if content_path is not None else f"/v1/vaults/{vault_id}/items/{item_id}/files/{file_id}/content" - def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> Union[bytes, str]: url = content_path if content_path is None: From 758f6fac74998bd832f9cec8c80748333aea8c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Mon, 23 Oct 2023 20:01:56 +0200 Subject: [PATCH 060/112] Add return types to AsyncClient. --- src/onepasswordconnectsdk/async_client.py | 38 +++++++++++------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 415924c..0f1cb81 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -10,29 +10,29 @@ FailedToRetrieveItemException, FailedToRetrieveVaultException, ) -from onepasswordconnectsdk.models import Item, ItemVault +from onepasswordconnectsdk.models import File, Item, ItemVault, SummaryItem, Vault class AsyncClient: """Python Async Client Class""" - def __init__(self, url: str, token: str): + def __init__(self, url: str, token: str) -> None: """Initialize async client""" self.url = url self.token = token self.session = self.create_session(url, token) self.serializer = Serializer() - def create_session(self, url: str, token: str): + def create_session(self, url: str, token: str) -> httpx.AsyncClient: return httpx.AsyncClient(base_url=url, headers=self.build_headers(token)) - def build_headers(self, token: str): + def build_headers(self, token: str) -> dict[str, str]: return build_headers(token) async def __aexit__(self): await self.session.aclose() - async def get_file(self, file_id: str, item_id: str, vault_id: str): + async def get_file(self, file_id: str, item_id: str, vault_id: str) -> File: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).build() response = await self.build_request("GET", url) try: @@ -44,7 +44,7 @@ async def get_file(self, file_id: str, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "File") - async def get_files(self, item_id: str, vault_id: str): + async def get_files(self, item_id: str, vault_id: str) -> list[File]: url = PathBuilder().vaults(vault_id).items(item_id).files().build() response = await self.build_request("GET", url) try: @@ -56,7 +56,7 @@ async def get_files(self, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "list[File]") - async def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None): + async def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> bytes | str: url = content_path if content_path is None: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() @@ -71,7 +71,7 @@ async def get_file_content(self, file_id: str, item_id: str, vault_id: str, cont return response.content async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): - file_object = await self.get_file(file_id, item_id, vault_id) + file_object = await self.get_file(file_id, item_id, vault_id) filename = file_object.name or "1password_item_file.txt" content = await self.get_file_content(file_id, item_id, vault_id, file_object.content_path) global_path = os.path.join(path, filename) @@ -80,7 +80,7 @@ async def download_file(self, file_id: str, item_id: str, vault_id: str, path: s file.write(content) file.close() - async def get_item(self, item: str, vault: str): + async def get_item(self, item: str, vault: str) -> Item: """Get a specific item Args: @@ -105,7 +105,7 @@ async def get_item(self, item: str, vault: str): else: return await self.get_item_by_title(item, vault_id) - async def get_item_by_id(self, item_id: str, vault_id: str): + async def get_item_by_id(self, item_id: str, vault_id: str) -> Item: """Get a specific item by uuid Args: @@ -130,7 +130,7 @@ async def get_item_by_id(self, item_id: str, vault_id: str): ) return self.serializer.deserialize(response.content, "Item") - async def get_item_by_title(self, title: str, vault_id: str): + async def get_item_by_title(self, title: str, vault_id: str) -> Item: """Get a specific item by title Args: @@ -164,7 +164,7 @@ async def get_item_by_title(self, title: str, vault_id: str): item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] return await self.get_item_by_id(item_summary.id, vault_id) - async def get_items(self, vault_id: str, filter_query: str = None): + async def get_items(self, vault_id: str, filter_query: str = None) -> list[SummaryItem]: """Returns a list of item summaries for the specified vault Args: @@ -192,7 +192,7 @@ async def get_items(self, vault_id: str, filter_query: str = None): return self.serializer.deserialize(response.content, "list[SummaryItem]") - async def delete_item(self, item_id: str, vault_id: str): + async def delete_item(self, item_id: str, vault_id: str) -> None: """Deletes a specified item from a specified vault Args: @@ -214,7 +214,7 @@ async def delete_item(self, item_id: str, vault_id: str): for {url} with message: {response.json().get('message')}" ) - async def create_item(self, vault_id: str, item: Item): + async def create_item(self, vault_id: str, item: Item) -> Item: """Creates an item at the specified vault Args: @@ -240,7 +240,7 @@ async def create_item(self, vault_id: str, item: Item): ) return self.serializer.deserialize(response.content, "Item") - async def update_item(self, item_uuid: str, vault_id: str, item: Item): + async def update_item(self, item_uuid: str, vault_id: str, item: Item) -> Item: """Update the specified item at the specified vault. Args: @@ -269,7 +269,7 @@ async def update_item(self, item_uuid: str, vault_id: str, item: Item): ) return self.serializer.deserialize(response.content, "Item") - async def get_vault(self, vault_id: str): + async def get_vault(self, vault_id: str) -> Vault: """Returns the vault with the given vault_id Args: @@ -294,7 +294,7 @@ async def get_vault(self, vault_id: str): return self.serializer.deserialize(response.content, "Vault") - async def get_vault_by_title(self, name: str): + async def get_vault_by_title(self, name: str) -> Vault: """Returns the vault with the given name Args: @@ -326,7 +326,7 @@ async def get_vault_by_title(self, name: str): return self.serializer.deserialize(response.content, "list[Vault]")[0] - async def get_vaults(self): + async def get_vaults(self) -> list[Vault]: """Returns all vaults for service account set in client Raises: @@ -348,7 +348,7 @@ async def get_vaults(self): return self.serializer.deserialize(response.content, "list[Vault]") - def build_request(self, method: str, path: str, body=None): + def build_request(self, method: str, path: str, body=None) -> httpx.Response: """Builds a http request Parameters: method (str): The rest method to be used From add71cb8b5ef7c8c8fe6469648f6cb6208058db4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Mon, 23 Oct 2023 22:30:45 +0200 Subject: [PATCH 061/112] Add return type none to downliad_file. --- src/onepasswordconnectsdk/async_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 0f1cb81..a44e154 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -70,8 +70,8 @@ async def get_file_content(self, file_id: str, item_id: str, vault_id: str, cont ) return response.content - async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str): - file_object = await self.get_file(file_id, item_id, vault_id) + async def download_file(self, file_id: str, item_id: str, vault_id: str, path: str) -> None: + file_object = await self.get_file(file_id, item_id, vault_id) filename = file_object.name or "1password_item_file.txt" content = await self.get_file_content(file_id, item_id, vault_id, file_object.content_path) global_path = os.path.join(path, filename) From e737fab5613c60fdf7b274a3f8d6826d4659f202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Tue, 24 Oct 2023 01:23:09 +0200 Subject: [PATCH 062/112] Use older style type annotations for lists and dicts. --- src/onepasswordconnectsdk/async_client.py | 9 +++++---- src/onepasswordconnectsdk/client.py | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index a44e154..4dd5467 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -2,6 +2,7 @@ import httpx from httpx import HTTPError import json +from typing import Dict, List import os from onepasswordconnectsdk.serializer import Serializer @@ -26,7 +27,7 @@ def __init__(self, url: str, token: str) -> None: def create_session(self, url: str, token: str) -> httpx.AsyncClient: return httpx.AsyncClient(base_url=url, headers=self.build_headers(token)) - def build_headers(self, token: str) -> dict[str, str]: + def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) async def __aexit__(self): @@ -44,7 +45,7 @@ async def get_file(self, file_id: str, item_id: str, vault_id: str) -> File: ) return self.serializer.deserialize(response.content, "File") - async def get_files(self, item_id: str, vault_id: str) -> list[File]: + async def get_files(self, item_id: str, vault_id: str) -> List[File]: url = PathBuilder().vaults(vault_id).items(item_id).files().build() response = await self.build_request("GET", url) try: @@ -164,7 +165,7 @@ async def get_item_by_title(self, title: str, vault_id: str) -> Item: item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] return await self.get_item_by_id(item_summary.id, vault_id) - async def get_items(self, vault_id: str, filter_query: str = None) -> list[SummaryItem]: + async def get_items(self, vault_id: str, filter_query: str = None) -> List[SummaryItem]: """Returns a list of item summaries for the specified vault Args: @@ -326,7 +327,7 @@ async def get_vault_by_title(self, name: str) -> Vault: return self.serializer.deserialize(response.content, "list[Vault]")[0] - async def get_vaults(self) -> list[Vault]: + async def get_vaults(self) -> List[Vault]: """Returns all vaults for service account set in client Raises: diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 97ba494..ea2cbba 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -3,6 +3,7 @@ import httpx from httpx import HTTPError import json +from typing import Dict, List import os from onepasswordconnectsdk.async_client import AsyncClient @@ -171,7 +172,7 @@ def get_item_by_title(self, title: str, vault_id: str) -> Item: item_summary = self.serializer.deserialize(response.content, "list[SummaryItem]")[0] return self.get_item_by_id(item_summary.id, vault_id) - def get_items(self, vault_id: str, filter_query: str = None) -> list[SummaryItem]: + def get_items(self, vault_id: str, filter_query: str = None) -> List[SummaryItem]: """Returns a list of item summaries for the specified vault Args: @@ -334,7 +335,7 @@ def get_vault_by_title(self, name: str) -> Vault: return self.serializer.deserialize(response.content, "list[Vault]")[0] - def get_vaults(self) -> list[Vault]: + def get_vaults(self) -> List[Vault]: """Returns all vaults for service account set in client Raises: From 15a8f05e4c2198dba8a345564175d242e8060a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Tue, 24 Oct 2023 16:38:38 +0200 Subject: [PATCH 063/112] Need to use Union for mixed return types. --- src/onepasswordconnectsdk/async_client.py | 4 ++-- src/onepasswordconnectsdk/client.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 4dd5467..7df6ad1 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -2,7 +2,7 @@ import httpx from httpx import HTTPError import json -from typing import Dict, List +from typing import Dict, List, Union import os from onepasswordconnectsdk.serializer import Serializer @@ -57,7 +57,7 @@ async def get_files(self, item_id: str, vault_id: str) -> List[File]: ) return self.serializer.deserialize(response.content, "list[File]") - async def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> bytes | str: + async def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> Union[bytes, str]: url = content_path if content_path is None: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index ea2cbba..1f83de8 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -3,7 +3,7 @@ import httpx from httpx import HTTPError import json -from typing import Dict, List +from typing import Dict, List, Union import os from onepasswordconnectsdk.async_client import AsyncClient From 094116e1ded4814c1bb98e31431f9fc56bae8ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Tue, 24 Oct 2023 19:09:59 +0200 Subject: [PATCH 064/112] An import had snuck in twice in rebase. --- src/onepasswordconnectsdk/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 1f83de8..140ccca 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -1,5 +1,4 @@ """Python Client for connecting to 1Password Connect""" -from typing import Dict, List, Union import httpx from httpx import HTTPError import json From 1390c2324ac51ff82e16a88388a4b5614c6ed989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A5vard=20Slettvold?= Date: Tue, 24 Oct 2023 23:47:52 +0200 Subject: [PATCH 065/112] Use self.serializer.deserialize instead of self.deserialize. --- src/onepasswordconnectsdk/client.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 140ccca..a62bae1 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -50,7 +50,7 @@ def get_file(self, file_id: str, item_id: str, vault_id: str) -> File: f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "File") + return self.serializer.deserialize(response.content, "File") def get_files(self, item_id: str, vault_id: str) -> List[File]: url = PathBuilder().vaults(vault_id).items(item_id).files().build() @@ -62,7 +62,7 @@ def get_files(self, item_id: str, vault_id: str) -> List[File]: f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "list[File]") + return self.serializer.deserialize(response.content, "list[File]") def get_file_content(self, file_id: str, item_id: str, vault_id: str, content_path: str = None) -> Union[bytes, str]: url = content_path @@ -135,7 +135,7 @@ def get_item_by_id(self, item_id: str, vault_id: str) -> Item: f"Unable to retrieve item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "Item") + return self.serializer.deserialize(response.content, "Item") def get_item_by_title(self, title: str, vault_id: str) -> Item: """Get a specific item by title @@ -199,7 +199,7 @@ def get_items(self, vault_id: str, filter_query: str = None) -> List[SummaryItem for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "list[SummaryItem]") + return self.serializer.deserialize(response.content, "list[SummaryItem]") def delete_item(self, item_id: str, vault_id: str) -> None: """Deletes a specified item from a specified vault @@ -246,7 +246,7 @@ def create_item(self, vault_id: str, item: Item) -> Item: f"Unable to post item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "Item") + return self.serializer.deserialize(response.content, "Item") def update_item(self, item_uuid: str, vault_id: str, item: Item) -> Item: """Update the specified item at the specified vault. @@ -275,7 +275,7 @@ def update_item(self, item_uuid: str, vault_id: str, item: Item) -> Item: f"Unable to post item. Received {response.status_code}\ for {url} with message: {response.json().get('message')}" ) - return self.deserialize(response.content, "Item") + return self.serializer.deserialize(response.content, "Item") def get_vault(self, vault_id: str) -> Vault: """Returns the vault with the given vault_id @@ -300,7 +300,7 @@ def get_vault(self, vault_id: str) -> Vault: for {url} with message {response.json().get('message')}" ) - return self.deserialize(response.content, "Vault") + return self.serializer.deserialize(response.content, "Vault") def get_vault_by_title(self, name: str) -> Vault: """Returns the vault with the given name @@ -354,7 +354,7 @@ def get_vaults(self) -> List[Vault]: for {url} with message {response.json().get('message')}" ) - return self.deserialize(response.content, "list[Vault]") + return self.serializer.deserialize(response.content, "list[Vault]") def build_request(self, method: str, path: str, body=None) -> httpx.Response: """Builds a http request From dc5e3903308ba718ba4c33f7265eea2ce5c22840 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 6 Oct 2023 14:43:37 +0100 Subject: [PATCH 066/112] Leave JSON encoding to HTTPX This ensures that the library also includes any content-type headers, and avoids a deprecation warning (`Use 'content=<...>' to upload raw bytes/text content.`) from HTTPX. --- src/onepasswordconnectsdk/async_client.py | 5 ++--- src/onepasswordconnectsdk/client.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 7df6ad1..f7e357b 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -1,7 +1,6 @@ """Python AsyncClient for connecting to 1Password Connect""" import httpx from httpx import HTTPError -import json from typing import Dict, List, Union import os @@ -361,8 +360,8 @@ def build_request(self, method: str, path: str, body=None) -> httpx.Response: """ if body: - serialized_body = json.dumps(self.serializer.sanitize_for_serialization(body)) - response = self.session.request(method, path, data=serialized_body) + sanitized_body = self.serializer.sanitize_for_serialization(body) + response = self.session.request(method, path, json=sanitized_body) else: response = self.session.request(method, path) return response diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index a62bae1..2b40327 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -368,8 +368,8 @@ def build_request(self, method: str, path: str, body=None) -> httpx.Response: """ if body: - serialized_body = json.dumps(self.serializer.sanitize_for_serialization(body)) - response = self.session.request(method, path, data=serialized_body) + sanitized_body = self.serializer.sanitize_for_serialization(body) + response = self.session.request(method, path, json=sanitized_body) else: response = self.session.request(method, path) return response From 99ae41a209372133ca868789ecce6ae3238aafc9 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 5 Jan 2024 14:10:45 -0600 Subject: [PATCH 067/112] Prepare release v1.4.1 --- CHANGELOG.md | 13 +++++++++++++ pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71981bd..99d4e7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,19 @@ --- +[//]: # (START/v1.4.1) +# v1.4.1 + +Credits to @mjpieters for the contribution! :rocket: + +## Fixes + * Undeclared dependency: six (in generated code). {#85} + * Leave JSON encoding to HTTPX. {#87} + * Correct function name in documentation. {#90} + * Update example main.py to handle 'n' input. {#91} + +--- + [//]: # "START/v1.4.0" # v1.4.0 diff --git a/pyproject.toml b/pyproject.toml index dd54dd1..e31fe73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "1.4.0" +version = "1.4.1" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From 5449bfac5dd31f38506429a73b306bee2495bed3 Mon Sep 17 00:00:00 2001 From: David Tran Date: Wed, 27 Sep 2023 21:33:20 -0400 Subject: [PATCH 068/112] Added excludeCharacters property to GeneratorRecipe class --- .../models/generator_recipe.py | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/onepasswordconnectsdk/models/generator_recipe.py b/src/onepasswordconnectsdk/models/generator_recipe.py index d156a6e..59e6431 100644 --- a/src/onepasswordconnectsdk/models/generator_recipe.py +++ b/src/onepasswordconnectsdk/models/generator_recipe.py @@ -35,25 +35,30 @@ class GeneratorRecipe(object): """ openapi_types = { 'length': 'int', - 'character_sets': 'list[str]' + 'character_sets': 'list[str]', + 'exclude_characters': 'str' } attribute_map = { 'length': 'length', - 'character_sets': 'characterSets' + 'character_sets': 'characterSets', + 'exclude_characters': 'excludeCharacters' } - def __init__(self, length=32, character_sets=None, local_vars_configuration=None): # noqa: E501 + def __init__(self, length=32, character_sets=None, exclude_characters=None, local_vars_configuration=None): # noqa: E501 """GeneratorRecipe - a model defined in OpenAPI""" # noqa: E501 self._length = None self._character_sets = None + self._exclude_characters = None self.discriminator = None if length is not None: self.length = length if character_sets is not None: self.character_sets = character_sets + if exclude_characters is not None: + self.exclude_characters = exclude_characters @property def length(self): @@ -110,6 +115,33 @@ def character_sets(self, character_sets): self._character_sets = character_sets + @property + def exclude_characters(self): + """Gets the exclude_characters of this GeneratorRecipe. + + + :return: The exclude_characters of this GeneratorRecipe. + :rtype: str + """ + return self._exclude_characters + + @exclude_characters.setter + def exclude_characters(self, exclude_characters): + """Sets the exclude_characters of this GeneratorRecipe. + + + :param exclude_characters: The exclude_characters of this GeneratorRecipe. + :type character_sets: str + """ + duplicates = self.find_duplicates(exclude_characters) + if duplicates: + raise ValueError( + "Invalid values for `exclude_characters` {0}, must not contain duplicate characters" + .format(", ".join(map(str, duplicates))) + ) + + self._exclude_characters = exclude_characters + def to_dict(self, serialize=False): """Returns the model properties as a dict""" result = {} @@ -163,3 +195,16 @@ def __ne__(self, other): return True return self.to_dict() != other.to_dict() + + def find_duplicates(self, s): + char_count = {} # Dictionary to store count of each character + duplicates = set() # Set to store duplicate characters + + for char in s: + char_count[char] = char_count.get(char, 0) + 1 # Increment count of char + + for char, count in char_count.items(): + if count > 1: + duplicates.add(char) # If count is more than 1, it's a duplicate + + return duplicates \ No newline at end of file From 5062263a05cce6c4f31a97722a7c200cda6c2ea0 Mon Sep 17 00:00:00 2001 From: Raphael Passini Date: Thu, 1 Feb 2024 19:31:21 -0300 Subject: [PATCH 069/112] [Feat] Allow custom timeout using env vars --- README.md | 6 ++++++ src/onepasswordconnectsdk/async_client.py | 15 +++++++++++++-- src/onepasswordconnectsdk/client.py | 6 +++--- src/tests/test_client_items.py | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 03b5a0b..73dc830 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,12 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i export OP_CONNECT_HOST= && \ export OP_CONNECT_TOKEN= ``` + + 2.1 If you need a higher timeout on the client requests you can export `OP_CLIENT_REQ_TIMEOUT` environment variable: + ```sh + # set the timeout to 90 seconds + export OP_CLIENT_REQ_TIMEOUT=90 + ``` 3. Use the SDK: diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index f7e357b..cda36f5 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -1,9 +1,11 @@ """Python AsyncClient for connecting to 1Password Connect""" import httpx -from httpx import HTTPError +from httpx import HTTPError, USE_CLIENT_DEFAULT from typing import Dict, List, Union import os +from httpx._client import UseClientDefault + from onepasswordconnectsdk.serializer import Serializer from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder from onepasswordconnectsdk.errors import ( @@ -13,6 +15,14 @@ from onepasswordconnectsdk.models import File, Item, ItemVault, SummaryItem, Vault +ENV_CLIENT_REQ_TIMEOUT = "OP_CONNECT_CLIENT_REQ_TIMEOUT" + + +def get_timeout() -> Union[int, UseClientDefault]: + timeout = int(os.getenv(ENV_CLIENT_REQ_TIMEOUT, 0)) + return timeout if timeout else USE_CLIENT_DEFAULT + + class AsyncClient: """Python Async Client Class""" @@ -24,7 +34,8 @@ def __init__(self, url: str, token: str) -> None: self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.AsyncClient: - return httpx.AsyncClient(base_url=url, headers=self.build_headers(token)) + # import here to avoid circular import + return httpx.AsyncClient(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 2b40327..98218b1 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -1,11 +1,11 @@ """Python Client for connecting to 1Password Connect""" import httpx -from httpx import HTTPError +from httpx import HTTPError, USE_CLIENT_DEFAULT import json from typing import Dict, List, Union import os -from onepasswordconnectsdk.async_client import AsyncClient +from onepasswordconnectsdk.async_client import AsyncClient, get_timeout from onepasswordconnectsdk.serializer import Serializer from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder from onepasswordconnectsdk.errors import ( @@ -32,7 +32,7 @@ def __init__(self, url: str, token: str) -> None: self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.Client: - return httpx.Client(base_url=url, headers=self.build_headers(token)) + return httpx.Client(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index 1f5f74d..ef034ca 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -1,6 +1,10 @@ +import os import pytest +from unittest import mock + from httpx import Response from onepasswordconnectsdk import client, models +from onepasswordconnectsdk.async_client import ENV_CLIENT_REQ_TIMEOUT VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" VAULT_TITLE = "VaultA" @@ -440,3 +444,16 @@ def generate_full_item(): id="Section_47DC4DDBF26640AB8B8618DA36D5A499"))], sections=[models.Section(id="id", label="label")]) return item + + +def test_set_timeout_using_env_variable(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQ_TIMEOUT: '120'}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == 120 + + +@pytest.mark.asyncio +def test_set_timeout_using_env_variable_async(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQ_TIMEOUT: '120'}): + client_instance = client.new_client(HOST, TOKEN, is_async=True) + assert client_instance.session.timeout.read == 120 \ No newline at end of file From 00eb30f9ad709b6e4627c1d584b6de861545a1af Mon Sep 17 00:00:00 2001 From: Kyle Wilson Date: Mon, 4 Mar 2024 20:07:59 -0800 Subject: [PATCH 070/112] Fixed example for onepasswordconnectsdk.load_dict() The example for onepasswordconnectsdk.load_dict() in USAGE.md was inaccurate and would result in errors. This has now been corrected and will now actually work. --- USAGE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/USAGE.md b/USAGE.md index 902b71b..214952e 100644 --- a/USAGE.md +++ b/USAGE.md @@ -120,14 +120,17 @@ CONFIG = { "database": { "opitem": "My database item", "opfield": ".database", + "opvault": "some_vault_id", }, "username": { "opitem": "My database item", "opfield": ".username", + "opvault": "some_vault_id", }, "password": { "opitem": "My database item", "opfield": ".password", + "opvault": "some_vault_id", }, } From 889a65848cca34cb18e55091e749f47a7945bffd Mon Sep 17 00:00:00 2001 From: Raphael Passini Date: Thu, 28 Mar 2024 14:45:54 -0300 Subject: [PATCH 071/112] [Feat] Rename OP_CLIENT_REQ_TIMEOUT and move get_timeout to utils package --- README.md | 2 +- src/onepasswordconnectsdk/async_client.py | 14 ++------------ src/onepasswordconnectsdk/client.py | 4 ++-- src/onepasswordconnectsdk/utils.py | 13 +++++++++++++ src/tests/test_client_items.py | 6 +++--- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 73dc830..1ef7540 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i 2.1 If you need a higher timeout on the client requests you can export `OP_CLIENT_REQ_TIMEOUT` environment variable: ```sh # set the timeout to 90 seconds - export OP_CLIENT_REQ_TIMEOUT=90 + export OP_CLIENT_REQUEST_TIMEOUT=90 ``` 3. Use the SDK: diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index cda36f5..23c1bfe 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -1,13 +1,11 @@ """Python AsyncClient for connecting to 1Password Connect""" import httpx -from httpx import HTTPError, USE_CLIENT_DEFAULT +from httpx import HTTPError from typing import Dict, List, Union import os -from httpx._client import UseClientDefault - from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, @@ -15,14 +13,6 @@ from onepasswordconnectsdk.models import File, Item, ItemVault, SummaryItem, Vault -ENV_CLIENT_REQ_TIMEOUT = "OP_CONNECT_CLIENT_REQ_TIMEOUT" - - -def get_timeout() -> Union[int, UseClientDefault]: - timeout = int(os.getenv(ENV_CLIENT_REQ_TIMEOUT, 0)) - return timeout if timeout else USE_CLIENT_DEFAULT - - class AsyncClient: """Python Async Client Class""" diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 98218b1..a6d5060 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -5,9 +5,9 @@ from typing import Dict, List, Union import os -from onepasswordconnectsdk.async_client import AsyncClient, get_timeout +from onepasswordconnectsdk.async_client import AsyncClient from onepasswordconnectsdk.serializer import Serializer -from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder +from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, FailedToRetrieveVaultException, diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index da35d50..6699e42 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -1,4 +1,11 @@ +import os +from typing import Union + +from httpx import USE_CLIENT_DEFAULT +from httpx._client import UseClientDefault + UUIDLength = 26 +ENV_CLIENT_REQUEST_TIMEOUT = "OP_CONNECT_CLIENT_REQ_TIMEOUT" def is_valid_uuid(uuid): @@ -59,3 +66,9 @@ def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilde self.path += f"/{path_chunk}" if query is not None: self.path += f"?{query}" + + +def get_timeout() -> Union[int, UseClientDefault]: + """Get the timeout to be used in the HTTP Client""" + timeout = int(os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0)) + return timeout if timeout else USE_CLIENT_DEFAULT diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index ef034ca..d9e23c9 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -4,7 +4,7 @@ from httpx import Response from onepasswordconnectsdk import client, models -from onepasswordconnectsdk.async_client import ENV_CLIENT_REQ_TIMEOUT +from onepasswordconnectsdk.utils import ENV_CLIENT_REQUEST_TIMEOUT VAULT_ID = "hfnjvi6aymbsnfc2xeeoheizda" VAULT_TITLE = "VaultA" @@ -447,13 +447,13 @@ def generate_full_item(): def test_set_timeout_using_env_variable(): - with mock.patch.dict(os.environ, {ENV_CLIENT_REQ_TIMEOUT: '120'}): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): client_instance = client.new_client(HOST, TOKEN) assert client_instance.session.timeout.read == 120 @pytest.mark.asyncio def test_set_timeout_using_env_variable_async(): - with mock.patch.dict(os.environ, {ENV_CLIENT_REQ_TIMEOUT: '120'}): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): client_instance = client.new_client(HOST, TOKEN, is_async=True) assert client_instance.session.timeout.read == 120 \ No newline at end of file From d01e5358950389bd645d2a5cc2d2f55d80e682dd Mon Sep 17 00:00:00 2001 From: Raphael Passini Date: Thu, 28 Mar 2024 14:47:30 -0300 Subject: [PATCH 072/112] [Fix] Minor mistake in README file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ef7540..df598e9 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i export OP_CONNECT_TOKEN= ``` - 2.1 If you need a higher timeout on the client requests you can export `OP_CLIENT_REQ_TIMEOUT` environment variable: + 2.1 If you need a higher timeout on the client requests you can export `OP_CLIENT_REQUEST_TIMEOUT` environment variable: ```sh # set the timeout to 90 seconds export OP_CLIENT_REQUEST_TIMEOUT=90 From a3e066fcddecd3fc275ebef97db452a8fb3c62f5 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 2 Apr 2024 10:51:47 -0500 Subject: [PATCH 073/112] Prepare release v1.5.0 --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d4e7e..d93f2c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ --- +[//]: # (START/v1.5.0) +# v1.5.0 + +## Features + * Allow custom timeout using env vars. {#94} + +--- + [//]: # (START/v1.4.1) # v1.4.1 diff --git a/pyproject.toml b/pyproject.toml index e31fe73..c56ba73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "1.4.1" +version = "1.5.0" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From 176eea3b0654b2dfcd7ffa61a49cf708aa432c4d Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 2 Apr 2024 21:36:35 -0500 Subject: [PATCH 074/112] Don't throw error when OP_CONNECT_CLIENT_REQ_TIMEOUT is not set --- src/onepasswordconnectsdk/async_client.py | 1 - src/onepasswordconnectsdk/utils.py | 10 ++++------ src/tests/test_client_items.py | 8 +++++++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 23c1bfe..2adf379 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -24,7 +24,6 @@ def __init__(self, url: str, token: str) -> None: self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.AsyncClient: - # import here to avoid circular import return httpx.AsyncClient(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) def build_headers(self, token: str) -> Dict[str, str]: diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index 6699e42..4ce4a26 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -1,8 +1,6 @@ import os -from typing import Union -from httpx import USE_CLIENT_DEFAULT -from httpx._client import UseClientDefault +from httpx._client import DEFAULT_TIMEOUT_CONFIG, Timeout UUIDLength = 26 ENV_CLIENT_REQUEST_TIMEOUT = "OP_CONNECT_CLIENT_REQ_TIMEOUT" @@ -68,7 +66,7 @@ def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilde self.path += f"?{query}" -def get_timeout() -> Union[int, UseClientDefault]: +def get_timeout() -> Timeout: """Get the timeout to be used in the HTTP Client""" - timeout = int(os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0)) - return timeout if timeout else USE_CLIENT_DEFAULT + timeout = float(os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0)) + return timeout if timeout else DEFAULT_TIMEOUT_CONFIG diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index d9e23c9..b9b1ca4 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -3,6 +3,7 @@ from unittest import mock from httpx import Response +from httpx._client import DEFAULT_TIMEOUT_CONFIG from onepasswordconnectsdk import client, models from onepasswordconnectsdk.utils import ENV_CLIENT_REQUEST_TIMEOUT @@ -446,6 +447,11 @@ def generate_full_item(): return item +def test_default_timeout(): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read + + def test_set_timeout_using_env_variable(): with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): client_instance = client.new_client(HOST, TOKEN) @@ -456,4 +462,4 @@ def test_set_timeout_using_env_variable(): def test_set_timeout_using_env_variable_async(): with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): client_instance = client.new_client(HOST, TOKEN, is_async=True) - assert client_instance.session.timeout.read == 120 \ No newline at end of file + assert client_instance.session.timeout.read == 120 From dad43fb8a639c78e97c031825ac97652bca23135 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 2 Apr 2024 22:00:09 -0500 Subject: [PATCH 075/112] Cover the case when timeout is None of empty string --- src/onepasswordconnectsdk/utils.py | 11 +++++++++-- src/tests/test_client_items.py | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index 4ce4a26..1de4678 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -68,5 +68,12 @@ def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilde def get_timeout() -> Timeout: """Get the timeout to be used in the HTTP Client""" - timeout = float(os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0)) - return timeout if timeout else DEFAULT_TIMEOUT_CONFIG + raw_timeout = os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0.0) + if raw_timeout == 'None': + return Timeout(None) # disable all timeouts + elif raw_timeout == '': + return DEFAULT_TIMEOUT_CONFIG + else: + timeout = float(raw_timeout) + t = timeout if timeout else DEFAULT_TIMEOUT_CONFIG + return t diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index b9b1ca4..6767c03 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -463,3 +463,21 @@ def test_set_timeout_using_env_variable_async(): with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '120'}): client_instance = client.new_client(HOST, TOKEN, is_async=True) assert client_instance.session.timeout.read == 120 + + +def test_disable_all_timeouts(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: 'None'}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read is None + + +def test_env_client_request_timeout_env_var_is_empty_string(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: ''}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read + + +def test_env_client_request_timeout_env_var_is_zero(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '0'}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read From 074c2391dd4ca5a7ece95c07c2effb88f94a1748 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 2 Apr 2024 22:04:40 -0500 Subject: [PATCH 076/112] Inline the return statement --- src/onepasswordconnectsdk/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index 1de4678..6c120b1 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -75,5 +75,4 @@ def get_timeout() -> Timeout: return DEFAULT_TIMEOUT_CONFIG else: timeout = float(raw_timeout) - t = timeout if timeout else DEFAULT_TIMEOUT_CONFIG - return t + return timeout if timeout else DEFAULT_TIMEOUT_CONFIG From 32366611468d6b048aa3be1fe50e7be343aa8883 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Wed, 3 Apr 2024 08:11:21 -0500 Subject: [PATCH 077/112] Improve get_timeout function to cover all non numeric strings --- src/onepasswordconnectsdk/utils.py | 8 ++++---- src/tests/test_client_items.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index 6c120b1..b287477 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -68,11 +68,11 @@ def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilde def get_timeout() -> Timeout: """Get the timeout to be used in the HTTP Client""" - raw_timeout = os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, 0.0) + raw_timeout = os.getenv(ENV_CLIENT_REQUEST_TIMEOUT, '0.0') if raw_timeout == 'None': return Timeout(None) # disable all timeouts - elif raw_timeout == '': - return DEFAULT_TIMEOUT_CONFIG - else: + elif raw_timeout.isnumeric(): timeout = float(raw_timeout) return timeout if timeout else DEFAULT_TIMEOUT_CONFIG + else: + return DEFAULT_TIMEOUT_CONFIG diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index 6767c03..9e471c4 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -477,6 +477,18 @@ def test_env_client_request_timeout_env_var_is_empty_string(): assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read +def test_env_client_request_timeout_env_var_is_single_space_string(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: ' '}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read + + +def test_env_client_request_timeout_env_var_is_not_numeric_string(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: 'abc'}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read + + def test_env_client_request_timeout_env_var_is_zero(): with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '0'}): client_instance = client.new_client(HOST, TOKEN) From 4bcf4d79089484c7dbc034123be24612c7dbe557 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Wed, 3 Apr 2024 10:12:12 -0500 Subject: [PATCH 078/112] Add additional unit test to check that it uses default timeout if env var has negative number --- src/tests/test_client_items.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index 9e471c4..b670f7d 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -493,3 +493,9 @@ def test_env_client_request_timeout_env_var_is_zero(): with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '0'}): client_instance = client.new_client(HOST, TOKEN) assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read + + +def test_env_client_request_timeout_env_var_is_negative_number(): + with mock.patch.dict(os.environ, {ENV_CLIENT_REQUEST_TIMEOUT: '-10'}): + client_instance = client.new_client(HOST, TOKEN) + assert client_instance.session.timeout.read == DEFAULT_TIMEOUT_CONFIG.read From 944036debd3392cff9b5ccae8193cc9cf3ee0e3b Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Wed, 3 Apr 2024 11:35:55 -0500 Subject: [PATCH 079/112] Fix the env var name to override default http client timeouts --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df598e9..3180272 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i export OP_CONNECT_TOKEN= ``` - 2.1 If you need a higher timeout on the client requests you can export `OP_CLIENT_REQUEST_TIMEOUT` environment variable: + 2.1 If you need a higher timeout on the client requests you can export `OP_CONNECT_CLIENT_REQ_TIMEOUT` environment variable: ```sh # set the timeout to 90 seconds - export OP_CLIENT_REQUEST_TIMEOUT=90 + export OP_CONNECT_CLIENT_REQ_TIMEOUT=90 ``` 3. Use the SDK: From 58f42e364ecfc8f6a869194aa997cba83b90e698 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Wed, 3 Apr 2024 11:45:39 -0500 Subject: [PATCH 080/112] Prepare release v1.5.1 --- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d93f2c7..34e9a51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ --- +[//]: # (START/v1.5.1) +# v1.5.1 + +## Fixes + * Fix default http client timeout. {#102} + * Update override http client timeout env var name in readme. {#105} + +--- + [//]: # (START/v1.5.0) # v1.5.0 diff --git a/pyproject.toml b/pyproject.toml index c56ba73..45ee811 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "1.5.0" +version = "1.5.1" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From b7531412d488cf95dee4f28256cfc0804d6bdef4 Mon Sep 17 00:00:00 2001 From: Eduard Filip Date: Tue, 28 May 2024 19:32:43 +0200 Subject: [PATCH 081/112] Run 1Password/check-signed-commits-action for PRs (#110) Add the 1Password/check-signed-commits-action that will leave a handy comment if a PR contains commits that are not signed. --- .github/workflows/pr-check-signed-commits.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/workflows/pr-check-signed-commits.yml diff --git a/.github/workflows/pr-check-signed-commits.yml b/.github/workflows/pr-check-signed-commits.yml new file mode 100644 index 0000000..77a8b8a --- /dev/null +++ b/.github/workflows/pr-check-signed-commits.yml @@ -0,0 +1,13 @@ +name: Check signed commits in PR +on: pull_request_target + +jobs: + build: + name: Check signed commits in PR + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Check signed commits in PR + uses: 1Password/check-signed-commits-action@v1 From 06bc1752e18d3814f36b3061ff429b9786cf2a31 Mon Sep 17 00:00:00 2001 From: Eduard Filip Date: Thu, 12 Dec 2024 18:14:52 +0100 Subject: [PATCH 082/112] Update bug bounty process (#113) --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3180272..8188107 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,9 @@ Check the [Python Connect SDK Example](example/README.md) to see an example of i export OP_CONNECT_HOST= && \ export OP_CONNECT_TOKEN= ``` - + 2.1 If you need a higher timeout on the client requests you can export `OP_CONNECT_CLIENT_REQ_TIMEOUT` environment variable: + ```sh # set the timeout to 90 seconds export OP_CONNECT_CLIENT_REQ_TIMEOUT=90 @@ -94,6 +95,4 @@ For more examples of how to use the SDK, check out [USAGE.md](USAGE.md). 1Password requests you practice responsible disclosure if you discover a vulnerability. -Please file requests via [**BugCrowd**](https://bugcrowd.com/agilebits). - -For information about security practices, please visit the [1Password Bug Bounty Program](https://bugcrowd.com/agilebits). +Please file requests by sending an email to bugbounty@agilebits.com. From 8871fe098258e696690b3cdd0213e0b5495bc4de Mon Sep 17 00:00:00 2001 From: Michael Abon <74213556+michaelAbon1p@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:17:59 -0600 Subject: [PATCH 083/112] Bump minimum supported Python version from 3.8 to 3.9 (#116) * Bump minimum supported Python version from 3.8 to 3.9 Fixes #115 * Remove codecov from workflow Currently no PRs can be merged since the codecov step doesn't work anymore. Fixing that is a lengthy process, so to temporarily unblock important PRs from being merged (i.e. #116, #114, #107), we will remove this from our pipeline. --------- Co-authored-by: Eddy Filip --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 14 ++---- poetry.lock | 93 +++-------------------------------- pyproject.toml | 2 +- 4 files changed, 13 insertions(+), 98 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e7d38f..6068729 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a396fb..31adb07 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,9 @@ name: Tests permissions: contents: read -on: - push: - branches: main +on: + push: + branches: main pull_request: jobs: @@ -14,7 +14,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v1 with: - python-version: 3.8 + python-version: 3.9 - name: Check out code uses: actions/checkout@v2 - name: Install dependencies @@ -25,9 +25,3 @@ jobs: - name: Test run: | poetry run pytest src/tests --cov=./ --cov-report=xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - fail_ci_if_error: true - files: ./coverage.xml - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/poetry.lock b/poetry.lock index 6ccac8c..6eca51b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "anyio" version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -16,7 +15,6 @@ files = [ exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] @@ -27,7 +25,6 @@ trio = ["trio (<0.22)"] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -39,7 +36,6 @@ files = [ name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -51,7 +47,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -127,7 +122,6 @@ toml = ["tomli"] name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -142,7 +136,6 @@ test = ["pytest (>=6)"] name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -150,14 +143,10 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] -[package.dependencies] -typing-extensions = {version = "*", markers = "python_version < \"3.8\""} - [[package]] name = "httpcore" version = "0.16.3" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -169,17 +158,16 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httpx" version = "0.23.3" description = "The next generation HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -195,15 +183,14 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -211,32 +198,10 @@ files = [ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, ] -[[package]] -name = "importlib-metadata" -version = "6.7.0" -description = "Read metadata from Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, - {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, -] - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -248,7 +213,6 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -260,7 +224,6 @@ files = [ name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -268,9 +231,6 @@ files = [ {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, ] -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] @@ -279,7 +239,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -290,7 +249,6 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" @@ -303,7 +261,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.20.3" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -313,7 +270,6 @@ files = [ [package.dependencies] pytest = ">=6.1.0" -typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""} [package.extras] docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] @@ -323,7 +279,6 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -342,7 +297,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -357,7 +311,6 @@ six = ">=1.5" name = "respx" version = "0.20.2" description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -372,7 +325,6 @@ httpx = ">=0.21.0" name = "rfc3986" version = "1.5.0" description = "Validating URI References per RFC 3986" -category = "main" optional = false python-versions = "*" files = [ @@ -390,7 +342,6 @@ idna2008 = ["idna"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -402,7 +353,6 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -414,7 +364,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -422,35 +371,7 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, -] - -[[package]] -name = "zipp" -version = "3.15.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, - {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - [metadata] lock-version = "2.0" -python-versions = "^3.7" -content-hash = "5c06df5db167647617c8fb7afc9da451dcec9d10e24df37e6024124cccd6da60" +python-versions = "^3.9" +content-hash = "c0f45ea02e27727437bb9e8bb12d3b1c3edd127b2013ec5a9887b6107e76f5b2" diff --git a/pyproject.toml b/pyproject.toml index 45ee811..f7c160b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ repository = "https://github.com/1Password/connect-sdk-python" "Report Security Issue" = "https://bugcrowd.com/agilebits" [tool.poetry.dependencies] -python = "^3.7" +python = "^3.9" python-dateutil = "^2.8.1" httpx = "^0.23.3" From 1f7c400063b847ce59f166e03165a2f6f794fdb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:28:28 +0100 Subject: [PATCH 084/112] Bump certifi from 2023.7.22 to 2024.7.4 (#114) Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.7.22 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2023.07.22...2024.07.04) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6eca51b..39e0597 100644 --- a/poetry.lock +++ b/poetry.lock @@ -23,13 +23,13 @@ trio = ["trio (<0.22)"] [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, ] [[package]] From 0bb9f60cbb3344ee9df45ce9a4b79f1cd29dc2b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:02:01 +0100 Subject: [PATCH 085/112] Bump idna from 3.4 to 3.7 (#107) Bumps [idna](https://github.com/kjd/idna) from 3.4 to 3.7. - [Release notes](https://github.com/kjd/idna/releases) - [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst) - [Commits](https://github.com/kjd/idna/compare/v3.4...v3.7) --- updated-dependencies: - dependency-name: idna dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Eddy Filip --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 39e0597..d9afdf8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -189,13 +189,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "idna" -version = "3.4" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] From 31d0e13e6fc0f6a678a42fc61180869e6c0f663e Mon Sep 17 00:00:00 2001 From: mattjacksoncello <156972821+mattjacksoncello@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:10:09 +1300 Subject: [PATCH 086/112] Add configclass --- USAGE.md | 66 ++++++++++++++++++++++- src/onepasswordconnectsdk/async_client.py | 23 ++++++-- src/onepasswordconnectsdk/client.py | 43 ++++++++++----- src/onepasswordconnectsdk/config.py | 41 +++++++++++++- src/tests/test_client_config.py | 44 +++++++++++++++ 5 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 src/tests/test_client_config.py diff --git a/USAGE.md b/USAGE.md index 902b71b..f0afc34 100644 --- a/USAGE.md +++ b/USAGE.md @@ -29,6 +29,70 @@ connect_async_client: Client = new_client( True) ``` +## Client Configuration + +The SDK provides a `ClientConfig` class that allows you to configure the underlying httpx client. This includes SSL certificate verification and all other httpx client options. + +### SSL Certificate Verification + +When connecting to a 1Password Connect server using HTTPS, you may need to configure SSL certificate verification: + +```python +from onepasswordconnectsdk.config import ClientConfig + +# Verify SSL using a custom CA certificate +config = ClientConfig(cafile="path/to/ca.pem") +client = new_client("https://connect.example.com", "your-token", config=config) + +# Disable SSL verification (not recommended for production) +config = ClientConfig(verify=False) +client = new_client("https://connect.example.com", "your-token", config=config) +``` + +### Additional Configuration Options + +The ClientConfig class accepts all httpx client options as keyword arguments. These options are passed directly to the underlying httpx client: + +```python +# Configure timeouts and redirects +config = ClientConfig( + cafile="path/to/ca.pem", + timeout=30.0, # 30 second timeout + follow_redirects=True, # Follow HTTP redirects + max_redirects=5 # Maximum number of redirects to follow +) + +# Configure proxy settings +config = ClientConfig( + proxies={ + "http://": "http://proxy.example.com", + "https://": "https://proxy.example.com" + } +) + +# Configure custom headers +config = ClientConfig( + headers={ + "User-Agent": "CustomApp/1.0", + "X-Custom-Header": "value" + } +) +``` + +### Async Client Configuration + +The same configuration options work for both synchronous and asynchronous clients: + +```python +config = ClientConfig( + cafile="path/to/ca.pem", + timeout=30.0 +) +async_client = new_client("https://connect.example.com", "your-token", is_async=True, config=config) +``` + +For a complete list of available configuration options, see the [httpx client documentation](https://www.python-httpx.org/api/#client). + ## Environment Variables - **OP_CONNECT_TOKEN** – The token to be used to authenticate with the 1Password Connect API. @@ -166,4 +230,4 @@ async def main(): await async_client.session.aclose() # close the client gracefully when you are done asyncio.run(main()) -``` \ No newline at end of file +``` diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 2adf379..802adda 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -1,10 +1,11 @@ """Python AsyncClient for connecting to 1Password Connect""" import httpx from httpx import HTTPError -from typing import Dict, List, Union +from typing import Dict, List, Union, Optional import os from onepasswordconnectsdk.serializer import Serializer +from onepasswordconnectsdk.config import ClientConfig from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout from onepasswordconnectsdk.errors import ( FailedToRetrieveItemException, @@ -16,15 +17,29 @@ class AsyncClient: """Python Async Client Class""" - def __init__(self, url: str, token: str) -> None: - """Initialize async client""" + def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None: + """Initialize async client + + Args: + url (str): The url of the 1Password Connect API + token (str): The 1Password Service Account token + config (Optional[ClientConfig]): Optional configuration for httpx client + """ self.url = url self.token = token + self.config = config self.session = self.create_session(url, token) self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.AsyncClient: - return httpx.AsyncClient(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) + headers = self.build_headers(token) + timeout = get_timeout() + + if self.config: + client_args = self.config.get_client_args(url, headers, timeout) + return httpx.AsyncClient(**client_args) + + return httpx.AsyncClient(base_url=url, headers=headers, timeout=timeout) def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index a6d5060..0d264b6 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -2,10 +2,11 @@ import httpx from httpx import HTTPError, USE_CLIENT_DEFAULT import json -from typing import Dict, List, Union +from typing import Dict, List, Union, Optional import os from onepasswordconnectsdk.async_client import AsyncClient +from onepasswordconnectsdk.config import ClientConfig from onepasswordconnectsdk.serializer import Serializer from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout from onepasswordconnectsdk.errors import ( @@ -24,15 +25,29 @@ class Client: """Python Client Class""" - def __init__(self, url: str, token: str) -> None: - """Initialize client""" + def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None: + """Initialize client + + Args: + url (str): The url of the 1Password Connect API + token (str): The 1Password Service Account token + config (Optional[ClientConfig]): Optional configuration for httpx client + """ self.url = url self.token = token + self.config = config self.session = self.create_session(url, token) self.serializer = Serializer() def create_session(self, url: str, token: str) -> httpx.Client: - return httpx.Client(base_url=url, headers=self.build_headers(token), timeout=get_timeout()) + headers = self.build_headers(token) + timeout = get_timeout() + + if self.config: + client_args = self.config.get_client_args(url, headers, timeout) + return httpx.Client(**client_args) + + return httpx.Client(base_url=url, headers=headers, timeout=timeout) def build_headers(self, token: str) -> Dict[str, str]: return build_headers(token) @@ -381,19 +396,21 @@ def sanitize_for_serialization(self, obj): return self.serializer.sanitize_for_serialization(obj) -def new_client(url: str, token: str, is_async: bool = False) -> Union[AsyncClient, Client]: +def new_client(url: str, token: str, is_async: bool = False, config: Optional[ClientConfig] = None) -> Union[AsyncClient, Client]: """Builds a new client for interacting with 1Password Connect - Parameters: - url: The url of the 1Password Connect API - token: The 1Password Service Account token - is_async: Initialize async or sync client - + + Args: + url (str): The url of the 1Password Connect API + token (str): The 1Password Service Account token + is_async (bool): Initialize async or sync client + config (Optional[ClientConfig]): Optional configuration for httpx client + Returns: - Client: The 1Password Connect client + Union[AsyncClient, Client]: The 1Password Connect client """ if is_async: - return AsyncClient(url, token) - return Client(url, token) + return AsyncClient(url, token, config) + return Client(url, token, config) def new_client_from_environment(url: str = None) -> Union[AsyncClient, Client]: diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 4670b97..8a8d8da 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -1,6 +1,7 @@ import os import shlex -from typing import List, Dict +from typing import List, Dict, Optional +import httpx from onepasswordconnectsdk.client import Client from onepasswordconnectsdk.models import ( Item, @@ -16,6 +17,44 @@ ) +class ClientConfig: + """Configuration class for 1Password Connect client. + Inherits from httpx.BaseClient to support all httpx client options. + """ + def __init__(self, cafile: Optional[str] = None, **kwargs): + """Initialize client configuration + + Args: + cafile (Optional[str]): Path to CA certificate file for SSL verification + **kwargs: Additional httpx client options + """ + self.cafile = cafile + self.httpx_options = kwargs + + def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float) -> Dict: + """Get arguments for httpx client initialization + + Args: + base_url (str): Base URL for the client + headers (Dict[str, str]): Headers to include in requests + timeout (float): Request timeout in seconds + + Returns: + Dict: Arguments for httpx client initialization + """ + args = { + 'base_url': base_url, + 'headers': headers, + 'timeout': timeout, + **self.httpx_options + } + + if self.cafile: + args['verify'] = self.cafile + + return args + + def load_dict(client: Client, config: dict): """Load: Takes a dictionary with keys specifiying the user desired naming scheme of the values to return. Each key's diff --git a/src/tests/test_client_config.py b/src/tests/test_client_config.py new file mode 100644 index 0000000..21e6034 --- /dev/null +++ b/src/tests/test_client_config.py @@ -0,0 +1,44 @@ +import pytest +from onepasswordconnectsdk.config import ClientConfig +import httpx + +def test_client_config_with_cafile(): + config = ClientConfig(cafile="path/to/ca.pem") + args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) + + assert args["verify"] == "path/to/ca.pem" + assert args["base_url"] == "https://test.com" + assert args["headers"] == {"Authorization": "Bearer token"} + assert args["timeout"] == 30.0 + +def test_client_config_with_kwargs(): + config = ClientConfig( + cafile="path/to/ca.pem", + follow_redirects=True, + timeout=60.0 + ) + args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) + + assert args["verify"] == "path/to/ca.pem" + assert args["follow_redirects"] == True + # kwargs should override default timeout + assert args["timeout"] == 60.0 + +def test_client_config_verify_override(): + # When verify is explicitly set in kwargs, it should override cafile + config = ClientConfig( + cafile="path/to/ca.pem", + verify=False + ) + args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) + + assert args["verify"] == False + +def test_client_config_no_cafile(): + config = ClientConfig() + args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) + + assert "verify" not in args + assert args["base_url"] == "https://test.com" + assert args["headers"] == {"Authorization": "Bearer token"} + assert args["timeout"] == 30.0 From 426b901c8af33133b16bf9d4c8345c7b97e1f540 Mon Sep 17 00:00:00 2001 From: mattjacksoncello <156972821+mattjacksoncello@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:43:01 +1300 Subject: [PATCH 087/112] fix cyclical imports --- src/onepasswordconnectsdk/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 8a8d8da..e63179b 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -1,8 +1,10 @@ import os import shlex -from typing import List, Dict, Optional +from typing import List, Dict, Optional, TYPE_CHECKING import httpx -from onepasswordconnectsdk.client import Client + +if TYPE_CHECKING: + from onepasswordconnectsdk.client import Client from onepasswordconnectsdk.models import ( Item, ParsedField, @@ -55,7 +57,7 @@ def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float return args -def load_dict(client: Client, config: dict): +def load_dict(client: "Client", config: dict): """Load: Takes a dictionary with keys specifiying the user desired naming scheme of the values to return. Each key's value is a dictionary that includes information on where @@ -122,7 +124,7 @@ def load_dict(client: Client, config: dict): return config_values -def load(client: Client, config: object): +def load(client: "Client", config: object): """Load: Takes a an object with class attributes annotated with tags describing where to find desired fields in 1Password. Manipulates given object and fills attributes in with 1Password item field values. @@ -201,7 +203,7 @@ def _vault_uuid_for_field(field: str, vault_tag: dict): def _set_values_for_item( - client: Client, + client: "Client", parsed_item: ParsedItem, config_dict={}, config_object: object = None, From 3fa8e903fc5d26c1cf96d4061172e5ba33c42e7b Mon Sep 17 00:00:00 2001 From: mattjacksoncello <156972821+mattjacksoncello@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:14:25 +1300 Subject: [PATCH 088/112] add example --- example/ca_file_example/list_secrets.py | 85 +++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 example/ca_file_example/list_secrets.py diff --git a/example/ca_file_example/list_secrets.py b/example/ca_file_example/list_secrets.py new file mode 100644 index 0000000..b830011 --- /dev/null +++ b/example/ca_file_example/list_secrets.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" +Example script demonstrating how to connect to a 1Password Connect server +using CA certificate verification and list all secrets in a vault. + +Shows both synchronous and asynchronous usage. +Update the configuration variables below with your values. +""" + +import asyncio +from onepasswordconnectsdk.client import new_client +from onepasswordconnectsdk.config import ClientConfig + +# Configuration +CONNECT_URL = "https://connect.example.com" # Your 1Password Connect server URL +TOKEN = "eyJhbGc..." # Your 1Password Connect token +VAULT_ID = "vaults_abc123" # ID of the vault to list secrets from +CA_FILE = "path/to/ca.pem" # Path to your CA certificate file + +def list_vault_secrets(): + """ + Connect to 1Password Connect server and list all secrets in the specified vault. + Uses CA certificate verification for secure connection. + """ + try: + # Configure client with CA certificate verification + config = ClientConfig( + cafile=CA_FILE, + timeout=30.0 # 30 second timeout + ) + + # Initialize client with configuration + client = new_client(CONNECT_URL, TOKEN, config=config) + + # Get all items in the vault + items = client.get_items(VAULT_ID) + + # Print items + print(f"\nSecrets in vault {VAULT_ID}:") + print("-" * 40) + for item in items: + print(f"- {item.title} ({item.category})") + + except Exception as e: + print(f"Error: {str(e)}") + + +async def list_vault_secrets_async(): + """ + Async version: Connect to 1Password Connect server and list all secrets in the specified vault. + Uses CA certificate verification for secure connection. + """ + try: + # Configure client with CA certificate verification + config = ClientConfig( + cafile=CA_FILE, + timeout=30.0 # 30 second timeout + ) + + # Initialize async client with configuration + client = new_client(CONNECT_URL, TOKEN, is_async=True, config=config) + + # Get all items in the vault + items = await client.get_items(VAULT_ID) + + # Print items + print(f"\nSecrets in vault {VAULT_ID} (async):") + print("-" * 40) + for item in items: + print(f"- {item.title} ({item.category})") + + # Close the client gracefully + await client.session.aclose() + + except Exception as e: + print(f"Error: {str(e)}") + +if __name__ == "__main__": + # Run sync version + print("Running synchronous example...") + list_vault_secrets() + + # Run async version + print("\nRunning asynchronous example...") + asyncio.run(list_vault_secrets_async()) From c2e521c3273ce6645257b396eb138fe1d9284af0 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 29 Jan 2025 21:54:42 +0000 Subject: [PATCH 089/112] Re order logic for getting httpx options --- src/onepasswordconnectsdk/config.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index e63179b..c127910 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -48,12 +48,15 @@ def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float 'base_url': base_url, 'headers': headers, 'timeout': timeout, - **self.httpx_options } + # Set verify from cafile first if self.cafile: args['verify'] = self.cafile + # Allow httpx_options (including verify) to override + args.update(self.httpx_options) + return args From d447dc326ea22089db431bec9003aca9af2cfd6a Mon Sep 17 00:00:00 2001 From: mattjacksoncello <156972821+mattjacksoncello@users.noreply.github.com> Date: Wed, 5 Mar 2025 08:37:52 +1300 Subject: [PATCH 090/112] Update src/onepasswordconnectsdk/config.py LGTM Co-authored-by: Andi Titu <45081667+AndyTitu@users.noreply.github.com> --- src/onepasswordconnectsdk/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index c127910..2aee3c9 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -23,7 +23,7 @@ class ClientConfig: """Configuration class for 1Password Connect client. Inherits from httpx.BaseClient to support all httpx client options. """ - def __init__(self, cafile: Optional[str] = None, **kwargs): + def __init__(self, ca_file: Optional[str] = None, **kwargs): """Initialize client configuration Args: From f4980bb5d52fe309614bb80b7be85fe7240dc27f Mon Sep 17 00:00:00 2001 From: mattjacksoncello <156972821+mattjacksoncello@users.noreply.github.com> Date: Wed, 5 Mar 2025 08:38:04 +1300 Subject: [PATCH 091/112] Update src/onepasswordconnectsdk/config.py LGTM Co-authored-by: Andi Titu <45081667+AndyTitu@users.noreply.github.com> --- src/onepasswordconnectsdk/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 2aee3c9..7063b58 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -30,7 +30,7 @@ def __init__(self, ca_file: Optional[str] = None, **kwargs): cafile (Optional[str]): Path to CA certificate file for SSL verification **kwargs: Additional httpx client options """ - self.cafile = cafile + self.ca_file = ca_file self.httpx_options = kwargs def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float) -> Dict: From f4a9352b8e7f9b48a02d0a660261cbb35359ea29 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 6 Mar 2025 20:22:46 +0000 Subject: [PATCH 092/112] Rename 'cafile' to 'ca_file' in ClientConfig for consistency --- USAGE.md | 6 +++--- example/ca_file_example/list_secrets.py | 4 ++-- src/onepasswordconnectsdk/config.py | 8 ++++---- src/tests/test_client_config.py | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/USAGE.md b/USAGE.md index f0afc34..d1554b9 100644 --- a/USAGE.md +++ b/USAGE.md @@ -41,7 +41,7 @@ When connecting to a 1Password Connect server using HTTPS, you may need to confi from onepasswordconnectsdk.config import ClientConfig # Verify SSL using a custom CA certificate -config = ClientConfig(cafile="path/to/ca.pem") +config = ClientConfig(ca_file="path/to/ca.pem") client = new_client("https://connect.example.com", "your-token", config=config) # Disable SSL verification (not recommended for production) @@ -56,7 +56,7 @@ The ClientConfig class accepts all httpx client options as keyword arguments. Th ```python # Configure timeouts and redirects config = ClientConfig( - cafile="path/to/ca.pem", + ca_file="path/to/ca.pem", timeout=30.0, # 30 second timeout follow_redirects=True, # Follow HTTP redirects max_redirects=5 # Maximum number of redirects to follow @@ -85,7 +85,7 @@ The same configuration options work for both synchronous and asynchronous client ```python config = ClientConfig( - cafile="path/to/ca.pem", + ca_file="path/to/ca.pem", timeout=30.0 ) async_client = new_client("https://connect.example.com", "your-token", is_async=True, config=config) diff --git a/example/ca_file_example/list_secrets.py b/example/ca_file_example/list_secrets.py index b830011..a10243c 100644 --- a/example/ca_file_example/list_secrets.py +++ b/example/ca_file_example/list_secrets.py @@ -25,7 +25,7 @@ def list_vault_secrets(): try: # Configure client with CA certificate verification config = ClientConfig( - cafile=CA_FILE, + ca_file=CA_FILE, timeout=30.0 # 30 second timeout ) @@ -53,7 +53,7 @@ async def list_vault_secrets_async(): try: # Configure client with CA certificate verification config = ClientConfig( - cafile=CA_FILE, + ca_file=CA_FILE, timeout=30.0 # 30 second timeout ) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 7063b58..bcde7a0 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -27,7 +27,7 @@ def __init__(self, ca_file: Optional[str] = None, **kwargs): """Initialize client configuration Args: - cafile (Optional[str]): Path to CA certificate file for SSL verification + ca_file (Optional[str]): Path to CA certificate file for SSL verification **kwargs: Additional httpx client options """ self.ca_file = ca_file @@ -50,9 +50,9 @@ def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float 'timeout': timeout, } - # Set verify from cafile first - if self.cafile: - args['verify'] = self.cafile + # Set verify from ca_file first + if self.ca_file: + args['verify'] = self.ca_file # Allow httpx_options (including verify) to override args.update(self.httpx_options) diff --git a/src/tests/test_client_config.py b/src/tests/test_client_config.py index 21e6034..524f1f4 100644 --- a/src/tests/test_client_config.py +++ b/src/tests/test_client_config.py @@ -2,8 +2,8 @@ from onepasswordconnectsdk.config import ClientConfig import httpx -def test_client_config_with_cafile(): - config = ClientConfig(cafile="path/to/ca.pem") +def test_client_config_with_ca_file(): + config = ClientConfig(ca_file="path/to/ca.pem") args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) assert args["verify"] == "path/to/ca.pem" @@ -13,7 +13,7 @@ def test_client_config_with_cafile(): def test_client_config_with_kwargs(): config = ClientConfig( - cafile="path/to/ca.pem", + ca_file="path/to/ca.pem", follow_redirects=True, timeout=60.0 ) @@ -25,16 +25,16 @@ def test_client_config_with_kwargs(): assert args["timeout"] == 60.0 def test_client_config_verify_override(): - # When verify is explicitly set in kwargs, it should override cafile + # When verify is explicitly set in kwargs, it should override ca_file config = ClientConfig( - cafile="path/to/ca.pem", + ca_file="path/to/ca.pem", verify=False ) args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) assert args["verify"] == False -def test_client_config_no_cafile(): +def test_client_config_no_ca_file(): config = ClientConfig() args = config.get_client_args("https://test.com", {"Authorization": "Bearer token"}, 30.0) From e0baca0f9addba25149c8c7887d9092ea180dc2c Mon Sep 17 00:00:00 2001 From: Rick van Galen Date: Tue, 20 May 2025 10:42:37 +0200 Subject: [PATCH 093/112] Update to HTTPX v0.28.1 --- poetry.lock | 101 ++++++++++++++++++++++++++----------------------- pyproject.toml | 4 +- 2 files changed, 56 insertions(+), 49 deletions(-) diff --git a/poetry.lock b/poetry.lock index d9afdf8..d9f97be 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "anyio" @@ -6,6 +6,7 @@ version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, @@ -18,7 +19,7 @@ sniffio = ">=1.1" [package.extras] doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4) ; python_version < \"3.8\"", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; python_version < \"3.12\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] trio = ["trio (<0.22)"] [[package]] @@ -27,6 +28,7 @@ version = "2024.7.4" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, @@ -38,6 +40,8 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -49,6 +53,7 @@ version = "7.2.7" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, @@ -116,7 +121,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "exceptiongroup" @@ -124,6 +129,8 @@ version = "1.1.3" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, @@ -134,58 +141,62 @@ test = ["pytest (>=6)"] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] [[package]] name = "httpcore" -version = "0.16.3" +version = "1.0.9" description = "A minimal low-level HTTP client." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"}, - {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] -anyio = ">=3.0,<5.0" certifi = "*" -h11 = ">=0.13,<0.15" -sniffio = "==1.*" +h11 = ">=0.16" [package.extras] +asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" -version = "0.23.3" +version = "0.28.1" description = "The next generation HTTP client." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"}, - {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"}, + {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, + {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, ] [package.dependencies] +anyio = "*" certifi = "*" -httpcore = ">=0.15.0,<0.17.0" -rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]} -sniffio = "*" +httpcore = "==1.*" +idna = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "idna" @@ -193,6 +204,7 @@ version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" +groups = ["main", "dev"] files = [ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, @@ -204,6 +216,7 @@ version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, @@ -215,6 +228,7 @@ version = "23.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, @@ -226,6 +240,7 @@ version = "1.2.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, @@ -241,6 +256,7 @@ version = "7.4.0" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, @@ -263,6 +279,7 @@ version = "0.20.3" description = "Pytest support for asyncio" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, @@ -281,6 +298,7 @@ version = "4.1.0" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, @@ -299,6 +317,7 @@ version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] files = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, @@ -309,34 +328,18 @@ six = ">=1.5" [[package]] name = "respx" -version = "0.20.2" +version = "0.22.0" description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"}, - {file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"}, + {file = "respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0"}, + {file = "respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91"}, ] [package.dependencies] -httpx = ">=0.21.0" - -[[package]] -name = "rfc3986" -version = "1.5.0" -description = "Validating URI References per RFC 3986" -optional = false -python-versions = "*" -files = [ - {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, - {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, -] - -[package.dependencies] -idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} - -[package.extras] -idna2008 = ["idna"] +httpx = ">=0.25.0" [[package]] name = "six" @@ -344,6 +347,7 @@ version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +groups = ["main"] files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, @@ -355,6 +359,7 @@ version = "1.3.0" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, @@ -366,12 +371,14 @@ version = "2.0.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.9" -content-hash = "c0f45ea02e27727437bb9e8bb12d3b1c3edd127b2013ec5a9887b6107e76f5b2" +content-hash = "cf614bf81534e8f4de1c808120b379ce4fd6fce45b6b85d5f5df7f35efa1046f" diff --git a/pyproject.toml b/pyproject.toml index f7c160b..bf0e648 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,13 +14,13 @@ repository = "https://github.com/1Password/connect-sdk-python" [tool.poetry.dependencies] python = "^3.9" python-dateutil = "^2.8.1" -httpx = "^0.23.3" +httpx = "^0.28.1" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" pytest-asyncio = "^0.20.3" pytest-cov = "^4.0.0" -respx = "^0.20.1" +respx = "^0.22.0" [build-system] requires = ["poetry>=0.12"] From ae2af8a1f04a58bcec40c586bc8520fa08382101 Mon Sep 17 00:00:00 2001 From: Eddy Filip Date: Mon, 14 Jul 2025 15:42:03 +0200 Subject: [PATCH 094/112] Improve workflow permissions Since our project has been created before February 2023, we have the more permissive permission `write-all` set for the `GITHUB_TOKEN` used by the workflow. Therefore, to apply the principle of least privilege, we now explicitly specify the permisisons needed for each workflow. In addition, I've applied the same styling format for all current workflows for consistency. --- .github/workflows/pr-check-signed-commits.yml | 8 +++++--- .github/workflows/release-pr.yml | 6 +++++- .github/workflows/release.yml | 3 +++ .github/workflows/test.yml | 6 ++++-- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr-check-signed-commits.yml b/.github/workflows/pr-check-signed-commits.yml index 77a8b8a..584bac2 100644 --- a/.github/workflows/pr-check-signed-commits.yml +++ b/.github/workflows/pr-check-signed-commits.yml @@ -1,12 +1,14 @@ name: Check signed commits in PR + on: pull_request_target +permissions: + contents: read + pull-requests: write + jobs: build: name: Check signed commits in PR - permissions: - contents: read - pull-requests: write runs-on: ubuntu-latest steps: - name: Check signed commits in PR diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 73a5930..782e18d 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -1,8 +1,12 @@ +name: Open Release PR for review + on: create: branches: -name: Open Release PR for review +permissions: + contents: read + pull-requests: write jobs: # This job is necessary because GitHub does not (yet) support diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6068729..63ffc9c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,9 @@ on: branches: main types: closed +permissions: + contents: write + jobs: release: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31adb07..8a66fdf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,11 +1,13 @@ name: Tests -permissions: - contents: read + on: push: branches: main pull_request: +permissions: + contents: read + jobs: test: name: Test From a4561407ae40cda95c75911c2cdc5f80529d01d6 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 08:55:06 -0500 Subject: [PATCH 095/112] Bump minimum python version for the project to v3.10 --- poetry.lock | 306 ++++++++++++++++++++++++++++++------------------- pyproject.toml | 2 +- 2 files changed, 192 insertions(+), 116 deletions(-) diff --git a/poetry.lock b/poetry.lock index d9f97be..341745a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,36 +2,35 @@ [[package]] name = "anyio" -version = "3.7.1" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4) ; python_version < \"3.8\"", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; python_version < \"3.12\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] -trio = ["trio (<0.22)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "certifi" -version = "2024.7.4" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] @@ -49,72 +48,100 @@ files = [ [[package]] name = "coverage" -version = "7.2.7" +version = "7.10.6" description = "Code coverage measurement for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, - {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, - {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, - {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, - {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, - {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, - {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, - {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, - {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, - {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, - {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, - {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, - {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, - {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, - {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, - {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, - {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, - {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, - {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, - {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, - {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, - {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, - {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, - {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, - {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, - {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, - {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, - {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, - {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, - {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, - {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, - {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, - {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, + {file = "coverage-7.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:70e7bfbd57126b5554aa482691145f798d7df77489a177a6bef80de78860a356"}, + {file = "coverage-7.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e41be6f0f19da64af13403e52f2dec38bbc2937af54df8ecef10850ff8d35301"}, + {file = "coverage-7.10.6-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c61fc91ab80b23f5fddbee342d19662f3d3328173229caded831aa0bd7595460"}, + {file = "coverage-7.10.6-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10356fdd33a7cc06e8051413140bbdc6f972137508a3572e3f59f805cd2832fd"}, + {file = "coverage-7.10.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80b1695cf7c5ebe7b44bf2521221b9bb8cdf69b1f24231149a7e3eb1ae5fa2fb"}, + {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2e4c33e6378b9d52d3454bd08847a8651f4ed23ddbb4a0520227bd346382bbc6"}, + {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c8a3ec16e34ef980a46f60dc6ad86ec60f763c3f2fa0db6d261e6e754f72e945"}, + {file = "coverage-7.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7d79dabc0a56f5af990cc6da9ad1e40766e82773c075f09cc571e2076fef882e"}, + {file = "coverage-7.10.6-cp310-cp310-win32.whl", hash = "sha256:86b9b59f2b16e981906e9d6383eb6446d5b46c278460ae2c36487667717eccf1"}, + {file = "coverage-7.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:e132b9152749bd33534e5bd8565c7576f135f157b4029b975e15ee184325f528"}, + {file = "coverage-7.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c706db3cabb7ceef779de68270150665e710b46d56372455cd741184f3868d8f"}, + {file = "coverage-7.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e0c38dc289e0508ef68ec95834cb5d2e96fdbe792eaccaa1bccac3966bbadcc"}, + {file = "coverage-7.10.6-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:752a3005a1ded28f2f3a6e8787e24f28d6abe176ca64677bcd8d53d6fe2ec08a"}, + {file = "coverage-7.10.6-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:689920ecfd60f992cafca4f5477d55720466ad2c7fa29bb56ac8d44a1ac2b47a"}, + {file = "coverage-7.10.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec98435796d2624d6905820a42f82149ee9fc4f2d45c2c5bc5a44481cc50db62"}, + {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b37201ce4a458c7a758ecc4efa92fa8ed783c66e0fa3c42ae19fc454a0792153"}, + {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2904271c80898663c810a6b067920a61dd8d38341244a3605bd31ab55250dad5"}, + {file = "coverage-7.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5aea98383463d6e1fa4e95416d8de66f2d0cb588774ee20ae1b28df826bcb619"}, + {file = "coverage-7.10.6-cp311-cp311-win32.whl", hash = "sha256:e3fb1fa01d3598002777dd259c0c2e6d9d5e10e7222976fc8e03992f972a2cba"}, + {file = "coverage-7.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:f35ed9d945bece26553d5b4c8630453169672bea0050a564456eb88bdffd927e"}, + {file = "coverage-7.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:99e1a305c7765631d74b98bf7dbf54eeea931f975e80f115437d23848ee8c27c"}, + {file = "coverage-7.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b2dd6059938063a2c9fee1af729d4f2af28fd1a545e9b7652861f0d752ebcea"}, + {file = "coverage-7.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:388d80e56191bf846c485c14ae2bc8898aa3124d9d35903fef7d907780477634"}, + {file = "coverage-7.10.6-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:90cb5b1a4670662719591aa92d0095bb41714970c0b065b02a2610172dbf0af6"}, + {file = "coverage-7.10.6-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:961834e2f2b863a0e14260a9a273aff07ff7818ab6e66d2addf5628590c628f9"}, + {file = "coverage-7.10.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf9a19f5012dab774628491659646335b1928cfc931bf8d97b0d5918dd58033c"}, + {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:99c4283e2a0e147b9c9cc6bc9c96124de9419d6044837e9799763a0e29a7321a"}, + {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:282b1b20f45df57cc508c1e033403f02283adfb67d4c9c35a90281d81e5c52c5"}, + {file = "coverage-7.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cdbe264f11afd69841bd8c0d83ca10b5b32853263ee62e6ac6a0ab63895f972"}, + {file = "coverage-7.10.6-cp312-cp312-win32.whl", hash = "sha256:a517feaf3a0a3eca1ee985d8373135cfdedfbba3882a5eab4362bda7c7cf518d"}, + {file = "coverage-7.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:856986eadf41f52b214176d894a7de05331117f6035a28ac0016c0f63d887629"}, + {file = "coverage-7.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:acf36b8268785aad739443fa2780c16260ee3fa09d12b3a70f772ef100939d80"}, + {file = "coverage-7.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffea0575345e9ee0144dfe5701aa17f3ba546f8c3bb48db62ae101afb740e7d6"}, + {file = "coverage-7.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d91d7317cde40a1c249d6b7382750b7e6d86fad9d8eaf4fa3f8f44cf171e80"}, + {file = "coverage-7.10.6-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e23dd5408fe71a356b41baa82892772a4cefcf758f2ca3383d2aa39e1b7a003"}, + {file = "coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0f3f56e4cb573755e96a16501a98bf211f100463d70275759e73f3cbc00d4f27"}, + {file = "coverage-7.10.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db4a1d897bbbe7339946ffa2fe60c10cc81c43fab8b062d3fcb84188688174a4"}, + {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fd7879082953c156d5b13c74aa6cca37f6a6f4747b39538504c3f9c63d043d"}, + {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:28395ca3f71cd103b8c116333fa9db867f3a3e1ad6a084aa3725ae002b6583bc"}, + {file = "coverage-7.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:61c950fc33d29c91b9e18540e1aed7d9f6787cc870a3e4032493bbbe641d12fc"}, + {file = "coverage-7.10.6-cp313-cp313-win32.whl", hash = "sha256:160c00a5e6b6bdf4e5984b0ef21fc860bc94416c41b7df4d63f536d17c38902e"}, + {file = "coverage-7.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:628055297f3e2aa181464c3808402887643405573eb3d9de060d81531fa79d32"}, + {file = "coverage-7.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:df4ec1f8540b0bcbe26ca7dd0f541847cc8a108b35596f9f91f59f0c060bfdd2"}, + {file = "coverage-7.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c9a8b7a34a4de3ed987f636f71881cd3b8339f61118b1aa311fbda12741bff0b"}, + {file = "coverage-7.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dd5af36092430c2b075cee966719898f2ae87b636cefb85a653f1d0ba5d5393"}, + {file = "coverage-7.10.6-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0353b0f0850d49ada66fdd7d0c7cdb0f86b900bb9e367024fd14a60cecc1e27"}, + {file = "coverage-7.10.6-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d6b9ae13d5d3e8aeca9ca94198aa7b3ebbc5acfada557d724f2a1f03d2c0b0df"}, + {file = "coverage-7.10.6-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:675824a363cc05781b1527b39dc2587b8984965834a748177ee3c37b64ffeafb"}, + {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:692d70ea725f471a547c305f0d0fc6a73480c62fb0da726370c088ab21aed282"}, + {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:851430a9a361c7a8484a36126d1d0ff8d529d97385eacc8dfdc9bfc8c2d2cbe4"}, + {file = "coverage-7.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d9369a23186d189b2fc95cc08b8160ba242057e887d766864f7adf3c46b2df21"}, + {file = "coverage-7.10.6-cp313-cp313t-win32.whl", hash = "sha256:92be86fcb125e9bda0da7806afd29a3fd33fdf58fba5d60318399adf40bf37d0"}, + {file = "coverage-7.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6b3039e2ca459a70c79523d39347d83b73f2f06af5624905eba7ec34d64d80b5"}, + {file = "coverage-7.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3fb99d0786fe17b228eab663d16bee2288e8724d26a199c29325aac4b0319b9b"}, + {file = "coverage-7.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6008a021907be8c4c02f37cdc3ffb258493bdebfeaf9a839f9e71dfdc47b018e"}, + {file = "coverage-7.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5e75e37f23eb144e78940b40395b42f2321951206a4f50e23cfd6e8a198d3ceb"}, + {file = "coverage-7.10.6-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0f7cb359a448e043c576f0da00aa8bfd796a01b06aa610ca453d4dde09cc1034"}, + {file = "coverage-7.10.6-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c68018e4fc4e14b5668f1353b41ccf4bc83ba355f0e1b3836861c6f042d89ac1"}, + {file = "coverage-7.10.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd4b2b0707fc55afa160cd5fc33b27ccbf75ca11d81f4ec9863d5793fc6df56a"}, + {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cec13817a651f8804a86e4f79d815b3b28472c910e099e4d5a0e8a3b6a1d4cb"}, + {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f2a6a8e06bbda06f78739f40bfb56c45d14eb8249d0f0ea6d4b3d48e1f7c695d"}, + {file = "coverage-7.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:081b98395ced0d9bcf60ada7661a0b75f36b78b9d7e39ea0790bb4ed8da14747"}, + {file = "coverage-7.10.6-cp314-cp314-win32.whl", hash = "sha256:6937347c5d7d069ee776b2bf4e1212f912a9f1f141a429c475e6089462fcecc5"}, + {file = "coverage-7.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:adec1d980fa07e60b6ef865f9e5410ba760e4e1d26f60f7e5772c73b9a5b0713"}, + {file = "coverage-7.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:a80f7aef9535442bdcf562e5a0d5a5538ce8abe6bb209cfbf170c462ac2c2a32"}, + {file = "coverage-7.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0de434f4fbbe5af4fa7989521c655c8c779afb61c53ab561b64dcee6149e4c65"}, + {file = "coverage-7.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6e31b8155150c57e5ac43ccd289d079eb3f825187d7c66e755a055d2c85794c6"}, + {file = "coverage-7.10.6-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:98cede73eb83c31e2118ae8d379c12e3e42736903a8afcca92a7218e1f2903b0"}, + {file = "coverage-7.10.6-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f863c08f4ff6b64fa8045b1e3da480f5374779ef187f07b82e0538c68cb4ff8e"}, + {file = "coverage-7.10.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b38261034fda87be356f2c3f42221fdb4171c3ce7658066ae449241485390d5"}, + {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e93b1476b79eae849dc3872faeb0bf7948fd9ea34869590bc16a2a00b9c82a7"}, + {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ff8a991f70f4c0cf53088abf1e3886edcc87d53004c7bb94e78650b4d3dac3b5"}, + {file = "coverage-7.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ac765b026c9f33044419cbba1da913cfb82cca1b60598ac1c7a5ed6aac4621a0"}, + {file = "coverage-7.10.6-cp314-cp314t-win32.whl", hash = "sha256:441c357d55f4936875636ef2cfb3bee36e466dcf50df9afbd398ce79dba1ebb7"}, + {file = "coverage-7.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:073711de3181b2e204e4870ac83a7c4853115b42e9cd4d145f2231e12d670930"}, + {file = "coverage-7.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:137921f2bac5559334ba66122b753db6dc5d1cf01eb7b64eb412bb0d064ef35b"}, + {file = "coverage-7.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90558c35af64971d65fbd935c32010f9a2f52776103a259f1dee865fe8259352"}, + {file = "coverage-7.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8953746d371e5695405806c46d705a3cd170b9cc2b9f93953ad838f6c1e58612"}, + {file = "coverage-7.10.6-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c83f6afb480eae0313114297d29d7c295670a41c11b274e6bca0c64540c1ce7b"}, + {file = "coverage-7.10.6-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7eb68d356ba0cc158ca535ce1381dbf2037fa8cb5b1ae5ddfc302e7317d04144"}, + {file = "coverage-7.10.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b15a87265e96307482746d86995f4bff282f14b027db75469c446da6127433b"}, + {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fc53ba868875bfbb66ee447d64d6413c2db91fddcfca57025a0e7ab5b07d5862"}, + {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:efeda443000aa23f276f4df973cb82beca682fd800bb119d19e80504ffe53ec2"}, + {file = "coverage-7.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9702b59d582ff1e184945d8b501ffdd08d2cee38d93a2206aa5f1365ce0b8d78"}, + {file = "coverage-7.10.6-cp39-cp39-win32.whl", hash = "sha256:2195f8e16ba1a44651ca684db2ea2b2d4b5345da12f07d9c22a395202a05b23c"}, + {file = "coverage-7.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:f32ff80e7ef6a5b5b606ea69a36e97b219cd9dc799bcf2963018a4d8f788cfbf"}, + {file = "coverage-7.10.6-py3-none-any.whl", hash = "sha256:92c4ecf6bf11b2e85fd4d8204814dc26e6a19f0c9d938c207c5cb0eadfcabbe3"}, + {file = "coverage-7.10.6.tar.gz", hash = "sha256:f644a3ae5933a552a29dbb9aa2f90c677a875f80ebea028e5a52a4f429044b90"}, ] [package.dependencies] @@ -125,17 +152,20 @@ toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "exceptiongroup" -version = "1.1.3" +version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["main", "dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ - {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, - {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + [package.extras] test = ["pytest (>=6)"] @@ -200,66 +230,69 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" groups = ["main", "dev"] files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] name = "packaging" -version = "23.1" +version = "25.0" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pluggy" -version = "1.2.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] @@ -313,14 +346,14 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-dateutil" -version = "2.8.2" +version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ] [package.dependencies] @@ -343,42 +376,85 @@ httpx = ">=0.25.0" [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] name = "sniffio" -version = "1.3.0" +version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] [[package]] name = "tomli" -version = "2.0.1" +version = "2.2.1" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["dev"] markers = "python_full_version <= \"3.11.0a6\"" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +markers = "python_version < \"3.13\"" +files = [ + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [metadata] lock-version = "2.1" -python-versions = "^3.9" -content-hash = "cf614bf81534e8f4de1c808120b379ce4fd6fce45b6b85d5f5df7f35efa1046f" +python-versions = "^3.10" +content-hash = "dd38ce55167de67b1f203164b149a53a060343bbecfbf3326e9d02d5d60d447e" diff --git a/pyproject.toml b/pyproject.toml index bf0e648..4c86fc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ repository = "https://github.com/1Password/connect-sdk-python" "Report Security Issue" = "https://bugcrowd.com/agilebits" [tool.poetry.dependencies] -python = "^3.9" +python = "^3.10" python-dateutil = "^2.8.1" httpx = "^0.28.1" From 220b0b9f8dc080acdd178a22de588d63663ce81b Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 08:55:24 -0500 Subject: [PATCH 096/112] Update workflows to use python v3.10 --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63ffc9c..85a229c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v1 with: - python-version: 3.9 + python-version: 3.10 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a66fdf..db37dc2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v1 with: - python-version: 3.9 + python-version: 3.10 - name: Check out code uses: actions/checkout@v2 - name: Install dependencies From 5af256e7c9b6f6652c11979b779506f3818715dd Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 09:02:54 -0500 Subject: [PATCH 097/112] Bump dependencies --- poetry.lock | 83 ++++++++++++++++++++++++++++++++++---------------- pyproject.toml | 6 ++-- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/poetry.lock b/poetry.lock index 341745a..405dfec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -21,6 +21,19 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] trio = ["trio (>=0.26.1)"] +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +groups = ["dev"] +markers = "python_version == \"3.10\"" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + [[package]] name = "certifi" version = "2025.8.3" @@ -283,66 +296,84 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pytest" -version = "7.4.4" +version = "8.4.2" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, + {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, + {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1", markers = "python_version < \"3.11\""} +iniconfig = ">=1" +packaging = ">=20" +pluggy = ">=1.5,<2" +pygments = ">=2.7.2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-asyncio" -version = "0.20.3" +version = "1.1.0" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"}, - {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"}, + {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"}, + {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"}, ] [package.dependencies] -pytest = ">=6.1.0" +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<9" [package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" -version = "4.1.0" +version = "6.3.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest_cov-6.3.0-py3-none-any.whl", hash = "sha256:440db28156d2468cafc0415b4f8e50856a0d11faefa38f30906048fe490f1749"}, + {file = "pytest_cov-6.3.0.tar.gz", hash = "sha256:35c580e7800f87ce892e687461166e1ac2bcb8fb9e13aea79032518d6e503ff2"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" +coverage = {version = ">=7.5", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=6.2.5" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "python-dateutil" @@ -457,4 +488,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "dd38ce55167de67b1f203164b149a53a060343bbecfbf3326e9d02d5d60d447e" +content-hash = "b775718374313e9c464aae71aa93a7aef133f272e3b28233f20b0bf8c17b559e" diff --git a/pyproject.toml b/pyproject.toml index 4c86fc0..bca551d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,9 +17,9 @@ python-dateutil = "^2.8.1" httpx = "^0.28.1" [tool.poetry.group.dev.dependencies] -pytest = "^7.2.0" -pytest-asyncio = "^0.20.3" -pytest-cov = "^4.0.0" +pytest = "^8.4.2" +pytest-asyncio = "^1.1.0" +pytest-cov = "^6.3.0" respx = "^0.22.0" [build-system] From d06c004a91617fd41bec36c875e004d69435a768 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 09:05:22 -0500 Subject: [PATCH 098/112] Use poetry core as build system to compliant with PEP-517 --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bca551d..15ae956 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,5 +23,5 @@ pytest-cov = "^6.3.0" respx = "^0.22.0" [build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" From 3f44494b40dea4bc11219b290a7f1215bbcff3b0 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 09:16:54 -0500 Subject: [PATCH 099/112] Bump github action versions to the latest --- .github/workflows/release-pr.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 782e18d..2298521 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -20,7 +20,7 @@ jobs: steps: - id: is_release_branch_without_pr name: Find matching PR - uses: actions/github-script@v3 + uses: actions/github-script@v8 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -46,7 +46,7 @@ jobs: name: Create Release Pull Request runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v5 - name: Parse release version id: get_version diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85a229c..3cb3adb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,11 +14,11 @@ jobs: if: github.event.pull_request.merged == true && contains(github.event.pull_request.head.ref, 'release/') steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Setup Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v6 with: python-version: 3.10 - name: Install dependencies diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index db37dc2..ed7f17c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,11 +14,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v6 with: python-version: 3.10 - name: Check out code - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Install dependencies run: | python -m pip install --upgrade pip From 62be545c73abe1c75f96b033b385c1a0d382995a Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 09:19:11 -0500 Subject: [PATCH 100/112] Wrap python version in quotes --- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3cb3adb..bfe2942 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: 3.10 + python-version: '3.10' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed7f17c..308e584 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: 3.10 + python-version: '3.10' - name: Check out code uses: actions/checkout@v5 - name: Install dependencies From be5c240ebef58871da699063b2e60f19aab10f83 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 18:59:00 -0500 Subject: [PATCH 101/112] Prepare release v2.0.0 --- CHANGELOG.md | 16 ++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e9a51..ae5ec09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,22 @@ --- +[//]: # (START/v2.0.0) +# v2.0.0 + +## 🔴 Breaking changes +* Minimum supported python version is 3.10. {#125} + +## 🚀 Features + * Added ConfigClass for HTTPX to simplify configuration. {#118} + +## 🔧 Fixes + * Updated to HTTPX v0.28.1. {#121} + * Bumped certifi from 2023.7.22 to 2024.7.4. {#114} + * Bumped idna from 3.4 to 3.7. {#107} + +--- + [//]: # (START/v1.5.1) # v1.5.1 diff --git a/pyproject.toml b/pyproject.toml index 15ae956..df766a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "1.5.1" +version = "2.0.0" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From 2df17719961239809dfaa2b78214afb11bb9d034 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Mon, 8 Sep 2025 19:09:51 -0500 Subject: [PATCH 102/112] Use `rest` object to check PRs list in a workflow --- .github/workflows/release-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 2298521..f0125c9 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -31,7 +31,7 @@ jobs: if(!releaseBranchName) { return false } - const {data: prs} = await github.pulls.list({ + const {data: prs} = await github.rest.pulls.list({ ...context.repo, state: 'open', head: `1Password:${releaseBranchName}`, From 18864b7993db0b9ce564195b6aaff06d6de79d4f Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Tue, 9 Sep 2025 09:19:14 -0500 Subject: [PATCH 103/112] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae5ec09..7064ff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ # v2.0.0 ## 🔴 Breaking changes -* Minimum supported python version is 3.10. {#125} +* Requires Python 3.10 or newer. {#125} ## 🚀 Features * Added ConfigClass for HTTPX to simplify configuration. {#118} From a9eb71feae204f73793da69bcccb181be58726ca Mon Sep 17 00:00:00 2001 From: Rishi Yemme Date: Tue, 10 Feb 2026 13:26:01 -0800 Subject: [PATCH 104/112] Update developer slack link in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8188107..393d2d0 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ For more examples of how to use the SDK, check out [USAGE.md](USAGE.md). ## 💙 Community & Support - File an [issue](https://github.com/1Password/connect-sdk-python/issues) for bugs and feature requests. -- Join the [Developer Slack workspace](https://join.slack.com/t/1password-devs/shared_invite/zt-1halo11ps-6o9pEv96xZ3LtX_VE0fJQA). +- Join the [Developer Slack workspace](https://developer.1password.com/joinslack). - Subscribe to the [Developer Newsletter](https://1password.com/dev-subscribe/). ## 🔐 Security From c0e9dec3c1cbd93db8dd08e528a1d26659977c0b Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Wed, 1 Apr 2026 14:59:05 -0400 Subject: [PATCH 105/112] Fall back to search by title --- src/onepasswordconnectsdk/async_client.py | 19 ++++--- src/onepasswordconnectsdk/client.py | 23 +++++---- src/onepasswordconnectsdk/errors.py | 4 +- src/onepasswordconnectsdk/utils.py | 2 +- src/tests/test_client_items.py | 60 +++++++++++++++++++++++ 5 files changed, 90 insertions(+), 18 deletions(-) diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 802adda..57a2268 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -19,7 +19,7 @@ class AsyncClient: def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None: """Initialize async client - + Args: url (str): The url of the 1Password Connect API token (str): The 1Password Service Account token @@ -34,11 +34,11 @@ def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) def create_session(self, url: str, token: str) -> httpx.AsyncClient: headers = self.build_headers(token) timeout = get_timeout() - + if self.config: client_args = self.config.get_client_args(url, headers, timeout) return httpx.AsyncClient(**client_args) - + return httpx.AsyncClient(base_url=url, headers=headers, timeout=timeout) def build_headers(self, token: str) -> Dict[str, str]: @@ -115,10 +115,14 @@ async def get_item(self, item: str, vault: str) -> Item: vault = await self.get_vault_by_title(vault) vault_id = vault.id - if is_valid_uuid(item): - return await self.get_item_by_id(item, vault_id) - else: + if not is_valid_uuid(item): return await self.get_item_by_title(item, vault_id) + try: + return await self.get_item_by_id(item, vault_id) + except FailedToRetrieveItemException as exc: + if exc.status_code == 404: + return await self.get_item_by_title(item, vault_id) + raise async def get_item_by_id(self, item_id: str, vault_id: str) -> Item: """Get a specific item by uuid @@ -141,7 +145,8 @@ async def get_item_by_id(self, item_id: str, vault_id: str) -> Item: except HTTPError: raise FailedToRetrieveItemException( f"Unable to retrieve item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" + for {url} with message: {response.json().get('message')}", + status_code=response.status_code, ) return self.serializer.deserialize(response.content, "Item") diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 0d264b6..0ba451e 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -27,7 +27,7 @@ class Client: def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None: """Initialize client - + Args: url (str): The url of the 1Password Connect API token (str): The 1Password Service Account token @@ -42,11 +42,11 @@ def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) def create_session(self, url: str, token: str) -> httpx.Client: headers = self.build_headers(token) timeout = get_timeout() - + if self.config: client_args = self.config.get_client_args(url, headers, timeout) return httpx.Client(**client_args) - + return httpx.Client(base_url=url, headers=headers, timeout=timeout) def build_headers(self, token: str) -> Dict[str, str]: @@ -122,10 +122,14 @@ def get_item(self, item: str, vault: str) -> Item: if not is_valid_uuid(vault): vault_id = self.get_vault_by_title(vault).id - if is_valid_uuid(item): - return self.get_item_by_id(item, vault_id) - else: + if not is_valid_uuid(item): return self.get_item_by_title(item, vault_id) + try: + return self.get_item_by_id(item, vault_id) + except FailedToRetrieveItemException as exc: + if exc.status_code == 404: + return self.get_item_by_title(item, vault_id) + raise def get_item_by_id(self, item_id: str, vault_id: str) -> Item: """Get a specific item by uuid @@ -148,7 +152,8 @@ def get_item_by_id(self, item_id: str, vault_id: str) -> Item: except HTTPError: raise FailedToRetrieveItemException( f"Unable to retrieve item. Received {response.status_code}\ - for {url} with message: {response.json().get('message')}" + for {url} with message: {response.json().get('message')}", + status_code=response.status_code, ) return self.serializer.deserialize(response.content, "Item") @@ -398,13 +403,13 @@ def sanitize_for_serialization(self, obj): def new_client(url: str, token: str, is_async: bool = False, config: Optional[ClientConfig] = None) -> Union[AsyncClient, Client]: """Builds a new client for interacting with 1Password Connect - + Args: url (str): The url of the 1Password Connect API token (str): The 1Password Service Account token is_async (bool): Initialize async or sync client config (Optional[ClientConfig]): Optional configuration for httpx client - + Returns: Union[AsyncClient, Client]: The 1Password Connect client """ diff --git a/src/onepasswordconnectsdk/errors.py b/src/onepasswordconnectsdk/errors.py index add5db0..41db50d 100644 --- a/src/onepasswordconnectsdk/errors.py +++ b/src/onepasswordconnectsdk/errors.py @@ -11,7 +11,9 @@ class EnvironmentHostNotSetException(OnePasswordConnectSDKError, TypeError): class FailedToRetrieveItemException(OnePasswordConnectSDKError): - pass + def __init__(self, message, *, status_code=None): + super().__init__(message) + self.status_code = status_code class FailedToRetrieveVaultException(OnePasswordConnectSDKError): diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index b287477..124aadd 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -7,7 +7,7 @@ def is_valid_uuid(uuid): - if len(uuid) is not UUIDLength: + if len(uuid) != UUIDLength: return False for c in uuid: valid = (c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') diff --git a/src/tests/test_client_items.py b/src/tests/test_client_items.py index b670f7d..eae6634 100644 --- a/src/tests/test_client_items.py +++ b/src/tests/test_client_items.py @@ -11,6 +11,8 @@ VAULT_TITLE = "VaultA" ITEM_ID = "wepiqdxdzncjtnvmv5fegud4qy" ITEM_TITLE = "Test Login" +# 26 lowercase alphanumeric chars: treated as item id by is_valid_uuid but may be a title (#80) +ITEM_TITLE_26_CHARS = "abcdefghijklmnop1234567890" HOST = "https://mock_host" TOKEN = "jwt_token" SS_CLIENT = client.new_client(HOST, TOKEN) @@ -193,6 +195,58 @@ async def test_get_item_by_item_title_vault_title_async(respx_mock): assert item_mock.called +def test_get_item_26_char_title_falls_back_from_id_to_title(respx_mock): + """Item titles matching the SDK item-id shape should resolve via title after 404 on id.""" + expected_item = get_item() + expected_path_by_id = f"/v1/vaults/{VAULT_ID}/items/{ITEM_TITLE_26_CHARS}" + expected_path_by_title = ( + f'/v1/vaults/{VAULT_ID}/items?filter=title eq "{ITEM_TITLE_26_CHARS}"' + ) + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + by_id_mock = respx_mock.get(expected_path_by_id).mock( + return_value=Response(404, json={"message": "not found"}) + ) + items_by_title_mock = respx_mock.get(expected_path_by_title).mock( + return_value=Response(200, json=get_items_with_title(ITEM_TITLE_26_CHARS)) + ) + item_mock = respx_mock.get(expected_path_item).mock( + return_value=Response(200, json=expected_item) + ) + + item = SS_CLIENT.get_item(ITEM_TITLE_26_CHARS, VAULT_ID) + compare_items(expected_item, item) + assert by_id_mock.called + assert items_by_title_mock.called + assert item_mock.called + + +@pytest.mark.asyncio +async def test_get_item_26_char_title_falls_back_from_id_to_title_async(respx_mock): + expected_item = get_item() + expected_path_by_id = f"/v1/vaults/{VAULT_ID}/items/{ITEM_TITLE_26_CHARS}" + expected_path_by_title = ( + f'/v1/vaults/{VAULT_ID}/items?filter=title eq "{ITEM_TITLE_26_CHARS}"' + ) + expected_path_item = f"/v1/vaults/{VAULT_ID}/items/{ITEM_ID}" + + by_id_mock = respx_mock.get(expected_path_by_id).mock( + return_value=Response(404, json={"message": "not found"}) + ) + items_by_title_mock = respx_mock.get(expected_path_by_title).mock( + return_value=Response(200, json=get_items_with_title(ITEM_TITLE_26_CHARS)) + ) + item_mock = respx_mock.get(expected_path_item).mock( + return_value=Response(200, json=expected_item) + ) + + item = await SS_CLIENT_ASYNC.get_item(ITEM_TITLE_26_CHARS, VAULT_ID) + compare_items(expected_item, item) + assert by_id_mock.called + assert items_by_title_mock.called + assert item_mock.called + + def test_get_items(respx_mock): expected_items = get_items() expected_path = f"/v1/vaults/{VAULT_ID}/items" @@ -346,6 +400,12 @@ def compare_sections(expected_section, returned_section): assert expected_section.get("label") == returned_section.label +def get_items_with_title(title: str): + row = dict(get_items()[0]) + row["title"] = title + return [row] + + def get_items(): return [{ "id": "wepiqdxdzncjtnvmv5fegud4qy", From d14e36d6a1483478d0e99c185878f80255214463 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Thu, 2 Apr 2026 08:12:46 -0400 Subject: [PATCH 106/112] Allow passing in a token --- src/onepasswordconnectsdk/client.py | 34 ++++++++++++++++------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 0d264b6..ebb1cb5 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -27,7 +27,7 @@ class Client: def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None: """Initialize client - + Args: url (str): The url of the 1Password Connect API token (str): The 1Password Service Account token @@ -42,11 +42,11 @@ def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) def create_session(self, url: str, token: str) -> httpx.Client: headers = self.build_headers(token) timeout = get_timeout() - + if self.config: client_args = self.config.get_client_args(url, headers, timeout) return httpx.Client(**client_args) - + return httpx.Client(base_url=url, headers=headers, timeout=timeout) def build_headers(self, token: str) -> Dict[str, str]: @@ -398,13 +398,13 @@ def sanitize_for_serialization(self, obj): def new_client(url: str, token: str, is_async: bool = False, config: Optional[ClientConfig] = None) -> Union[AsyncClient, Client]: """Builds a new client for interacting with 1Password Connect - + Args: url (str): The url of the 1Password Connect API token (str): The 1Password Service Account token is_async (bool): Initialize async or sync client config (Optional[ClientConfig]): Optional configuration for httpx client - + Returns: Union[AsyncClient, Client]: The 1Password Connect client """ @@ -413,18 +413,19 @@ def new_client(url: str, token: str, is_async: bool = False, config: Optional[Cl return Client(url, token, config) -def new_client_from_environment(url: str = None) -> Union[AsyncClient, Client]: +def new_client_from_environment( + url: Optional[str] = None, token: Optional[str] = None +) -> Union[AsyncClient, Client]: """Builds a new client for interacting with 1Password Connect - using the OP_TOKEN environment variable + using OP_CONNECT_HOST and OP_CONNECT_TOKEN when url or token are omitted. Parameters: - url: The url of the 1Password Connect API - token: The 1Password Service Account token + url: The url of the 1Password Connect API; if omitted, read from OP_CONNECT_HOST. + token: The Connect token; if omitted, read from OP_CONNECT_TOKEN. Returns: - Client: The 1Password Connect client + Union[AsyncClient, Client]: The 1Password Connect client (async if OP_CONNECT_CLIENT_ASYNC is True). """ - token = os.environ.get(ENV_SERVICE_ACCOUNT_JWT_VARIABLE) is_async = os.environ.get(ENV_IS_ASYNC_CLIENT) == "True" if url is None: @@ -435,9 +436,12 @@ def new_client_from_environment(url: str = None) -> Union[AsyncClient, Client]: ) if token is None: - raise EnvironmentTokenNotSetException( - "There is no token available in the " - f"{ENV_SERVICE_ACCOUNT_JWT_VARIABLE} variable" - ) + token = os.environ.get(ENV_SERVICE_ACCOUNT_JWT_VARIABLE) + if token is None: + raise EnvironmentTokenNotSetException( + "There is no token available in the " + f"{ENV_SERVICE_ACCOUNT_JWT_VARIABLE} variable" + ) return new_client(url, token, is_async) + From 18e2f5743a743970ed2968d768d9c10992334ab7 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Tue, 28 Apr 2026 10:05:41 -0400 Subject: [PATCH 107/112] Add change log --- CHANGELOG.md | 12 +++++++++++- pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7064ff6..392cb77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,16 @@ --- +[//]: # (START/v2.1.0) +# v2.1.0 + +## Fixes + * Fix `new_client_from_environment()` to support optional token parameter, matching URL parameter behavior. {#88} + * Fix `get_item()` to successfully retrieve items with 26-character titles by adding fallback to title search when ID lookup fails. {#80} + * Updated Slack developer link in documentation. {#130} + +--- + [//]: # (START/v2.0.0) # v2.0.0 @@ -33,7 +43,7 @@ ## Fixes * Fix default http client timeout. {#102} - * Update override http client timeout env var name in readme. {#105} + * Update override http client timeout env var name in readme. {#105} --- diff --git a/pyproject.toml b/pyproject.toml index df766a6..3d3acf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "onepasswordconnectsdk" -version = "2.0.0" +version = "2.1.0" description = "Python SDK for 1Password Connect" license = "MIT" authors = ["1Password"] From 95e28f0d6558c85479e697a05e2071ed6f32baf7 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Tue, 5 May 2026 13:57:47 -0400 Subject: [PATCH 108/112] Move bullet to features --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 392cb77..4269893 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,10 @@ [//]: # (START/v2.1.0) # v2.1.0 +## Features + * Update `new_client_from_environment()` to support optional token parameter, matching URL parameter behavior. {#88} + ## Fixes - * Fix `new_client_from_environment()` to support optional token parameter, matching URL parameter behavior. {#88} * Fix `get_item()` to successfully retrieve items with 26-character titles by adding fallback to title search when ID lookup fails. {#80} * Updated Slack developer link in documentation. {#130} From 9fd569ae08f49084a8235e9d44befe95d3f3c907 Mon Sep 17 00:00:00 2001 From: Jill Regan Date: Fri, 22 May 2026 14:56:12 -0400 Subject: [PATCH 109/112] Update load-dict hint --- src/onepasswordconnectsdk/config.py | 10 +++---- src/tests/test_config.py | 45 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index bcde7a0..9de206b 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -49,14 +49,14 @@ def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float 'headers': headers, 'timeout': timeout, } - + # Set verify from ca_file first if self.ca_file: args['verify'] = self.ca_file - + # Allow httpx_options (including verify) to override args.update(self.httpx_options) - + return args @@ -100,7 +100,7 @@ def load_dict(client: "Client", config: dict): """ items: dict = {} - config_values: Dict[str, str] = {} + config_values: Dict[str, Optional[str]] = {} for field, tags in config.items(): item_tag = tags.get(ITEM_TAG) @@ -236,7 +236,7 @@ def _set_values_for_item( section_id = field.section.id except AttributeError: section_id = None - + if field.label == path_parts[1]: if ( section_id is None diff --git a/src/tests/test_config.py b/src/tests/test_config.py index 551cbcf..04835d5 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -73,6 +73,31 @@ def test_load_dict(respx_mock): assert config_with_values['password'] == PASSWORD_VALUE +def test_load_dict_empty_field_returns_none(respx_mock): + config_dict = { + "username": { + "opitem": ITEM_NAME1, + "opfield": ".username", + "opvault": VAULT_ID + }, + "empty": { + "opitem": ITEM_NAME1, + "opfield": ".empty_field", + "opvault": VAULT_ID + } + } + + respx_mock.get(f"v1/vaults/{VAULT_ID}/items?filter=title eq \"{ITEM_NAME1}\"").mock( + return_value=Response(200, json=[item_with_empty_field])) + respx_mock.get(f"v1/vaults/{VAULT_ID}/items/{ITEM_ID1}").mock( + return_value=Response(200, json=item_with_empty_field)) + + config_with_values = onepasswordconnectsdk.load_dict(SS_CLIENT, config_dict) + + assert config_with_values['username'] == USERNAME_VALUE + assert config_with_values['empty'] is None + + item = { "id": ITEM_ID1, "title": ITEM_NAME1, @@ -103,6 +128,26 @@ def test_load_dict(respx_mock): ] } +item_with_empty_field = { + "id": ITEM_ID1, + "title": ITEM_NAME1, + "vault": { + "id": VAULT_ID + }, + "category": "LOGIN", + "fields": [ + { + "id": "username", + "label": "username", + "value": USERNAME_VALUE + }, + { + "id": "empty_field", + "label": "empty_field" + } + ] +} + item2 = { "id": ITEM_ID2, "title": ITEM_NAME2, From b40c32c9372be9f12f7d9bf1cbbac817b64b3682 Mon Sep 17 00:00:00 2001 From: Lucy Butcher <89952129+libutcher@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:53:28 -0400 Subject: [PATCH 110/112] Add Terms of Service to README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 393d2d0..6c2b808 100644 --- a/README.md +++ b/README.md @@ -96,3 +96,6 @@ For more examples of how to use the SDK, check out [USAGE.md](USAGE.md). 1Password requests you practice responsible disclosure if you discover a vulnerability. Please file requests by sending an email to bugbounty@agilebits.com. + +*By accessing or using 1Password Developer Tools, you agree to the [API and SDK Terms of Service](https://1password.com/legal/api-sdk-terms-of-service).* + From 37d91721cbd48199e998920e5cdc4991ec066e75 Mon Sep 17 00:00:00 2001 From: Lucy Butcher <89952129+libutcher@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:33:23 -0400 Subject: [PATCH 111/112] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c2b808..78a5edf 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ The 1Password Connect SDK provides access to 1Password via [1Password Connect](https://developer.1password.com/docs/connect) hosted in your infrastructure. The library is intended to be used by Python applications to simplify accessing items in 1Password vaults. +*By accessing or using 1Password Developer Tools, you agree to the [API and SDK Terms of Service](https://1password.com/legal/api-sdk-terms-of-service).* + ## 🪄 See it in action Check the [Python Connect SDK Example](example/README.md) to see an example of item manipulation using the SDK that you can execute on your machine. @@ -96,6 +98,3 @@ For more examples of how to use the SDK, check out [USAGE.md](USAGE.md). 1Password requests you practice responsible disclosure if you discover a vulnerability. Please file requests by sending an email to bugbounty@agilebits.com. - -*By accessing or using 1Password Developer Tools, you agree to the [API and SDK Terms of Service](https://1password.com/legal/api-sdk-terms-of-service).* - From 4a823a3fea7fbd95c73a1c07d7764500ea4cc95a Mon Sep 17 00:00:00 2001 From: Bert Ramirez <13988480+bertrmz@users.noreply.github.com> Date: Fri, 5 Jun 2026 15:14:55 -0500 Subject: [PATCH 112/112] Scope ToS notice to the APIs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 78a5edf..ebc10d1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The 1Password Connect SDK provides access to 1Password via [1Password Connect](https://developer.1password.com/docs/connect) hosted in your infrastructure. The library is intended to be used by Python applications to simplify accessing items in 1Password vaults. -*By accessing or using 1Password Developer Tools, you agree to the [API and SDK Terms of Service](https://1password.com/legal/api-sdk-terms-of-service).* +*This project is licensed under [MIT](./LICENSE.md). Use of the 1Password APIs and services accessed through these tools is governed by the [1Password API Terms of Service](https://1password.com/legal/api-sdk-terms-of-service).* ## 🪄 See it in action