diff --git a/poetry.lock b/poetry.lock index 6ccac8c..6ac227f 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.6.1 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 = [ @@ -27,7 +26,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 +37,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 +48,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 +123,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 +137,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 = [ @@ -157,7 +151,6 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""} name = "httpcore" version = "0.16.3" description = "A minimal low-level HTTP client." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -169,17 +162,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 +187,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 = [ @@ -215,7 +206,6 @@ files = [ name = "importlib-metadata" version = "6.7.0" description = "Read metadata from Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -236,7 +226,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -248,7 +237,6 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -260,7 +248,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 = [ @@ -279,7 +266,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 = [ @@ -303,7 +289,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 = [ @@ -323,7 +308,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 +326,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 +340,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 +354,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 +371,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 +382,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 +393,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -426,7 +404,6 @@ files = [ 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 = [ @@ -438,7 +415,6 @@ files = [ 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 = [ @@ -453,4 +429,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.7" -content-hash = "5c06df5db167647617c8fb7afc9da451dcec9d10e24df37e6024124cccd6da60" +content-hash = "21400e8862cff02e2f4a774aacc9985773cb54ecfbb0982200c6c5418a219663" diff --git a/pyproject.toml b/pyproject.toml index dd54dd1..3beac06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ repository = "https://github.com/1Password/connect-sdk-python" python = "^3.7" python-dateutil = "^2.8.1" httpx = "^0.23.3" +typing-extensions = "<4.8.0" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" diff --git a/src/onepasswordconnectsdk/async_client.py b/src/onepasswordconnectsdk/async_client.py index 415924c..aadfb60 100644 --- a/src/onepasswordconnectsdk/async_client.py +++ b/src/onepasswordconnectsdk/async_client.py @@ -3,6 +3,7 @@ from httpx import HTTPError import json import os +from typing import Optional from onepasswordconnectsdk.serializer import Serializer from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder @@ -56,7 +57,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: Optional[str] = None): url = content_path if content_path is None: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() @@ -164,7 +165,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: Optional[str] = None): """Returns a list of item summaries for the specified vault Args: diff --git a/src/onepasswordconnectsdk/client.py b/src/onepasswordconnectsdk/client.py index 4202ccf..12cd713 100644 --- a/src/onepasswordconnectsdk/client.py +++ b/src/onepasswordconnectsdk/client.py @@ -3,6 +3,8 @@ from httpx import HTTPError import json import os +from typing import Optional, Union, overload +from typing_extensions import Literal from onepasswordconnectsdk.async_client import AsyncClient from onepasswordconnectsdk.serializer import Serializer @@ -63,7 +65,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: Optional[str] = None): url = content_path if content_path is None: url = PathBuilder().vaults(vault_id).items(item_id).files(file_id).content().build() @@ -170,7 +172,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: Optional[str] = None): """Returns a list of item summaries for the specified vault Args: @@ -380,7 +382,29 @@ def sanitize_for_serialization(self, obj): return self.serializer.sanitize_for_serialization(obj) -def new_client(url: str, token: str, is_async: bool = False): +@overload +def new_client( + url: str, token: str, is_async: Literal[False] = ... +) -> Client: + ... + + +@overload +def new_client( + url: str, token: str, is_async: Literal[True] = ... +) -> AsyncClient: + ... + +@overload +def new_client( + url: str, token: str, is_async: bool = ... +) -> Union[Client, AsyncClient]: + ... + + +def new_client( + url: str, token: str, is_async: bool = False +) -> Union[Client, AsyncClient]: """Builds a new client for interacting with 1Password Connect Parameters: url: The url of the 1Password Connect API @@ -395,19 +419,45 @@ def new_client(url: str, token: str, is_async: bool = False): return Client(url, token) -def new_client_from_environment(url: str = None): +@overload +def new_client_from_environment( + url: Optional[str] = ..., is_async: Literal[False] = ... +) -> Client: + ... + + +@overload +def new_client_from_environment( + url: Optional[str] = ..., is_async: Literal[True] = ... +) -> AsyncClient: + ... + + +@overload +def new_client_from_environment( + url: Optional[str] = ..., is_async: None = ... +) -> Union[Client, AsyncClient]: + ... + + +def new_client_from_environment( + url: Optional[str] = None, is_async: Optional[bool] = None +) -> Union[Client, AsyncClient]: """Builds a new client for interacting with 1Password Connect using the OP_TOKEN environment variable Parameters: url: The url of the 1Password Connect API - token: The 1Password Service Account token + is_async: If set to True, return an async client; if False, return a sync client. + If not set or set to None, return an async client if the OP_CONNECT_CLIENT_ASYNC + environment variable is set to "True", a sync client otherwise. Returns: - Client: The 1Password Connect client + Client or AsyncClient: The 1Password Connect client """ token = os.environ.get(ENV_SERVICE_ACCOUNT_JWT_VARIABLE) - is_async = os.environ.get(ENV_IS_ASYNC_CLIENT) == "True" + if is_async is None: + is_async = os.environ.get(ENV_IS_ASYNC_CLIENT).lower() in ("t", "true", "1") if url is None: url = os.environ.get(CONNECT_HOST_ENV_VARIABLE) diff --git a/src/onepasswordconnectsdk/config.py b/src/onepasswordconnectsdk/config.py index 7398232..5beeaf2 100644 --- a/src/onepasswordconnectsdk/config.py +++ b/src/onepasswordconnectsdk/config.py @@ -1,6 +1,6 @@ import os import shlex -from typing import List, Dict +from typing import List, Dict, Optional from onepasswordconnectsdk.client import Client from onepasswordconnectsdk.models import ( SummaryItem, @@ -166,7 +166,7 @@ def _set_values_for_item( client: Client, parsed_item: ParsedItem, config_dict={}, - config_object: object = None, + config_object: Optional[object] = None, ): # Fetching the full item item: Item = client.get_item(parsed_item.item_title, parsed_item.vault_uuid) diff --git a/src/onepasswordconnectsdk/utils.py b/src/onepasswordconnectsdk/utils.py index da35d50..efe7286 100644 --- a/src/onepasswordconnectsdk/utils.py +++ b/src/onepasswordconnectsdk/utils.py @@ -1,3 +1,6 @@ +from typing import Optional + + UUIDLength = 26 @@ -27,19 +30,19 @@ def __init__(self, version: str = "/v1"): def build(self) -> str: return self.path - def vaults(self, uuid: str = None) -> 'PathBuilder': + def vaults(self, uuid: Optional[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': + def items(self, uuid: Optional[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': + def files(self, uuid: Optional[str] = None) -> 'PathBuilder': self._append_path("files") if uuid is not None: self._append_path(uuid) @@ -54,7 +57,7 @@ def query(self, key: str, value: str) -> 'PathBuilder': self._append_path(query=key_value_pair) return self - def _append_path(self, path_chunk: str = None, query: str = None) -> 'PathBuilder': + def _append_path(self, path_chunk: Optional[str] = None, query: Optional[str] = None) -> 'PathBuilder': if path_chunk is not None: self.path += f"/{path_chunk}" if query is not None: