Skip to content

Commit

Permalink
Better typing annotations and some updates to match TS lib
Browse files Browse the repository at this point in the history
  • Loading branch information
vaphes committed Nov 18, 2024
1 parent 2e26131 commit 6deb2ec
Show file tree
Hide file tree
Showing 39 changed files with 968 additions and 636 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


.vscode/
.ruff_cache/

### Python ###
# Byte-compiled / optimized / DLL files
Expand Down
74 changes: 30 additions & 44 deletions pocketbase/client.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,30 @@
from __future__ import annotations

from typing import Any, Dict
from urllib.parse import quote, urlencode

import httpx

from pocketbase.errors import ClientResponseError
from pocketbase.models import FileUpload
from pocketbase.models.record import Record
from pocketbase.services.admin_service import AdminService
from pocketbase.services.backups_service import BackupsService
from pocketbase.services.collection_service import CollectionService
from pocketbase.services.files_service import FileService
from pocketbase.services.health_service import HealthService
from pocketbase.services.log_service import LogService
from pocketbase.services.realtime_service import RealtimeService
from pocketbase.services.record_service import RecordService
from pocketbase.services.settings_service import SettingsService
from pocketbase.stores.base_auth_store import BaseAuthStore
from pocketbase.utils import ClientResponseError
from pocketbase.stores.base_auth_store import AuthStore, BaseAuthStore


class Client:
base_url: str
lang: str
auth_store: BaseAuthStore
settings: SettingsService
admins: AdminService
collections: CollectionService
records: RecordService
logs: LogService
realtime: RealtimeService
record_service: Dict[str, RecordService]

def __init__(
self,
base_url: str = "/",
lang: str = "en-US",
auth_store: BaseAuthStore | None = None,
auth_store: AuthStore | None = None,
timeout: float = 120,
http_client: httpx.Client | None = None,
) -> None:
Expand All @@ -47,16 +37,12 @@ def __init__(
self.admins = AdminService(self)
self.backups = BackupsService(self)
self.collections = CollectionService(self)
self.files = FileService(self)
self.health = HealthService(self)
self.logs = LogService(self)
self.settings = SettingsService(self)
self.realtime = RealtimeService(self)
self.record_service = {}

def collection(self, id_or_name: str) -> RecordService:
"""Returns the RecordService associated to the specified collection."""
if id_or_name not in self.record_service:
self.record_service[id_or_name] = RecordService(self, id_or_name)
return self.record_service[id_or_name]
self.record_service: Dict[str, RecordService] = {}

def _send(self, path: str, req_config: dict[str, Any]) -> httpx.Response:
"""Sends an api http request returning response object."""
Expand All @@ -74,9 +60,9 @@ def _send(self, path: str, req_config: dict[str, Any]) -> httpx.Response:
method = config.get("method", "GET")
params = config.get("params", None)
headers = config.get("headers", None)
body = config.get("body", None)
body: dict[str, Any] | None = config.get("body", None)
# handle requests including files as multipart:
data = {}
data: dict[str, Any] | None = {}
files = ()
for k, v in (body if isinstance(body, dict) else {}).items():
if isinstance(v, FileUpload):
Expand Down Expand Up @@ -108,6 +94,12 @@ def _send(self, path: str, req_config: dict[str, Any]) -> httpx.Response:
)
return response

def collection(self, id_or_name: str) -> RecordService:
"""Returns the RecordService associated to the specified collection."""
if id_or_name not in self.record_service:
self.record_service[id_or_name] = RecordService(self, id_or_name)
return self.record_service[id_or_name]

def send_raw(self, path: str, req_config: dict[str, Any]) -> bytes:
"""Sends an api http request returning raw bytes response."""
response = self._send(path, req_config)
Expand All @@ -123,35 +115,29 @@ def send(self, path: str, req_config: dict[str, Any]) -> Any:
if response.status_code >= 400:
raise ClientResponseError(
f"Response error. Status code:{response.status_code}",
url=response.url,
url=str(response.url),
status=response.status_code,
data=data,
)
return data

def get_file_url(self, record: Record, filename: str, query_params: dict):
parts = [
"api",
"files",
quote(record.collection_id or record.collection_name),
quote(record.id),
quote(filename),
]
result = self.build_url("/".join(parts))
if len(query_params) != 0:
params: str = urlencode(query_params)
result += "&" if "?" in result else "?"
result += params
return result

def get_file_token(self):
res = self.send("/api/files/token", req_config={"method": "POST"})
return res["token"]

def build_url(self, path: str) -> str:
url = self.base_url
if not self.base_url.endswith("/"):
url += "/"
if path.startswith("/"):
path = path[1:]
return url + path

# TODO: add deprecated decorator
def get_file_url(
self,
record: Record,
filename: str,
query_params: dict[str, Any] | None = None,
):
return self.files.get_url(record, filename, query_params)

# TODO: add deprecated decorator
def get_file_token(self) -> str:
return self.files.get_token()
22 changes: 22 additions & 0 deletions pocketbase/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from __future__ import annotations

from typing import Any


class ClientResponseError(Exception):
def __init__(
self,
*args: Any,
url: str = "",
status: int = 0,
data: dict[str, Any] | None = None,
is_abort: bool = False,
original_error: Any = None,
**kwargs: Any,
) -> None:
super().__init__(*args)
self.url = url
self.status = status
self.data = data or {}
self.is_abort = is_abort
self.original_error = original_error
4 changes: 3 additions & 1 deletion pocketbase/models/admin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from __future__ import annotations

from typing import Any

from pocketbase.models.utils.base_model import BaseModel


class Admin(BaseModel):
avatar: int
email: str

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
super().load(data)
self.avatar = data.get("avatar", 0)
self.email = data.get("email", "")
3 changes: 2 additions & 1 deletion pocketbase/models/backups.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import datetime
from typing import Any

from pocketbase.models.utils import BaseModel
from pocketbase.utils import to_datetime
Expand All @@ -11,7 +12,7 @@ class Backup(BaseModel):
modified: str | datetime.datetime
size: int

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
super().load(data)
self.key = data.get("key", "")
self.modified = to_datetime(data.pop("modified", ""))
Expand Down
6 changes: 4 additions & 2 deletions pocketbase/models/collection.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Any

from pocketbase.models.utils.base_model import BaseModel
from pocketbase.models.utils.schema_field import SchemaField

Expand All @@ -14,9 +16,9 @@ class Collection(BaseModel):
create_rule: str | None
update_rule: str | None
delete_rule: str | None
options: dict
options: dict[str, Any]

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
super().load(data)
self.name = data.get("name", "")
self.system = data.get("system", False)
Expand Down
4 changes: 3 additions & 1 deletion pocketbase/models/external_auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Any

from pocketbase.models.utils.base_model import BaseModel


Expand All @@ -9,7 +11,7 @@ class ExternalAuth(BaseModel):
provider: str
provider_id: str

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
super().load(data)
self.record_id = data.get("recordId", "")
self.collection_id = data.get("collectionId", "")
Expand Down
4 changes: 2 additions & 2 deletions pocketbase/models/file_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@


class FileUpload:
def __init__(self, *args):
self.files: FileUploadTypes = args
def __init__(self, *args: FileUploadTypes):
self.files = args

def get(self, key: str):
if isinstance(self.files[0], Sequence) and not isinstance(
Expand Down
6 changes: 4 additions & 2 deletions pocketbase/models/log_request.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from typing import Any

from pocketbase.models.utils.base_model import BaseModel


Expand All @@ -12,9 +14,9 @@ class LogRequest(BaseModel):
user_ip: str
referer: str
user_agent: str
meta: dict
meta: dict[str, Any]

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
super().load(data)
self.url = data.get("url", "")
self.method = data.get("method", "")
Expand Down
10 changes: 6 additions & 4 deletions pocketbase/models/record.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from __future__ import annotations

from typing import Any

from pocketbase.models.utils.base_model import BaseModel
from pocketbase.utils import camel_to_snake


class Record(BaseModel):
collection_id: str
collection_name: str
expand: dict
expand: dict[str, Any]

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
super().load(data)
self.expand = {}
for key, value in data.items():
Expand All @@ -18,9 +20,9 @@ def load(self, data: dict) -> None:
self.load_expanded()

@classmethod
def parse_expanded(cls, data: dict):
def parse_expanded(cls, data: Any):
if isinstance(data, list):
return [cls(a) for a in data]
return [cls(a) for a in data] # type: ignore
return cls(data)

def load_expanded(self) -> None:
Expand Down
16 changes: 14 additions & 2 deletions pocketbase/models/utils/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@

import datetime
from abc import ABC
from typing import Any, Protocol

from pocketbase.utils import to_datetime


class Model(Protocol):
id: str
created: str | datetime.datetime
updated: str | datetime.datetime

def load(self, data: dict[str, Any]) -> None: ...

@property
def is_new(self) -> bool: ...


class BaseModel(ABC):
id: str
created: str | datetime.datetime
updated: str | datetime.datetime

def __init__(self, data: dict = {}) -> None:
def __init__(self, data: dict[str, Any] = {}) -> None:
super().__init__()
self.load(data)

Expand All @@ -21,7 +33,7 @@ def __str__(self) -> str:
def __repr__(self) -> str:
return self.__str__()

def load(self, data: dict) -> None:
def load(self, data: dict[str, Any]) -> None:
"""Loads `data` into the current model."""
self.id = data.pop("id", "")
self.created = to_datetime(data.pop("created", ""))
Expand Down
6 changes: 3 additions & 3 deletions pocketbase/models/utils/list_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

from dataclasses import dataclass, field

from pocketbase.models.utils.base_model import BaseModel
from pocketbase.models.utils.base_model import Model


@dataclass
class ListResult:
class ListResult[T: Model]:
page: int = 1
per_page: int = 0
total_items: int = 0
total_pages: int = 0
items: list[BaseModel] = field(default_factory=list)
items: list[T] = field(default_factory=list)
3 changes: 2 additions & 1 deletion pocketbase/models/utils/schema_field.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


@dataclass
Expand All @@ -12,4 +13,4 @@ class SchemaField:
required: bool = False
presentable: bool = False
unique: bool = False
options: dict = field(default_factory=dict)
options: dict[str, Any] = field(default_factory=dict)
Loading

0 comments on commit 6deb2ec

Please sign in to comment.