Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async Support in Python SDK #453

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
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
Next Next commit
feat: update python.php to create aio submodule
ms7m committed May 23, 2022
commit 5275f5c121cd47fd9798930a02f20e111bde3a1b
52 changes: 52 additions & 0 deletions src/SDK/Language/Python.php
Original file line number Diff line number Diff line change
@@ -203,6 +203,58 @@ public function getFiles()
'template' => 'python/.travis.yml.twig',
'minify' => false,
],

/* 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/query.py',
'template' => 'python/package/aio/query.py.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => '{{ spec.title | caseSnake}}/aio/exception.py',
'template' => 'python/package/aio/exception.py.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => '{{ spec.title | caseSnake}}/aio/input_file.py',
'template' => 'python/package/aio/input_file.py.twig',
'minify' => false,
],
[
'scope' => 'default',
'destination' => '{{ spec.title | caseSnake}}/aio/service.py',
'template' => 'python/package/aio/service.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,
],


];
}

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 @@

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

class Client:
def __init__(self):
self._chunk_size = 5*1024*1024
self._self_signed = False
self._endpoint = '{{spec.endpoint}}'
self._global_headers = {
'content-type': '',
'x-sdk-version': '{{spec.title | caseDash}}:{{ language.name | caseLower }}:{{ sdk.version }}',
{% for key,header in spec.global.defaultHeaders %}
'{{key}}' : '{{header}}',
{% endfor %}
}

def set_self_signed(self, status=True):
self._self_signed = status
return self

def set_endpoint(self, endpoint):
self._endpoint = endpoint
return self

def add_header(self, key, value):
self._global_headers[key.lower()] = value
return self
{% for header in spec.global.headers %}

def set_{{header.key | caseSnake}}(self, value):
{% if header.description %}
"""{{header.description}}"""

{% endif %}
self._global_headers['{{header.name|lower}}'] = value
return self
{% endfor %}

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() 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,
headers=headers,
verify=(not self._self_signed),
)

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

def flatten(self, data, prefix='', stringify=False):
output = {}
i = 0

for key in data:
value = data[key] if isinstance(data, dict) else key
finalKey = prefix + '[' + key +']' if prefix else key
finalKey = prefix + '[' + str(i) +']' if isinstance(data, list) else finalKey
i += 1

if isinstance(value, list) or isinstance(value, dict):
output = {**output, **self.flatten(value, finalKey, stringify)}
else:
if stringify:
output[finalKey] = str(value)
else:
output[finalKey] = value

return output

7 changes: 7 additions & 0 deletions templates/python/package/aio/exception.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class {{spec.title | caseUcfirst}}Exception(Exception):
def __init__(self, message, code = 0, type = None, response = None):
self.message = message
self.code = code
self.type = type
self.response = response
super().__init__(self.message)
5 changes: 5 additions & 0 deletions templates/python/package/aio/input_file.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class InputFile:
def __init__(self, path, name, file):
self.path = path
self.name = name
self.file = file
42 changes: 42 additions & 0 deletions templates/python/package/aio/query.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class Query:
@staticmethod
def equal(attribute, value):
return Query.addQuery(attribute, "equal", value)

@staticmethod
def notEqual(attribute, value):
return Query.addQuery(attribute, "notEqual", value)

@staticmethod
def lesser(attribute, value):
return Query.addQuery(attribute, "lesser", value)

@staticmethod
def lesserEqual(attribute, value):
return Query.addQuery(attribute, "lesserEqual", value)

@staticmethod
def greater(attribute, value):
return Query.addQuery(attribute, "greater", value)

@staticmethod
def greaterEqual(attribute, value):
return Query.addQuery(attribute, "greaterEqual", value)

@staticmethod
def search(attribute, value):
return Query.addQuery(attribute, "search", value)

@staticmethod
def addQuery(attribute, oper, value):
if type(value) == list:
return '{}.{}({})'.format(attribute,oper, ','.join(map(Query.parseValues, value)))
else:
return '{}.{}({})'.format(attribute,oper, Query.parseValues(value))

@staticmethod
def parseValues(value):
if type(value) == str:
return '"{}"'.format(value)
else:
return value
6 changes: 6 additions & 0 deletions templates/python/package/aio/service.py.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .client import Client


class Service:
def __init__(self, client: Client):
self.client = client
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 @@

Loading