Compare commits
1 Commits
master
...
v0.3.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10496553ed |
@ -1,46 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, Dict
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
import httpx
|
||||
|
||||
from pocketbase.services.admins import Admins
|
||||
from pocketbase.services.collections import Collections
|
||||
from pocketbase.services.logs import Logs
|
||||
from pocketbase.services.realtime import Realtime
|
||||
from pocketbase.services.records import Records
|
||||
from pocketbase.services.users import Users
|
||||
from pocketbase.services.settings import Settings
|
||||
from pocketbase.utils import ClientResponseError
|
||||
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
|
||||
|
||||
|
||||
class ClientResponseError(Exception):
|
||||
url: str = ""
|
||||
status: int = 0
|
||||
data: dict = {}
|
||||
is_abort: bool = False
|
||||
original_error: Any | None = None
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args)
|
||||
self.url = kwargs.get("url", "")
|
||||
self.status = kwargs.get("status", 0)
|
||||
self.data = kwargs.get("data", {})
|
||||
self.is_abort = kwargs.get("is_abort", False)
|
||||
self.original_error = kwargs.get("original_error", None)
|
||||
|
||||
|
||||
class Client:
|
||||
base_url: str
|
||||
lang: str
|
||||
auth_store: BaseAuthStore
|
||||
settings: Settings
|
||||
admins: Admins
|
||||
users: Users
|
||||
collections: Collections
|
||||
records: Records
|
||||
logs: Logs
|
||||
realtime: Realtime
|
||||
settings: SettingsService
|
||||
admins: AdminService
|
||||
records: Record
|
||||
collections: CollectionService
|
||||
records: RecordService
|
||||
logs: LogService
|
||||
realtime: RealtimeService
|
||||
record_service: Dict[str, RecordService]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -52,13 +39,18 @@ class Client:
|
||||
self.lang = lang
|
||||
self.auth_store = auth_store or BaseAuthStore() # LocalAuthStore()
|
||||
# services
|
||||
self.admins = Admins(self)
|
||||
self.users = Users(self)
|
||||
self.records = Records(self)
|
||||
self.collections = Collections(self)
|
||||
self.logs = Logs(self)
|
||||
self.settings = Settings(self)
|
||||
self.realtime = Realtime(self)
|
||||
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."""
|
||||
@ -68,12 +60,9 @@ class Client:
|
||||
if self.auth_store.token and (
|
||||
"headers" not in config or "Authorization" not in config["headers"]
|
||||
):
|
||||
auth_type = "Admin"
|
||||
if hasattr(self.auth_store.model, "verified"):
|
||||
auth_type = "User"
|
||||
config["headers"] = config.get("headers", {})
|
||||
config["headers"].update(
|
||||
{"Authorization": f"{auth_type} {self.auth_store.token}"}
|
||||
{"Authorization": self.auth_store.token}
|
||||
)
|
||||
# build url + path
|
||||
url = self.build_url(path)
|
||||
@ -109,6 +98,21 @@ class Client:
|
||||
)
|
||||
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("/"):
|
||||
|
||||
@ -3,4 +3,3 @@ from .collection import Collection
|
||||
from .external_auth import ExternalAuth
|
||||
from .log_request import LogRequest
|
||||
from .record import Record
|
||||
from .user import User
|
||||
|
||||
@ -9,10 +9,8 @@ from pocketbase.models.utils.base_model import BaseModel
|
||||
class Admin(BaseModel):
|
||||
avatar: int
|
||||
email: str
|
||||
last_reset_sent_at: str | datetime.datetime
|
||||
|
||||
def load(self, data: dict) -> None:
|
||||
super().load(data)
|
||||
self.avatar = data.get("avatar", 0)
|
||||
self.email = data.get("email", "")
|
||||
self.last_reset_sent_at = to_datetime(data.get("lastResetSentAt", ""))
|
||||
|
||||
@ -6,6 +6,7 @@ from pocketbase.models.utils.schema_field import SchemaField
|
||||
|
||||
class Collection(BaseModel):
|
||||
name: str
|
||||
type: str
|
||||
schema: list[SchemaField]
|
||||
system: bool
|
||||
list_rule: str | None
|
||||
@ -13,17 +14,33 @@ class Collection(BaseModel):
|
||||
create_rule: str | None
|
||||
update_rule: str | None
|
||||
delete_rule: str | None
|
||||
options: dict
|
||||
|
||||
def load(self, data: dict) -> None:
|
||||
super().load(data)
|
||||
self.name = data.get("name", "")
|
||||
self.system = data.get("system", False)
|
||||
self.type = data.get("type", "base")
|
||||
self.options = data.get("options", {})
|
||||
|
||||
# rules
|
||||
self.list_rule = data.get("listRule", None)
|
||||
self.view_rule = data.get("viewRule", None)
|
||||
self.create_rule = data.get("createRule", None)
|
||||
self.update_rule = data.get("updateRule", None)
|
||||
self.delete_rule = data.get("deleteRule", "")
|
||||
|
||||
# schema
|
||||
schema = data.get("schema", [])
|
||||
self.schema = []
|
||||
for field in schema:
|
||||
self.schema.append(SchemaField(**field))
|
||||
|
||||
def is_base(self):
|
||||
return self.type == 'base'
|
||||
|
||||
def is_auth(self):
|
||||
return self.type == 'auth'
|
||||
|
||||
def is_single(self):
|
||||
return self.type == 'single'
|
||||
|
||||
@ -4,12 +4,14 @@ from pocketbase.models.utils.base_model import BaseModel
|
||||
|
||||
|
||||
class ExternalAuth(BaseModel):
|
||||
user_id: str
|
||||
record_id: str
|
||||
collection_id: str
|
||||
provider: str
|
||||
provider_id: str
|
||||
|
||||
def load(self, data: dict) -> None:
|
||||
super().load(data)
|
||||
self.user_id = data.get("userId", "")
|
||||
self.record_id = data.get("recordId", "")
|
||||
self.collection_id = data.get("collectionId", "")
|
||||
self.provider = data.get("provider", "")
|
||||
self.provider_id = data.get("providerId", "")
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Union
|
||||
import datetime
|
||||
|
||||
from pocketbase.utils import to_datetime
|
||||
from pocketbase.models.record import Record
|
||||
from pocketbase.models.utils.base_model import BaseModel
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
email: str
|
||||
verified: bool
|
||||
last_reset_sent_at: str | datetime.datetime
|
||||
last_verification_sent_at: str | datetime.datetime
|
||||
profile: Record | None
|
||||
|
||||
def load(self, data: dict) -> None:
|
||||
super().load(data)
|
||||
self.email = data.get("email", "")
|
||||
self.verified = data.get("verified", "")
|
||||
self.last_reset_sent_at = to_datetime(data.get("lastResetSentAt", ""))
|
||||
self.last_verification_sent_at = to_datetime(
|
||||
data.get("lastVerificationSentAt", "")
|
||||
)
|
||||
profile = data.get("profile", None)
|
||||
self.profile = None
|
||||
if profile:
|
||||
self.profile = Record(profile)
|
||||
@ -30,4 +30,4 @@ class BaseModel(ABC):
|
||||
@property
|
||||
def is_new(self) -> bool:
|
||||
"""Returns whether the current loaded data represent a stored db record."""
|
||||
return not self.id or self.id == "00000000-0000-0000-0000-000000000000"
|
||||
return not self.id
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
from .admins import Admins, AdminAuthResponse
|
||||
from .collections import Collections
|
||||
from .logs import Logs, HourlyStats
|
||||
from .realtime import Realtime
|
||||
from .records import Records
|
||||
from .settings import Settings
|
||||
from .users import Users, UserAuthResponse, AuthMethodsList, AuthProviderInfo
|
||||
from .admin_service import AdminService, AdminAuthResponse
|
||||
from .collection_service import CollectionService
|
||||
from .log_service import LogService, HourlyStats
|
||||
from .realtime_service import RealtimeService
|
||||
from .record_service import RecordService
|
||||
from .settings_service import SettingsService
|
||||
|
||||
@ -16,13 +16,39 @@ class AdminAuthResponse:
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class Admins(CrudService):
|
||||
class AdminService(CrudService):
|
||||
def decode(self, data: dict) -> BaseModel:
|
||||
return Admin(data)
|
||||
|
||||
def base_crud_path(self) -> str:
|
||||
return "/api/admins"
|
||||
|
||||
def update(self, id: str, body_params: dict, query_params: dict) -> BaseModel:
|
||||
"""
|
||||
If the current `client.auth_store.model` matches with the updated id,
|
||||
then on success the `client.auth_store.model` will be updated with the result.
|
||||
"""
|
||||
item = super(AdminService).update(id, body_params)
|
||||
try:
|
||||
if self.client.auth_store.model.collection_id is not None and item.id == self.client.auth_store.model.id:
|
||||
self.client.auth_store.save(self.client.auth_store.token, item)
|
||||
except:
|
||||
pass
|
||||
return item
|
||||
|
||||
def delete(self, id: str, body_params: dict, query_params: dict) -> BaseModel:
|
||||
"""
|
||||
If the current `client.auth_store.model` matches with the deleted id,
|
||||
then on success the `client.auth_store` will be cleared.
|
||||
"""
|
||||
item = super(AdminService).delete(id, body_params)
|
||||
try:
|
||||
if self.client.auth_store.model.collection_id is not None and item.id == self.client.auth_store.model.id:
|
||||
self.client.auth_store.save(self.client.auth_store.token, item)
|
||||
except:
|
||||
pass
|
||||
return item
|
||||
|
||||
def auth_response(self, response_data: dict) -> AdminAuthResponse:
|
||||
"""Prepare successful authorize response."""
|
||||
admin = self.decode(response_data.pop("admin", {}))
|
||||
@ -31,18 +57,18 @@ class Admins(CrudService):
|
||||
self.client.auth_store.save(token, admin)
|
||||
return AdminAuthResponse(token=token, admin=admin, **response_data)
|
||||
|
||||
def auth_via_email(
|
||||
def auth_with_password(
|
||||
self, email: str, password: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> AdminAuthResponse:
|
||||
"""
|
||||
Authenticate an admin account by its email and password
|
||||
Authenticate an admin account with its email and password
|
||||
and returns a new admin token and data.
|
||||
|
||||
On success this method automatically updates the client's AuthStore data.
|
||||
"""
|
||||
body_params.update({"email": email, "password": password})
|
||||
body_params.update({"identity": email, "password": password})
|
||||
response_data = self.client.send(
|
||||
self.base_crud_path() + "/auth-via-email",
|
||||
self.base_crud_path() + "/auth-with-password",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
@ -52,7 +78,7 @@ class Admins(CrudService):
|
||||
)
|
||||
return self.auth_response(response_data)
|
||||
|
||||
def refresh(
|
||||
def authRefresh(
|
||||
self, body_params: dict = {}, query_params: dict = {}
|
||||
) -> AdminAuthResponse:
|
||||
"""
|
||||
@ -63,7 +89,7 @@ class Admins(CrudService):
|
||||
"""
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/refresh",
|
||||
self.base_crud_path() + "/auth-refresh",
|
||||
{"method": "POST", "params": query_params, "body": body_params},
|
||||
)
|
||||
)
|
||||
@ -5,7 +5,7 @@ from pocketbase.models.utils.base_model import BaseModel
|
||||
from pocketbase.models.collection import Collection
|
||||
|
||||
|
||||
class Collections(CrudService):
|
||||
class CollectionService(CrudService):
|
||||
def decode(self, data: dict) -> BaseModel:
|
||||
return Collection(data)
|
||||
|
||||
@ -17,7 +17,7 @@ class HourlyStats:
|
||||
date: Union[str, datetime.datetime]
|
||||
|
||||
|
||||
class Logs(BaseService):
|
||||
class LogService(BaseService):
|
||||
def get_request_list(
|
||||
self, page: int = 1, per_page: int = 30, query_params: dict = {}
|
||||
) -> ListResult:
|
||||
@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from typing import Callable, List
|
||||
import dataclasses
|
||||
import json
|
||||
|
||||
@ -15,7 +15,7 @@ class MessageData:
|
||||
record: Record
|
||||
|
||||
|
||||
class Realtime(BaseService):
|
||||
class RealtimeService(BaseService):
|
||||
subscriptions: dict
|
||||
client_id: str = ""
|
||||
event_source: SSEClient | None = None
|
||||
@ -40,28 +40,53 @@ class Realtime(BaseService):
|
||||
elif self.client_id:
|
||||
self._submit_subscriptions()
|
||||
|
||||
def unsubscribe(self, subscription: str | None = None) -> None:
|
||||
def unsubscribe_by_prefix(self, subscription_prefix: str):
|
||||
"""
|
||||
Unsubscribe from all subscriptions starting with the provided prefix.
|
||||
|
||||
This method is no-op if there are no active subscriptions with the provided prefix.
|
||||
|
||||
The related sse connection will be autoclosed if after the
|
||||
unsubscribe operation there are no active subscriptions left.
|
||||
"""
|
||||
to_unsubscribe = []
|
||||
for sub in self.subscriptions:
|
||||
if sub.startswith(subscription_prefix):
|
||||
to_unsubscribe.append(sub)
|
||||
if len(to_unsubscribe) == 0:
|
||||
return
|
||||
return self.unsubscribe(*to_unsubscribe)
|
||||
|
||||
def unsubscribe(self, subscriptions: List[str] | None = None) -> None:
|
||||
"""
|
||||
Unsubscribe from a subscription.
|
||||
|
||||
If the `subscription` argument is not set,
|
||||
If the `subscriptions` argument is not set,
|
||||
then the client will unsubscribe from all registered subscriptions.
|
||||
|
||||
The related sse connection will be autoclosed if after the
|
||||
unsubscribe operations there are no active subscriptions left.
|
||||
"""
|
||||
if not subscription:
|
||||
if not subscriptions or len(subscriptions) == 0:
|
||||
# remove all subscriptions
|
||||
self._remove_subscription_listeners()
|
||||
self.subscriptions = {}
|
||||
elif subscription in self.subscriptions:
|
||||
self.event_source.remove_event_listener(
|
||||
subscription, self.subscriptions[subscription]
|
||||
)
|
||||
self.subscriptions.pop(subscription)
|
||||
else:
|
||||
return
|
||||
# remove each passed subscription
|
||||
found = False
|
||||
for sub in self.subscriptions:
|
||||
found = True
|
||||
self.event_source.remove_event_listener(
|
||||
sub, self.subscriptions[sub]
|
||||
)
|
||||
self.subscriptions.pop(sub)
|
||||
if not found:
|
||||
return
|
||||
|
||||
if self.client_id:
|
||||
self._submit_subscriptions()
|
||||
|
||||
# no more subscriptions -> close the sse connection
|
||||
if not self.subscriptions:
|
||||
self._disconnect()
|
||||
|
||||
@ -116,13 +141,15 @@ class Realtime(BaseService):
|
||||
def _connect(self) -> None:
|
||||
self._disconnect()
|
||||
self.event_source = SSEClient(self.client.build_url("/api/realtime"))
|
||||
self.event_source.add_event_listener("PB_CONNECT", self._connect_handler)
|
||||
self.event_source.add_event_listener(
|
||||
"PB_CONNECT", self._connect_handler)
|
||||
|
||||
def _disconnect(self) -> None:
|
||||
self._remove_subscription_listeners()
|
||||
self.client_id = ""
|
||||
if not self.event_source:
|
||||
return
|
||||
self.event_source.remove_event_listener("PB_CONNECT", self._connect_handler)
|
||||
self.event_source.remove_event_listener(
|
||||
"PB_CONNECT", self._connect_handler)
|
||||
self.event_source.close()
|
||||
self.event_source = None
|
||||
262
pocketbase/services/record_service.py
Normal file
262
pocketbase/services/record_service.py
Normal file
@ -0,0 +1,262 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from urllib.parse import quote, urlencode
|
||||
from pocketbase.services.realtime_service import Callable, MessageData
|
||||
|
||||
from pocketbase.models.utils.base_model import BaseModel
|
||||
from pocketbase.models.record import Record
|
||||
from pocketbase.services.utils.crud_service import CrudService
|
||||
from pocketbase.utils import camel_to_snake
|
||||
|
||||
|
||||
class RecordAuthResponse:
|
||||
token: str
|
||||
record: Record
|
||||
|
||||
def __init__(self, token: str, record: Record, **kwargs) -> None:
|
||||
self.token = token
|
||||
self.record = record
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthProviderInfo:
|
||||
name: str
|
||||
state: str
|
||||
code_verifier: str
|
||||
code_challenge: str
|
||||
code_challenge_method: str
|
||||
auth_url: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthMethodsList:
|
||||
username_password: bool
|
||||
email_password: bool
|
||||
auth_providers: list[AuthProviderInfo]
|
||||
|
||||
|
||||
class RecordService(CrudService):
|
||||
collection_id_or_name: str
|
||||
|
||||
def __init__(self, client, collection_id_or_name) -> None:
|
||||
super().__init__(client)
|
||||
self.collection_id_or_name = collection_id_or_name
|
||||
|
||||
def decode(self, data: dict) -> BaseModel:
|
||||
return Record(data)
|
||||
|
||||
def base_crud_path(self) -> str:
|
||||
return self.base_collection_path() + "/records"
|
||||
|
||||
def base_collection_path(self) -> str:
|
||||
"""Returns the current collection service base path."""
|
||||
return "/api/collections/" + quote(self.collection_id_or_name)
|
||||
|
||||
def get_file_url(
|
||||
self, record: Record, filename: str, query_params: dict = {}
|
||||
) -> str:
|
||||
"""Builds and returns an absolute record file url."""
|
||||
base_url = self.client.base_url
|
||||
if base_url.endswith("/"):
|
||||
base_url = base_url[:-1]
|
||||
result = f"{base_url}/api/files/{record.collection_id}/{record.id}/{filename}"
|
||||
if query_params:
|
||||
result += "?" + urlencode(query_params)
|
||||
return result
|
||||
|
||||
def subscribe(self, callback: Callable[[MessageData], None]):
|
||||
"""Subscribe to realtime changes of any record from the collection."""
|
||||
return self.client.realtime.subscribe(self.collection_id_or_name, callback)
|
||||
|
||||
def subscribeOne(self, record_id: str, callback: Callable[[MessageData], None]):
|
||||
"""Subscribe to the realtime changes of a single record in the collection."""
|
||||
return self.client.realtime.subscribe(self.collection_id_or_name + '/' + record_id, callback)
|
||||
|
||||
def unsubscribe(self, *record_ids: List[str]):
|
||||
"""Subscribe to the realtime changes of a single record in the collection."""
|
||||
if record_ids and len(record_ids) == 0:
|
||||
subs = []
|
||||
for id in record_ids:
|
||||
subs.append(self.collection_id_or_name + '/' + id)
|
||||
return self.client.realtime.unsubscribe(*subs)
|
||||
return self.client.realtime.subscribe_by_prefix(self.collection_id_or_name)
|
||||
|
||||
def update(self, id: str, body_params: dict = {}, query_params: dict = {}):
|
||||
"""
|
||||
If the current `client.auth_store.model` matches with the updated id, then
|
||||
on success the `client.auth_store.model` will be updated with the result.
|
||||
"""
|
||||
item = super().update(id, body_params) # super(Record).update
|
||||
try:
|
||||
if self.client.auth_store.model.collection_id is not None and item.id == self.client.auth_store.model.id:
|
||||
self.client.auth_store.save(self.client.auth_store.token, item)
|
||||
except:
|
||||
pass
|
||||
return item
|
||||
|
||||
def delete(self, id: str, body_params: dict = {}, query_params: dict = {}):
|
||||
"""
|
||||
If the current `client.auth_store.model` matches with the deleted id,
|
||||
then on success the `client.auth_store` will be cleared.
|
||||
"""
|
||||
success = super().delete(id, body_params) # super(Record).delete
|
||||
try:
|
||||
if success and self.client.auth_store.model.collection_id is not None and id == self.client.auth_store.model.id:
|
||||
self.client.auth_store.clear()
|
||||
except:
|
||||
pass
|
||||
return success
|
||||
|
||||
def auth_response(self, response_data: dict) -> RecordAuthResponse:
|
||||
"""Prepare successful collection authorization response."""
|
||||
record = self.decode(response_data.pop("record", {}))
|
||||
token = response_data.pop("token", "")
|
||||
if token and record:
|
||||
self.client.auth_store.save(token, record)
|
||||
return RecordAuthResponse(token=token, record=record, **response_data)
|
||||
|
||||
def list_auth_methods(self, query_params: str = {}):
|
||||
"""Returns all available collection auth methods."""
|
||||
response_data = self.client.send(
|
||||
self.base_collection_path() + "/auth-methods",
|
||||
{"method": "GET", "params": query_params},
|
||||
)
|
||||
username_password = response_data.pop("usernamePassword", False)
|
||||
email_password = response_data.pop("emailPassword", False)
|
||||
|
||||
def apply_pythonic_keys(ap):
|
||||
pythonic_keys_ap = {camel_to_snake(key).replace(
|
||||
"@", ""): value for key, value in ap.items()}
|
||||
return pythonic_keys_ap
|
||||
|
||||
auth_providers = [
|
||||
AuthProviderInfo(**auth_provider)
|
||||
for auth_provider in map(apply_pythonic_keys, response_data.get("authProviders", []))
|
||||
]
|
||||
return AuthMethodsList(username_password, email_password, auth_providers)
|
||||
|
||||
def auth_with_password(
|
||||
self, username_or_email: str, password: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> RecordAuthResponse:
|
||||
"""
|
||||
Authenticate a single auth collection record via its username/email and password.
|
||||
|
||||
On success, this method also automatically updates
|
||||
the client's AuthStore data and returns:
|
||||
- the authentication token
|
||||
- the authenticated record model
|
||||
"""
|
||||
body_params.update(
|
||||
{"identity": username_or_email, "password": password})
|
||||
response_data = self.client.send(
|
||||
self.base_collection_path() + "/auth-with-password",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
"headers": {"Authorization": ""},
|
||||
},
|
||||
)
|
||||
return self.auth_response(response_data)
|
||||
|
||||
def auth_with_oauth2(
|
||||
self,
|
||||
provider: str,
|
||||
code: str,
|
||||
code_verifier: str,
|
||||
redirct_url: str,
|
||||
create_data={},
|
||||
body_params={},
|
||||
query_params={},
|
||||
):
|
||||
"""
|
||||
Authenticate a single auth collection record with OAuth2.
|
||||
|
||||
On success, this method also automatically updates
|
||||
the client's AuthStore data and returns:
|
||||
- the authentication token
|
||||
- the authenticated record model
|
||||
- the OAuth2 account data (eg. name, email, avatar, etc.)
|
||||
"""
|
||||
body_params.update({
|
||||
'provider': provider,
|
||||
'code': code,
|
||||
'codeVerifier': code_verifier,
|
||||
'redirectUrl': redirct_url,
|
||||
'createData': create_data,
|
||||
})
|
||||
response_data = self.client.send(
|
||||
self.base_collection_path() + "/auth-with-password",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
return self.auth_response(response_data)
|
||||
|
||||
def authRefresh(
|
||||
self, body_params: dict = {}, query_params: dict = {}
|
||||
) -> RecordAuthResponse:
|
||||
"""
|
||||
Refreshes the current authenticated record instance and
|
||||
returns a new token and record data.
|
||||
|
||||
On success this method also automatically updates the client's AuthStore.
|
||||
"""
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_collection_path() + "/auth-refresh",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def requestPasswordReset(
|
||||
self, email: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> bool:
|
||||
"""Sends auth record password reset request."""
|
||||
body_params.update({"email": email})
|
||||
self.client.send(
|
||||
self.base_collection_path() + "/request-password-reset",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
return True
|
||||
|
||||
def confirmPasswordReset(
|
||||
self,
|
||||
password_reset_token: str,
|
||||
password: str,
|
||||
password_confirm: str,
|
||||
body_params: dict = {},
|
||||
query_params: dict = {},
|
||||
) -> RecordAuthResponse:
|
||||
"""Confirms auth record password reset reque"""
|
||||
body_params.update(
|
||||
{
|
||||
"token": password_reset_token,
|
||||
"password": password,
|
||||
"passwordConfirm": password_confirm,
|
||||
}
|
||||
)
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_collection_path() + "/confirm-password-reset",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
)
|
||||
@ -1,27 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
from pocketbase.services.utils.sub_crud_service import SubCrudService
|
||||
from pocketbase.models.utils.base_model import BaseModel
|
||||
from pocketbase.models.record import Record
|
||||
|
||||
|
||||
class Records(SubCrudService):
|
||||
def decode(self, data: dict) -> BaseModel:
|
||||
return Record(data)
|
||||
|
||||
def base_crud_path(self, collection_id_or_name: str) -> str:
|
||||
return "/api/collections/" + quote(collection_id_or_name) + "/records"
|
||||
|
||||
def get_file_url(
|
||||
self, record: Record, filename: str, query_params: dict = {}
|
||||
) -> str:
|
||||
"""Builds and returns an absolute record file url."""
|
||||
base_url = self.client.base_url
|
||||
if base_url.endswith("/"):
|
||||
base_url = base_url[:-1]
|
||||
result = f"{base_url}/api/files/{record.collection_id}/{record.id}/{filename}"
|
||||
if query_params:
|
||||
result += "?" + urlencode(query_params)
|
||||
return result
|
||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from pocketbase.services.utils.base_service import BaseService
|
||||
|
||||
|
||||
class Settings(BaseService):
|
||||
class SettingsService(BaseService):
|
||||
def get_all(self, query_params: dict = {}) -> dict:
|
||||
"""Fetch all available app settings."""
|
||||
return self.client.send(
|
||||
@ -1,283 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
from pocketbase.services.utils.crud_service import CrudService
|
||||
from pocketbase.models.utils.base_model import BaseModel
|
||||
from pocketbase.models.user import User
|
||||
from pocketbase.models.external_auth import ExternalAuth
|
||||
|
||||
|
||||
class UserAuthResponse:
|
||||
token: str
|
||||
user: User
|
||||
|
||||
def __init__(self, token: str, user: User, **kwargs) -> None:
|
||||
self.token = token
|
||||
self.user = user
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthProviderInfo:
|
||||
name: str
|
||||
state: str
|
||||
code_verifier: str
|
||||
code_challenge: str
|
||||
code_challenge_method: str
|
||||
auth_url: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuthMethodsList:
|
||||
email_password: bool
|
||||
auth_providers: list[AuthProviderInfo]
|
||||
|
||||
|
||||
class Users(CrudService):
|
||||
def decode(self, data: dict) -> BaseModel:
|
||||
return User(data)
|
||||
|
||||
def base_crud_path(self) -> str:
|
||||
return "/api/users"
|
||||
|
||||
def auth_response(self, response_data: Any) -> UserAuthResponse:
|
||||
"""Prepare successful authorization response."""
|
||||
user = self.decode(response_data.pop("user", {}))
|
||||
token = response_data.pop("token", "")
|
||||
if token and user:
|
||||
self.client.auth_store.save(token, user)
|
||||
return UserAuthResponse(token=token, user=user, **response_data)
|
||||
|
||||
def list_auth_methods(self, query_params: dict = {}) -> AuthMethodsList:
|
||||
"""Returns all available application auth methods."""
|
||||
response_data = self.client.send(
|
||||
self.base_crud_path() + "/auth-methods",
|
||||
{"method": "GET", "params": query_params},
|
||||
)
|
||||
email_password = response_data.get("emailPassword", False)
|
||||
auth_providers = [
|
||||
AuthProviderInfo(auth_provider)
|
||||
for auth_provider in response_data.get("authProviders", [])
|
||||
]
|
||||
return AuthMethodsList(email_password, auth_providers)
|
||||
|
||||
def auth_via_email(
|
||||
self, email: str, password: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> UserAuthResponse:
|
||||
"""
|
||||
Authenticate a user via its email and password.
|
||||
|
||||
On success, this method also automatically updates
|
||||
the client's AuthStore data and returns:
|
||||
- new user authentication token
|
||||
- the authenticated user model record
|
||||
"""
|
||||
body_params.update({"email": email, "password": password})
|
||||
response_data = self.client.send(
|
||||
self.base_crud_path() + "/auth-via-email",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
"headers": {"Authorization": ""},
|
||||
},
|
||||
)
|
||||
return self.auth_response(response_data)
|
||||
|
||||
def auth_via_oauth2(
|
||||
self,
|
||||
provider: str,
|
||||
code: str,
|
||||
code_verifier: str,
|
||||
redirect_url: str,
|
||||
body_params: dict = {},
|
||||
query_params: dict = {},
|
||||
) -> UserAuthResponse:
|
||||
"""
|
||||
Authenticate a user via OAuth2 client provider.
|
||||
|
||||
On success, this method also automatically updates
|
||||
the client's AuthStore data and returns:
|
||||
- new user authentication token
|
||||
- the authenticated user model record
|
||||
- the OAuth2 user profile data (eg. name, email, avatar, etc.)
|
||||
"""
|
||||
body_params.update(
|
||||
{
|
||||
"provider": provider,
|
||||
"code": code,
|
||||
"codeVerifier": code_verifier,
|
||||
"redirectUrl": redirect_url,
|
||||
}
|
||||
)
|
||||
response_data = self.client.send(
|
||||
self.base_crud_path() + "/auth-via-oauth2",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
"headers": {"Authorization": ""},
|
||||
},
|
||||
)
|
||||
return self.auth_response(response_data)
|
||||
|
||||
def refresh(
|
||||
self, body_params: dict = {}, query_params: dict = {}
|
||||
) -> UserAuthResponse:
|
||||
"""
|
||||
Refreshes the current user authenticated instance and
|
||||
returns a new token and user data.
|
||||
|
||||
On success this method also automatically updates the client's AuthStore data.
|
||||
"""
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/refresh",
|
||||
{"method": "POST", "params": query_params, "body": body_params},
|
||||
)
|
||||
)
|
||||
|
||||
def request_password_reset(
|
||||
self, email: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> bool:
|
||||
"""Sends user password reset request."""
|
||||
body_params.update({"email": email})
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/request-password-reset",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
return True
|
||||
|
||||
def confirm_password_reset(
|
||||
self,
|
||||
password_reset_token: str,
|
||||
password: str,
|
||||
password_confirm: str,
|
||||
body_params: dict = {},
|
||||
query_params: dict = {},
|
||||
) -> UserAuthResponse:
|
||||
"""Confirms user password reset request."""
|
||||
body_params.update(
|
||||
{
|
||||
"token": password_reset_token,
|
||||
"password": password,
|
||||
"passwordConfirm": password_confirm,
|
||||
}
|
||||
)
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/confirm-password-reset",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def request_verification(
|
||||
self, email: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> bool:
|
||||
"""Sends user verification email request."""
|
||||
body_params.update({"email": email})
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/request-verification",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
return True
|
||||
|
||||
def confirm_verification(
|
||||
self, verification_token: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> UserAuthResponse:
|
||||
"""Confirms user email verification request."""
|
||||
body_params.update(
|
||||
{
|
||||
"token": verification_token,
|
||||
}
|
||||
)
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/confirm-verification",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def request_email_change(
|
||||
self, new_email: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> bool:
|
||||
"""Sends an email change request to the authenticated user."""
|
||||
body_params.update({"newEmail": new_email})
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/request-email-change",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
return True
|
||||
|
||||
def confirm_email_change(
|
||||
self,
|
||||
email_change_token: str,
|
||||
password: str,
|
||||
body_params: dict = {},
|
||||
query_params: dict = {},
|
||||
) -> UserAuthResponse:
|
||||
"""Confirms user new email address."""
|
||||
body_params.update(
|
||||
{
|
||||
"token": email_change_token,
|
||||
"password": password,
|
||||
}
|
||||
)
|
||||
return self.auth_response(
|
||||
self.client.send(
|
||||
self.base_crud_path() + "/confirm-email-change",
|
||||
{
|
||||
"method": "POST",
|
||||
"params": query_params,
|
||||
"body": body_params,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def list_external_auths(
|
||||
self, user_id: str, query_params: dict = {}
|
||||
) -> list[ExternalAuth]:
|
||||
"""Lists all linked external auth providers for the specified user."""
|
||||
response_data = self.client.send(
|
||||
self.base_crud_path() + "/" + quote(user_id) + "/external-auths",
|
||||
{"method": "GET", "params": query_params},
|
||||
)
|
||||
return [ExternalAuth(item) for item in response_data]
|
||||
|
||||
def unlink_external_auth(
|
||||
self, user_id: str, provider: str, query_params: dict = {}
|
||||
) -> bool:
|
||||
"""Unlink a single external auth provider from the specified user."""
|
||||
self.client.send(
|
||||
self.base_crud_path()
|
||||
+ "/"
|
||||
+ quote(user_id)
|
||||
+ "/external-auths/"
|
||||
+ quote(provider),
|
||||
{"method": "DELETE", "params": query_params},
|
||||
)
|
||||
return True
|
||||
@ -1,4 +1,3 @@
|
||||
from .base_crud_service import BaseCrudService
|
||||
from .base_service import BaseService
|
||||
from .crud_service import CrudService
|
||||
from .sub_crud_service import SubCrudService
|
||||
|
||||
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from abc import ABC
|
||||
from urllib.parse import quote
|
||||
from pocketbase.utils import ClientResponseError
|
||||
|
||||
from pocketbase.models.utils.base_model import BaseModel
|
||||
from pocketbase.models.utils.list_result import ListResult
|
||||
@ -52,10 +53,29 @@ class BaseCrudService(BaseService, ABC):
|
||||
def _get_one(self, base_path: str, id: str, query_params: dict = {}) -> BaseModel:
|
||||
return self.decode(
|
||||
self.client.send(
|
||||
f"{base_path}/{quote(id)}", {"method": "GET", "params": query_params}
|
||||
f"{base_path}/{quote(id)}",
|
||||
{
|
||||
"method": "GET",
|
||||
"params": query_params
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
def _get_first_list_item(self, base_path: str, filter: str, query_params={}):
|
||||
query_params.update({
|
||||
'filter': filter,
|
||||
'$cancelKey': 'one_by_filter_' + base_path + '_' + filter,
|
||||
})
|
||||
result = self._get_list(base_path, 1, 1, query_params)
|
||||
try:
|
||||
if len(result.items) == 0:
|
||||
raise
|
||||
except:
|
||||
raise ClientResponseError(
|
||||
"The requested resource wasn't found.",
|
||||
status=404
|
||||
)
|
||||
|
||||
def _create(
|
||||
self, base_path: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> BaseModel:
|
||||
@ -78,6 +98,7 @@ class BaseCrudService(BaseService, ABC):
|
||||
|
||||
def _delete(self, base_path: str, id: str, query_params: dict = {}) -> bool:
|
||||
self.client.send(
|
||||
f"{base_path}/{quote(id)}", {"method": "DELETE", "params": query_params}
|
||||
f"{base_path}/{quote(id)}", {"method": "DELETE",
|
||||
"params": query_params}
|
||||
)
|
||||
return True
|
||||
|
||||
@ -12,16 +12,31 @@ class CrudService(BaseCrudService, ABC):
|
||||
"""Base path for the crud actions (without trailing slash, eg. '/admins')."""
|
||||
|
||||
def get_full_list(
|
||||
self, batch_size: int = 100, query_params: dict = {}
|
||||
self, batch: int = 200, query_params: dict = {}
|
||||
) -> list[BaseModel]:
|
||||
return self._get_full_list(self.base_crud_path(), batch_size, query_params)
|
||||
return self._get_full_list(self.base_crud_path(), batch, query_params)
|
||||
|
||||
def get_list(
|
||||
self, page: int = 1, per_page: int = 30, query_params: dict = {}
|
||||
) -> ListResult:
|
||||
return self._get_list(self.base_crud_path(), page, per_page, query_params)
|
||||
|
||||
def _get_first_list_item(self, base_path: str, filter: str, query_params):
|
||||
"""
|
||||
Returns the first found item by the specified filter.
|
||||
|
||||
Internally it calls `getList(1, 1, { filter })` and returns the
|
||||
first found item.
|
||||
|
||||
For consistency with `getOne`, this method will throw a 404
|
||||
ClientResponseError if no item was found.
|
||||
"""
|
||||
return self._get_first_list_item(base_path, filter, query_params)
|
||||
|
||||
def get_one(self, id: str, query_params: dict = {}) -> BaseModel:
|
||||
"""
|
||||
Returns single item by its id.
|
||||
"""
|
||||
return self._get_one(self.base_crud_path(), id, query_params)
|
||||
|
||||
def create(self, body_params: dict = {}, query_params: dict = {}) -> BaseModel:
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC
|
||||
|
||||
from pocketbase.models.utils.base_model import BaseModel
|
||||
from pocketbase.models.utils.list_result import ListResult
|
||||
from pocketbase.services.utils.base_crud_service import BaseCrudService
|
||||
|
||||
|
||||
class SubCrudService(BaseCrudService, ABC):
|
||||
def base_crud_path(self) -> str:
|
||||
"""Base path for the crud actions (without trailing slash, eg. '/admins')."""
|
||||
|
||||
def get_full_list(
|
||||
self, sub: str, batch_size: int = 100, query_params: dict = {}
|
||||
) -> list[BaseModel]:
|
||||
return self._get_full_list(self.base_crud_path(sub), batch_size, query_params)
|
||||
|
||||
def get_list(
|
||||
self, sub: str, page: int = 1, per_page: int = 30, query_params: dict = {}
|
||||
) -> ListResult:
|
||||
return self._get_list(self.base_crud_path(sub), page, per_page, query_params)
|
||||
|
||||
def get_one(self, sub: str, id: str, query_params: dict = {}) -> BaseModel:
|
||||
return self._get_one(self.base_crud_path(sub), id, query_params)
|
||||
|
||||
def create(
|
||||
self, sub: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> BaseModel:
|
||||
return self._create(self.base_crud_path(sub), body_params, query_params)
|
||||
|
||||
def update(
|
||||
self, sub: str, id: str, body_params: dict = {}, query_params: dict = {}
|
||||
) -> BaseModel:
|
||||
return self._update(self.base_crud_path(sub), id, body_params, query_params)
|
||||
|
||||
def delete(self, sub: str, id: str, query_params: dict = {}) -> bool:
|
||||
return self._delete(self.base_crud_path(sub), id, query_params)
|
||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from abc import ABC
|
||||
|
||||
from pocketbase.models.admin import Admin
|
||||
from pocketbase.models.user import User
|
||||
from pocketbase.models.record import Record
|
||||
|
||||
|
||||
class BaseAuthStore(ABC):
|
||||
@ -13,10 +13,10 @@ class BaseAuthStore(ABC):
|
||||
"""
|
||||
|
||||
base_token: str
|
||||
base_model: User | Admin | None
|
||||
base_model: Record | Admin | None
|
||||
|
||||
def __init__(
|
||||
self, base_token: str = "", base_model: User | Admin | None = None
|
||||
self, base_token: str = "", base_model: Record | Admin | None = None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.base_token = base_token
|
||||
@ -28,14 +28,15 @@ class BaseAuthStore(ABC):
|
||||
return self.base_token
|
||||
|
||||
@property
|
||||
def model(self) -> User | Admin | None:
|
||||
def model(self) -> Record | Admin | None:
|
||||
"""Retrieves the stored model data (if any)."""
|
||||
return self.base_model
|
||||
|
||||
def save(self, token: str = "", model: User | Admin | None = None) -> None:
|
||||
def save(self, token: str = "", model: Record | Admin | None = None) -> None:
|
||||
"""Saves the provided new token and model data in the auth store."""
|
||||
self.base_token = token
|
||||
self.base_model = model
|
||||
|
||||
self.base_token = token if token else ""
|
||||
self.base_model = model if model else None
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Removes the stored token and model data form the auth store."""
|
||||
|
||||
@ -5,7 +5,7 @@ import pickle
|
||||
import os
|
||||
|
||||
from pocketbase.stores.base_auth_store import BaseAuthStore
|
||||
from pocketbase.models.user import User
|
||||
from pocketbase.models.record import Record
|
||||
from pocketbase.models.admin import Admin
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ class LocalAuthStore(BaseAuthStore):
|
||||
filename: str = "pocketbase_auth.data",
|
||||
filepath: str = "",
|
||||
base_token: str = "",
|
||||
base_model: User | Admin | None = None,
|
||||
base_model: Record | Admin | None = None,
|
||||
) -> None:
|
||||
super().__init__(base_token, base_model)
|
||||
self.filename = filename
|
||||
@ -33,13 +33,13 @@ class LocalAuthStore(BaseAuthStore):
|
||||
return data["token"]
|
||||
|
||||
@property
|
||||
def model(self) -> User | Admin | None:
|
||||
def model(self) -> Record | Admin | None:
|
||||
data = self._storage_get(self.complete_filepath)
|
||||
if not data or "model" not in data:
|
||||
return None
|
||||
return data["model"]
|
||||
|
||||
def save(self, token: str = "", model: User | Admin | None = None) -> None:
|
||||
def save(self, token: str = "", model: Record | Admin | None = None) -> None:
|
||||
self._storage_set(self.complete_filepath, {"token": token, "model": model})
|
||||
super().save(token, model)
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
def camel_to_snake(name: str) -> str:
|
||||
@ -17,3 +18,19 @@ def to_datetime(
|
||||
return datetime.datetime.strptime(str_datetime, format)
|
||||
except Exception:
|
||||
return str_datetime
|
||||
|
||||
|
||||
class ClientResponseError(Exception):
|
||||
url: str = ""
|
||||
status: int = 0
|
||||
data: dict = {}
|
||||
is_abort: bool = False
|
||||
original_error: Any | None = None
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args)
|
||||
self.url = kwargs.get("url", "")
|
||||
self.status = kwargs.get("status", 0)
|
||||
self.data = kwargs.get("data", {})
|
||||
self.is_abort = kwargs.get("is_abort", False)
|
||||
self.original_error = kwargs.get("original_error", None)
|
||||
|
||||
202
poetry.lock
generated
202
poetry.lock
generated
@ -1,6 +1,6 @@
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.1"
|
||||
version = "3.6.2"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -14,7 +14,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
trio = ["trio (>=0.16,<0.22)"]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
@ -28,15 +28,15 @@ python-versions = ">=3.5"
|
||||
dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
|
||||
tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
|
||||
tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
|
||||
tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.8.0"
|
||||
version = "22.10.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
@ -75,25 +75,22 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "coverage"
|
||||
version = "6.4.4"
|
||||
description = "Code coverage measurement for Python"
|
||||
name = "exceptiongroup"
|
||||
version = "1.0.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
|
||||
|
||||
[package.extras]
|
||||
toml = ["tomli"]
|
||||
test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "flake8"
|
||||
@ -224,15 +221,15 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
version = "2.5.3"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
@ -249,14 +246,6 @@ importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
dev = ["pre-commit", "tox"]
|
||||
testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "py"
|
||||
version = "1.11.0"
|
||||
description = "library with cross-python path, ini-parsing, io, code, log facilities"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "pycodestyle"
|
||||
version = "2.9.1"
|
||||
@ -286,7 +275,7 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "7.1.3"
|
||||
version = "7.2.0"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@ -295,31 +284,16 @@ python-versions = ">=3.7"
|
||||
[package.dependencies]
|
||||
attrs = ">=19.2.0"
|
||||
colorama = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
|
||||
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
|
||||
iniconfig = "*"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<2.0"
|
||||
py = ">=1.8.2"
|
||||
tomli = ">=1.0.0"
|
||||
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[package.extras]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-cov"
|
||||
version = "3.0.0"
|
||||
description = "Pytest plugin for measuring coverage."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
coverage = {version = ">=5.2.1", extras = ["toml"]}
|
||||
pytest = ">=4.6"
|
||||
|
||||
[package.extras]
|
||||
testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
version = "1.5.0"
|
||||
@ -360,7 +334,7 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.3.0"
|
||||
version = "4.4.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -368,54 +342,52 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.8.1"
|
||||
version = "3.10.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
|
||||
testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
|
||||
testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "78af276171c1e41e0cb3205383b288b9ed15cb8fdf68e7184954909e8b07c7c6"
|
||||
content-hash = "8f9534771a19adba002263f1c9c563e5d1bd7e1f134fd42aa85689b614fd6e0a"
|
||||
|
||||
[metadata.files]
|
||||
anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
{file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
|
||||
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
|
||||
{file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce957f1d6b78a8a231b18e0dd2d94a33d2ba738cd88a7fe64f53f659eea49fdd"},
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5107ea36b2b61917956d018bd25129baf9ad1125e39324a9b18248d362156a27"},
|
||||
{file = "black-22.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8166b7bfe5dcb56d325385bd1d1e0f635f24aae14b3ae437102dedc0c186747"},
|
||||
{file = "black-22.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd82842bb272297503cbec1a2600b6bfb338dae017186f8f215c8958f8acf869"},
|
||||
{file = "black-22.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d839150f61d09e7217f52917259831fe2b689f5c8e5e32611736351b89bb2a90"},
|
||||
{file = "black-22.8.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a05da0430bd5ced89176db098567973be52ce175a55677436a271102d7eaa3fe"},
|
||||
{file = "black-22.8.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a098a69a02596e1f2a58a2a1c8d5a05d5a74461af552b371e82f9fa4ada8342"},
|
||||
{file = "black-22.8.0-cp36-cp36m-win_amd64.whl", hash = "sha256:5594efbdc35426e35a7defa1ea1a1cb97c7dbd34c0e49af7fb593a36bd45edab"},
|
||||
{file = "black-22.8.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983526af1bea1e4cf6768e649990f28ee4f4137266921c2c3cee8116ae42ec3"},
|
||||
{file = "black-22.8.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b2c25f8dea5e8444bdc6788a2f543e1fb01494e144480bc17f806178378005e"},
|
||||
{file = "black-22.8.0-cp37-cp37m-win_amd64.whl", hash = "sha256:78dd85caaab7c3153054756b9fe8c611efa63d9e7aecfa33e533060cb14b6d16"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cea1b2542d4e2c02c332e83150e41e3ca80dc0fb8de20df3c5e98e242156222c"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b879eb439094751185d1cfdca43023bc6786bd3c60372462b6f051efa6281a5"},
|
||||
{file = "black-22.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a12e4e1353819af41df998b02c6742643cfef58282915f781d0e4dd7a200411"},
|
||||
{file = "black-22.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a73f66b6d5ba7288cd5d6dad9b4c9b43f4e8a4b789a94bf5abfb878c663eb3"},
|
||||
{file = "black-22.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:e981e20ec152dfb3e77418fb616077937378b322d7b26aa1ff87717fb18b4875"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8ce13ffed7e66dda0da3e0b2eb1bdfc83f5812f66e09aca2b0978593ed636b6c"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:32a4b17f644fc288c6ee2bafdf5e3b045f4eff84693ac069d87b1a347d861497"},
|
||||
{file = "black-22.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ad827325a3a634bae88ae7747db1a395d5ee02cf05d9aa7a9bd77dfb10e940c"},
|
||||
{file = "black-22.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53198e28a1fb865e9fe97f88220da2e44df6da82b18833b588b1883b16bb5d41"},
|
||||
{file = "black-22.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:bc4d4123830a2d190e9cc42a2e43570f82ace35c3aeb26a512a2102bce5af7ec"},
|
||||
{file = "black-22.8.0-py3-none-any.whl", hash = "sha256:d2c21d439b2baf7aa80d6dd4e3659259be64c6f49dfd0f32091063db0e006db4"},
|
||||
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
|
||||
{file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"},
|
||||
{file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"},
|
||||
{file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"},
|
||||
{file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"},
|
||||
{file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"},
|
||||
{file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"},
|
||||
{file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"},
|
||||
{file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"},
|
||||
{file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"},
|
||||
{file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"},
|
||||
{file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"},
|
||||
{file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"},
|
||||
{file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"},
|
||||
{file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"},
|
||||
{file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"},
|
||||
{file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"},
|
||||
{file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"},
|
||||
{file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"},
|
||||
{file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"},
|
||||
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
|
||||
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
||||
@ -426,60 +398,12 @@ click = [
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
coverage = [
|
||||
{file = "coverage-6.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-win32.whl", hash = "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0"},
|
||||
{file = "coverage-6.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-win32.whl", hash = "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796"},
|
||||
{file = "coverage-6.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-win32.whl", hash = "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c"},
|
||||
{file = "coverage-6.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-win32.whl", hash = "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781"},
|
||||
{file = "coverage-6.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-win32.whl", hash = "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145"},
|
||||
{file = "coverage-6.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827"},
|
||||
{file = "coverage-6.4.4-pp36.pp37.pp38-none-any.whl", hash = "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1"},
|
||||
{file = "coverage-6.4.4.tar.gz", hash = "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58"},
|
||||
exceptiongroup = [
|
||||
{file = "exceptiongroup-1.0.1-py3-none-any.whl", hash = "sha256:4d6c0aa6dd825810941c792f53d7b8d71da26f5e5f84f20f9508e8f2d33b140a"},
|
||||
{file = "exceptiongroup-1.0.1.tar.gz", hash = "sha256:73866f7f842ede6cb1daa42c4af078e2035e5f7607f0e2c762cc51bb31bbe7b2"},
|
||||
]
|
||||
flake8 = [
|
||||
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
|
||||
@ -526,17 +450,13 @@ pathspec = [
|
||||
{file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
{file = "platformdirs-2.5.3-py3-none-any.whl", hash = "sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb"},
|
||||
{file = "platformdirs-2.5.3.tar.gz", hash = "sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"},
|
||||
]
|
||||
pluggy = [
|
||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
||||
]
|
||||
py = [
|
||||
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
|
||||
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
|
||||
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
|
||||
@ -550,12 +470,8 @@ pyparsing = [
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
|
||||
{file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
|
||||
]
|
||||
pytest-cov = [
|
||||
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
|
||||
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
|
||||
{file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"},
|
||||
{file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"},
|
||||
]
|
||||
rfc3986 = [
|
||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||
@ -596,10 +512,10 @@ typed-ast = [
|
||||
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
|
||||
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
|
||||
{file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
|
||||
{file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"},
|
||||
{file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"},
|
||||
]
|
||||
|
||||
@ -5,6 +5,7 @@ requires-python = ">=3.7"
|
||||
license = "MIT"
|
||||
authors = [
|
||||
{ name = "Vithor Jaeger", email = "vaphes@gmail.com" },
|
||||
{ name = "Max Amling", email = "max-amling@web.de" },
|
||||
]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user