From 5275f5c121cd47fd9798930a02f20e111bde3a1b Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 04:28:25 -0400 Subject: [PATCH 01/16] feat: update python.php to create aio submodule --- src/SDK/Language/Python.php | 52 +++++ templates/python/package/aio/__init__.py.twig | 1 + templates/python/package/aio/client.py.twig | 192 ++++++++++++++++++ .../python/package/aio/exception.py.twig | 7 + .../python/package/aio/input_file.py.twig | 5 + templates/python/package/aio/query.py.twig | 42 ++++ templates/python/package/aio/service.py.twig | 6 + .../package/aio/services/__init__.py.twig | 1 + .../package/aio/services/service.py.twig | 83 ++++++++ templates/python/setup.py.twig | 2 +- 10 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 templates/python/package/aio/__init__.py.twig create mode 100644 templates/python/package/aio/client.py.twig create mode 100644 templates/python/package/aio/exception.py.twig create mode 100644 templates/python/package/aio/input_file.py.twig create mode 100644 templates/python/package/aio/query.py.twig create mode 100644 templates/python/package/aio/service.py.twig create mode 100644 templates/python/package/aio/services/__init__.py.twig create mode 100644 templates/python/package/aio/services/service.py.twig diff --git a/src/SDK/Language/Python.php b/src/SDK/Language/Python.php index a49940fb2..4cb54abbe 100644 --- a/src/SDK/Language/Python.php +++ b/src/SDK/Language/Python.php @@ -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, + ], + + ]; } diff --git a/templates/python/package/aio/__init__.py.twig b/templates/python/package/aio/__init__.py.twig new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/templates/python/package/aio/__init__.py.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/python/package/aio/client.py.twig b/templates/python/package/aio/client.py.twig new file mode 100644 index 000000000..31500f34e --- /dev/null +++ b/templates/python/package/aio/client.py.twig @@ -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 + diff --git a/templates/python/package/aio/exception.py.twig b/templates/python/package/aio/exception.py.twig new file mode 100644 index 000000000..6e5d4c8ff --- /dev/null +++ b/templates/python/package/aio/exception.py.twig @@ -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) \ No newline at end of file diff --git a/templates/python/package/aio/input_file.py.twig b/templates/python/package/aio/input_file.py.twig new file mode 100644 index 000000000..4351d071a --- /dev/null +++ b/templates/python/package/aio/input_file.py.twig @@ -0,0 +1,5 @@ +class InputFile: + def __init__(self, path, name, file): + self.path = path + self.name = name + self.file = file \ No newline at end of file diff --git a/templates/python/package/aio/query.py.twig b/templates/python/package/aio/query.py.twig new file mode 100644 index 000000000..3a6179df3 --- /dev/null +++ b/templates/python/package/aio/query.py.twig @@ -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 \ No newline at end of file diff --git a/templates/python/package/aio/service.py.twig b/templates/python/package/aio/service.py.twig new file mode 100644 index 000000000..b5b60e6c2 --- /dev/null +++ b/templates/python/package/aio/service.py.twig @@ -0,0 +1,6 @@ +from .client import Client + + +class Service: + def __init__(self, client: Client): + self.client = client diff --git a/templates/python/package/aio/services/__init__.py.twig b/templates/python/package/aio/services/__init__.py.twig new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/templates/python/package/aio/services/__init__.py.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/python/package/aio/services/service.py.twig b/templates/python/package/aio/services/service.py.twig new file mode 100644 index 000000000..7127d3619 --- /dev/null +++ b/templates/python/package/aio/services/service.py.twig @@ -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 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 %} diff --git a/templates/python/setup.py.twig b/templates/python/setup.py.twig index 82867e2fe..7a4d6eb4b 100644 --- a/templates/python/setup.py.twig +++ b/templates/python/setup.py.twig @@ -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', From 668d725d700d64b36536989933f81f8ae6e57fa2 Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 04:30:41 -0400 Subject: [PATCH 02/16] fix: use httpx for non-sync client --- templates/python/package/client.py.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig index 181f3dca2..89beda83e 100644 --- a/templates/python/package/client.py.twig +++ b/templates/python/package/client.py.twig @@ -1,5 +1,5 @@ import io -import requests +import httpx import os from .input_file import InputFile from .exception import {{spec.title | caseUcfirst}}Exception @@ -70,7 +70,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), From f8f65017f58d0a0d76c28dc26cebcb2ab9115521 Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 04:30:53 -0400 Subject: [PATCH 03/16] chore: lock httpx dependency to 0.22.0 --- templates/python/requirements.txt.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/python/requirements.txt.twig b/templates/python/requirements.txt.twig index 9cdfca0c2..302663052 100644 --- a/templates/python/requirements.txt.twig +++ b/templates/python/requirements.txt.twig @@ -1 +1 @@ -requests==2.27.1 \ No newline at end of file +httpx==0.22.0 \ No newline at end of file From 52115869dcac30ff111a0e0af3f5d6f95f2dde31 Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 15:00:37 -0400 Subject: [PATCH 04/16] chore: add tests for async python --- .travis.yml | 3 + tests/Python310AsyncTest.php | 24 +++++ tests/Python38AsyncTest.php | 24 +++++ tests/Python39AsyncTest.php | 24 +++++ tests/languages/python/tests_async.py | 139 ++++++++++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 tests/Python310AsyncTest.php create mode 100644 tests/Python38AsyncTest.php create mode 100644 tests/Python39AsyncTest.php create mode 100644 tests/languages/python/tests_async.py diff --git a/.travis.yml b/.travis.yml index 451a7056c..0d92e18d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,8 +37,11 @@ env: - SDK=PHP74 - SDK=PHP80 - SDK=Python38 + - SDK=Python39Async - SDK=Python39 + - SDK=Python39Async - SDK=Python310 + - SDK=Python310Async - SDK=Ruby27 - SDK=Ruby30 - SDK=Ruby31 diff --git a/tests/Python310AsyncTest.php b/tests/Python310AsyncTest.php new file mode 100644 index 000000000..502ca95ca --- /dev/null +++ b/tests/Python310AsyncTest.php @@ -0,0 +1,24 @@ + 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, + ]; +} diff --git a/tests/Python38AsyncTest.php b/tests/Python38AsyncTest.php new file mode 100644 index 000000000..5c8dcf990 --- /dev/null +++ b/tests/Python38AsyncTest.php @@ -0,0 +1,24 @@ + 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, + ]; +} diff --git a/tests/Python39AsyncTest.php b/tests/Python39AsyncTest.php new file mode 100644 index 000000000..b82fc7d89 --- /dev/null +++ b/tests/Python39AsyncTest.php @@ -0,0 +1,24 @@ + 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, + ]; +} diff --git a/tests/languages/python/tests_async.py b/tests/languages/python/tests_async.py new file mode 100644 index 000000000..c57b527c8 --- /dev/null +++ b/tests/languages/python/tests_async.py @@ -0,0 +1,139 @@ +from appwrite.aio.client import Client +from appwrite.aio.services.foo import Foo +from appwrite.aio.services.bar import Bar +from appwrite.aio.services.general import General +from appwrite.aio.exception import AppwriteException +import os.path +import asyncio + + +loop = asyncio.get_event_loop() + +client = Client() +foo = Foo(client) +bar = Bar(client) +general = General(client) + +client.add_header('Origin', 'http://localhost') +client.set_self_signed() + +print("\nTest Started") + +# Foo Tests + + +async def test_foo_get(): + response = await foo.get('string',123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_foo_get()) + +async def test_foo_post(): + response = await foo.post('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_foo_post()) + +async def test_foo_put(): + response = await foo.put('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_foo_put()) + + +async def test_foo_patch(): + response = await foo.patch('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_foo_patch()) + + +async def test_foo_delete(): + response = await foo.delete('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_foo_delete()) + + +# Bar Tests + +async def test_bar_get(): + response = await bar.get('string',123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_bar_get()) + +async def test_bar_post(): + response = await bar.post('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_bar_post()) + + +async def test_bar_put(): + response = await bar.put('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_bar_put()) + +async def test_bar_patch(): + response = await bar.patch('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_bar_patch()) + +async def test_bar_delete(): + response = await bar.delete('string', 123, ['string in array']) + print(response['result']) + +loop.run_until_complete(test_bar_delete()) + +# General Tests + +async def test_general_redirect(): + response = await general.redirect() + print(response['result']) + +loop.run_until_complete(test_general_redirect()) + +async def test_general_upload(): + response = await general.upload('string', 123, ['string in array'], './tests/resources/file.png') + print(response['result']) + +loop.run_until_complete(test_general_upload()) + +async def test_general_large_file(): + response = await general.upload('string', 123, ['string in array'], './tests/resources/large_file.mp4') + print(response['result']) + +loop.run_until_complete(test_general_large_file()) + + +async def test_general_400(): + try: + response = await general.error400() + except AppwriteException as e: + print(e.message) + +loop.run_until_complete(test_general_400()) + +async def test_general_error_500(): + try: + response = await general.error500() + except AppwriteException as e: + print(e.message) + +loop.run_until_complete(test_general_error_500()) + +async def test_general_502(): + try: + response = await general.error502() + except AppwriteException as e: + print(e.message) + +loop.run_until_complete(test_general_502()) + +async def test_general_empty(): + await general.empty() + +loop.run_until_complete(test_general_empty()) \ No newline at end of file From 9d19d8e39966be16716b4399fb1daf7ec436555a Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 15:00:58 -0400 Subject: [PATCH 05/16] fix: add await when calling chunked upload --- templates/python/package/aio/services/service.py.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/python/package/aio/services/service.py.twig b/templates/python/package/aio/services/service.py.twig index 7127d3619..f63ea1b0d 100644 --- a/templates/python/package/aio/services/service.py.twig +++ b/templates/python/package/aio/services/service.py.twig @@ -62,7 +62,7 @@ class {{ service.name | caseUcfirst }}(Service): {% endif %} {% endfor %} - return self.client.chunked_upload(path, { + return await self.client.chunked_upload(path, { {% for parameter in method.parameters.header %} '{{ parameter.name }}': {{ parameter.name | escapeKeyword | caseSnake }}, {% endfor %} From 7420c952b6fa050c0ed947a461c7376154cb777d Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 15:01:18 -0400 Subject: [PATCH 06/16] fix: set follow_redirects to True and move verify to AsyncClient arg --- templates/python/package/aio/client.py.twig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/templates/python/package/aio/client.py.twig b/templates/python/package/aio/client.py.twig index 31500f34e..1073a3e91 100644 --- a/templates/python/package/aio/client.py.twig +++ b/templates/python/package/aio/client.py.twig @@ -70,7 +70,7 @@ class Client: del data[key] response = None try: - async with httpx.AsyncClient() as client: + 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, @@ -79,7 +79,6 @@ class Client: json=json, files=files, headers=headers, - verify=(not self._self_signed), ) response.raise_for_status() From a58e917c7247191b1b8ee043cb17ab80636bd9ab Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Mon, 23 May 2022 15:04:25 -0400 Subject: [PATCH 07/16] fix: add follow_redirects arg to httpx.request --- templates/python/package/client.py.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig index 89beda83e..c6e00a76c 100644 --- a/templates/python/package/client.py.twig +++ b/templates/python/package/client.py.twig @@ -79,6 +79,7 @@ class Client: files=files, headers=headers, verify=(not self._self_signed), + follow_redirects=True ) response.raise_for_status() From b5cf913125d2b16c5ec05386f4a57c6bd1af5cd6 Mon Sep 17 00:00:00 2001 From: Mustafa Date: Fri, 17 Jun 2022 22:36:55 -0500 Subject: [PATCH 08/16] fix: exclude duplicate modules in aio --- src/SDK/Language/Python.php | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/SDK/Language/Python.php b/src/SDK/Language/Python.php index 4cb54abbe..ad3ccc0a8 100644 --- a/src/SDK/Language/Python.php +++ b/src/SDK/Language/Python.php @@ -217,30 +217,6 @@ public function getFiles() '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', From fdb07c47b719e58786006bc6208f43bd364189ca Mon Sep 17 00:00:00 2001 From: Mustafa Date: Fri, 17 Jun 2022 22:37:55 -0500 Subject: [PATCH 09/16] fix: import path for service and exceptions for aio --- templates/python/package/aio/services/service.py.twig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/python/package/aio/services/service.py.twig b/templates/python/package/aio/services/service.py.twig index f63ea1b0d..b0262b1ff 100644 --- a/templates/python/package/aio/services/service.py.twig +++ b/templates/python/package/aio/services/service.py.twig @@ -1,5 +1,5 @@ -from ..service import Service -from ..exception import AppwriteException +from ...service import Service +from ...exception import AppwriteException class {{ service.name | caseUcfirst }}(Service): From 4214ba8d9a3f8ce3d99a487c43ff9da38eeb0cd4 Mon Sep 17 00:00:00 2001 From: Mustafa Date: Fri, 17 Jun 2022 22:39:06 -0500 Subject: [PATCH 10/16] feat: Create AsyncClient (inheritied from Client) fix: import paths for Client, InputFile and exceptions --- templates/python/package/aio/client.py.twig | 62 ++------------------- 1 file changed, 6 insertions(+), 56 deletions(-) diff --git a/templates/python/package/aio/client.py.twig b/templates/python/package/aio/client.py.twig index 1073a3e91..3a796b12e 100644 --- a/templates/python/package/aio/client.py.twig +++ b/templates/python/package/aio/client.py.twig @@ -1,43 +1,11 @@ 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 %} +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: @@ -78,6 +46,7 @@ class Client: data=self.flatten(data), json=json, files=files, + timeout=None, headers=headers, ) @@ -169,23 +138,4 @@ class Client: 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 From 4005f4fc65fc8ef0372f716aa9c9f61896d754bb Mon Sep 17 00:00:00 2001 From: Mustafa Date: Fri, 17 Jun 2022 22:40:21 -0500 Subject: [PATCH 11/16] fix: remove unneeded templates in aio module --- .../python/package/aio/exception.py.twig | 7 ---- .../python/package/aio/input_file.py.twig | 5 --- templates/python/package/aio/query.py.twig | 42 ------------------- templates/python/package/aio/service.py.twig | 6 --- 4 files changed, 60 deletions(-) delete mode 100644 templates/python/package/aio/exception.py.twig delete mode 100644 templates/python/package/aio/input_file.py.twig delete mode 100644 templates/python/package/aio/query.py.twig delete mode 100644 templates/python/package/aio/service.py.twig diff --git a/templates/python/package/aio/exception.py.twig b/templates/python/package/aio/exception.py.twig deleted file mode 100644 index 6e5d4c8ff..000000000 --- a/templates/python/package/aio/exception.py.twig +++ /dev/null @@ -1,7 +0,0 @@ -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) \ No newline at end of file diff --git a/templates/python/package/aio/input_file.py.twig b/templates/python/package/aio/input_file.py.twig deleted file mode 100644 index 4351d071a..000000000 --- a/templates/python/package/aio/input_file.py.twig +++ /dev/null @@ -1,5 +0,0 @@ -class InputFile: - def __init__(self, path, name, file): - self.path = path - self.name = name - self.file = file \ No newline at end of file diff --git a/templates/python/package/aio/query.py.twig b/templates/python/package/aio/query.py.twig deleted file mode 100644 index 3a6179df3..000000000 --- a/templates/python/package/aio/query.py.twig +++ /dev/null @@ -1,42 +0,0 @@ -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 \ No newline at end of file diff --git a/templates/python/package/aio/service.py.twig b/templates/python/package/aio/service.py.twig deleted file mode 100644 index b5b60e6c2..000000000 --- a/templates/python/package/aio/service.py.twig +++ /dev/null @@ -1,6 +0,0 @@ -from .client import Client - - -class Service: - def __init__(self, client: Client): - self.client = client From fb5ad46fb05b0052d182a309d7e66a53088c2657 Mon Sep 17 00:00:00 2001 From: Mustafa Date: Fri, 17 Jun 2022 22:41:15 -0500 Subject: [PATCH 12/16] fix(ci): add Python38Async test --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0d92e18d1..6dd264351 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,7 +37,7 @@ env: - SDK=PHP74 - SDK=PHP80 - SDK=Python38 - - SDK=Python39Async + - SDK=Python38Async - SDK=Python39 - SDK=Python39Async - SDK=Python310 From b0c058895a96a7258dba9383859739af4734b99f Mon Sep 17 00:00:00 2001 From: Mustafa Date: Fri, 17 Jun 2022 22:41:42 -0500 Subject: [PATCH 13/16] fix: tests for python aio submodule --- tests/languages/python/tests_async.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/languages/python/tests_async.py b/tests/languages/python/tests_async.py index c57b527c8..6fbaa2860 100644 --- a/tests/languages/python/tests_async.py +++ b/tests/languages/python/tests_async.py @@ -1,15 +1,15 @@ -from appwrite.aio.client import Client -from appwrite.aio.services.foo import Foo -from appwrite.aio.services.bar import Bar -from appwrite.aio.services.general import General -from appwrite.aio.exception import AppwriteException +from appwrite.aio.client import AsyncClient +from appwrite.services.foo import Foo +from appwrite.services.bar import Bar +from appwrite.services.general import General +from appwrite.exception import AppwriteException import os.path import asyncio loop = asyncio.get_event_loop() -client = Client() +client = AsyncClient() foo = Foo(client) bar = Bar(client) general = General(client) From 1de8140de8ee07c98720451620bbf6433e05f080 Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Tue, 27 Dec 2022 18:30:05 -0600 Subject: [PATCH 14/16] fix: add timeout parameter to .call (default to None) --- templates/python/package/aio/client.py.twig | 8 ++++---- templates/python/package/client.py.twig | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/templates/python/package/aio/client.py.twig b/templates/python/package/aio/client.py.twig index 3a796b12e..867f905f3 100644 --- a/templates/python/package/aio/client.py.twig +++ b/templates/python/package/aio/client.py.twig @@ -7,7 +7,7 @@ from ..client import Client class AsyncClient(Client): - async def call(self, method, path='', headers=None, params=None): + async def call(self, method, path='', headers=None, params=None, timeout=None): if headers is None: headers = {} @@ -39,15 +39,15 @@ class AsyncClient(Client): 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 + response = await client.request( method=method, url=self._endpoint + path, params=self.flatten(params, stringify=stringify), data=self.flatten(data), json=json, files=files, - timeout=None, headers=headers, + timeout=timeout ) response.raise_for_status() @@ -75,7 +75,7 @@ class AsyncClient(Client): params = None, param_name = '', on_progress = None, - upload_id = '' + upload_id = '', ): file_path = str(params[param_name]) file_name = os.path.basename(file_path) diff --git a/templates/python/package/client.py.twig b/templates/python/package/client.py.twig index 2bf485c59..234c18c33 100644 --- a/templates/python/package/client.py.twig +++ b/templates/python/package/client.py.twig @@ -42,7 +42,7 @@ class Client: return self {% endfor %} - def call(self, method, path='', headers=None, params=None): + def call(self, method, path='', headers=None, params=None, timeout=None): if headers is None: headers = {} @@ -73,7 +73,7 @@ class Client: del data[key] response = None try: - response = httpx.request( # call method dynamically https://stackoverflow.com/a/4246075/2299554 + response = httpx.request( method=method, url=self._endpoint + path, params=self.flatten(params, stringify=stringify), @@ -82,7 +82,8 @@ class Client: files=files, headers=headers, verify=(not self._self_signed), - follow_redirects=True + follow_redirects=True, + timeout=timeout ) response.raise_for_status() From 726f7b57133fe3158f4db6471cda1f9004afa7d6 Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Tue, 27 Dec 2022 18:30:25 -0600 Subject: [PATCH 15/16] fix: use latest python tests for async --- tests/Python310AsyncTest.php | 17 ++++++- tests/Python38AsyncTest.php | 15 +++++- tests/Python39AsyncTest.php | 17 ++++++- tests/languages/python/tests_async.py | 70 ++++++++++++++++++++++++--- 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/tests/Python310AsyncTest.php b/tests/Python310AsyncTest.php index 502ca95ca..7b5a4e036 100644 --- a/tests/Python310AsyncTest.php +++ b/tests/Python310AsyncTest.php @@ -1,15 +1,23 @@ 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', + '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.10-alpine python tests/sdks/python/test.py'; @@ -19,6 +27,11 @@ class Python310AsyncTest extends Base ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, ...Base::EXCEPTION_RESPONSES, + ...Base::QUERY_HELPER_RESPONSES, + ...Base::PERMISSION_HELPER_RESPONSES, + ...Base::ID_HELPER_RESPONSES ]; } diff --git a/tests/Python38AsyncTest.php b/tests/Python38AsyncTest.php index 5c8dcf990..957d47bfe 100644 --- a/tests/Python38AsyncTest.php +++ b/tests/Python38AsyncTest.php @@ -1,9 +1,17 @@ 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', + '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.9-alpine python tests/sdks/python/test.py'; @@ -19,6 +27,11 @@ class Python39AsyncTest extends Base ...Base::BAR_RESPONSES, ...Base::GENERAL_RESPONSES, ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, + ...Base::LARGE_FILE_RESPONSES, ...Base::EXCEPTION_RESPONSES, + ...Base::QUERY_HELPER_RESPONSES, + ...Base::PERMISSION_HELPER_RESPONSES, + ...Base::ID_HELPER_RESPONSES ]; } diff --git a/tests/languages/python/tests_async.py b/tests/languages/python/tests_async.py index 6fbaa2860..920160e85 100644 --- a/tests/languages/python/tests_async.py +++ b/tests/languages/python/tests_async.py @@ -3,6 +3,12 @@ from appwrite.services.bar import Bar from appwrite.services.general import General from appwrite.exception import AppwriteException +from appwrite.input_file import InputFile +from appwrite.query import Query +from appwrite.permission import Permission +from appwrite.role import Role +from appwrite.id import ID + import os.path import asyncio @@ -96,17 +102,33 @@ async def test_general_redirect(): loop.run_until_complete(test_general_redirect()) -async def test_general_upload(): - response = await general.upload('string', 123, ['string in array'], './tests/resources/file.png') +async def test_general_file_png_upload(): + response = await general.upload('string', 123, ['string in array'], InputFile.from_path('./tests/resources/file.png')) print(response['result']) -loop.run_until_complete(test_general_upload()) +loop.run_until_complete(test_general_file_png_upload()) + +async def test_general_file_png_bytes_upload(): + data = open('./tests/resources/file.png', 'rb').read() + response = await general.upload('string', 123, ['string in array'], InputFile.from_bytes(data, 'file.png', 'image/png')) + print(response['result']) + +loop.run_until_complete(test_general_file_png_bytes_upload()) + + +async def test_general_large_file_mp4_bytes_upload(): + data = open('./tests/resources/large_file.mp4', 'rb').read() + response = await general.upload('string', 123, ['string in array'], InputFile.from_bytes(data, 'large_file.mp4','video/mp4')) + print(response['result']) + +loop.run_until_complete(test_general_large_file_mp4_bytes_upload()) + -async def test_general_large_file(): - response = await general.upload('string', 123, ['string in array'], './tests/resources/large_file.mp4') +async def test_general_large_file_mp4(): + response = await general.upload('string', 123, ['string in array'], InputFile.from_path('./tests/resources/large_file.mp4')) print(response['result']) -loop.run_until_complete(test_general_large_file()) +loop.run_until_complete(test_general_large_file_mp4()) async def test_general_400(): @@ -136,4 +158,38 @@ async def test_general_502(): async def test_general_empty(): await general.empty() -loop.run_until_complete(test_general_empty()) \ No newline at end of file +loop.run_until_complete(test_general_empty()) + + + +# Query helper tests +print(Query.equal('released', [True])) +print(Query.equal('title', ['Spiderman', 'Dr. Strange'])) +print(Query.notEqual('title', 'Spiderman')) +print(Query.lessThan('releasedYear', 1990)) +print(Query.greaterThan('releasedYear', 1990)) +print(Query.search('name', 'john')) +print(Query.orderAsc("title")) +print(Query.orderDesc("title")) +print(Query.cursorAfter("my_movie_id")) +print(Query.cursorBefore("my_movie_id")) +print(Query.limit(50)) +print(Query.offset(20)) + +# Permission & Role helper tests +print(Permission.read(Role.any())) +print(Permission.write(Role.user(ID.custom('userid')))) +print(Permission.create(Role.users())) +print(Permission.update(Role.guests())) +print(Permission.delete(Role.team('teamId', 'owner'))) +print(Permission.delete(Role.team('teamId'))) +print(Permission.create(Role.member('memberId'))) +print(Permission.update(Role.users('verified'))) +print(Permission.update(Role.user(ID.custom('userid'), 'unverified'))) + +# ID helper tests +print(ID.unique()) +print(ID.custom('custom_id')) + +response = general.headers() +print(response['result']) From 27d32107b19fd72b1c5060d372eeedee1cede2ab Mon Sep 17 00:00:00 2001 From: Mustafa Mohamed Date: Wed, 28 Dec 2022 15:37:12 -0600 Subject: [PATCH 16/16] fix: phpcs formatting issue with tests --- tests/Python310AsyncTest.php | 3 ++- tests/Python310Test.php | 4 ++++ tests/Python38AsyncTest.php | 1 + tests/Python38Test.php | 4 ++++ tests/Python39AsyncTest.php | 5 +++-- tests/Python39Test.php | 4 ++++ 6 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/Python310AsyncTest.php b/tests/Python310AsyncTest.php index 7b5a4e036..4daf453ca 100644 --- a/tests/Python310AsyncTest.php +++ b/tests/Python310AsyncTest.php @@ -1,6 +1,7 @@ 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', + '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'; diff --git a/tests/Python310Test.php b/tests/Python310Test.php index a8c5f2cef..33b453e66 100644 --- a/tests/Python310Test.php +++ b/tests/Python310Test.php @@ -2,6 +2,10 @@ namespace Tests; +/** + * @group python + * Tests python + */ class Python310Test extends Base { protected string $sdkName = 'python'; diff --git a/tests/Python38AsyncTest.php b/tests/Python38AsyncTest.php index 957d47bfe..b1abd0f53 100644 --- a/tests/Python38AsyncTest.php +++ b/tests/Python38AsyncTest.php @@ -1,6 +1,7 @@ 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', + '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'; diff --git a/tests/Python39Test.php b/tests/Python39Test.php index 5dbfe4fcf..ff52920c0 100644 --- a/tests/Python39Test.php +++ b/tests/Python39Test.php @@ -2,6 +2,10 @@ namespace Tests; +/** + * @group python + * Tests python + */ class Python39Test extends Base { protected string $sdkName = 'python';