diff --git a/templates/python/base/requests/api.twig b/templates/python/base/requests/api.twig index defae2819..f751f299e 100644 --- a/templates/python/base/requests/api.twig +++ b/templates/python/base/requests/api.twig @@ -1,8 +1,8 @@ - return self.client.call('{{ method.method | caseLower }}', path, { + return self.client.call('{{ method.method | caseLower }}', path=api_path, headers={ {% for parameter in method.parameters.header %} '{{ parameter.name }}': {{ parameter.name | escapeKeyword | caseSnake }}, {% endfor %} {% for key, header in method.headers %} '{{ key }}': '{{ header }}', {% endfor %} - }, params) \ No newline at end of file + }, params=params) \ No newline at end of file diff --git a/templates/python/base/requests/file.twig b/templates/python/base/requests/file.twig index 8a8d223ff..4d7f2c969 100644 --- a/templates/python/base/requests/file.twig +++ b/templates/python/base/requests/file.twig @@ -12,11 +12,11 @@ {% endif %} {% endfor %} - return self.client.chunked_upload(path, { + return self.client.chunked_upload(path=api_path, headers={ {% for parameter in method.parameters.header %} '{{ parameter.name }}': {{ parameter.name | escapeKeyword | caseSnake }}, {% endfor %} {% for key, header in method.headers %} '{{ key }}': '{{ header }}', {% endfor %} - }, params, param_name, on_progress, upload_id) \ No newline at end of file + }, params=params, param_name=param_name, on_progress=on_progress, upload_id=upload_id) \ No newline at end of file diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig index 057693f1f..0173086ee 100644 --- a/templates/python/package/client.py.twig +++ b/templates/python/package/client.py.twig @@ -1,15 +1,16 @@ +from __future__ import annotations +import io import requests import os from .input_file import InputFile from .exception import {{spec.title | caseUcfirst}}Exception -from typing import Optional class Client: - def __init__(self): - self._chunk_size = 5*1024*1024 - self._self_signed = False - self._endpoint = '{{spec.endpoint}}' - self._global_headers = { + def __init__(self) -> None: + self._chunk_size: int = 5*1024*1024 + self._self_signed: bool = False + self._endpoint: str = '{{spec.endpoint}}' + self._global_headers: dict[str, str] = { 'content-type': '', 'user-agent' : '{{spec.title | caseUcfirst}}{{ language.name | caseUcfirst }}SDK/{{ sdk.version }} (${os.uname().sysname}; ${os.uname().version}; ${os.uname().machine})', 'x-sdk-name': '{{ sdk.name }}', @@ -21,20 +22,20 @@ class Client: {% endfor %} } - def set_self_signed(self, status: bool = True): + def set_self_signed(self, status: bool = True) -> 'Client': self._self_signed = status return self - def set_endpoint(self, endpoint: str): + def set_endpoint(self, endpoint: str) -> 'Client': self._endpoint = endpoint return self - def add_header(self, key: str, value: str): + def add_header(self, key: str, value: str) -> 'Client': self._global_headers[key.lower()] = value return self {% for header in spec.global.headers %} - def set_{{header.key | caseSnake}}(self, value: str): + def set_{{header.key | caseSnake}}(self, value: str) -> 'Client': {% if header.description %} """{{header.description}}""" @@ -43,17 +44,13 @@ class Client: return self {% endfor %} - def call(self, method: str, path: str = '', headers: Optional[dict] = None, params: Optional[dict] = None): - if headers is None: - headers = {} + def call(self, method: str, path: str = '', headers: dict[str, str] = {}, params: dict[str, Any] = {}) -> dict[str, Any] | bytes: + params = {k: v for k, v in params.items() if v is not None} # Remove None values from params dictionary - if params is None: - params = {} - - data = {} - json = {} - files = {} - stringify = False + data: dict[str, Any] = {} + json: dict[str, Any] = {} + files: dict[str, Any] = {} + stringify: bool = False headers = {**self._global_headers, **headers} @@ -106,12 +103,12 @@ class Client: def chunked_upload( self, path: str, - headers: Optional[dict] = None, - params: Optional[dict] = None, + headers: dict[str, str] = {}, + params: dict[str, Any] = {}, param_name: str = '', - on_progress = None, + on_progress: Any = None, upload_id: str = '' - ): + ) -> dict[str, Any]: input_file = params[param_name] if input_file.source_type == 'path': @@ -158,7 +155,7 @@ class Client: input_file.data = input[offset:end] params[param_name] = input_file - headers["content-range"] = f'bytes {offset}-{min((offset + self._chunk_size) - 1, size)}/{size}' + headers["content-range"] = f'bytes {offset}-{min((offset + self._chunk_size) - 1, size - 1)}/{size}' result = self.call( 'post', @@ -173,7 +170,7 @@ class Client: headers["x-{{ spec.title | caseLower }}-id"] = result["$id"] if on_progress is not None: - end = min((((counter * self._chunk_size) + self._chunk_size) - 1), size) + end = min((((counter * self._chunk_size) + self._chunk_size) - 1), size - 1) on_progress({ "$id": result["$id"], "progress": min(offset, size)/size * 100, @@ -186,8 +183,8 @@ class Client: return result - def flatten(self, data: dict, prefix: str = '', stringify: bool = False): - output = {} + def flatten(self, data: Union[dict[str, Any], list], prefix: str = '', stringify: bool = False) -> dict[str, Any]: + output: dict[str, Any] = {} i = 0 for key in data: diff --git a/templates/python/package/exception.py.twig b/templates/python/package/exception.py.twig index 3383d21d6..3fbc8867c 100644 --- a/templates/python/package/exception.py.twig +++ b/templates/python/package/exception.py.twig @@ -1,9 +1,10 @@ -from typing import Optional +from __future__ import annotations +from typing import Any class {{spec.title | caseUcfirst}}Exception(Exception): - def __init__(self, message: str, code: int = 0, type: Optional[str] = None, response: Optional[dict] = None): - self.message = message - self.code = code - self.type = type - self.response = response + def __init__(self, message: str, code: int = 0, type: str | None = None, response: Any | None = None) -> None: + self.message: str = message + self.code: int = code + self.type: str | None = type + self.response: Any | None = response super().__init__(self.message) diff --git a/templates/python/package/id.py.twig b/templates/python/package/id.py.twig index 6c9c7db92..efcb074cf 100644 --- a/templates/python/package/id.py.twig +++ b/templates/python/package/id.py.twig @@ -1,8 +1,8 @@ class ID: @staticmethod - def custom(id): + def custom(id: str) -> str: return id @staticmethod - def unique(): - return 'unique()' + def unique() -> str: + return 'unique()' \ No newline at end of file diff --git a/templates/python/package/input_file.py.twig b/templates/python/package/input_file.py.twig index d1e0c7f2f..f498f0224 100644 --- a/templates/python/package/input_file.py.twig +++ b/templates/python/package/input_file.py.twig @@ -1,22 +1,22 @@ +from __future__ import annotations import os import mimetypes -from typing import Optional class InputFile: @classmethod def from_path(cls, path: str) -> 'InputFile': - instance = cls() - instance.path = path - instance.filename = os.path.basename(path) - instance.mime_type = mimetypes.guess_type(path) - instance.source_type = 'path' + instance: 'InputFile' = cls() + instance.path: str = path + instance.filename: str = os.path.basename(path) + instance.mime_type: str | None = mimetypes.guess_type(path)[0] + instance.source_type: str = 'path' return instance @classmethod - def from_bytes(cls, bytes, filename: Optional[str] = None, mime_type: Optional[str] = None) -> 'InputFile': - instance = cls() - instance.data = bytes - instance.filename = filename - instance.mime_type = mime_type - instance.source_type = 'bytes' + def from_bytes(cls, bytes: bytes, filename: str | None = None, mime_type: str | None = None) -> 'InputFile': + instance: 'InputFile' = cls() + instance.data: bytes = bytes + instance.filename: str | None = filename + instance.mime_type: str | None = mime_type + instance.source_type: str = 'bytes' return instance diff --git a/templates/python/package/permission.py.twig b/templates/python/package/permission.py.twig index ffbad7a23..0c494f4d0 100644 --- a/templates/python/package/permission.py.twig +++ b/templates/python/package/permission.py.twig @@ -1,21 +1,21 @@ class Permission: @staticmethod - def read(role) -> str: + def read(role: str) -> str: return f'read("{role}")' @staticmethod - def write(role) -> str: + def write(role: str) -> str: return f'write("{role}")' @staticmethod - def create(role) -> str: + def create(role: str) -> str: return f'create("{role}")' @staticmethod - def update(role) -> str: + def update(role: str) -> str: return f'update("{role}")' @staticmethod - def delete(role) -> str: + def delete(role: str) -> str: return f'delete("{role}")' diff --git a/templates/python/package/query.py.twig b/templates/python/package/query.py.twig index d92930477..2b8bf65f0 100644 --- a/templates/python/package/query.py.twig +++ b/templates/python/package/query.py.twig @@ -1,92 +1,94 @@ +from __future__ import annotations + class Query: @staticmethod - def equal(attribute, value) -> str: + def equal(attribute: str, value: str | list[str]) -> str: return Query.add_query(attribute, "equal", value) @staticmethod - def not_equal(attribute, value) -> str: + def not_equal(attribute: str, value: str | list[str]) -> str: return Query.add_query(attribute, "notEqual", value) @staticmethod - def less_than(attribute, value) -> str: + def less_than(attribute: str, value: str | list[str]) -> str: return Query.add_query(attribute, "lessThan", value) @staticmethod - def less_than_equal(attribute, value) -> str: + def less_than_equal(attribute: str, value: str | list[str]) -> str: return Query.add_query(attribute, "lessThanEqual", value) @staticmethod - def greater_than(attribute, value) -> str: + def greater_than(attribute: str, value: str | list[str]) -> str: return Query.add_query(attribute, "greaterThan", value) @staticmethod - def greater_than_equal(attribute, value) -> str: + def greater_than_equal(attribute: str, value: str | list[str]) -> str: return Query.add_query(attribute, "greaterThanEqual", value) @staticmethod - def is_null(attribute) -> str: + def is_null(attribute: str) -> str: return f'isNull("{attribute}")' @staticmethod - def is_not_null(attribute) -> str: + def is_not_null(attribute: str) -> str: return f'isNotNull("{attribute}")' @staticmethod - def between(attribute, start, end) -> str: - return Query.add_query(attribute, "between", [start, end]) + def between(attribute: str, start: str | int, end: str | int) -> str: + return f'between("{attribute}", {Query.parseValues(start)}, {Query.parseValues(end)})' @staticmethod - def starts_with(attribute, value) -> str: + def starts_with(attribute: str, value: str) -> str: return Query.add_query(attribute, "startsWith", value) @staticmethod - def ends_with(attribute, value) -> str: + def ends_with(attribute: str, value: str) -> str: return Query.add_query(attribute, "endsWith", value) @staticmethod - def select(attributes) -> str: + def select(attributes: list[str]) -> str: return f'select([{",".join(map(Query.parseValues, attributes))}])' @staticmethod - def search(attribute, value) -> str: + def search(attribute: str, value: str) -> str: return Query.add_query(attribute, "search", value) @staticmethod - def order_asc(attribute) -> str: + def order_asc(attribute: str) -> str: return f'orderAsc("{attribute}")' @staticmethod - def order_desc(attribute) -> str: + def order_desc(attribute: str) -> str: return f'orderDesc("{attribute}")' @staticmethod - def cursor_before(id) -> str: + def cursor_before(id: str) -> str: return f'cursorBefore("{id}")' @staticmethod - def cursor_after(id) -> str: + def cursor_after(id: str) -> str: return f'cursorAfter("{id}")' @staticmethod - def limit(limit) -> str: + def limit(limit: int) -> str: return f'limit({limit})' @staticmethod - def offset(offset) -> str: + def offset(offset: int) -> str: return f'offset({offset})' @staticmethod - def add_query(attribute, method, value) -> str: - if type(value) == list: + def add_query(attribute: str, method: str, value: str | list[str]) -> str: + if isinstance(value, list): return f'{method}("{attribute}", [{",".join(map(Query.parseValues, value))}])' else: return f'{method}("{attribute}", [{Query.parseValues(value)}])' @staticmethod - def parseValues(value) -> str: - if type(value) == str: + def parseValues(value: str | int | bool) -> str: + if isinstance(value, str): return f'"{value}"' - elif type(value) == bool: + elif isinstance(value, bool): return str(value).lower() else: - return str(value) + return str(value) \ No newline at end of file diff --git a/templates/python/package/role.py.twig b/templates/python/package/role.py.twig index ae3b7db50..8d294a1a7 100644 --- a/templates/python/package/role.py.twig +++ b/templates/python/package/role.py.twig @@ -1,30 +1,111 @@ class Role: + """Helper class to generate role strings for `Permission`.""" @staticmethod def any() -> str: + """Grants access to anyone. + + This includes authenticated and unauthenticated users. + """ return 'any' @staticmethod - def user(id, status: str = "") -> str: + def user(id: str, status: str = "") -> str: + """Grants access to a specific user by user ID. + + You can optionally pass verified or unverified for + `status` to target specific types of users. + + Parameters + ---------- + id : str + status : str, optional + + Returns + ------- + str + """ if status: return f'user:{id}/{status}' return f'user:{id}' @staticmethod def users(status: str = "") -> str: + """Grants access to any authenticated or anonymous user. + + You can optionally pass verified or unverified for + `status` to target specific types of users. + + Parameters + ---------- + status : str, optional + + Returns + ------- + str + """ if status: return f'users/{status}' return 'users' @staticmethod def guests() -> str: + """Grants access to any guest user without a session. + + Authenticated users don't have access to this role. + + Returns + ------- + str + """ return 'guests' @staticmethod - def team(id, role: str = "") -> str: + def team(id: str, role: str = "") -> str: + """Grants access to a team by team ID. + + You can optionally pass a role for `role` to target + team members with the specified role. + + Parameters + ---------- + id : str + role : str, optional + + Returns + ------- + str + """ if role: return f'team:{id}/{role}' return f'team:{id}' @staticmethod - def member(id) -> str: + def member(id: str) -> str: + """Grants access to a specific member of a team. + + When the member is removed from the team, they will + no longer have access. + + Parameters + ---------- + id : str + + Returns + ------- + str + """ return f'member:{id}' + + @staticmethod + def label(name: str) -> str: + """Grants access to a user with the specified label. + + Parameters + ---------- + name : str + + Returns + ------- + str + """ + return f'label:{name}' \ No newline at end of file diff --git a/templates/python/package/service.py.twig b/templates/python/package/service.py.twig index b5b60e6c2..63ac1cfe9 100644 --- a/templates/python/package/service.py.twig +++ b/templates/python/package/service.py.twig @@ -2,5 +2,5 @@ from .client import Client class Service: - def __init__(self, client: Client): - self.client = client + def __init__(self, client: Client) -> None: + self.client: Client = client diff --git a/templates/python/package/services/service.py.twig b/templates/python/package/services/service.py.twig index df665f003..09bd0d710 100644 --- a/templates/python/package/services/service.py.twig +++ b/templates/python/package/services/service.py.twig @@ -1,18 +1,21 @@ +from __future__ import annotations from ..service import Service from ..exception import AppwriteException +from typing import Any class {{ service.name | caseUcfirst }}(Service): def __init__(self, client): super({{ service.name | caseUcfirst }}, self).__init__(client) +{% set typeMapping = {'string': 'str', 'integer': 'int', 'boolean': 'bool', 'object': 'dict', 'array': 'list', 'file': 'Any'} %} {% for method in service.methods %} + def {{ method.name | caseSnake }}(self{% if method.parameters.all|length > 0 %}, {% endif %}{% for parameter in method.parameters.all %}{% if parameter.type == 'array' and parameter.items.type is not null %}{% set pythonType = 'list[' + typeMapping[parameter.items.type] + ']' %}{% elseif parameter.type is not null %}{% set pythonType = typeMapping[parameter.type] %}{% else %}{% set pythonType = 'Any' %}{% endif %}{{ parameter.name | escapeKeyword | caseSnake }}: {{ pythonType }}{% if not parameter.required %} = None{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, on_progress = None{% endif %}): - def {{ method.name | caseSnake }}(self{% if method.parameters.all|length > 0 %}, {% endif %}{% for parameter in method.parameters.all %}{{ parameter.name | escapeKeyword | caseSnake }}{% if not parameter.required %} = None{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if 'multipart/form-data' in method.consumes %}, on_progress = None{% endif %}): {% if method.title %} """{{ method.title }}""" {% endif %} - path = '{{ method.path }}' + api_path = '{{ method.path }}' {{ include('python/base/params.twig') }} {% if 'multipart/form-data' in method.consumes %} {{ include('python/base/requests/file.twig') }}