Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ env:
- SDK=PHP74
- SDK=PHP80
- SDK=Python38
- SDK=Python38Async
- SDK=Python39
- SDK=Python39Async
- SDK=Python310
- SDK=Python310Async
- SDK=Ruby27
- SDK=Ruby30
- SDK=Ruby31
Expand Down
28 changes: 28 additions & 0 deletions src/SDK/Language/Python.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,34 @@ public function getFiles(): array
'destination' => '.travis.yml',
'template' => 'python/.travis.yml.twig',
],

/* Async */
[
'scope' => 'default',
'destination' => '{{ spec.title | caseSnake}}/aio/__init__.py',
'template' => 'python/package/aio/__init__.py.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => '{{ spec.title | caseSnake}}/aio/client.py',
'template' => 'python/package/aio/client.py.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => '{{ spec.title | caseSnake}}/aio/services/__init__.py',
'template' => 'python/package/aio/services/__init__.py.twig',
'minify' => false,
],
[
'scope' => 'service',
'destination' => '{{ spec.title | caseSnake}}/aio/services/{{service.name | caseSnake}}.py',
'template' => 'python/package/aio/services/service.py.twig',
'minify' => false,
],


];
}

Expand Down
1 change: 1 addition & 0 deletions templates/python/package/aio/__init__.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

141 changes: 141 additions & 0 deletions templates/python/package/aio/client.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import io
import httpx
import os
from ..input_file import InputFile
from ..exception import {{spec.title | caseUcfirst}}Exception
from ..client import Client

class AsyncClient(Client):

async def call(self, method, path='', headers=None, params=None):
if headers is None:
headers = {}

if params is None:
params = {}

data = {}
json = {}
files = {}
stringify = False

headers = {**self._global_headers, **headers}

if method != 'get':
data = params
params = {}

if headers['content-type'].startswith('application/json'):
json = data
data = {}

if headers['content-type'].startswith('multipart/form-data'):
del headers['content-type']
stringify = True
for key in data.copy():
if isinstance(data[key], InputFile):
files[key] = (data[key].name, data[key].file)
del data[key]
response = None
try:
async with httpx.AsyncClient(verify=(not self._self_signed), follow_redirects=True) as client:
response = await client.request( # call method dynamically https://stackoverflow.com/a/4246075/2299554
method=method,
url=self._endpoint + path,
params=self.flatten(params, stringify=stringify),
data=self.flatten(data),
json=json,
files=files,
timeout=None,
headers=headers,
)

response.raise_for_status()

content_type = response.headers['Content-Type']

if content_type.startswith('application/json'):
return response.json()

return response._content
except Exception as e:
if response != None:
content_type = response.headers['Content-Type']
if content_type.startswith('application/json'):
raise {{spec.title | caseUcfirst}}Exception(response.json()['message'], response.status_code, response.json().get('type'), response.json())
else:
raise {{spec.title | caseUcfirst}}Exception(response.text, response.status_code)
else:
raise {{spec.title | caseUcfirst}}Exception(e)

async def chunked_upload(
self,
path,
headers = None,
params = None,
param_name = '',
on_progress = None,
upload_id = ''
):
file_path = str(params[param_name])
file_name = os.path.basename(file_path)
size = os.stat(file_path).st_size

if size < self._chunk_size:
slice = open(file_path, 'rb').read()
params[param_name] = InputFile(file_path, file_name, slice)
return await self.call(
'post',
path,
headers,
params
)

input = open(file_path, 'rb')
offset = 0
counter = 0

if upload_id != 'unique()':
try:
result = await self.call('get', path + '/' + upload_id, headers)
counter = result['chunksUploaded']
except:
pass

if counter > 0:
offset = counter * self._chunk_size
input.seek(offset)

while offset < size:
slice = input.read(self._chunk_size) or input.read(size - offset)

params[param_name] = InputFile(file_path, file_name, slice)
headers["content-range"] = f'bytes {offset}-{min((offset + self._chunk_size) - 1, size)}/{size}'

result = await self.call(
'post',
path,
headers,
params,
)

offset = offset + self._chunk_size

if "$id" in result:
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)
on_progress({
"$id": result["$id"],
"progress": min(offset, size)/size * 100,
"sizeUploaded": end+1,
"chunksTotal": result["chunksTotal"],
"chunksUploaded": result["chunksUploaded"],
})

counter = counter + 1

return result


1 change: 1 addition & 0 deletions templates/python/package/aio/services/__init__.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

83 changes: 83 additions & 0 deletions templates/python/package/aio/services/service.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from ...service import Service
from ...exception import AppwriteException

class {{ service.name | caseUcfirst }}(Service):

def __init__(self, client):
super({{ service.name | caseUcfirst }}, self).__init__(client)
{% for method in service.methods %}

async 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 %}
{% for parameter in method.parameters.all %}
{% if parameter.required %}
if {{ parameter.name | escapeKeyword | caseSnake }} is None:
raise {{spec.title | caseUcfirst}}Exception('Missing required parameter: "{{ parameter.name | escapeKeyword | caseSnake }}"')

{% endif %}
{% endfor %}
params = {}
path = '{{ method.path }}'
{% for parameter in method.parameters.path %}
path = path.replace('{{ '{' }}{{ parameter.name | caseCamel }}{{ '}' }}', {{ parameter.name | escapeKeyword | caseSnake }})
{% endfor %}

{% for parameter in method.parameters.query %}
if {{ parameter.name | escapeKeyword | caseSnake }} is not None:
params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}

{% endfor %}
{% for parameter in method.parameters.body %}
if {{ parameter.name | escapeKeyword | caseSnake }} is not None:
{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" ) %}
params['{{ parameter.name }}'] = str({{ parameter.name | escapeKeyword | caseSnake }}).lower() if type({{ parameter.name | escapeKeyword | caseSnake }}) is bool else {{ parameter.name | escapeKeyword | caseSnake }}
{% else %}
params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
{% endif %}
{% endfor %}
{% for parameter in method.parameters.formData %}
if {{ parameter.name | escapeKeyword | caseSnake }} is not None:
{% if method.consumes[0] == "multipart/form-data" and ( parameter.type != "string" and parameter.type != "array" ) %}
params['{{ parameter.name }}'] = str({{ parameter.name | escapeKeyword | caseSnake }}).lower() if type({{ parameter.name | escapeKeyword | caseSnake }}) is bool else {{ parameter.name | escapeKeyword | caseSnake }}
{% else %}
params['{{ parameter.name }}'] = {{ parameter.name | escapeKeyword | caseSnake }}
{% endif %}

{% endfor %}
{% if 'multipart/form-data' in method.consumes %}
{% for parameter in method.parameters.all %}
{% if parameter.type == 'file' %}
param_name = '{{ parameter.name }}'

{% endif %}
{% endfor %}

upload_id = ''
{% for parameter in method.parameters.all %}
{% if parameter.isUploadID %}
upload_id = {{ parameter.name | escapeKeyword | caseSnake }}
{% endif %}
{% endfor %}

return await self.client.chunked_upload(path, {
{% 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)
{% else %}
return await self.client.call('{{ method.method | caseLower }}', path, {
{% for parameter in method.parameters.header %}
'{{ parameter.name }}': {{ parameter.name | escapeKeyword | caseSnake }},
{% endfor %}
{% for key, header in method.headers %}
'{{ key }}': '{{ header }}',
{% endfor %}
}, params)
{% endif %}
{% endfor %}
5 changes: 3 additions & 2 deletions templates/python/package/client.py.twig
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import io
import requests
import httpx
import os
from .input_file import InputFile
from .exception import {{spec.title | caseUcfirst}}Exception
Expand Down Expand Up @@ -73,7 +73,7 @@ class Client:
del data[key]
response = None
try:
response = requests.request( # call method dynamically https://stackoverflow.com/a/4246075/2299554
response = httpx.request( # call method dynamically https://stackoverflow.com/a/4246075/2299554
method=method,
url=self._endpoint + path,
params=self.flatten(params, stringify=stringify),
Expand All @@ -82,6 +82,7 @@ class Client:
files=files,
headers=headers,
verify=(not self._self_signed),
follow_redirects=True
)

response.raise_for_status()
Expand Down
2 changes: 1 addition & 1 deletion templates/python/requirements.txt.twig
Original file line number Diff line number Diff line change
@@ -1 +1 @@
requests==2.28.1
httpx==0.22.0
2 changes: 1 addition & 1 deletion templates/python/setup.py.twig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ setuptools.setup(
download_url='https://github.com/{{sdk.gitUserName}}/{{sdk.gitRepoName}}/archive/{{sdk.version}}.tar.gz',
# keywords = ['SOME', 'MEANINGFULL', 'KEYWORDS'],
install_requires=[
'requests',
'httpx',
],
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down
24 changes: 24 additions & 0 deletions tests/Python310AsyncTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Tests;

class Python310AsyncTest extends Base
{
protected string $language = 'python';
protected string $class = 'Appwrite\SDK\Language\Python';
protected array $build = [
'cp tests/languages/python/tests_async.py tests/sdks/python/test.py',
'echo "" > tests/sdks/python/__init__.py',
'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.10 pip install -r tests/sdks/python/requirements.txt --upgrade',
];
protected string $command =
'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.10-alpine python tests/sdks/python/test.py';

protected array $expectedOutput = [
...Base::FOO_RESPONSES,
...Base::BAR_RESPONSES,
...Base::GENERAL_RESPONSES,
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
];
}
24 changes: 24 additions & 0 deletions tests/Python38AsyncTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Tests;

class Python38AsyncTest extends Base
{
protected string $language = 'python';
protected string $class = 'Appwrite\SDK\Language\Python';
protected array $build = [
'cp tests/languages/python/tests_async.py tests/sdks/python/test.py',
'echo "" > tests/sdks/python/__init__.py',
'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.8 pip install -r tests/sdks/python/requirements.txt --upgrade',
];
protected string $command =
'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.8-alpine python tests/sdks/python/test.py';

protected array $expectedOutput = [
...Base::FOO_RESPONSES,
...Base::BAR_RESPONSES,
...Base::GENERAL_RESPONSES,
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
];
}
24 changes: 24 additions & 0 deletions tests/Python39AsyncTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Tests;

class Python39AsyncTest extends Base
{
protected string $language = 'python';
protected string $class = 'Appwrite\SDK\Language\Python';
protected array $build = [
'cp tests/languages/python/tests_async.py tests/sdks/python/test.py',
'echo "" > tests/sdks/python/__init__.py',
'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor python:3.9 pip install -r tests/sdks/python/requirements.txt --upgrade',
];
protected string $command =
'docker run --rm -v $(pwd):/app -w /app --env PIP_TARGET=tests/sdks/python/vendor --env PYTHONPATH=tests/sdks/python/vendor python:3.9-alpine python tests/sdks/python/test.py';

protected array $expectedOutput = [
...Base::FOO_RESPONSES,
...Base::BAR_RESPONSES,
...Base::GENERAL_RESPONSES,
...Base::LARGE_FILE_RESPONSES,
...Base::EXCEPTION_RESPONSES,
];
}
Loading