Skip to content

Commit 07bcdb3

Browse files
committed
Merge branch 'develop' into enhancement/254-product-base-types-add-support
2 parents cb4a9f9 + 5a0a964 commit 07bcdb3

36 files changed

Lines changed: 13707 additions & 10975 deletions

.github/workflows/python-publish.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# separate terms of service, privacy policy, and support
77
# documentation.
88

9-
name: Upload Python Package
9+
name: ⬆️ Upload Python Package
1010

1111
on:
1212
release:
@@ -17,8 +17,10 @@ permissions:
1717

1818
jobs:
1919
deploy:
20-
2120
runs-on: ubuntu-latest
21+
environment:
22+
name: pypi
23+
url: https://pypi.org/p/ayon-python-api
2224

2325
steps:
2426
- uses: actions/checkout@v3

automated_api.py

Lines changed: 132 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import typing
2121

2222
# Fake modules to avoid import errors
23-
2423
requests = type(sys)("requests")
2524
requests.__dict__["Response"] = type(
2625
"Response", (), {"__module__": "requests"}
@@ -29,9 +28,7 @@
2928
sys.modules["requests"] = requests
3029
sys.modules["unidecode"] = type(sys)("unidecode")
3130

32-
import ayon_api # noqa: E402
33-
from ayon_api.server_api import ServerAPI, _PLACEHOLDER # noqa: E402
34-
from ayon_api.utils import NOT_SET # noqa: E402
31+
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
3532

3633
EXCLUDED_METHODS = {
3734
"get_default_service_username",
@@ -120,34 +117,38 @@ def prepare_docstring(func):
120117
return f'"""{docstring}{line_char}\n"""'
121118

122119

120+
def _find_obj(obj_full, api_globals):
121+
parts = list(reversed(obj_full.split(".")))
122+
_name = None
123+
for part in parts:
124+
if _name is None:
125+
_name = part
126+
else:
127+
_name = f"{part}.{_name}"
128+
try:
129+
# Test if typehint is valid for known '_api' content
130+
exec(f"_: {_name} = None", api_globals)
131+
return _name
132+
except NameError:
133+
pass
134+
return None
135+
136+
123137
def _get_typehint(annotation, api_globals):
138+
if isinstance(annotation, str):
139+
annotation = annotation.replace("'", "")
140+
124141
if inspect.isclass(annotation):
125-
module_name_parts = list(str(annotation.__module__).split("."))
126-
module_name_parts.append(annotation.__name__)
127-
module_name_parts.reverse()
128-
options = []
129-
_name = None
130-
for name in module_name_parts:
131-
if _name is None:
132-
_name = name
133-
options.append(name)
134-
else:
135-
_name = f"{name}.{_name}"
136-
options.append(_name)
137-
138-
options.reverse()
139-
for option in options:
140-
try:
141-
# Test if typehint is valid for known '_api' content
142-
exec(f"_: {option} = None", api_globals)
143-
return option
144-
except NameError:
145-
pass
146-
147-
typehint = options[0]
148-
print("Unknown typehint:", typehint)
149-
typehint = f'"{typehint}"'
150-
return typehint
142+
module_name = str(annotation.__module__)
143+
full_name = annotation.__name__
144+
if module_name:
145+
full_name = f"{module_name}.{full_name}"
146+
obj_name = _find_obj(full_name, api_globals)
147+
if obj_name is not None:
148+
return obj_name
149+
150+
print("Unknown typehint:", full_name)
151+
return full_name
151152

152153
typehint = (
153154
str(annotation)
@@ -156,25 +157,67 @@ def _get_typehint(annotation, api_globals):
156157
full_path_regex = re.compile(
157158
r"(?P<full>(?P<name>[a-zA-Z0-9_\.]+))"
158159
)
160+
159161
for item in full_path_regex.finditer(str(typehint)):
160162
groups = item.groupdict()
161-
name = groups["name"].split(".")[-1]
163+
name = groups["name"]
164+
obj_name = _find_obj(name, api_globals)
165+
if obj_name:
166+
name = obj_name
167+
else:
168+
name = name.split(".")[-1]
162169
typehint = typehint.replace(groups["full"], name)
163170

164171
forwardref_regex = re.compile(
165172
r"(?P<full>ForwardRef\('(?P<name>[a-zA-Z0-9]+)'\))"
166173
)
167174
for item in forwardref_regex.finditer(str(typehint)):
168175
groups = item.groupdict()
169-
name = groups["name"].split(".")[-1]
170-
typehint = typehint.replace(groups["full"], f'"{name}"')
176+
name = groups["name"]
177+
obj_name = _find_obj(name, api_globals)
178+
if obj_name:
179+
name = obj_name
180+
else:
181+
name = name.split(".")[-1]
182+
typehint = typehint.replace(groups["full"], name)
171183

172184
try:
173185
# Test if typehint is valid for known '_api' content
174186
exec(f"_: {typehint} = None", api_globals)
187+
return typehint
175188
except NameError:
176189
print("Unknown typehint:", typehint)
177-
typehint = f'"{typehint}"'
190+
191+
_typehint = typehint
192+
_typehing_parents = []
193+
while True:
194+
# Too hard to manage typehints with commas
195+
if "[" not in _typehint:
196+
break
197+
198+
parts = _typehint.split("[")
199+
parent = parts.pop(0)
200+
201+
try:
202+
# Test if typehint is valid for known '_api' content
203+
exec(f"_: {parent} = None", api_globals)
204+
except NameError:
205+
_typehint = parent
206+
break
207+
208+
_typehint = "[".join(parts)[:-1]
209+
if "," in _typehint:
210+
_typing = parent
211+
break
212+
213+
_typehing_parents.append(parent)
214+
215+
if _typehing_parents:
216+
typehint = _typehint
217+
for parent in reversed(_typehing_parents):
218+
typehint = f"{parent}[{typehint}]"
219+
return typehint
220+
178221
return typehint
179222

180223

@@ -192,6 +235,9 @@ def _add_typehint(param_name, param, api_globals):
192235

193236

194237
def _kw_default_to_str(param_name, param, api_globals):
238+
from ayon_api._api_helpers.base import _PLACEHOLDER
239+
from ayon_api.utils import NOT_SET
240+
195241
if param.default is inspect.Parameter.empty:
196242
return _add_typehint(param_name, param, api_globals)
197243

@@ -249,6 +295,9 @@ def sig_params_to_str(sig, param_names, api_globals, indent=0):
249295
body_params.append(f"*{var_positional}")
250296
func_params.append(f"*{var_positional}")
251297

298+
elif kw_only:
299+
func_params.append("*")
300+
252301
for param_name, param in kw_only:
253302
body_params.append(f"{param_name}={param_name}")
254303
func_params.append(_kw_default_to_str(param_name, param, api_globals))
@@ -284,8 +333,54 @@ def sig_params_to_str(sig, param_names, api_globals, indent=0):
284333

285334

286335
def prepare_api_functions(api_globals):
336+
from ayon_api.server_api import ( # noqa: E402
337+
ServerAPI,
338+
InstallersAPI,
339+
DependencyPackagesAPI,
340+
SecretsAPI,
341+
BundlesAddonsAPI,
342+
EventsAPI,
343+
AttributesAPI,
344+
ProjectsAPI,
345+
FoldersAPI,
346+
TasksAPI,
347+
ProductsAPI,
348+
VersionsAPI,
349+
RepresentationsAPI,
350+
WorkfilesAPI,
351+
ThumbnailsAPI,
352+
ActivitiesAPI,
353+
ActionsAPI,
354+
LinksAPI,
355+
ListsAPI,
356+
)
357+
287358
functions = []
288-
for attr_name, attr in ServerAPI.__dict__.items():
359+
_items = list(ServerAPI.__dict__.items())
360+
_items.extend(InstallersAPI.__dict__.items())
361+
_items.extend(DependencyPackagesAPI.__dict__.items())
362+
_items.extend(SecretsAPI.__dict__.items())
363+
_items.extend(ActionsAPI.__dict__.items())
364+
_items.extend(ActivitiesAPI.__dict__.items())
365+
_items.extend(BundlesAddonsAPI.__dict__.items())
366+
_items.extend(EventsAPI.__dict__.items())
367+
_items.extend(AttributesAPI.__dict__.items())
368+
_items.extend(ProjectsAPI.__dict__.items())
369+
_items.extend(FoldersAPI.__dict__.items())
370+
_items.extend(TasksAPI.__dict__.items())
371+
_items.extend(ProductsAPI.__dict__.items())
372+
_items.extend(VersionsAPI.__dict__.items())
373+
_items.extend(RepresentationsAPI.__dict__.items())
374+
_items.extend(WorkfilesAPI.__dict__.items())
375+
_items.extend(LinksAPI.__dict__.items())
376+
_items.extend(ListsAPI.__dict__.items())
377+
_items.extend(ThumbnailsAPI.__dict__.items())
378+
379+
processed = set()
380+
for attr_name, attr in _items:
381+
if attr_name in processed:
382+
continue
383+
processed.add(attr_name)
289384
if (
290385
attr_name.startswith("_")
291386
or attr_name in EXCLUDED_METHODS
@@ -323,10 +418,7 @@ def prepare_api_functions(api_globals):
323418
def main():
324419
print("Creating public API functions based on ServerAPI methods")
325420
# TODO order methods in some order
326-
dirpath = os.path.dirname(os.path.dirname(
327-
os.path.abspath(ayon_api.__file__)
328-
))
329-
ayon_api_root = os.path.join(dirpath, "ayon_api")
421+
ayon_api_root = os.path.join(CURRENT_DIR, "ayon_api")
330422
init_filepath = os.path.join(ayon_api_root, "__init__.py")
331423
api_filepath = os.path.join(ayon_api_root, "_api.py")
332424

@@ -350,15 +442,13 @@ def main():
350442
# Read content of first part of `_api.py` to get global variables
351443
# - disable type checking so imports done only during typechecking are
352444
# not executed
353-
old_value = typing.TYPE_CHECKING
354445
typing.TYPE_CHECKING = False
355446
api_globals = {"__name__": "ayon_api._api"}
356447
exec(parts[0], api_globals)
448+
357449
for attr_name in dir(__builtins__):
358450
api_globals[attr_name] = getattr(__builtins__, attr_name)
359-
typing.TYPE_CHECKING = old_value
360451

361-
# print(api_globals)
362452
print("(3/5) Preparing functions body based on 'ServerAPI' class")
363453
result = prepare_api_functions(api_globals)
364454

0 commit comments

Comments
 (0)