ayon-python-api 1.2.7__tar.gz → 1.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {ayon_python_api-1.2.7/ayon_python_api.egg-info → ayon_python_api-1.2.9}/PKG-INFO +1 -1
  2. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/__init__.py +4 -0
  3. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api.py +104 -1
  4. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/base.py +6 -1
  5. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/folders.py +9 -5
  6. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/lists.py +44 -5
  7. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/products.py +14 -7
  8. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/representations.py +17 -9
  9. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/tasks.py +20 -7
  10. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/versions.py +14 -6
  11. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/constants.py +1 -4
  12. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/entity_hub.py +2 -0
  13. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/graphql_queries.py +12 -0
  14. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/server_api.py +87 -3
  15. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/typing.py +20 -0
  16. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/version.py +1 -1
  17. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9/ayon_python_api.egg-info}/PKG-INFO +1 -1
  18. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/pyproject.toml +1 -1
  19. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/LICENSE +0 -0
  20. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/README.md +0 -0
  21. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/__init__.py +0 -0
  22. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/actions.py +0 -0
  23. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/activities.py +0 -0
  24. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/attributes.py +0 -0
  25. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/bundles_addons.py +0 -0
  26. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/dependency_packages.py +0 -0
  27. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/events.py +0 -0
  28. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/installers.py +0 -0
  29. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/links.py +0 -0
  30. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/projects.py +0 -0
  31. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/secrets.py +0 -0
  32. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/thumbnails.py +0 -0
  33. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/_api_helpers/workfiles.py +0 -0
  34. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/events.py +0 -0
  35. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/exceptions.py +0 -0
  36. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/graphql.py +0 -0
  37. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/operations.py +0 -0
  38. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_api/utils.py +0 -0
  39. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  40. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  41. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_python_api.egg-info/requires.txt +0 -0
  42. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/ayon_python_api.egg-info/top_level.txt +0 -0
  43. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/setup.cfg +0 -0
  44. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/setup.py +0 -0
  45. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/tests/test_entity_hub.py +0 -0
  46. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/tests/test_folder_hierarchy.py +0 -0
  47. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/tests/test_get_events.py +0 -0
  48. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/tests/test_graphql_queries.py +0 -0
  49. {ayon_python_api-1.2.7 → ayon_python_api-1.2.9}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayon_python_api
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -70,6 +70,8 @@ from ._api import (
70
70
  delete,
71
71
  download_file_to_stream,
72
72
  download_file,
73
+ download_project_file,
74
+ download_project_file_to_stream,
73
75
  upload_file_from_stream,
74
76
  upload_file,
75
77
  upload_reviewable,
@@ -349,6 +351,8 @@ __all__ = (
349
351
  "delete",
350
352
  "download_file_to_stream",
351
353
  "download_file",
354
+ "download_project_file",
355
+ "download_project_file_to_stream",
352
356
  "upload_file_from_stream",
353
357
  "upload_file",
354
358
  "upload_reviewable",
@@ -84,6 +84,7 @@ if typing.TYPE_CHECKING:
84
84
  ActionModeType,
85
85
  StreamType,
86
86
  EntityListAttributeDefinitionDict,
87
+ AdvancedFilterDict,
87
88
  )
88
89
  from ._api_helpers.links import CreateLinkData
89
90
 
@@ -992,6 +993,80 @@ def download_file(
992
993
  )
993
994
 
994
995
 
996
+ def download_project_file(
997
+ project_name: str,
998
+ file_id: str,
999
+ filepath: str,
1000
+ *,
1001
+ chunk_size: Optional[int] = None,
1002
+ progress: Optional[TransferProgress] = None,
1003
+ ) -> TransferProgress:
1004
+ """Download project file to filepath.
1005
+
1006
+ Project files are usually binary files, such as images, videos,
1007
+ or other media files that can be accessed via api endpoint
1008
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1009
+
1010
+ Args:
1011
+ project_name (str): Project name.
1012
+ file_id (str): File id.
1013
+ filepath (str): Path where file will be downloaded.
1014
+ chunk_size (Optional[int]): Size of chunks that are received
1015
+ in single loop.
1016
+ progress (Optional[TransferProgress]): Object that gives ability
1017
+ to track download progress.
1018
+
1019
+ Returns:
1020
+ TransferProgress: Progress object.
1021
+
1022
+ """
1023
+ con = get_server_api_connection()
1024
+ return con.download_project_file(
1025
+ project_name=project_name,
1026
+ file_id=file_id,
1027
+ filepath=filepath,
1028
+ chunk_size=chunk_size,
1029
+ progress=progress,
1030
+ )
1031
+
1032
+
1033
+ def download_project_file_to_stream(
1034
+ project_name: str,
1035
+ file_id: str,
1036
+ stream: StreamType,
1037
+ *,
1038
+ chunk_size: Optional[int] = None,
1039
+ progress: Optional[TransferProgress] = None,
1040
+ ) -> TransferProgress:
1041
+ """Download project file to a stream.
1042
+
1043
+ Project files are usually binary files, such as images, videos,
1044
+ or other media files that can be accessed via api endpoint
1045
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1046
+
1047
+ Args:
1048
+ project_name (str): Project name.
1049
+ file_id (str): File id.
1050
+ stream (StreamType): Stream where output will be stored.
1051
+ chunk_size (Optional[int]): Size of chunks that are received
1052
+ in single loop.
1053
+ progress (Optional[TransferProgress]): Object that gives ability
1054
+ to track download progress.
1055
+
1056
+ Returns:
1057
+ TransferProgress: Progress object.
1058
+
1059
+ """
1060
+ con = get_server_api_connection()
1061
+ return con.download_project_file_to_stream(
1062
+ project_name=project_name,
1063
+ file_id=file_id,
1064
+ stream=stream,
1065
+ chunk_size=chunk_size,
1066
+ progress=progress,
1067
+ )
1068
+
1069
+
995
1070
  def upload_file_from_stream(
996
1071
  endpoint: str,
997
1072
  stream: StreamType,
@@ -4144,6 +4219,7 @@ def get_folders(
4144
4219
  tags: Optional[Iterable[str]] = None,
4145
4220
  active: Optional[bool] = True,
4146
4221
  has_links: Optional[bool] = None,
4222
+ filters: Optional[AdvancedFilterDict] = None,
4147
4223
  fields: Optional[Iterable[str]] = None,
4148
4224
  own_attributes: bool = False,
4149
4225
  ) -> Generator[FolderDict, None, None]:
@@ -4185,6 +4261,7 @@ def get_folders(
4185
4261
  Both are returned if is set to None.
4186
4262
  has_links (Optional[Literal[IN, OUT, ANY]]): Filter
4187
4263
  representations with IN/OUT/ANY links.
4264
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
4188
4265
  fields (Optional[Iterable[str]]): Fields to be queried for
4189
4266
  folder. All possible folder fields are returned
4190
4267
  if 'None' is passed.
@@ -4212,6 +4289,7 @@ def get_folders(
4212
4289
  tags=tags,
4213
4290
  active=active,
4214
4291
  has_links=has_links,
4292
+ filters=filters,
4215
4293
  fields=fields,
4216
4294
  own_attributes=own_attributes,
4217
4295
  )
@@ -4497,6 +4575,7 @@ def get_tasks(
4497
4575
  statuses: Optional[Iterable[str]] = None,
4498
4576
  tags: Optional[Iterable[str]] = None,
4499
4577
  active: Optional[bool] = True,
4578
+ filters: Optional[AdvancedFilterDict] = None,
4500
4579
  fields: Optional[Iterable[str]] = None,
4501
4580
  own_attributes: bool = False,
4502
4581
  ) -> Generator[TaskDict, None, None]:
@@ -4521,6 +4600,7 @@ def get_tasks(
4521
4600
  filtering.
4522
4601
  active (Optional[bool]): Filter active/inactive tasks.
4523
4602
  Both are returned if is set to None.
4603
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
4524
4604
  fields (Optional[Iterable[str]]): Fields to be queried for
4525
4605
  folder. All possible folder fields are returned
4526
4606
  if 'None' is passed.
@@ -4543,6 +4623,7 @@ def get_tasks(
4543
4623
  statuses=statuses,
4544
4624
  tags=tags,
4545
4625
  active=active,
4626
+ filters=filters,
4546
4627
  fields=fields,
4547
4628
  own_attributes=own_attributes,
4548
4629
  )
@@ -4621,6 +4702,7 @@ def get_tasks_by_folder_paths(
4621
4702
  statuses: Optional[Iterable[str]] = None,
4622
4703
  tags: Optional[Iterable[str]] = None,
4623
4704
  active: Optional[bool] = True,
4705
+ filters: Optional[AdvancedFilterDict] = None,
4624
4706
  fields: Optional[Iterable[str]] = None,
4625
4707
  own_attributes: bool = False,
4626
4708
  ) -> dict[str, list[TaskDict]]:
@@ -4643,6 +4725,7 @@ def get_tasks_by_folder_paths(
4643
4725
  filtering.
4644
4726
  active (Optional[bool]): Filter active/inactive tasks.
4645
4727
  Both are returned if is set to None.
4728
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
4646
4729
  fields (Optional[Iterable[str]]): Fields to be queried for
4647
4730
  folder. All possible folder fields are returned
4648
4731
  if 'None' is passed.
@@ -4665,6 +4748,7 @@ def get_tasks_by_folder_paths(
4665
4748
  statuses=statuses,
4666
4749
  tags=tags,
4667
4750
  active=active,
4751
+ filters=filters,
4668
4752
  fields=fields,
4669
4753
  own_attributes=own_attributes,
4670
4754
  )
@@ -4914,6 +4998,7 @@ def get_products(
4914
4998
  statuses: Optional[Iterable[str]] = None,
4915
4999
  tags: Optional[Iterable[str]] = None,
4916
5000
  active: Optional[bool] = True,
5001
+ filters: Optional[AdvancedFilterDict] = None,
4917
5002
  fields: Optional[Iterable[str]] = None,
4918
5003
  own_attributes=_PLACEHOLDER,
4919
5004
  ) -> Generator[ProductDict, None, None]:
@@ -4932,6 +5017,8 @@ def get_products(
4932
5017
  Use 'None' if folder is direct child of project.
4933
5018
  product_types (Optional[Iterable[str]]): Product types used for
4934
5019
  filtering.
5020
+ product_base_types (Optional[Iterable[str]]): Product base types
5021
+ used for filtering.
4935
5022
  product_name_regex (Optional[str]): Filter products by name regex.
4936
5023
  product_path_regex (Optional[str]): Filter products by path regex.
4937
5024
  Path starts with folder path and ends with product name.
@@ -4943,6 +5030,7 @@ def get_products(
4943
5030
  for filtering.
4944
5031
  active (Optional[bool]): Filter active/inactive products.
4945
5032
  Both are returned if is set to None.
5033
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
4946
5034
  fields (Optional[Iterable[str]]): Fields to be queried for
4947
5035
  folder. All possible folder fields are returned
4948
5036
  if 'None' is passed.
@@ -4967,6 +5055,7 @@ def get_products(
4967
5055
  statuses=statuses,
4968
5056
  tags=tags,
4969
5057
  active=active,
5058
+ filters=filters,
4970
5059
  fields=fields,
4971
5060
  own_attributes=own_attributes,
4972
5061
  )
@@ -5249,6 +5338,7 @@ def get_versions(
5249
5338
  statuses: Optional[Iterable[str]] = None,
5250
5339
  tags: Optional[Iterable[str]] = None,
5251
5340
  active: Optional[bool] = True,
5341
+ filters: Optional[AdvancedFilterDict] = None,
5252
5342
  fields: Optional[Iterable[str]] = None,
5253
5343
  own_attributes=_PLACEHOLDER,
5254
5344
  ) -> Generator[VersionDict, None, None]:
@@ -5275,6 +5365,7 @@ def get_versions(
5275
5365
  for filtering.
5276
5366
  active (Optional[bool]): Receive active/inactive entities.
5277
5367
  Both are returned when 'None' is passed.
5368
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
5278
5369
  fields (Optional[Iterable[str]]): Fields to be queried
5279
5370
  for version. All possible folder fields are returned
5280
5371
  if 'None' is passed.
@@ -5298,6 +5389,7 @@ def get_versions(
5298
5389
  statuses=statuses,
5299
5390
  tags=tags,
5300
5391
  active=active,
5392
+ filters=filters,
5301
5393
  fields=fields,
5302
5394
  own_attributes=own_attributes,
5303
5395
  )
@@ -5743,6 +5835,7 @@ def get_representations(
5743
5835
  tags: Optional[Iterable[str]] = None,
5744
5836
  active: Optional[bool] = True,
5745
5837
  has_links: Optional[str] = None,
5838
+ filters: Optional[AdvancedFilterDict] = None,
5746
5839
  fields: Optional[Iterable[str]] = None,
5747
5840
  own_attributes=_PLACEHOLDER,
5748
5841
  ) -> Generator[RepresentationDict, None, None]:
@@ -5773,6 +5866,7 @@ def get_representations(
5773
5866
  Both are returned when 'None' is passed.
5774
5867
  has_links (Optional[Literal[IN, OUT, ANY]]): Filter
5775
5868
  representations with IN/OUT/ANY links.
5869
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
5776
5870
  fields (Optional[Iterable[str]]): Fields to be queried for
5777
5871
  representation. All possible fields are returned if 'None' is
5778
5872
  passed.
@@ -5795,6 +5889,7 @@ def get_representations(
5795
5889
  tags=tags,
5796
5890
  active=active,
5797
5891
  has_links=has_links,
5892
+ filters=filters,
5798
5893
  fields=fields,
5799
5894
  own_attributes=own_attributes,
5800
5895
  )
@@ -7206,7 +7301,15 @@ def get_entity_lists(
7206
7301
  active: Optional[bool] = None,
7207
7302
  fields: Optional[Iterable[str]] = None,
7208
7303
  ) -> Generator[dict[str, Any], None, None]:
7209
- """Fetch entity lists from server.
7304
+ """Fetch entity lists from AYON server.
7305
+
7306
+ Warnings:
7307
+ You can't get list items for lists with different 'entityType' in
7308
+ one call.
7309
+
7310
+ Notes:
7311
+ To get list items, you have to pass 'items' field or
7312
+ 'items.{sub-fields you want}' to 'fields' argument.
7210
7313
 
7211
7314
  Args:
7212
7315
  project_name (str): Project name where entity lists are.
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import logging
4
4
  import typing
5
- from typing import Optional, Any, Iterable
5
+ from typing import Optional, Any, Iterable, Union
6
6
 
7
7
  import requests
8
8
 
@@ -142,6 +142,11 @@ class BaseServerAPI:
142
142
  ):
143
143
  raise NotImplementedError()
144
144
 
145
+ def _prepare_advanced_filters(
146
+ self, filters: Union[str, dict[str, Any], None]
147
+ ) -> Optional[str]:
148
+ raise NotImplementedError()
149
+
145
150
  def _convert_entity_data(self, entity: AnyEntityDict):
146
151
  raise NotImplementedError()
147
152
 
@@ -21,6 +21,7 @@ if typing.TYPE_CHECKING:
21
21
  FolderDict,
22
22
  FlatFolderDict,
23
23
  ProjectHierarchyDict,
24
+ AdvancedFilterDict,
24
25
  )
25
26
 
26
27
 
@@ -216,6 +217,7 @@ class FoldersAPI(BaseServerAPI):
216
217
  tags: Optional[Iterable[str]] = None,
217
218
  active: Optional[bool] = True,
218
219
  has_links: Optional[bool] = None,
220
+ filters: Optional[AdvancedFilterDict] = None,
219
221
  fields: Optional[Iterable[str]] = None,
220
222
  own_attributes: bool = False
221
223
  ) -> Generator[FolderDict, None, None]:
@@ -257,6 +259,7 @@ class FoldersAPI(BaseServerAPI):
257
259
  Both are returned if is set to None.
258
260
  has_links (Optional[Literal[IN, OUT, ANY]]): Filter
259
261
  representations with IN/OUT/ANY links.
262
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
260
263
  fields (Optional[Iterable[str]]): Fields to be queried for
261
264
  folder. All possible folder fields are returned
262
265
  if 'None' is passed.
@@ -270,11 +273,11 @@ class FoldersAPI(BaseServerAPI):
270
273
  if not project_name:
271
274
  return
272
275
 
273
- filters = {
276
+ graphql_filters = {
274
277
  "projectName": project_name
275
278
  }
276
279
  if not prepare_list_filters(
277
- filters,
280
+ graphql_filters,
278
281
  ("folderIds", folder_ids),
279
282
  ("folderPaths", folder_paths),
280
283
  ("folderNames", folder_names),
@@ -291,9 +294,10 @@ class FoldersAPI(BaseServerAPI):
291
294
  ("folderHasTasks", has_tasks),
292
295
  ("folderHasLinks", has_links),
293
296
  ("folderHasChildren", has_children),
297
+ ("filter", self._prepare_advanced_filters(filters)),
294
298
  ):
295
299
  if filter_value is not None:
296
- filters[filter_key] = filter_value
300
+ graphql_filters[filter_key] = filter_value
297
301
 
298
302
  if parent_ids is not None:
299
303
  parent_ids = set(parent_ids)
@@ -313,7 +317,7 @@ class FoldersAPI(BaseServerAPI):
313
317
  parent_ids.remove(project_name)
314
318
  parent_ids.add("root")
315
319
 
316
- filters["parentFolderIds"] = list(parent_ids)
320
+ graphql_filters["parentFolderIds"] = list(parent_ids)
317
321
 
318
322
  if not fields:
319
323
  fields = self.get_default_fields_for_type("folder")
@@ -328,7 +332,7 @@ class FoldersAPI(BaseServerAPI):
328
332
  fields.add("ownAttrib")
329
333
 
330
334
  query = folders_graphql_query(fields)
331
- for attr, filter_value in filters.items():
335
+ for attr, filter_value in graphql_filters.items():
332
336
  query.set_variable_value(attr, filter_value)
333
337
 
334
338
  for parsed_data in query.continuous_query(self):
@@ -26,7 +26,15 @@ class ListsAPI(BaseServerAPI):
26
26
  active: Optional[bool] = None,
27
27
  fields: Optional[Iterable[str]] = None,
28
28
  ) -> Generator[dict[str, Any], None, None]:
29
- """Fetch entity lists from server.
29
+ """Fetch entity lists from AYON server.
30
+
31
+ Warnings:
32
+ You can't get list items for lists with different 'entityType' in
33
+ one call.
34
+
35
+ Notes:
36
+ To get list items, you have to pass 'items' field or
37
+ 'items.{sub-fields you want}' to 'fields' argument.
30
38
 
31
39
  Args:
32
40
  project_name (str): Project name where entity lists are.
@@ -42,7 +50,30 @@ class ListsAPI(BaseServerAPI):
42
50
  """
43
51
  if fields is None:
44
52
  fields = self.get_default_fields_for_type("entityList")
45
- fields = set(fields)
53
+
54
+ # List does not have 'attrib' field but has 'allAttrib' field
55
+ # which is json string and contains only values that are set
56
+ o_fields = tuple(fields)
57
+ fields = set()
58
+ requires_attrib = False
59
+ for field in o_fields:
60
+ if field == "attrib" or field.startswith("attrib."):
61
+ requires_attrib = True
62
+ field = "allAttrib"
63
+ fields.add(field)
64
+
65
+ if "items" in fields:
66
+ fields.discard("items")
67
+ fields |= {
68
+ "items.id",
69
+ "items.entityId",
70
+ "items.entityType",
71
+ "items.position",
72
+ }
73
+
74
+ available_attribs = []
75
+ if requires_attrib:
76
+ available_attribs = self.get_attributes_for_type("list")
46
77
 
47
78
  if active is not None:
48
79
  fields.add("active")
@@ -66,6 +97,15 @@ class ListsAPI(BaseServerAPI):
66
97
  if isinstance(attributes, str):
67
98
  entity_list["attributes"] = json.loads(attributes)
68
99
 
100
+ if requires_attrib:
101
+ all_attrib = json.loads(
102
+ entity_list.get("allAttrib") or "{}"
103
+ )
104
+ entity_list["attrib"] = {
105
+ attrib_name: all_attrib.get(attrib_name)
106
+ for attrib_name in available_attribs
107
+ }
108
+
69
109
  self._convert_entity_data(entity_list)
70
110
 
71
111
  yield entity_list
@@ -170,9 +210,8 @@ class ListsAPI(BaseServerAPI):
170
210
  kwargs[key] = value
171
211
 
172
212
  response = self.post(
173
- f"projects/{project_name}/lists/{list_id}/items",
213
+ f"projects/{project_name}/lists",
174
214
  **kwargs
175
-
176
215
  )
177
216
  response.raise_for_status()
178
217
  return list_id
@@ -343,7 +382,7 @@ class ListsAPI(BaseServerAPI):
343
382
  mode (EntityListItemMode): Mode of items update.
344
383
 
345
384
  """
346
- response = self.post(
385
+ response = self.patch(
347
386
  f"projects/{project_name}/lists/{list_id}/items",
348
387
  items=items,
349
388
  mode=mode,
@@ -18,7 +18,11 @@ from ayon_api.graphql_queries import (
18
18
  from .base import BaseServerAPI, _PLACEHOLDER
19
19
 
20
20
  if typing.TYPE_CHECKING:
21
- from ayon_api.typing import ProductDict, ProductTypeDict
21
+ from ayon_api.typing import (
22
+ ProductDict,
23
+ ProductTypeDict,
24
+ AdvancedFilterDict,
25
+ )
22
26
 
23
27
 
24
28
  class ProductsAPI(BaseServerAPI):
@@ -43,6 +47,7 @@ class ProductsAPI(BaseServerAPI):
43
47
  statuses: Optional[Iterable[str]] = None,
44
48
  tags: Optional[Iterable[str]] = None,
45
49
  active: Optional[bool] = True,
50
+ filters: Optional[AdvancedFilterDict] = None,
46
51
  fields: Optional[Iterable[str]] = None,
47
52
  own_attributes=_PLACEHOLDER
48
53
  ) -> Generator[ProductDict, None, None]:
@@ -74,6 +79,7 @@ class ProductsAPI(BaseServerAPI):
74
79
  for filtering.
75
80
  active (Optional[bool]): Filter active/inactive products.
76
81
  Both are returned if is set to None.
82
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
77
83
  fields (Optional[Iterable[str]]): Fields to be queried for
78
84
  folder. All possible folder fields are returned
79
85
  if 'None' is passed.
@@ -145,18 +151,18 @@ class ProductsAPI(BaseServerAPI):
145
151
  fields.add("folderId")
146
152
 
147
153
  # Prepare filters for query
148
- filters = {
154
+ graphql_filters = {
149
155
  "projectName": project_name
150
156
  }
151
157
 
152
158
  if filter_folder_ids:
153
- filters["folderIds"] = list(filter_folder_ids)
159
+ graphql_filters["folderIds"] = list(filter_folder_ids)
154
160
 
155
161
  if filter_product_names:
156
- filters["productNames"] = list(filter_product_names)
162
+ graphql_filters["productNames"] = list(filter_product_names)
157
163
 
158
164
  if not prepare_list_filters(
159
- filters,
165
+ graphql_filters,
160
166
  ("productIds", product_ids),
161
167
  ("productTypes", product_types),
162
168
  ("productBaseTypes", product_base_types),
@@ -168,12 +174,13 @@ class ProductsAPI(BaseServerAPI):
168
174
  for filter_key, filter_value in (
169
175
  ("productNameRegex", product_name_regex),
170
176
  ("productPathRegex", product_path_regex),
177
+ ("filter", self._prepare_advanced_filters(filters)),
171
178
  ):
172
179
  if filter_value:
173
- filters[filter_key] = filter_value
180
+ graphql_filters[filter_key] = filter_value
174
181
 
175
182
  query = products_graphql_query(fields)
176
- for attr, filter_value in filters.items():
183
+ for attr, filter_value in graphql_filters.items():
177
184
  query.set_variable_value(attr, filter_value)
178
185
 
179
186
  parsed_data = query.query(self)
@@ -20,7 +20,7 @@ from ayon_api.graphql_queries import (
20
20
  from .base import BaseServerAPI, _PLACEHOLDER
21
21
 
22
22
  if typing.TYPE_CHECKING:
23
- from ayon_api.typing import RepresentationDict
23
+ from ayon_api.typing import RepresentationDict, AdvancedFilterDict
24
24
 
25
25
 
26
26
  class RepresentationsAPI(BaseServerAPI):
@@ -42,6 +42,7 @@ class RepresentationsAPI(BaseServerAPI):
42
42
  tags: Optional[Iterable[str]] = None,
43
43
  active: Optional[bool] = True,
44
44
  has_links: Optional[str] = None,
45
+ filters: Optional[AdvancedFilterDict] = None,
45
46
  fields: Optional[Iterable[str]] = None,
46
47
  own_attributes=_PLACEHOLDER,
47
48
  ) -> Generator[RepresentationDict, None, None]:
@@ -72,6 +73,7 @@ class RepresentationsAPI(BaseServerAPI):
72
73
  Both are returned when 'None' is passed.
73
74
  has_links (Optional[Literal[IN, OUT, ANY]]): Filter
74
75
  representations with IN/OUT/ANY links.
76
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
75
77
  fields (Optional[Iterable[str]]): Fields to be queried for
76
78
  representation. All possible fields are returned if 'None' is
77
79
  passed.
@@ -106,7 +108,7 @@ class RepresentationsAPI(BaseServerAPI):
106
108
  fields.discard("files")
107
109
  fields |= REPRESENTATION_FILES_FIELDS
108
110
 
109
- filters = {
111
+ graphql_filters = {
110
112
  "projectName": project_name
111
113
  }
112
114
 
@@ -114,7 +116,7 @@ class RepresentationsAPI(BaseServerAPI):
114
116
  representation_ids = set(representation_ids)
115
117
  if not representation_ids:
116
118
  return
117
- filters["representationIds"] = list(representation_ids)
119
+ graphql_filters["representationIds"] = list(representation_ids)
118
120
 
119
121
  version_ids_filter = None
120
122
  representation_names_filter = None
@@ -140,29 +142,35 @@ class RepresentationsAPI(BaseServerAPI):
140
142
  return
141
143
 
142
144
  if version_ids_filter:
143
- filters["versionIds"] = list(version_ids_filter)
145
+ graphql_filters["versionIds"] = list(version_ids_filter)
144
146
 
145
147
  if representation_names_filter:
146
- filters["representationNames"] = list(representation_names_filter)
148
+ graphql_filters["representationNames"] = list(
149
+ representation_names_filter
150
+ )
147
151
 
148
152
  if statuses is not None:
149
153
  statuses = set(statuses)
150
154
  if not statuses:
151
155
  return
152
- filters["representationStatuses"] = list(statuses)
156
+ graphql_filters["representationStatuses"] = list(statuses)
153
157
 
154
158
  if tags is not None:
155
159
  tags = set(tags)
156
160
  if not tags:
157
161
  return
158
- filters["representationTags"] = list(tags)
162
+ graphql_filters["representationTags"] = list(tags)
159
163
 
160
164
  if has_links is not None:
161
- filters["representationHasLinks"] = has_links.upper()
165
+ graphql_filters["representationHasLinks"] = has_links.upper()
166
+
167
+ filters = self._prepare_advanced_filters(filters)
168
+ if filters:
169
+ graphql_filters["filter"] = filters
162
170
 
163
171
  query = representations_graphql_query(fields)
164
172
 
165
- for attr, filter_value in filters.items():
173
+ for attr, filter_value in graphql_filters.items():
166
174
  query.set_variable_value(attr, filter_value)
167
175
 
168
176
  for parsed_data in query.continuous_query(self):
@@ -17,7 +17,7 @@ from ayon_api.graphql_queries import (
17
17
  from .base import BaseServerAPI
18
18
 
19
19
  if typing.TYPE_CHECKING:
20
- from ayon_api.typing import TaskDict
20
+ from ayon_api.typing import TaskDict, AdvancedFilterDict
21
21
 
22
22
 
23
23
  class TasksAPI(BaseServerAPI):
@@ -38,6 +38,7 @@ class TasksAPI(BaseServerAPI):
38
38
  statuses: Optional[Iterable[str]] = None,
39
39
  tags: Optional[Iterable[str]] = None,
40
40
  active: Optional[bool] = True,
41
+ filters: Optional[AdvancedFilterDict] = None,
41
42
  fields: Optional[Iterable[str]] = None,
42
43
  own_attributes: bool = False
43
44
  ) -> Generator[TaskDict, None, None]:
@@ -62,6 +63,7 @@ class TasksAPI(BaseServerAPI):
62
63
  filtering.
63
64
  active (Optional[bool]): Filter active/inactive tasks.
64
65
  Both are returned if is set to None.
66
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
65
67
  fields (Optional[Iterable[str]]): Fields to be queried for
66
68
  folder. All possible folder fields are returned
67
69
  if 'None' is passed.
@@ -75,11 +77,11 @@ class TasksAPI(BaseServerAPI):
75
77
  if not project_name:
76
78
  return
77
79
 
78
- filters = {
80
+ graphql_filters = {
79
81
  "projectName": project_name
80
82
  }
81
83
  if not prepare_list_filters(
82
- filters,
84
+ graphql_filters,
83
85
  ("taskIds", task_ids),
84
86
  ("taskNames", task_names),
85
87
  ("taskTypes", task_types),
@@ -91,6 +93,10 @@ class TasksAPI(BaseServerAPI):
91
93
  ):
92
94
  return
93
95
 
96
+ filters = self._prepare_advanced_filters(filters)
97
+ if filters:
98
+ graphql_filters["filter"] = filters
99
+
94
100
  if not fields:
95
101
  fields = self.get_default_fields_for_type("task")
96
102
  else:
@@ -101,7 +107,7 @@ class TasksAPI(BaseServerAPI):
101
107
  fields.add("active")
102
108
 
103
109
  query = tasks_graphql_query(fields)
104
- for attr, filter_value in filters.items():
110
+ for attr, filter_value in graphql_filters.items():
105
111
  query.set_variable_value(attr, filter_value)
106
112
 
107
113
  for parsed_data in query.continuous_query(self):
@@ -193,6 +199,7 @@ class TasksAPI(BaseServerAPI):
193
199
  statuses: Optional[Iterable[str]] = None,
194
200
  tags: Optional[Iterable[str]] = None,
195
201
  active: Optional[bool] = True,
202
+ filters: Optional[AdvancedFilterDict] = None,
196
203
  fields: Optional[Iterable[str]] = None,
197
204
  own_attributes: bool = False
198
205
  ) -> dict[str, list[TaskDict]]:
@@ -215,6 +222,7 @@ class TasksAPI(BaseServerAPI):
215
222
  filtering.
216
223
  active (Optional[bool]): Filter active/inactive tasks.
217
224
  Both are returned if is set to None.
225
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
218
226
  fields (Optional[Iterable[str]]): Fields to be queried for
219
227
  folder. All possible folder fields are returned
220
228
  if 'None' is passed.
@@ -230,12 +238,13 @@ class TasksAPI(BaseServerAPI):
230
238
  if not project_name or not folder_paths:
231
239
  return {}
232
240
 
233
- filters = {
241
+ graphql_filters = {
234
242
  "projectName": project_name,
235
243
  "folderPaths": list(folder_paths),
236
244
  }
245
+
237
246
  if not prepare_list_filters(
238
- filters,
247
+ graphql_filters,
239
248
  ("taskNames", task_names),
240
249
  ("taskTypes", task_types),
241
250
  ("taskAssigneesAny", assignees),
@@ -245,6 +254,10 @@ class TasksAPI(BaseServerAPI):
245
254
  ):
246
255
  return {}
247
256
 
257
+ filters = self._prepare_advanced_filters(filters)
258
+ if filters:
259
+ graphql_filters["filter"] = filters
260
+
248
261
  if not fields:
249
262
  fields = self.get_default_fields_for_type("task")
250
263
  else:
@@ -255,7 +268,7 @@ class TasksAPI(BaseServerAPI):
255
268
  fields.add("active")
256
269
 
257
270
  query = tasks_by_folder_paths_graphql_query(fields)
258
- for attr, filter_value in filters.items():
271
+ for attr, filter_value in graphql_filters.items():
259
272
  query.set_variable_value(attr, filter_value)
260
273
 
261
274
  output = {
@@ -15,7 +15,7 @@ from ayon_api.graphql_queries import versions_graphql_query
15
15
  from .base import BaseServerAPI, _PLACEHOLDER
16
16
 
17
17
  if typing.TYPE_CHECKING:
18
- from ayon_api.typing import VersionDict
18
+ from ayon_api.typing import VersionDict, AdvancedFilterDict
19
19
 
20
20
 
21
21
  class VersionsAPI(BaseServerAPI):
@@ -37,6 +37,7 @@ class VersionsAPI(BaseServerAPI):
37
37
  statuses: Optional[Iterable[str]] = None,
38
38
  tags: Optional[Iterable[str]] = None,
39
39
  active: Optional[bool] = True,
40
+ filters: Optional[AdvancedFilterDict] = None,
40
41
  fields: Optional[Iterable[str]] = None,
41
42
  own_attributes=_PLACEHOLDER
42
43
  ) -> Generator[VersionDict, None, None]:
@@ -63,6 +64,7 @@ class VersionsAPI(BaseServerAPI):
63
64
  for filtering.
64
65
  active (Optional[bool]): Receive active/inactive entities.
65
66
  Both are returned when 'None' is passed.
67
+ filters (Optional[AdvancedFilterDict]): Advanced filtering options.
66
68
  fields (Optional[Iterable[str]]): Fields to be queried
67
69
  for version. All possible folder fields are returned
68
70
  if 'None' is passed.
@@ -98,11 +100,12 @@ class VersionsAPI(BaseServerAPI):
98
100
  if not hero and not standard:
99
101
  return
100
102
 
101
- filters = {
103
+ graphql_filters = {
102
104
  "projectName": project_name
103
105
  }
106
+
104
107
  if not prepare_list_filters(
105
- filters,
108
+ graphql_filters,
106
109
  ("taskIds", task_ids),
107
110
  ("versionIds", version_ids),
108
111
  ("productIds", product_ids),
@@ -113,6 +116,11 @@ class VersionsAPI(BaseServerAPI):
113
116
  ):
114
117
  return
115
118
 
119
+
120
+ filters = self._prepare_advanced_filters(filters)
121
+ if filters:
122
+ graphql_filters["filter"] = filters
123
+
116
124
  queries = []
117
125
  # Add filters based on 'hero' and 'standard'
118
126
  # NOTE: There is not a filter to "ignore" hero versions or to get
@@ -123,14 +131,14 @@ class VersionsAPI(BaseServerAPI):
123
131
  # This query all versions standard + hero
124
132
  # - hero must be filtered out if is not enabled during loop
125
133
  query = versions_graphql_query(fields)
126
- for attr, filter_value in filters.items():
134
+ for attr, filter_value in graphql_filters.items():
127
135
  query.set_variable_value(attr, filter_value)
128
136
  queries.append(query)
129
137
  else:
130
138
  if hero:
131
139
  # Add hero query if hero is enabled
132
140
  hero_query = versions_graphql_query(fields)
133
- for attr, filter_value in filters.items():
141
+ for attr, filter_value in graphql_filters.items():
134
142
  hero_query.set_variable_value(attr, filter_value)
135
143
 
136
144
  hero_query.set_variable_value("heroOnly", True)
@@ -138,7 +146,7 @@ class VersionsAPI(BaseServerAPI):
138
146
 
139
147
  if standard:
140
148
  standard_query = versions_graphql_query(fields)
141
- for attr, filter_value in filters.items():
149
+ for attr, filter_value in graphql_filters.items():
142
150
  standard_query.set_variable_value(attr, filter_value)
143
151
 
144
152
  if latest:
@@ -247,6 +247,7 @@ DEFAULT_ACTIVITY_FIELDS = {
247
247
  DEFAULT_ENTITY_LIST_FIELDS = {
248
248
  "id",
249
249
  "count",
250
+ "allAttrib",
250
251
  "attributes",
251
252
  "active",
252
253
  "createdBy",
@@ -259,8 +260,4 @@ DEFAULT_ENTITY_LIST_FIELDS = {
259
260
  "tags",
260
261
  "updatedAt",
261
262
  "updatedBy",
262
- "items.id",
263
- "items.entityId",
264
- "items.entityType",
265
- "items.position",
266
263
  }
@@ -1582,6 +1582,8 @@ class BaseEntity(ABC):
1582
1582
  self._tags = copy.deepcopy(tags)
1583
1583
  self._thumbnail_id = thumbnail_id
1584
1584
 
1585
+ if name == label:
1586
+ label = None
1585
1587
  self._orig_name = name
1586
1588
  self._orig_label = label
1587
1589
  self._orig_status = status
@@ -139,6 +139,7 @@ def folders_graphql_query(fields):
139
139
  "folderAssigneesAll", "[String!]"
140
140
  )
141
141
  tags_var = query.add_variable("folderTags", "[String!]")
142
+ filter_var = query.add_variable("filter", "String!")
142
143
 
143
144
  project_field = query.add_field("project")
144
145
  project_field.set_filter("name", project_name_var)
@@ -157,6 +158,7 @@ def folders_graphql_query(fields):
157
158
  folders_field.set_filter("hasTasks", has_tasks_var)
158
159
  folders_field.set_filter("hasLinks", has_links_var)
159
160
  folders_field.set_filter("hasChildren", has_children_var)
161
+ folders_field.set_filter("filter", filter_var)
160
162
 
161
163
  nested_fields = fields_to_dict(fields)
162
164
  add_links_fields(folders_field, nested_fields)
@@ -188,6 +190,7 @@ def tasks_graphql_query(fields):
188
190
  assignees_all_var = query.add_variable("taskAssigneesAll", "[String!]")
189
191
  statuses_var = query.add_variable("taskStatuses", "[String!]")
190
192
  tags_var = query.add_variable("taskTags", "[String!]")
193
+ filter_var = query.add_variable("filter", "String!")
191
194
 
192
195
  project_field = query.add_field("project")
193
196
  project_field.set_filter("name", project_name_var)
@@ -203,6 +206,7 @@ def tasks_graphql_query(fields):
203
206
  tasks_field.set_filter("assignees", assignees_all_var)
204
207
  tasks_field.set_filter("statuses", statuses_var)
205
208
  tasks_field.set_filter("tags", tags_var)
209
+ tasks_field.set_filter("filter", filter_var)
206
210
 
207
211
  nested_fields = fields_to_dict(fields)
208
212
  add_links_fields(tasks_field, nested_fields)
@@ -233,6 +237,7 @@ def tasks_by_folder_paths_graphql_query(fields):
233
237
  assignees_all_var = query.add_variable("taskAssigneesAll", "[String!]")
234
238
  statuses_var = query.add_variable("taskStatuses", "[String!]")
235
239
  tags_var = query.add_variable("taskTags", "[String!]")
240
+ filter_var = query.add_variable("filter", "String!")
236
241
 
237
242
  project_field = query.add_field("project")
238
243
  project_field.set_filter("name", project_name_var)
@@ -250,6 +255,7 @@ def tasks_by_folder_paths_graphql_query(fields):
250
255
  tasks_field.set_filter("assignees", assignees_all_var)
251
256
  tasks_field.set_filter("statuses", statuses_var)
252
257
  tasks_field.set_filter("tags", tags_var)
258
+ tasks_field.set_filter("filter", filter_var)
253
259
 
254
260
  nested_fields = fields_to_dict(fields)
255
261
  add_links_fields(tasks_field, nested_fields)
@@ -284,6 +290,7 @@ def products_graphql_query(fields):
284
290
  product_path_regex_var = query.add_variable("productPathRegex", "String!")
285
291
  statuses_var = query.add_variable("productStatuses", "[String!]")
286
292
  tags_var = query.add_variable("productTags", "[String!]")
293
+ filter_var = query.add_variable("filter", "String!")
287
294
 
288
295
  project_field = query.add_field("project")
289
296
  project_field.set_filter("name", project_name_var)
@@ -298,6 +305,7 @@ def products_graphql_query(fields):
298
305
  products_field.set_filter("tags", tags_var)
299
306
  products_field.set_filter("nameEx", product_name_regex_var)
300
307
  products_field.set_filter("pathEx", product_path_regex_var)
308
+ products_field.set_filter("filter", filter_var)
301
309
 
302
310
  nested_fields = fields_to_dict(set(fields))
303
311
  add_links_fields(products_field, nested_fields)
@@ -333,6 +341,7 @@ def versions_graphql_query(fields):
333
341
  )
334
342
  statuses_var = query.add_variable("versionStatuses", "[String!]")
335
343
  tags_var = query.add_variable("versionTags", "[String!]")
344
+ filter_var = query.add_variable("filter", "String!")
336
345
 
337
346
  project_field = query.add_field("project")
338
347
  project_field.set_filter("name", project_name_var)
@@ -347,6 +356,7 @@ def versions_graphql_query(fields):
347
356
  versions_field.set_filter("heroOrLatestOnly", hero_or_latest_only_var)
348
357
  versions_field.set_filter("statuses", statuses_var)
349
358
  versions_field.set_filter("tags", tags_var)
359
+ versions_field.set_filter("filter", filter_var)
350
360
 
351
361
  nested_fields = fields_to_dict(set(fields))
352
362
  add_links_fields(versions_field, nested_fields)
@@ -383,6 +393,7 @@ def representations_graphql_query(fields):
383
393
  tags_var = query.add_variable(
384
394
  "representationTags", "[String!]"
385
395
  )
396
+ filter_var = query.add_variable("filter", "String!")
386
397
 
387
398
  project_field = query.add_field("project")
388
399
  project_field.set_filter("name", project_name_var)
@@ -394,6 +405,7 @@ def representations_graphql_query(fields):
394
405
  repres_field.set_filter("hasLinks", has_links_var)
395
406
  repres_field.set_filter("statuses", statuses_var)
396
407
  repres_field.set_filter("tags", tags_var)
408
+ repres_field.set_filter("filter", filter_var)
397
409
 
398
410
  nested_fields = fields_to_dict(set(fields))
399
411
  add_links_fields(repres_field, nested_fields)
@@ -1377,9 +1377,11 @@ class ServerAPI(
1377
1377
  try:
1378
1378
  with get_func(url, **kwargs) as response:
1379
1379
  response.raise_for_status()
1380
- progress.set_content_size(
1381
- response.headers["Content-length"]
1382
- )
1380
+ if progress.get_content_size() is None:
1381
+ progress.set_content_size(
1382
+ response.headers["Content-length"]
1383
+ )
1384
+
1383
1385
  for chunk in response.iter_content(chunk_size=chunk_size):
1384
1386
  stream.write(chunk)
1385
1387
  progress.add_transferred_chunk(len(chunk))
@@ -1496,6 +1498,76 @@ class ServerAPI(
1496
1498
 
1497
1499
  return progress
1498
1500
 
1501
+ def download_project_file(
1502
+ self,
1503
+ project_name: str,
1504
+ file_id: str,
1505
+ filepath: str,
1506
+ *,
1507
+ chunk_size: Optional[int] = None,
1508
+ progress: Optional[TransferProgress] = None,
1509
+ ) -> TransferProgress:
1510
+ """Download project file to filepath.
1511
+
1512
+ Project files are usually binary files, such as images, videos,
1513
+ or other media files that can be accessed via api endpoint
1514
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1515
+
1516
+ Args:
1517
+ project_name (str): Project name.
1518
+ file_id (str): File id.
1519
+ filepath (str): Path where file will be downloaded.
1520
+ chunk_size (Optional[int]): Size of chunks that are received
1521
+ in single loop.
1522
+ progress (Optional[TransferProgress]): Object that gives ability
1523
+ to track download progress.
1524
+
1525
+ Returns:
1526
+ TransferProgress: Progress object.
1527
+
1528
+ """
1529
+ return self.download_file(
1530
+ f"api/projects/{project_name}/files/{file_id}",
1531
+ filepath,
1532
+ chunk_size=chunk_size,
1533
+ progress=progress,
1534
+ )
1535
+
1536
+ def download_project_file_to_stream(
1537
+ self,
1538
+ project_name: str,
1539
+ file_id: str,
1540
+ stream: StreamType,
1541
+ *,
1542
+ chunk_size: Optional[int] = None,
1543
+ progress: Optional[TransferProgress] = None,
1544
+ ) -> TransferProgress:
1545
+ """Download project file to a stream.
1546
+
1547
+ Project files are usually binary files, such as images, videos,
1548
+ or other media files that can be accessed via api endpoint
1549
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1550
+
1551
+ Args:
1552
+ project_name (str): Project name.
1553
+ file_id (str): File id.
1554
+ stream (StreamType): Stream where output will be stored.
1555
+ chunk_size (Optional[int]): Size of chunks that are received
1556
+ in single loop.
1557
+ progress (Optional[TransferProgress]): Object that gives ability
1558
+ to track download progress.
1559
+
1560
+ Returns:
1561
+ TransferProgress: Progress object.
1562
+
1563
+ """
1564
+ return self.download_file_to_stream(
1565
+ f"api/projects/{project_name}/files/{file_id}",
1566
+ stream,
1567
+ chunk_size=chunk_size,
1568
+ progress=progress,
1569
+ )
1570
+
1499
1571
  @staticmethod
1500
1572
  def _upload_chunks_iter(
1501
1573
  file_stream: StreamType,
@@ -1884,6 +1956,8 @@ class ServerAPI(
1884
1956
 
1885
1957
  elif entity_type == "entityList":
1886
1958
  entity_type_defaults = set(DEFAULT_ENTITY_LIST_FIELDS)
1959
+ # Attributes scope is 'list'
1960
+ entity_type = "list"
1887
1961
 
1888
1962
  else:
1889
1963
  raise ValueError(f"Unknown entity type \"{entity_type}\"")
@@ -2191,6 +2265,16 @@ class ServerAPI(
2191
2265
  )
2192
2266
  }
2193
2267
 
2268
+ def _prepare_advanced_filters(
2269
+ self, filters: Union[str, dict[str, Any], None]
2270
+ ) -> Optional[str]:
2271
+ if not filters:
2272
+ return None
2273
+
2274
+ if isinstance(filters, dict):
2275
+ return json.dumps(filters)
2276
+ return filters
2277
+
2194
2278
  def _convert_entity_data(self, entity: AnyEntityDict):
2195
2279
  if not entity or "data" not in entity:
2196
2280
  return
@@ -602,3 +602,23 @@ StreamType = Union[io.BytesIO, BinaryIO]
602
602
  class EntityListAttributeDefinitionDict(TypedDict):
603
603
  name: str
604
604
  data: dict[str, Any]
605
+
606
+
607
+ AdvancedFilterOperator = Literal["and", "or"]
608
+ AdvancedFilterConditionOperator = Literal[
609
+ "eq", "lt", "gt", "lte", "gte", "ne",
610
+ "isnull", "notnull",
611
+ "in", "notin", "contains", "excludes",
612
+ "any", "like"
613
+ ]
614
+
615
+
616
+ class AdvancedFilterConditionDict(TypedDict):
617
+ key: str
618
+ value: Any
619
+ operator: AdvancedFilterConditionOperator
620
+
621
+
622
+ class AdvancedFilterDict(TypedDict):
623
+ conditions: list[Union[AdvancedFilterConditionDict, "AdvancedFilterDict"]]
624
+ operator: AdvancedFilterOperator
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for AYON server."""
2
- __version__ = "1.2.7"
2
+ __version__ = "1.2.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayon_python_api
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ayon_python_api"
3
- version = "1.2.7"
3
+ version = "1.2.9"
4
4
  description = "AYON Python API"
5
5
  license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
File without changes