From c40063b8d7d64af4c8e52b4d56760c3033c6b088 Mon Sep 17 00:00:00 2001 From: Guillaume Mazoyer Date: Sat, 24 Aug 2024 17:00:23 +0200 Subject: [PATCH 1/3] Add type hints and improve code style with ruff --- docs/conf.py | 12 +++---- pyixapi/__init__.py | 2 +- pyixapi/core/api.py | 30 +++++++++-------- pyixapi/core/endpoint.py | 26 ++++++++------ pyixapi/core/query.py | 71 ++++++++++++++++++++------------------ pyixapi/core/response.py | 73 +++++++++++++++++++++------------------- pyixapi/core/token.py | 13 +++---- pyixapi/core/util.py | 2 +- pyproject.toml | 39 +++++++++++++-------- tests/test_api.py | 4 +-- tests/util.py | 17 ++++++---- 11 files changed, 163 insertions(+), 126 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e21d538..cbfa546 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# noqa: INP001 # # pyixapi documentation build configuration file, created by # sphinx-quickstart on Sun Sep 18 18:31:27 2022. @@ -16,12 +16,12 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -import os import sys +from pathlib import Path -from pkg_resources import get_distribution +from pyixapi import __version__ as pyixapi_version -sys.path.insert(0, os.path.abspath("../")) +sys.path.insert(0, str(Path().parent.resolve())) # -- General configuration ------------------------------------------------ @@ -49,14 +49,14 @@ # General information about the project. project = "pyixapi" -copyright = "2012, Guillaume Mazoyer" +copyright = "2022, Guillaume Mazoyer" author = "Guillaume Mazoyer" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The full version, including alpha/beta/rc tags. -release = get_distribution("pyixapi").version +release = pyixapi_version # # The short X.Y version. version = ".".join(release.split(".")[:2]) diff --git a/pyixapi/__init__.py b/pyixapi/__init__.py index ea606ce..ed03db5 100644 --- a/pyixapi/__init__.py +++ b/pyixapi/__init__.py @@ -1,4 +1,4 @@ -from .core.api import API as api +from .core.api import API as api # noqa: N811 from .core.api import __version__ # noqa: F401 from .core.query import ContentError, RequestError diff --git a/pyixapi/core/api.py b/pyixapi/core/api.py index 1b9e31c..efffa9c 100644 --- a/pyixapi/core/api.py +++ b/pyixapi/core/api.py @@ -1,3 +1,5 @@ +from typing import Any + import requests from pyixapi.core.endpoint import Endpoint @@ -32,7 +34,7 @@ __version__ = "0.2.2" -class API(object): +class API: """ The API object is the entrypoint for pyixapi. @@ -42,13 +44,13 @@ class API(object): def __init__( self, - url, - key, - secret, - access_token="", - refresh_token="", - user_agent=f"pyixapi/{__version__}", - ): + url: str, + key: str, + secret: str, + access_token: str = "", + refresh_token: str = "", + user_agent: str = f"pyixapi/{__version__}", + ) -> None: self.url = url.rstrip("/") self.key = key self.secret = secret @@ -90,7 +92,7 @@ def __init__( self.role_assignments = Endpoint(self, "role-assignments", model=RoleAssignment) @property - def version(self): + def version(self) -> int: """ Get the API version of IX-API. """ @@ -99,20 +101,20 @@ def version(self): ).get_version() @property - def accounts(self): + def accounts(self) -> Endpoint: return Endpoint( self, "customers" if self.version == 1 else "accounts", model=Account ) @property - def product_offerings(self): + def product_offerings(self) -> Endpoint: return Endpoint( self, "products" if self.version == 1 else "product-offerings", model=ProductOffering, ) - def authenticate(self): + def authenticate(self) -> Record: """ Authenticate and generate a pair of tokens. @@ -139,7 +141,7 @@ def authenticate(self): return Record(r, self, self.auth) - def refresh_authentication(self): + def refresh_authentication(self) -> Record: """ Prolong authentication by refreshing the tokens pair. """ @@ -154,7 +156,7 @@ def refresh_authentication(self): return Record(r, self, self.auth) - def health(self): + def health(self) -> dict[str, Any]: """ Get the health information from IX-API. diff --git a/pyixapi/core/endpoint.py b/pyixapi/core/endpoint.py index cd19b1e..a8768ff 100644 --- a/pyixapi/core/endpoint.py +++ b/pyixapi/core/endpoint.py @@ -1,9 +1,15 @@ +from http import HTTPStatus +from typing import TYPE_CHECKING + from pyixapi.core.query import Request, RequestError from pyixapi.core.response import Record, RecordSet from pyixapi.core.util import cat +if TYPE_CHECKING: + from .api import API + -class Endpoint(object): +class Endpoint: """ Represent actions available on endpoints in the IX-API. @@ -11,16 +17,16 @@ class Endpoint(object): object. """ - def __init__(self, api, name, model=None): + def __init__(self, api: "API", name: str, model: Record | None = None) -> None: self.return_obj = model if model else Record self.api = api self.url = cat(api.url, name) self.name = name - def __str__(self): + def __str__(self) -> str: return self.url - def all(self): + def all(self) -> RecordSet: """ Return all objects from an endpoint. """ @@ -31,7 +37,7 @@ def all(self): ) return RecordSet(self, r) - def filter(self, *args, **kwargs): + def filter(self, *args, **kwargs) -> RecordSet: """ Query the list of a given endpoint. Also take named arguments that match the usable filters on a given endpoint. @@ -44,7 +50,7 @@ def filter(self, *args, **kwargs): ) return RecordSet(self, r) - def get(self, *args, **kwargs): + def get(self, *args, **kwargs) -> Record | None: """ Return a single object from an endpoint. """ @@ -78,12 +84,12 @@ def get(self, *args, **kwargs): try: return next(RecordSet(self, r), None) except RequestError as e: - if e.req.status_code == 404: + if e.req.status_code == HTTPStatus.NOT_FOUND: return None - else: - raise e - def create(self, *args, **kwargs): + raise e + + def create(self, *args, **kwargs) -> Record: """ Creates an object on an endpoint. diff --git a/pyixapi/core/query.py b/pyixapi/core/query.py index 76ebfe0..d27e182 100644 --- a/pyixapi/core/query.py +++ b/pyixapi/core/query.py @@ -1,4 +1,6 @@ import json +from http import HTTPStatus +from typing import Any from pyixapi.core.util import cat @@ -10,10 +12,10 @@ class RequestError(Exception): return is JSON we decode and add it to the message. """ - def __init__(self, r): - if r.status_code == 404: + def __init__(self, r) -> None: + if r.status_code == HTTPStatus.NOT_FOUND: self.message = f"The requested url: {r.url} could not be found." - elif r.status_code == 401: + elif r.status_code == HTTPStatus.UNAUTHORIZED: self.message = ( "Authentication credentials are invalid, tokens renewal required." ) @@ -29,11 +31,11 @@ def __init__(self, r): f"{r.reason} but details were not found as JSON." ) - super(RequestError, self).__init__(r) + super().__init__(r) self.req = r self.error = r.text - def __str__(self): + def __str__(self) -> str: return self.message @@ -44,19 +46,19 @@ class ContentError(Exception): those cases. """ - def __init__(self, req): - super(ContentError, self).__init__(req) + def __init__(self, req) -> None: + super().__init__(req) self.req = req self.error = ( "The server returned invalid (non-JSON) data. Maybe not an IX-API server?" ) - def __str__(self): + def __str__(self) -> str: return self.error -class Request(object): +class Request: """ Create requests to the IX-API. @@ -71,7 +73,7 @@ class Request(object): def __init__( self, base, http_session, filters=None, key=None, token=None, user_agent=None - ): + ) -> None: self.base = base self.filters = filters or None self.key = key @@ -80,7 +82,7 @@ def __init__( self.url = self.base if not key else cat(self.base, key) self.user_agent = user_agent - def get_openapi(self): + def get_openapi(self) -> Any: """ Get the OpenAPI Spec. """ @@ -90,10 +92,10 @@ def get_openapi(self): ) if req.ok: return req.json() - else: - raise RequestError(req) - def get_version(self): + raise RequestError(req) + + def get_version(self) -> int: """ Get the API version of IX-API. @@ -107,7 +109,7 @@ def get_version(self): except RequestError: return 1 - def get_health(self): + def get_health(self) -> dict[str, Any]: """ Get the health from /api/health endpoint in IX-API. """ @@ -117,11 +119,17 @@ def get_health(self): r = self.http_session.get(cat(self.base, "health"), headers=headers) if r.ok: return r.json() - else: - raise RequestError(r) - def _make_call(self, verb="get", url_override=None, add_params=None, data=None): - if verb in ("post", "put") or verb == "delete" and data: + raise RequestError(r) + + def _make_call( + self, + verb: str = "get", + url_override: str | None = None, + add_params: dict[str, Any] | None = None, + data: Any | None = None, + ) -> Any: + if verb in {"post", "put"} or verb == "delete" and data: headers = {"Content-Type": "application/json;"} else: headers = {"accept": "application/json;"} @@ -143,17 +151,17 @@ def _make_call(self, verb="get", url_override=None, add_params=None, data=None): if verb == "delete": if r.ok: return True - else: - raise RequestError(r) - elif r.ok: + + raise RequestError(r) + if r.ok: try: return r.json() - except json.JSONDecodeError: - raise ContentError(r) + except json.JSONDecodeError as e: + raise ContentError(r) from e else: raise RequestError(r) - def get(self, add_params=None): + def get(self, add_params: dict[str, Any] | None = None) -> Any: """ Make a GET request to IX-API. @@ -166,13 +174,12 @@ def get(self, add_params=None): req = self._make_call(add_params=add_params) if isinstance(req, list): self.count = len(req) - for i in req: - yield i + yield from req else: self.count = len(req) yield req - def put(self, data): + def put(self, data: Any) -> Any: """ Make a PUT request to IX-API. @@ -184,7 +191,7 @@ def put(self, data): """ return self._make_call(verb="put", data=data) - def post(self, data): + def post(self, data: Any) -> Any: """ Make a POST request to IX-API. @@ -196,7 +203,7 @@ def post(self, data): """ return self._make_call(verb="post", data=data) - def delete(self, data=None): + def delete(self, data: Any | None = None) -> Any: """ Make a DELETE request to IX-API. @@ -210,7 +217,7 @@ def delete(self, data=None): """ return self._make_call(verb="delete", data=data) - def patch(self, data): + def patch(self, data: Any) -> Any: """ Make a PATCH request to IX-API. @@ -222,7 +229,7 @@ def patch(self, data): """ return self._make_call(verb="patch", data=data) - def options(self): + def options(self) -> Any: """ Make an OPTIONS request to IX-API. diff --git a/pyixapi/core/response.py b/pyixapi/core/response.py index 7391bc3..e33a894 100644 --- a/pyixapi/core/response.py +++ b/pyixapi/core/response.py @@ -1,8 +1,14 @@ +from typing import TYPE_CHECKING, Any, Generator + from pyixapi.core.query import Request from pyixapi.core.util import Hashabledict +if TYPE_CHECKING: + from pyixapi.core.api import API + from pyixapi.core.endpoint import Endpoint + -def get_return(lookup): +def get_return(lookup: Any) -> int: """ Return simple representations for items passed to lookup. @@ -10,12 +16,11 @@ def get_return(lookup): lookup. We check if it's a :py:class:`.Record`, if so simply return its ID. """ if isinstance(lookup, Record): - return getattr(lookup, "id") - else: - return lookup + return lookup.id + return lookup -class RecordSet(object): +class RecordSet: """ Iterator containing :py:class:`.Record` objects. @@ -52,16 +57,16 @@ class RecordSet(object): >>> """ - def __init__(self, endpoint, request, **kwargs): + def __init__(self, endpoint: "Endpoint", request: "Request") -> None: self.endpoint = endpoint self.request = request self.response = self.request.get() self._response_cache = [] - def __iter__(self): + def __iter__(self) -> "RecordSet": return self - def __next__(self): + def __next__(self) -> "Record": if self._response_cache: return self.endpoint.return_obj( self._response_cache.pop(), self.endpoint.api, self.endpoint @@ -70,7 +75,7 @@ def __next__(self): next(self.response), self.endpoint.api, self.endpoint ) - def __len__(self): + def __len__(self) -> int: try: return self.request.count except AttributeError: @@ -81,7 +86,7 @@ def __len__(self): return self.request.count -class Record(object): +class Record: """ Create Python objects from IX-API responses. @@ -144,7 +149,9 @@ class Record(object): url = None - def __init__(self, values, api, endpoint): + def __init__( + self, values: dict[str, Any], api: "API", endpoint: "Endpoint" + ) -> None: self._full_cache = [] self._init_cache = [] self.api = api @@ -153,7 +160,7 @@ def __init__(self, values, api, endpoint): if values: self._parse_values(values) - def __iter__(self): + def __iter__(self) -> Generator[Any, Any, None]: for i in dict(self._init_cache): a = getattr(self, i) if isinstance(a, Record): @@ -166,31 +173,27 @@ def __iter__(self): def __getitem__(self, k): return dict(self)[k] - def __str__(self): - if hasattr(self, "id"): - return self.id - else: - return str(self.endpoint) + def __str__(self) -> str: + return getattr(self, "id", str(self.endpoint)) - def __repr__(self): + def __repr__(self) -> str: return str(dict(self)) - def __getstate__(self): + def __getstate__(self) -> dict[str, Any]: return self.__dict__ - def __setstate__(self, d): + def __setstate__(self, d) -> None: self.__dict__.update(d) def __key__(self): if hasattr(self, "id"): return (self.endpoint.name, self.id) - else: - return self.endpoint.name + return self.endpoint.name - def __hash__(self): + def __hash__(self) -> int: return hash(self.__key__()) - def __eq__(self, other): + def __eq__(self, other) -> bool: if isinstance(other, Record): return self.__key__() == other.__key__() return NotImplemented @@ -211,24 +214,26 @@ def list_parser(key_name, list_item): # This is *list_parser*, so if the custom model field is not # a list (or is not defined), just return the default model return self.default_ret(list_item, self.api, self.endpoint) - else: - model = lookup[0] - return model(list_item, self.api, self.endpoint) + + model = lookup[0] + return model(list_item, self.api, self.endpoint) return list_item for k, v in values.items(): + parsed_value = None + if isinstance(v, dict): lookup = getattr(self.__class__, k, None) if lookup: - v = lookup(v, self.api, self.endpoint) - self._add_cache((k, v)) + parsed_value = lookup(v, self.api, self.endpoint) + self._add_cache((k, parsed_value)) elif isinstance(v, list): - v = [list_parser(k, i) for i in v] - to_cache = list(v) + parsed_value = [list_parser(k, i) for i in v] + to_cache = list(parsed_value) self._add_cache((k, to_cache)) else: self._add_cache((k, v)) - setattr(self, k, v) + setattr(self, k, parsed_value or v) def serialize(self, nested=False, init=False): """ @@ -248,7 +253,7 @@ def serialize(self, nested=False, init=False): for i in dict(self): current_val = getattr(self, i) if not init else init_vals.get(i) if isinstance(current_val, Record): - current_val = getattr(current_val, "serialize")(nested=True) + current_val = current_val.serialize(nested=True) if isinstance(current_val, list): current_val = [ v.id if isinstance(v, Record) else v for v in current_val @@ -268,7 +273,7 @@ def fmt_dict(k, v): init = Hashabledict( {fmt_dict(k, v) for k, v in self.serialize(init=True).items()} ) - return set([i[0] for i in set(current.items()) ^ set(init.items())]) + return {i[0] for i in set(current.items()) ^ set(init.items())} def updates(self): """ diff --git a/pyixapi/core/token.py b/pyixapi/core/token.py index b1bd743..33a40fa 100644 --- a/pyixapi/core/token.py +++ b/pyixapi/core/token.py @@ -4,11 +4,11 @@ import jwt -class TokenException(Exception): +class TokenError(Exception): pass -class InvalidTokenException(Exception): +class InvalidTokenError(Exception): pass @@ -26,8 +26,8 @@ class Token: """ def __init__(self, token: str, expires_at: datetime) -> None: - self.encoded: str = token # Cache signed token data - self.expires_at: datetime = expires_at + self.encoded = token # Cache signed token data + self.expires_at = expires_at def __str__(self) -> str: return self.encoded @@ -40,6 +40,7 @@ def issued_at(self) -> datetime: warnings.warn( "Property 'issued_at' is deprecated and value will always be set to now.", DeprecationWarning, + stacklevel=1, ) return datetime.now(tz=timezone.utc) @@ -69,7 +70,7 @@ def from_jwt(cls, token: str) -> "Token": try: payload = jwt.decode(token, options={"verify_signature": False}) except Exception as e: - raise TokenException(e) + raise TokenError(e) from e try: return cls( @@ -77,4 +78,4 @@ def from_jwt(cls, token: str) -> "Token": expires_at=datetime.fromtimestamp(payload["exp"], tz=timezone.utc), ) except Exception as e: - raise InvalidTokenException(e) + raise InvalidTokenError(e) from e diff --git a/pyixapi/core/util.py b/pyixapi/core/util.py index 852bf29..f1ab357 100644 --- a/pyixapi/core/util.py +++ b/pyixapi/core/util.py @@ -2,7 +2,7 @@ class Hashabledict(dict): - def __hash__(self): + def __hash__(self) -> int: return hash(frozenset(self)) diff --git a/pyproject.toml b/pyproject.toml index 810cb57..2485f31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,19 +34,33 @@ exclude = [".git", ".tox", ".venv", "env", "_build", "build", "dist", "examples" preview = true select = [ - "C90", # mccabe complexity - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort-like checks - "DTZ", # flake8-datetimez - "ICN", # flake8-import-conventions - "TCH", # flake8-type-checking - "T10", # flake8-debugger - "Q", # flake8-quotes - "YTT", # flake8-2020 + "ASYNC", # flake8-async + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "C90", # mccabe complexity + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "EXE", # flake8-executable + "F", # pyflakes + "I", # isort-like checks + "ICN", # flake8-import-conventions + "INP", # flake8-no-pep420 + "N", # pep8-naming + "PIE", # flake8-pie + "PL", # pylint + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi + "Q", # flake8-quotes + "RET", # flake8-return + "TCH", # flake8-type-checking + "T10", # flake8-debugger + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 ] +ignore = ["PLR0913", "PLR0917", "PLW3201"] + task-tags = ["FIXME", "TODO", "XXX"] [tool.ruff.format] @@ -55,9 +69,6 @@ indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" -[tool.ruff.lint.isort] -known-first-party = ["pyixapi"] - [tool.ruff.lint.pycodestyle] max-line-length = 88 diff --git a/tests/test_api.py b/tests/test_api.py index 8798000..91dfe26 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -50,7 +50,7 @@ class ResponseWithFailure: class ResponseWithSuccess: ok = True - def json(self): + def json(self): # noqa: PLR6301 return {"status": "pass", "version": 2} @patch( @@ -74,7 +74,7 @@ class ApiHealthTestCase(unittest.TestCase): class ResponseWithHealth: ok = True - def json(self): + def json(self): # noqa: PLR6301 return {"status": "pass", "version": "2"} @patch( diff --git a/tests/util.py b/tests/util.py index 9f463b6..965904c 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,23 +1,28 @@ import json from pathlib import Path -from typing import Any, Optional +from typing import Any -class Response(object): +class Response: def __init__( self, - fixture: Optional[str], + fixture: str | None, status_code: int = 200, ok: bool = True, - content: Optional[Any] = None, + content: Any | None = None, ) -> None: self.status_code = status_code - self.content = json.dumps(content) if content else self.load_fixture(fixture) + + if content: + self.content = json.dumps(content) + else: + self.load_fixture(fixture) + self.ok = ok def load_fixture(self, path: str) -> str: f = Path("tests/fixtures") / path - return f.read_text() + self.content = f.read_text() def json(self) -> Any: return json.loads(self.content) From 19b51154c787e02f810d3601b259c5134ccd202c Mon Sep 17 00:00:00 2001 From: Guillaume Mazoyer Date: Sat, 24 Aug 2024 17:06:40 +0200 Subject: [PATCH 2/3] Make line length 120 characters --- docs/conf.py | 4 +--- pyixapi/core/api.py | 38 +++++++++++--------------------------- pyixapi/core/query.py | 28 +++++++--------------------- pyixapi/core/response.py | 20 +++++--------------- pyproject.toml | 7 +------ 5 files changed, 25 insertions(+), 72 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index cbfa546..9e0f134 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -98,9 +98,7 @@ # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] -html_sidebars = { - "**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"] -} +html_sidebars = {"**": ["globaltoc.html", "relations.html", "sourcelink.html", "searchbox.html"]} # -- Options for HTMLHelp output ------------------------------------------ diff --git a/pyixapi/core/api.py b/pyixapi/core/api.py index efffa9c..e74b2fd 100644 --- a/pyixapi/core/api.py +++ b/pyixapi/core/api.py @@ -67,27 +67,17 @@ def __init__( self.facilities = Endpoint(self, "facilities", model=Facility) self.ips = Endpoint(self, "ips", model=IP) self.macs = Endpoint(self, "macs", model=MAC) - self.network_feature_configs = Endpoint( - self, "network-feature-configs", model=NetworkFeatureConfig - ) + self.network_feature_configs = Endpoint(self, "network-feature-configs", model=NetworkFeatureConfig) self.network_features = Endpoint(self, "network-features", model=NetworkFeature) - self.network_service_configs = Endpoint( - self, "network-service-configs", model=NetworkServiceConfig - ) + self.network_service_configs = Endpoint(self, "network-service-configs", model=NetworkServiceConfig) self.network_services = Endpoint(self, "network-services", model=NetworkService) self.pops = Endpoint(self, "pops", model=PoP) # Version 2+ - self.member_joining_rules = Endpoint( - self, "member-joining-rules", model=MemberJoiningRule - ) + self.member_joining_rules = Endpoint(self, "member-joining-rules", model=MemberJoiningRule) self.metro_areas = Endpoint(self, "metro-areas", model=MetroArea) - self.metro_area_networks = Endpoint( - self, "metro-area-networks", model=MetroAreaNetwork - ) + self.metro_area_networks = Endpoint(self, "metro-area-networks", model=MetroAreaNetwork) self.ports = Endpoint(self, "ports", model=Port) - self.port_reservations = Endpoint( - self, "port-reservations", model=PortReservation - ) + self.port_reservations = Endpoint(self, "port-reservations", model=PortReservation) self.roles = Endpoint(self, "roles", model=Role) self.role_assignments = Endpoint(self, "role-assignments", model=RoleAssignment) @@ -96,15 +86,11 @@ def version(self) -> int: """ Get the API version of IX-API. """ - return Request( - base=self.url, token=self.access_token, http_session=self.http_session - ).get_version() + return Request(base=self.url, token=self.access_token, http_session=self.http_session).get_version() @property def accounts(self) -> Endpoint: - return Endpoint( - self, "customers" if self.version == 1 else "accounts", model=Account - ) + return Endpoint(self, "customers" if self.version == 1 else "accounts", model=Account) @property def product_offerings(self) -> Endpoint: @@ -132,9 +118,9 @@ def authenticate(self) -> Record: if self.refresh_token and not self.refresh_token.is_expired: return self.refresh_authentication() - r = Request( - cat(self.url, "auth", "token"), http_session=self.http_session - ).post(data={"api_key": self.key, "api_secret": self.secret}) + r = Request(cat(self.url, "auth", "token"), http_session=self.http_session).post( + data={"api_key": self.key, "api_secret": self.secret} + ) self.access_token = Token.from_jwt(r["access_token"]) self.refresh_token = Token.from_jwt(r["refresh_token"]) @@ -165,6 +151,4 @@ def health(self) -> dict[str, Any]: if self.version == 1: return {} - return Request( - base=self.url, token=self.access_token, http_session=self.http_session - ).get_health() + return Request(base=self.url, token=self.access_token, http_session=self.http_session).get_health() diff --git a/pyixapi/core/query.py b/pyixapi/core/query.py index d27e182..9fa27a2 100644 --- a/pyixapi/core/query.py +++ b/pyixapi/core/query.py @@ -16,19 +16,13 @@ def __init__(self, r) -> None: if r.status_code == HTTPStatus.NOT_FOUND: self.message = f"The requested url: {r.url} could not be found." elif r.status_code == HTTPStatus.UNAUTHORIZED: - self.message = ( - "Authentication credentials are invalid, tokens renewal required." - ) + self.message = "Authentication credentials are invalid, tokens renewal required." else: try: - self.message = ( - f"The request failed with code {r.status_code} " - f"{r.reason}: {r.json()}" - ) + self.message = f"The request failed with code {r.status_code} " f"{r.reason}: {r.json()}" except ValueError: self.message = ( - f"The request failed with code {r.status_code} " - f"{r.reason} but details were not found as JSON." + f"The request failed with code {r.status_code} " f"{r.reason} but details were not found as JSON." ) super().__init__(r) @@ -50,9 +44,7 @@ def __init__(self, req) -> None: super().__init__(req) self.req = req - self.error = ( - "The server returned invalid (non-JSON) data. Maybe not an IX-API server?" - ) + self.error = "The server returned invalid (non-JSON) data. Maybe not an IX-API server?" def __str__(self) -> str: return self.error @@ -71,9 +63,7 @@ class Request: dict. """ - def __init__( - self, base, http_session, filters=None, key=None, token=None, user_agent=None - ) -> None: + def __init__(self, base, http_session, filters=None, key=None, token=None, user_agent=None) -> None: self.base = base self.filters = filters or None self.key = key @@ -87,9 +77,7 @@ def get_openapi(self) -> Any: Get the OpenAPI Spec. """ headers = {"Content-Type": "application/json;"} - req = self.http_session.get( - cat(self.base, "docs/?format=openapi"), headers=headers - ) + req = self.http_session.get(cat(self.base, "docs/?format=openapi"), headers=headers) if req.ok: return req.json() @@ -144,9 +132,7 @@ def _make_call( if add_params: params.update(add_params) - r = getattr(self.http_session, verb)( - url_override or self.url, headers=headers, params=params, json=data - ) + r = getattr(self.http_session, verb)(url_override or self.url, headers=headers, params=params, json=data) if verb == "delete": if r.ok: diff --git a/pyixapi/core/response.py b/pyixapi/core/response.py index e33a894..e19dd41 100644 --- a/pyixapi/core/response.py +++ b/pyixapi/core/response.py @@ -68,12 +68,8 @@ def __iter__(self) -> "RecordSet": def __next__(self) -> "Record": if self._response_cache: - return self.endpoint.return_obj( - self._response_cache.pop(), self.endpoint.api, self.endpoint - ) - return self.endpoint.return_obj( - next(self.response), self.endpoint.api, self.endpoint - ) + return self.endpoint.return_obj(self._response_cache.pop(), self.endpoint.api, self.endpoint) + return self.endpoint.return_obj(next(self.response), self.endpoint.api, self.endpoint) def __len__(self) -> int: try: @@ -149,9 +145,7 @@ class Record: url = None - def __init__( - self, values: dict[str, Any], api: "API", endpoint: "Endpoint" - ) -> None: + def __init__(self, values: dict[str, Any], api: "API", endpoint: "Endpoint") -> None: self._full_cache = [] self._init_cache = [] self.api = api @@ -255,9 +249,7 @@ def serialize(self, nested=False, init=False): if isinstance(current_val, Record): current_val = current_val.serialize(nested=True) if isinstance(current_val, list): - current_val = [ - v.id if isinstance(v, Record) else v for v in current_val - ] + current_val = [v.id if isinstance(v, Record) else v for v in current_val] r[i] = current_val return r @@ -270,9 +262,7 @@ def fmt_dict(k, v): return k, v current = Hashabledict({fmt_dict(k, v) for k, v in self.serialize().items()}) - init = Hashabledict( - {fmt_dict(k, v) for k, v in self.serialize(init=True).items()} - ) + init = Hashabledict({fmt_dict(k, v) for k, v in self.serialize(init=True).items()}) return {i[0] for i in set(current.items()) ^ set(init.items())} def updates(self): diff --git a/pyproject.toml b/pyproject.toml index 2485f31..656cbec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,13 +26,11 @@ pytest = "*" ruff = "~0.7" [tool.ruff] -line-length = 88 +line-length = 120 exclude = [".git", ".tox", ".venv", "env", "_build", "build", "dist", "examples", "__main__.py"] [tool.ruff.lint] -preview = true - select = [ "ASYNC", # flake8-async "B", # flake8-bugbear @@ -69,9 +67,6 @@ indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" -[tool.ruff.lint.pycodestyle] -max-line-length = 88 - [tool.ruff.lint.mccabe] max-complexity = 31 From a88652a41e8dc34b4cba7c9756c3e6eca3dbb75f Mon Sep 17 00:00:00 2001 From: Guillaume Mazoyer Date: Sat, 24 Aug 2024 18:36:13 +0200 Subject: [PATCH 3/3] Ignore local test file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index ac15f9f..436982d 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,6 @@ ENV/ # Visual Studio Code settings .vscode/ + +# Local testing +pyixapi/__main__.py