* fix services * Port of PR #6 to branch 0.8.0 (#7) * Switch from JSON to multipart file encoding for file upload File upload is detected when body data contains a value of class FileUpload Remaining JSON is converted to FormData Enitre message is sent as multipart * Switch from JSON to multipart file encoding for file upload (#6) File upload is detected when body data contains a value of class FileUpload Remaining JSON is converted to FormData Enitre message is sent as multipart * fix readme * fix client * Remove "@" chars (#11) * Remove "@" chars that led to empty collectionId, collectionName and expand * Make load method more generic * fix license --------- Co-authored-by: Paulo Coutinho <paulocoutinhox@gmail.com> Co-authored-by: Martin <mahe@quantentunnel.de> Co-authored-by: Eoin Fennessy <85010533+eoinfennessy@users.noreply.github.com>
139 lines
4.7 KiB
Python
139 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Dict
|
|
from urllib.parse import quote, urlencode
|
|
|
|
import httpx
|
|
|
|
from pocketbase.models import FileUpload
|
|
from pocketbase.models.record import Record
|
|
from pocketbase.services.admin_service import AdminService
|
|
from pocketbase.services.collection_service import CollectionService
|
|
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
|
|
|
|
|
|
class Client:
|
|
base_url: str
|
|
lang: str
|
|
auth_store: BaseAuthStore
|
|
settings: SettingsService
|
|
admins: AdminService
|
|
records: Record
|
|
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,
|
|
) -> None:
|
|
self.base_url = base_url
|
|
self.lang = lang
|
|
self.auth_store = auth_store or BaseAuthStore() # LocalAuthStore()
|
|
# services
|
|
self.admins = AdminService(self)
|
|
self.collections = CollectionService(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]
|
|
|
|
def send(self, path: str, req_config: dict[str:Any]) -> Any:
|
|
"""Sends an api http request."""
|
|
config = {"method": "GET"}
|
|
config.update(req_config)
|
|
# check if Authorization header can be added
|
|
if self.auth_store.token and (
|
|
"headers" not in config or "Authorization" not in config["headers"]
|
|
):
|
|
config["headers"] = config.get("headers", {})
|
|
config["headers"].update({"Authorization": self.auth_store.token})
|
|
# build url + path
|
|
url = self.build_url(path)
|
|
# send the request
|
|
method = config.get("method", "GET")
|
|
params = config.get("params", None)
|
|
headers = config.get("headers", None)
|
|
body = config.get("body", None)
|
|
# handle requests including files as multipart:
|
|
data = {}
|
|
files = ()
|
|
for k, v in (body if isinstance(body, dict) else {}).items():
|
|
if isinstance(v, FileUpload):
|
|
files += v.get(k)
|
|
else:
|
|
data[k] = v
|
|
if len(files) > 0:
|
|
# discard body, switch to multipart encoding
|
|
body = None
|
|
else:
|
|
# discard files+data (do not use multipart encoding)
|
|
files = None
|
|
data = None
|
|
try:
|
|
response = httpx.request(
|
|
method=method,
|
|
url=url,
|
|
params=params,
|
|
headers=headers,
|
|
json=body,
|
|
data=data,
|
|
files=files,
|
|
timeout=120,
|
|
)
|
|
except Exception as e:
|
|
raise ClientResponseError(
|
|
f"General request error. Original error: {e}",
|
|
original_error=e,
|
|
)
|
|
try:
|
|
data = response.json()
|
|
except Exception:
|
|
data = None
|
|
if response.status_code >= 400:
|
|
raise ClientResponseError(
|
|
f"Response error. Status code:{response.status_code}",
|
|
url=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 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
|