From a65283958b67d9d64e0378e7c4014382f16e79e5 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 16 Jan 2024 11:33:31 -0500 Subject: [PATCH] implement NeonUser and NeonAPIKey classes --- Makefile | 17 +- Pipfile | 1 + Pipfile.lock | 21 +- .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 233 bytes neon_client/exceptions.py | 6 + neon_client/http_client.py | 7 +- neon_client/openapi_models.py | 1835 ++++++++++------- neon_client/resources.py | 103 +- neon_client/v2_client.py | 85 +- 9 files changed, 1298 insertions(+), 777 deletions(-) create mode 100644 neon_client/__pycache__/__init__.cpython-311.pyc diff --git a/Makefile b/Makefile index ff2c6c7..466630b 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,9 @@ gen-model: fetch-v2-schema - datamodel-codegen --input v2.json --output model.py --use-standard-collections --output models.py \ + datamodel-codegen \ + --input v2.json \ + --collapse-root-models \ + --output neon_client/openapi_models.py \ + --use-standard-collections \ --output-model-type pydantic_v2.BaseModel \ --input-file-type openapi \ --use-standard-collections \ @@ -8,12 +12,17 @@ gen-model: fetch-v2-schema --use-schema-description \ --snake-case-field \ --enable-version-header \ - --enum-field-as-literal one \ --use-double-quotes \ --field-constraints \ --allow-population-by-field-name \ - --strict-nullable \ - --use-title-as-name + --use-title-as-name \ + --reuse-model \ + --field-constraints \ + --disable-appending-item-suffix \ + --allow-extra-fields \ + --use-annotated \ + --capitalise-enum-members \ + --use-unique-items-as-set fetch-v2-schema: curl -O https://neon.tech/api_spec/release/v2.json diff --git a/Pipfile b/Pipfile index 48340cf..6c233c2 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ openapi-python-client = "*" pip = "*" datamodel-code-generator = {extras = ["http"], version = "*"} requests = "*" +fastapi = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index 147e705..ef84c0b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "704bfec6d465cd7b19bf01a611497b98b94d548ae602841c6d6f958663313a07" + "sha256": "1259587fdd4e8600832ae72610bbc1e18db0400b8d4d6cc9ea2c982df34cd285" }, "pipfile-spec": 6, "requires": { @@ -303,6 +303,15 @@ ], "version": "==2.1.0.post1" }, + "fastapi": { + "hashes": [ + "sha256:8c77515984cd8e8cfeb58364f8cc7a28f0692088475e2614f7bf03275eba9093", + "sha256:b978095b9ee01a5cf49b19f4bc1ac9b8ca83aa076e770ef8fd9af09a2b88d191" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.109.0" + }, "frozenlist": { "hashes": [ "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", @@ -652,7 +661,7 @@ "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a", "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4" ], - "markers": "python_version >= '3.7'", + "markers": "python_version >= '3.12' and python_version < '4.0'", "version": "==2.5.3" }, "pydantic-core": { @@ -886,6 +895,14 @@ "markers": "python_version >= '3.7'", "version": "==1.3.0" }, + "starlette": { + "hashes": [ + "sha256:3e2639dac3520e4f58734ed22553f950d3f3cb1001cd2eaac4d57e8cdc5f66bc", + "sha256:50bbbda9baa098e361f398fda0928062abbaf1f54f4fadcbe17c092a01eb9a25" + ], + "markers": "python_version >= '3.8'", + "version": "==0.35.1" + }, "typer": { "hashes": [ "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2", diff --git a/neon_client/__pycache__/__init__.cpython-311.pyc b/neon_client/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc9acc2350b89e0b7b55b12c5322bb76a974e513 GIT binary patch literal 233 zcmZ3^%ge<81UYhxQx|srh-%Ihm<>C4QPrw>Zm;;*-I=B4(h1l?" + + +class CollectionView: + def __init__(self, collection, key_ids=None): + if not key_ids: + key_ids = [] + + self._key_ids = key_ids + self._collection = collection + + def __iter__(self): + return iter(self._collection) + + def __getitem__(self, key): + for k in key_ids: + for item in self._collection: + if getattr(item, k) == key: + return item + + return self._collection[key] + + def __len__(self): + return len(self._collection) + + def __repr__(self): + return repr(self._collection) + + +class NeonAPIKey(BaseNeonItem, openapi_models.ApiKeysListResponseItem): + @classmethod + def from_list_response(cls, r, *, neon): + """Create a list of APIKeys from an API response.""" + + def gen(): + for key in r: + k = cls.model_validate(key.model_dump()) + k.neon = neon + + yield k + + return [g for g in gen()] + + def __str__(self): + return f"" + + def revoke(self, *, neon): + """Revoke this API key.""" + + return bool(neon.resources.api_keys.revoke(self.id)) + class NeonClient: def __init__(self, api_key: str, **kwargs): self.api = Neon_API_V2(api_key, **kwargs) self.resources = ResourceCollection(self.api) - # self.api_keys = APIKeyResource(self.api) - # self.users = UserResource(self.api) - # self.projects = ProjectResource(self.api) - # self.databases = DatabaseResource(self.api) + @property + def me(self): + user = self.resources.users.get_current_user_info() + return NeonUser.from_get_response(user) + + @property + def api_keys(self): + keys = self.resources.api_keys.get_list() + return CollectionView( + NeonAPIKey.from_list_response(keys, neon=self), key_ids=["id"] + )