Skip to content

Commit 82c57e0

Browse files
chore: update Python SDK to 20.0.0
1 parent fdfbf5f commit 82c57e0

62 files changed

Lines changed: 1410 additions & 400 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
# Change Log
22

3-
## 19.1.0
4-
5-
* Added `DENO_1_21`, `DENO_1_24`, and `DENO_1_35` runtime options
6-
* Added `sizeactual` field to `File` model for compressed file size
7-
* Updated `BillingLimits` model fields to be optional
8-
* Updated `Project` model `billinglimits` field to be optional
9-
* Updated authentication examples in advisor documentation
3+
## 20.0.0
4+
5+
* Breaking: Removed `githubImagine` and `googleImagine` from `ProjectOAuthProviderId`
6+
* Breaking: Removed `deno-1.21`, `deno-1.24`, and `deno-1.35` from `Runtime` and `BuildRuntime`
7+
* Breaking: Dropped numeric suffixes from `StatusCode` redirect members
8+
* Added: `Organization` service for managing projects and API keys
9+
* Added: `PolicyDenyAliasedEmail`, `PolicyDenyDisposableEmail`, and `PolicyDenyFreeEmail` policy models
10+
* Added: `deny-aliased-email`, `deny-disposable-email`, and `deny-free-email` to `ProjectPolicyId`
11+
* Added: `BrowserTheme`, `HealthQueueName`, `OrganizationKeyScopes`, and `Region` enums
12+
* Added: `dart-3.12` and `flutter-3.44` runtimes
13+
* Added: `ProjectList` model and new attributes on `Function`, `Site`, and `UsageGauge`
14+
* Updated: `functions`, `sites`, `usage`, `health`, and `avatars` services
15+
* Updated: Renamed `updatePresence` to `update` in the `presences` service
1016

1117
## 19.0.0
1218

appwrite/client.py

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import platform
55
import sys
66
import requests
7+
from concurrent.futures import ThreadPoolExecutor, as_completed
8+
from threading import Lock
79
from .input_file import InputFile
810
from .exception import AppwriteException
911
from .encoders.value_class_encoder import ValueClassEncoder
@@ -15,11 +17,11 @@ def __init__(self):
1517
self._endpoint = 'https://cloud.appwrite.io/v1'
1618
self._global_headers = {
1719
'content-type': '',
18-
'user-agent' : f'AppwritePythonSDK/19.1.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})',
20+
'user-agent' : f'AppwritePythonSDK/20.0.0 ({platform.uname().system}; {platform.uname().version}; {platform.uname().machine})',
1921
'x-sdk-name': 'Python',
2022
'x-sdk-platform': 'server',
2123
'x-sdk-language': 'python',
22-
'x-sdk-version': '19.1.0',
24+
'x-sdk-version': '20.0.0',
2325
'X-Appwrite-Response-Format' : '1.9.5',
2426
}
2527

@@ -186,14 +188,15 @@ def chunked_upload(
186188

187189
if input_file.source_type == 'path':
188190
size = os.stat(input_file.path).st_size
189-
input = open(input_file.path, 'rb')
191+
input = None
190192
elif input_file.source_type == 'bytes':
191193
size = len(input_file.data)
192194
input = input_file.data
193195

194196
if size < self._chunk_size:
195197
if input_file.source_type == 'path':
196-
input_file.data = input.read()
198+
with open(input_file.path, 'rb') as input:
199+
input_file.data = input.read()
197200

198201
params[param_name] = input_file
199202
return self.call(
@@ -214,46 +217,103 @@ def chunked_upload(
214217

215218
if counter > 0:
216219
offset = counter * self._chunk_size
217-
input.seek(offset)
218220

221+
total_chunks = (size + self._chunk_size - 1) // self._chunk_size
222+
chunks = []
219223
while offset < size:
220-
if input_file.source_type == 'path':
221-
input_file.data = input.read(self._chunk_size) or input.read(size - offset)
222-
elif input_file.source_type == 'bytes':
223-
if offset + self._chunk_size < size:
224-
end = offset + self._chunk_size
225-
else:
226-
end = size
227-
input_file.data = input[offset:end]
224+
end = min(offset + self._chunk_size, size)
225+
chunks.append({
226+
'index': counter,
227+
'start': offset,
228+
'end': end,
229+
})
230+
offset = end
231+
counter = counter + 1
228232

229-
params[param_name] = input_file
230-
headers["content-range"] = f'bytes {offset}-{min((offset + self._chunk_size) - 1, size - 1)}/{size}'
233+
if not chunks:
234+
return result
235+
236+
def read_chunk(start, end):
237+
if input_file.source_type == 'path':
238+
with open(input_file.path, 'rb') as chunk_file:
239+
chunk_file.seek(start)
240+
return chunk_file.read(end - start)
241+
return input[start:end]
242+
243+
upload_id_header = upload_id
244+
completed_count = chunks[0]['index']
245+
uploaded_size = chunks[0]['start']
246+
progress_lock = Lock()
247+
last_result = None
248+
final_result = None
249+
250+
def is_upload_complete(chunk_result):
251+
chunks_uploaded = chunk_result.get('chunksUploaded')
252+
if chunks_uploaded is None:
253+
return False
254+
chunks_total = chunk_result.get('chunksTotal', total_chunks)
255+
return int(chunks_uploaded) >= int(chunks_total)
256+
257+
def upload_chunk(chunk, current_upload_id):
258+
chunk_input = InputFile.from_bytes(
259+
read_chunk(chunk['start'], chunk['end']),
260+
input_file.filename,
261+
getattr(input_file, 'mime_type', None)
262+
)
263+
chunk_params = {**params, param_name: chunk_input}
264+
chunk_headers = {**headers}
265+
chunk_headers["content-range"] = f"bytes {chunk['start']}-{chunk['end'] - 1}/{size}"
266+
if current_upload_id:
267+
chunk_headers["x-appwrite-id"] = current_upload_id
231268

232-
result = self.call(
269+
return self.call(
233270
'post',
234271
path,
235-
headers,
236-
params,
272+
chunk_headers,
273+
chunk_params,
237274
)
238275

239-
offset = offset + self._chunk_size
240-
241-
if "$id" in result:
242-
headers["x-appwrite-id"] = result["$id"]
243-
244-
if on_progress is not None:
245-
end = min((((counter * self._chunk_size) + self._chunk_size) - 1), size - 1)
246-
on_progress({
247-
"$id": result["$id"],
248-
"progress": min(offset, size)/size * 100,
249-
"sizeUploaded": end+1,
250-
"chunksTotal": result["chunksTotal"],
251-
"chunksUploaded": result["chunksUploaded"],
252-
})
253-
254-
counter = counter + 1
255-
256-
return result
276+
result = upload_chunk(chunks[0], upload_id_header)
277+
last_result = result
278+
if "$id" in result:
279+
upload_id_header = result["$id"]
280+
281+
completed_count = chunks[0]['index'] + 1
282+
uploaded_size = chunks[0]['end']
283+
284+
if on_progress is not None:
285+
on_progress({
286+
"$id": result.get("$id"),
287+
"progress": uploaded_size / size * 100,
288+
"sizeUploaded": uploaded_size,
289+
"chunksTotal": total_chunks,
290+
"chunksUploaded": completed_count,
291+
})
292+
293+
def upload_remaining_chunk(chunk):
294+
nonlocal completed_count, uploaded_size, last_result, final_result
295+
chunk_result = upload_chunk(chunk, upload_id_header)
296+
with progress_lock:
297+
completed_count = completed_count + 1
298+
uploaded_size = uploaded_size + (chunk['end'] - chunk['start'])
299+
last_result = chunk_result
300+
if is_upload_complete(chunk_result):
301+
final_result = chunk_result
302+
if on_progress is not None:
303+
on_progress({
304+
"$id": upload_id_header,
305+
"progress": uploaded_size / size * 100,
306+
"sizeUploaded": uploaded_size,
307+
"chunksTotal": total_chunks,
308+
"chunksUploaded": completed_count,
309+
})
310+
311+
with ThreadPoolExecutor(max_workers=8) as executor:
312+
futures = [executor.submit(upload_remaining_chunk, chunk) for chunk in chunks[1:]]
313+
for future in as_completed(futures):
314+
future.result()
315+
316+
return final_result or last_result
257317

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

appwrite/encoders/value_class_encoder.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ..enums.browser import Browser
77
from ..enums.credit_card import CreditCard
88
from ..enums.flag import Flag
9-
from ..enums.theme import Theme
9+
from ..enums.browser_theme import BrowserTheme
1010
from ..enums.timezone import Timezone
1111
from ..enums.browser_permission import BrowserPermission
1212
from ..enums.image_format import ImageFormat
@@ -16,16 +16,17 @@
1616
from ..enums.databases_index_type import DatabasesIndexType
1717
from ..enums.order_by import OrderBy
1818
from ..enums.runtime import Runtime
19-
from ..enums.scopes import Scopes
19+
from ..enums.project_key_scopes import ProjectKeyScopes
2020
from ..enums.template_reference_type import TemplateReferenceType
2121
from ..enums.vcs_reference_type import VCSReferenceType
2222
from ..enums.deployment_download_type import DeploymentDownloadType
2323
from ..enums.execution_method import ExecutionMethod
24-
from ..enums.name import Name
24+
from ..enums.health_queue_name import HealthQueueName
2525
from ..enums.message_priority import MessagePriority
2626
from ..enums.smtp_encryption import SmtpEncryption
27+
from ..enums.organization_key_scopes import OrganizationKeyScopes
28+
from ..enums.region import Region
2729
from ..enums.project_auth_method_id import ProjectAuthMethodId
28-
from ..enums.project_key_scopes import ProjectKeyScopes
2930
from ..enums.project_o_auth2_google_prompt import ProjectOAuth2GooglePrompt
3031
from ..enums.project_o_auth_provider_id import ProjectOAuthProviderId
3132
from ..enums.project_policy_id import ProjectPolicyId
@@ -82,7 +83,7 @@ def default(self, o):
8283
if isinstance(o, Flag):
8384
return o.value
8485

85-
if isinstance(o, Theme):
86+
if isinstance(o, BrowserTheme):
8687
return o.value
8788

8889
if isinstance(o, Timezone):
@@ -112,7 +113,7 @@ def default(self, o):
112113
if isinstance(o, Runtime):
113114
return o.value
114115

115-
if isinstance(o, Scopes):
116+
if isinstance(o, ProjectKeyScopes):
116117
return o.value
117118

118119
if isinstance(o, TemplateReferenceType):
@@ -127,7 +128,7 @@ def default(self, o):
127128
if isinstance(o, ExecutionMethod):
128129
return o.value
129130

130-
if isinstance(o, Name):
131+
if isinstance(o, HealthQueueName):
131132
return o.value
132133

133134
if isinstance(o, MessagePriority):
@@ -136,10 +137,13 @@ def default(self, o):
136137
if isinstance(o, SmtpEncryption):
137138
return o.value
138139

139-
if isinstance(o, ProjectAuthMethodId):
140+
if isinstance(o, OrganizationKeyScopes):
140141
return o.value
141142

142-
if isinstance(o, ProjectKeyScopes):
143+
if isinstance(o, Region):
144+
return o.value
145+
146+
if isinstance(o, ProjectAuthMethodId):
143147
return o.value
144148

145149
if isinstance(o, ProjectOAuth2GooglePrompt):

appwrite/enums/backup_services.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ class BackupServices(Enum):
55
TABLESDB = "tablesdb"
66
DOCUMENTSDB = "documentsdb"
77
VECTORSDB = "vectorsdb"
8+
DEDICATEDDATABASES = "dedicatedDatabases"
89
FUNCTIONS = "functions"
910
STORAGE = "storage"
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from enum import Enum
22

3-
class Theme(Enum):
3+
class BrowserTheme(Enum):
44
LIGHT = "light"
55
DARK = "dark"

appwrite/enums/build_runtime.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ class BuildRuntime(Enum):
3232
PYTHON_ML_3_11 = "python-ml-3.11"
3333
PYTHON_ML_3_12 = "python-ml-3.12"
3434
PYTHON_ML_3_13 = "python-ml-3.13"
35-
DENO_1_21 = "deno-1.21"
36-
DENO_1_24 = "deno-1.24"
37-
DENO_1_35 = "deno-1.35"
3835
DENO_1_40 = "deno-1.40"
3936
DENO_1_46 = "deno-1.46"
4037
DENO_2_0 = "deno-2.0"
@@ -53,6 +50,7 @@ class BuildRuntime(Enum):
5350
DART_3_9 = "dart-3.9"
5451
DART_3_10 = "dart-3.10"
5552
DART_3_11 = "dart-3.11"
53+
DART_3_12 = "dart-3.12"
5654
DOTNET_6_0 = "dotnet-6.0"
5755
DOTNET_7_0 = "dotnet-7.0"
5856
DOTNET_8_0 = "dotnet-8.0"
@@ -93,3 +91,4 @@ class BuildRuntime(Enum):
9391
FLUTTER_3_35 = "flutter-3.35"
9492
FLUTTER_3_38 = "flutter-3.38"
9593
FLUTTER_3_41 = "flutter-3.41"
94+
FLUTTER_3_44 = "flutter-3.44"
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from enum import Enum
22

3-
class Name(Enum):
3+
class HealthQueueName(Enum):
44
V1_DATABASE = "v1-database"
55
V1_DELETES = "v1-deletes"
66
V1_AUDITS = "v1-audits"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from enum import Enum
2+
3+
class OrganizationKeyScopes(Enum):
4+
PROJECTS_READ = "projects.read"
5+
PROJECTS_WRITE = "projects.write"
6+
DEVKEYS_READ = "devKeys.read"
7+
DEVKEYS_WRITE = "devKeys.write"
8+
ORGANIZATION_KEYS_READ = "organization.keys.read"
9+
ORGANIZATION_KEYS_WRITE = "organization.keys.write"
10+
DOMAINS_READ = "domains.read"
11+
DOMAINS_WRITE = "domains.write"
12+
KEYS_READ = "keys.read"
13+
KEYS_WRITE = "keys.write"

appwrite/enums/project_o_auth_provider_id.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,3 @@ class ProjectOAuthProviderId(Enum):
4444
YANDEX = "yandex"
4545
ZOHO = "zoho"
4646
ZOOM = "zoom"
47-
GITHUBIMAGINE = "githubImagine"
48-
GOOGLEIMAGINE = "googleImagine"

appwrite/enums/project_policy_id.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ class ProjectPolicyId(Enum):
1010
SESSION_LIMIT = "session-limit"
1111
USER_LIMIT = "user-limit"
1212
MEMBERSHIP_PRIVACY = "membership-privacy"
13+
DENY_ALIASED_EMAIL = "deny-aliased-email"
14+
DENY_DISPOSABLE_EMAIL = "deny-disposable-email"
15+
DENY_FREE_EMAIL = "deny-free-email"

0 commit comments

Comments
 (0)