mirror of
https://github.com/kennethreitz/neon-api-python.git
synced 2026-06-05 14:50:16 +00:00
Refactor imports and remove unused code
This commit is contained in:
@@ -17,7 +17,7 @@ gen-model: fetch-v2-schema
|
||||
--allow-population-by-field-name \
|
||||
--use-title-as-name \
|
||||
--reuse-model \
|
||||
--field-constraints \
|
||||
# --field-constraints \
|
||||
--disable-appending-item-suffix \
|
||||
--allow-extra-fields \
|
||||
--use-annotated \
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# neon_client: an api wrapper for neon.tech v2 api
|
||||
|
||||
|
||||
|
||||
## Future Installation
|
||||
|
||||
```bash
|
||||
$ pip install neon-client
|
||||
```
|
||||
|
||||
**Please Note**: this repository is a work in progress. The package is not yet available on PyPi. The above command will not work.
|
||||
|
||||
## Usage
|
||||
|
||||
```pycon
|
||||
>>> from neon_client import NeonClient
|
||||
|
||||
>>> neon = NeonClient(api_key='your_api_key')
|
||||
|
||||
>>> # Get all the projects
|
||||
>>> projects = neon.projects()
|
||||
|
||||
|
||||
>>> # Get a specific project
|
||||
>>> project = neon.project(project_id='your_project_id')
|
||||
|
||||
>>> # Get all the databases
|
||||
>>> databases = neon.databases()
|
||||
|
||||
>>> # Get a specific database
|
||||
>>> database = neon.database(database_id='your_database_id')
|
||||
|
||||
>>> # Get all the branches for a given database.
|
||||
>>> branches = neon.branches(database_id='your_database_id')
|
||||
|
||||
>>> # Get a specific branch
|
||||
>>> branch = neon.branch(database_id='your_database_id', branch_id='your_branch_id')
|
||||
|
||||
|
||||
```
|
||||
@@ -1 +1 @@
|
||||
from .v2_client import NeonClient
|
||||
from .client import NeonClient
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
from .http_client import Neon_API_V2
|
||||
from .resources import ResourceCollection
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class BaseNeonItem:
|
||||
def install_neon_client(self, neon):
|
||||
self.neon = neon
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class NeonUser(BaseNeonItem, models.CurrentUserAuthAccount):
|
||||
@classmethod
|
||||
def from_get_response(cls, r):
|
||||
"""Create a NeonUser from an API response."""
|
||||
|
||||
# TODO: is this the right way to do this?
|
||||
me = r.auth_accounts[0]
|
||||
|
||||
return cls.model_validate(me.model_dump())
|
||||
|
||||
def __str__(self):
|
||||
return f"<NeonUser email={self.email}>"
|
||||
|
||||
|
||||
class NeonProject(BaseNeonItem, models.ProjectListItem):
|
||||
"""A Neon project."""
|
||||
|
||||
@classmethod
|
||||
def from_list_response(cls, r):
|
||||
"""Create a list of NeonProjects from an API response."""
|
||||
|
||||
def gen():
|
||||
for project in r.projects:
|
||||
p = cls.model_validate(project.model_dump())
|
||||
|
||||
yield p
|
||||
|
||||
return [g for g in gen()]
|
||||
|
||||
@classmethod
|
||||
def from_get_response(cls, r):
|
||||
"""Create a NeonProject from an API response."""
|
||||
|
||||
return cls.model_validate(r.model_dump())
|
||||
|
||||
def __str__(self):
|
||||
return f"<NeonProject id={self.id} name={self.name}>"
|
||||
|
||||
def delete(self):
|
||||
"""Delete this project."""
|
||||
|
||||
return bool(self.neon.resources.projects.delete(self.id))
|
||||
|
||||
|
||||
class ItemView:
|
||||
"""A view into a single item."""
|
||||
|
||||
def __init__(self, item, key_id=None, neon=None):
|
||||
self.neon = neon
|
||||
self._item = item
|
||||
self._key_id = key_id
|
||||
|
||||
@property
|
||||
def item(self):
|
||||
if self._key_id:
|
||||
return getattr(self._item, self._key_id)
|
||||
|
||||
return self._item
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.item, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name == "_item":
|
||||
return super().__setattr__(name, value)
|
||||
|
||||
return setattr(self.item, name, value)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.item)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.item)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.item == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self.item != other
|
||||
|
||||
|
||||
class CollectionView:
|
||||
"""A view into a collection of items."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
collection,
|
||||
key_ids=None,
|
||||
collection_id=None,
|
||||
neon=None,
|
||||
):
|
||||
self.neon = neon
|
||||
self.pagination = None
|
||||
|
||||
if not key_ids:
|
||||
key_ids = []
|
||||
|
||||
self._key_ids = key_ids
|
||||
if collection_id:
|
||||
self._collection = getattr(collection, collection_id)
|
||||
self.pagination = collection.pagination
|
||||
else:
|
||||
self._collection = collection
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._collection)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for k in self._key_ids:
|
||||
for item in self._collection:
|
||||
if str(getattr(item, k)) == str(key):
|
||||
return item
|
||||
|
||||
return self._collection[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._collection)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._collection)
|
||||
|
||||
def __str__(self):
|
||||
return str(self._collection)
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._collection
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._collection == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._collection != other
|
||||
|
||||
|
||||
class NeonClient:
|
||||
def __init__(self, api_key: str, **kwargs):
|
||||
self.api = Neon_API_V2(api_key, **kwargs)
|
||||
self.resources = ResourceCollection(self.api)
|
||||
|
||||
def me(self):
|
||||
return self.resources.users.get_current_user_info()
|
||||
|
||||
def api_keys(self):
|
||||
return CollectionView(self.resources.api_keys.get_list(), key_ids=["id"])
|
||||
|
||||
def projects(self, **kwargs):
|
||||
return CollectionView(
|
||||
self.resources.projects.get_list(**kwargs),
|
||||
key_ids=["id", "name"],
|
||||
collection_id="projects",
|
||||
)
|
||||
|
||||
def project(self, project_id: str, **kwargs):
|
||||
return self.resources.projects.get(project_id, **kwargs)
|
||||
|
||||
def project_create(self, **kwargs):
|
||||
return self.resources.projects.create(**kwargs)
|
||||
|
||||
def project_delete(self, project_id: str, **kwargs):
|
||||
return self.resources.projects.delete(project_id, **kwargs)
|
||||
|
||||
def databases(self, project_id: str, branch_id: str, **kwargs):
|
||||
return CollectionView(
|
||||
self.resources.databases.get_list(project_id, branch_id, **kwargs),
|
||||
key_ids=["id"],
|
||||
collection_id="databases",
|
||||
)
|
||||
|
||||
def database(self, project_id: str, database_id: str, **kwargs):
|
||||
return self.resources.databases.get(project_id, database_id, **kwargs)
|
||||
|
||||
def branches(self, project_id: str, **kwargs):
|
||||
return CollectionView(
|
||||
self.resources.branches.get_list(project_id, **kwargs),
|
||||
key_ids=["id", "name"],
|
||||
)
|
||||
|
||||
def branch(self, project_id: str, branch_id: str):
|
||||
return self.resources.branches.get(project_id, branch_id)
|
||||
@@ -2,7 +2,7 @@ from typing import List
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .openapi_models import *
|
||||
from .models import *
|
||||
from .exceptions import NeonClientException
|
||||
from .__version__ import __version__
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+46
-62
@@ -2,44 +2,17 @@ from typing import List
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from .http_client import Neon_API_V2
|
||||
from .openapi_models import (
|
||||
ApiKeyCreateRequest,
|
||||
ApiKeyCreateResponse,
|
||||
ApiKeyRevokeResponse,
|
||||
ApiKeysListResponseItem,
|
||||
CurrentUserInfoResponse,
|
||||
Project,
|
||||
ProjectResponse,
|
||||
ProjectUpdateRequest,
|
||||
ProjectsResponse,
|
||||
DatabaseResponse,
|
||||
DatabasesResponse,
|
||||
DatabaseUpdateRequest,
|
||||
Database,
|
||||
Database1,
|
||||
Database2,
|
||||
DatabaseCreateRequest,
|
||||
BranchesResponse,
|
||||
BranchResponse,
|
||||
OperationResponse,
|
||||
OperationsResponse,
|
||||
PaginationResponse,
|
||||
Branch,
|
||||
Branch2,
|
||||
Branch3,
|
||||
BranchCreateRequestEndpointOptions,
|
||||
BranchCreateRequest,
|
||||
)
|
||||
from . import models
|
||||
from .utils import validate_obj_model
|
||||
|
||||
|
||||
class PagedOperationsResponse(OperationsResponse, PaginationResponse):
|
||||
class PagedOperationsResponse(models.OperationsResponse, models.PaginationResponse):
|
||||
"""A response containing a list of operations and pagination information."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PagedProjectsResponse(ProjectsResponse, PaginationResponse):
|
||||
class PagedProjectsResponse(models.ProjectsResponse, models.PaginationResponse):
|
||||
"""A response containing a list of projects and pagination information."""
|
||||
|
||||
pass
|
||||
@@ -63,7 +36,7 @@ class UserResource(Resource):
|
||||
return self.api.request(
|
||||
method="GET",
|
||||
path=self.api.url_join(self.base_path, "me"),
|
||||
response_model=CurrentUserInfoResponse,
|
||||
response_model=models.CurrentUserInfoResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -78,7 +51,7 @@ class APIKeyResource(Resource):
|
||||
return self.api.request(
|
||||
method="GET",
|
||||
path=self.base_path,
|
||||
response_model=ApiKeysListResponseItem,
|
||||
response_model=models.ApiKeysListResponseItem,
|
||||
response_is_array=True,
|
||||
)
|
||||
|
||||
@@ -88,8 +61,8 @@ class APIKeyResource(Resource):
|
||||
return self.api.request(
|
||||
method="POST",
|
||||
path=self.base_path,
|
||||
json=ApiKeyCreateRequest(key_name=key_name).model_dump(),
|
||||
response_model=ApiKeyCreateResponse,
|
||||
json=models.ApiKeyCreateRequest(key_name=key_name).model_dump(),
|
||||
response_model=models.ApiKeyCreateResponse,
|
||||
)
|
||||
|
||||
def revoke(self, key_id: str):
|
||||
@@ -98,7 +71,7 @@ class APIKeyResource(Resource):
|
||||
return self.api.request(
|
||||
method="DELETE",
|
||||
path=self.api.url_join(self.base_path, str(key_id)),
|
||||
response_model=ApiKeyRevokeResponse,
|
||||
response_model=models.ApiKeyRevokeResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -139,29 +112,29 @@ class ProjectResource(Resource):
|
||||
return self.api.request(
|
||||
method="GET",
|
||||
path=self.api.url_join(self.base_path, project_id),
|
||||
response_model=ProjectResponse,
|
||||
response_model=models.ProjectResponse,
|
||||
)
|
||||
|
||||
# def create(self, name: str, **kwargs):
|
||||
# """Create a new project."""
|
||||
def create(self, **kwargs):
|
||||
"""Create a new project."""
|
||||
|
||||
# return self.api.request(
|
||||
# method="POST",
|
||||
# path=self.base_path,
|
||||
# json={"project": {"name": name, **kwargs}},
|
||||
# response_model=ProjectResponse,
|
||||
# ).model_dump()
|
||||
return self.api.request(
|
||||
method="POST",
|
||||
path=self.base_path,
|
||||
json={"project": kwargs},
|
||||
response_model=models.ProjectResponse,
|
||||
).model_dump()
|
||||
|
||||
def update(self, project: Project):
|
||||
def update(self, project: models.Project):
|
||||
"""Update a project."""
|
||||
|
||||
payload = ProjectUpdateRequest(project=project.model_dump())
|
||||
payload = models.ProjectUpdateRequest(project=project.model_dump())
|
||||
|
||||
return self.api.request(
|
||||
method="PATCH",
|
||||
path=self.api.url_join(self.base_path, project.id),
|
||||
json={"project": payload.model_dump()},
|
||||
response_model=ProjectResponse,
|
||||
response_model=models.ProjectResponse,
|
||||
)
|
||||
|
||||
def delete(self, project_id: str):
|
||||
@@ -170,7 +143,7 @@ class ProjectResource(Resource):
|
||||
return self.api.request(
|
||||
method="DELETE",
|
||||
path=self.api.url_join(self.base_path, project_id),
|
||||
response_model=ProjectResponse,
|
||||
response_model=models.ProjectResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -180,16 +153,24 @@ class DatabaseResource(Resource):
|
||||
def _extract_database(self, obj):
|
||||
"""Extract a database from the specified object."""
|
||||
|
||||
assert isinstance(obj, (DatabaseCreateRequest, Database1, Database2, Database))
|
||||
assert isinstance(
|
||||
obj,
|
||||
(
|
||||
models.DatabaseCreateRequest,
|
||||
models.Database1,
|
||||
models.Database2,
|
||||
models.Database,
|
||||
),
|
||||
)
|
||||
|
||||
# Object mappings.
|
||||
if isinstance(obj, DatabaseCreateRequest):
|
||||
if isinstance(obj, models.DatabaseCreateRequest):
|
||||
obj = obj.database.model_dump()
|
||||
if isinstance(obj, Database1):
|
||||
if isinstance(obj, models.Database1):
|
||||
obj = obj.database.model_dump()
|
||||
if isinstance(obj, Database2):
|
||||
if isinstance(obj, models.Database2):
|
||||
obj = obj.database.model_dump()
|
||||
if isinstance(obj, Database):
|
||||
if isinstance(obj, models.Database):
|
||||
obj = obj.model_dump()
|
||||
|
||||
return obj
|
||||
@@ -233,7 +214,10 @@ class DatabaseResource(Resource):
|
||||
self,
|
||||
project_id: str,
|
||||
branch_id: str,
|
||||
db: DatabaseCreateRequest | Database1 | Database2 | Database,
|
||||
db: models.DatabaseCreateRequest
|
||||
| models.Database1
|
||||
| models.Database2
|
||||
| models.Database,
|
||||
):
|
||||
"""Create a new database."""
|
||||
|
||||
@@ -253,7 +237,7 @@ class DatabaseResource(Resource):
|
||||
project_id: str,
|
||||
branch_id: str,
|
||||
database_id: str,
|
||||
db: DatabaseUpdateRequest | Database2,
|
||||
db: models.DatabaseUpdateRequest | models.Database2,
|
||||
):
|
||||
"""Update a database."""
|
||||
|
||||
@@ -264,8 +248,8 @@ class DatabaseResource(Resource):
|
||||
path=self.api.url_join(
|
||||
"projects", project_id, "branches", branch_id, "databases", database_id
|
||||
),
|
||||
json=DatabaseUpdateRequest(database=db).model_dump(),
|
||||
response_model=DatabaseResponse,
|
||||
json=models.DatabaseUpdateRequest(database=db).model_dump(),
|
||||
response_model=models.DatabaseResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -280,7 +264,7 @@ class BranchResource(Resource):
|
||||
return self.api.request(
|
||||
method="GET",
|
||||
path=self.api.url_join("projects", project_id, "branches"),
|
||||
response_model=BranchesResponse,
|
||||
response_model=models.BranchesResponse,
|
||||
)
|
||||
|
||||
def get(self, project_id: str, branch_id: str):
|
||||
@@ -289,17 +273,17 @@ class BranchResource(Resource):
|
||||
return self.api.request(
|
||||
method="GET",
|
||||
path=self.api.url_join("projects", project_id, "branches", branch_id),
|
||||
response_model=BranchResponse,
|
||||
response_model=models.BranchResponse,
|
||||
)
|
||||
|
||||
def create(self, project_id: str, request: BranchCreateRequest):
|
||||
def create(self, project_id: str, request: models.BranchCreateRequest):
|
||||
"""Create a new branch."""
|
||||
|
||||
return self.api.request(
|
||||
method="POST",
|
||||
path=self.api.url_join("projects", project_id, "branches"),
|
||||
json=request.model_dump(),
|
||||
response_model=BranchResponse,
|
||||
response_model=models.BranchResponse,
|
||||
)
|
||||
|
||||
|
||||
@@ -335,7 +319,7 @@ class OperationResource(Resource):
|
||||
return self.api.request(
|
||||
method="GET",
|
||||
path=self.api.url_join("projects", project_id, "operations", operation_id),
|
||||
response_model=OperationResponse,
|
||||
response_model=models.OperationResponse,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
from .http_client import Neon_API_V2
|
||||
from .resources import ResourceCollection
|
||||
|
||||
from . import openapi_models
|
||||
|
||||
|
||||
class BaseNeonItem:
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
|
||||
class NeonUser(BaseNeonItem, openapi_models.CurrentUserAuthAccount):
|
||||
@classmethod
|
||||
def from_get_response(cls, r):
|
||||
"""Create a NeonUser from an API response."""
|
||||
|
||||
# TODO: is this the right way to do this?
|
||||
me = r.auth_accounts[0]
|
||||
|
||||
return cls.model_validate(me.model_dump())
|
||||
|
||||
def __str__(self):
|
||||
return f"<NeonUser email={self.email}>"
|
||||
|
||||
|
||||
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"<NeonAPIKey id={self.id}>"
|
||||
|
||||
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)
|
||||
|
||||
@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"]
|
||||
)
|
||||
Reference in New Issue
Block a user