Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions polyapi/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto, TableSpecDto
from .api import render_api_function
from .server import render_server_function
from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace
from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace, to_type_module_alias
from .variables import generate_variables
from .poly_tables import generate_tables
from . import http_client
Expand Down Expand Up @@ -506,8 +506,10 @@ def add_function_file(
with open(init_path, "r", encoding='utf-8') as f:
init_content = f.read()

# Prepare new content to append to __init__.py
new_init_content = init_content + f"\n\nfrom . import {func_namespace}\n\n{func_str}"
# Import the generated type module under a private alias so PascalCase function
# names do not shadow their own module symbol in __init__.py.
type_module_alias = to_type_module_alias(function_name)
new_init_content = init_content + f"\n\nfrom . import {func_namespace} as {type_module_alias}\n\n{func_str}"

# Use temporary files for atomic writes
# Write to __init__.py atomically
Expand Down
16 changes: 11 additions & 5 deletions polyapi/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,17 @@ def print_red(s: str):
print(Fore.RED + s + Style.RESET_ALL)


def to_type_module_alias(function_name: str) -> str:
"""Return the internal alias used for a function's generated type module."""
return f"_{to_func_namespace(function_name)}_types"


def add_type_import_path(function_name: str, arg: str) -> str:
"""if not basic type, coerce to camelCase and add the import path"""
# from now, we start qualifying non-basic types :))
# e.g. Callable[[EmailAddress, Dict, Dict, Dict], None]
# becomes Callable[[Set_profile_email.EmailAddress, Dict, Dict, Dict], None]
type_module_alias = to_type_module_alias(function_name)

if arg.startswith("Callable"):
inner = arg[len("Callable["):-1] # strip outer Callable[...]
Expand All @@ -84,7 +90,7 @@ def add_type_import_path(function_name: str, arg: str) -> str:
for p in parts:
clean = p.strip("[] ")
if clean and clean not in BASIC_PYTHON_TYPES:
replacement = f"{to_func_namespace(function_name)}.{camelCase(clean)}"
replacement = f"{type_module_alias}.{camelCase(clean)}"
p = p.replace(clean, replacement)
qualified.append(p)
return "Callable[" + ",".join(qualified) + "]"
Expand All @@ -100,11 +106,11 @@ def add_type_import_path(function_name: str, arg: str) -> str:
else:
if '"' in sub:
sub = sub.replace('"', "")
return f'List["{to_func_namespace(function_name)}.{camelCase(sub)}"]'
return f'List["{type_module_alias}.{camelCase(sub)}"]'
else:
return f"List[{to_func_namespace(function_name)}.{camelCase(sub)}]"
return f"List[{type_module_alias}.{camelCase(sub)}]"

return f"{to_func_namespace(function_name)}.{camelCase(arg)}"
return f"{type_module_alias}.{camelCase(arg)}"


def get_type_and_def(
Expand Down Expand Up @@ -352,4 +358,4 @@ def return_type_already_defined_in_args(return_type_name: str, args_def: str) ->
basic_match = bool(re.search(basic_pattern, args_def, re.MULTILINE))
class_pattern = rf"^class {re.escape(return_type_name)}\(TypedDict"
class_match = bool(re.search(class_pattern, args_def, re.MULTILINE))
return basic_match or class_match
return basic_match or class_match
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "polyapi-python"
version = "0.3.14.dev4"
version = "0.3.15.dev1"
description = "The Python Client for PolyAPI, the IPaaS by Developers for Developers"
authors = [{ name = "Dan Fellin", email = "[email protected]" }]
dependencies = [
Expand Down
10 changes: 5 additions & 5 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest

from polyapi.api import render_api_function
from polyapi.utils import to_func_namespace
from polyapi.utils import to_type_module_alias

ACCUWEATHER = {
"id": "f7588018-2364-4586-b60d",
Expand Down Expand Up @@ -236,7 +236,7 @@ def test_render_function_accuweather(self):
)
self.assertIn(ACCUWEATHER["id"], func_str)
self.assertIn("locationId: int,", func_str)
self.assertIn(f"-> {to_func_namespace(name)}.{name}Response", func_str)
self.assertIn(f"-> {to_type_module_alias(name)}.{name}Response", func_str)

def test_render_function_zillow(self):
name = ZILLOW["name"]
Expand All @@ -250,7 +250,7 @@ def test_render_function_zillow(self):
)
self.assertIn(ZILLOW["id"], func_str)
self.assertIn("locationId: int,", func_str)
self.assertIn(f"-> {to_func_namespace(name)}.{name}Response", func_str)
self.assertIn(f"-> {to_type_module_alias(name)}.{name}Response", func_str)

def test_render_function_twilio_api(self):
name = TWILIO["name"]
Expand All @@ -265,7 +265,7 @@ def test_render_function_twilio_api(self):
self.assertIn(TWILIO["id"], func_str)
self.assertIn("conversationSID: str", func_str)
self.assertIn("authToken: str", func_str)
self.assertIn(f"-> {to_func_namespace(name)}.{name}Response", func_str)
self.assertIn(f"-> {to_type_module_alias(name)}.{name}Response", func_str)

def test_render_function_twilio_get_details(self):
# same test but try it as a serverFunction rather than an apiFunction
Expand All @@ -279,6 +279,6 @@ def test_render_function_twilio_get_details(self):
TWILIO_GET_DETAILS["function"]["returnType"],
)
self.assertIn(TWILIO_GET_DETAILS["id"], func_str)
self.assertIn(f"-> {to_func_namespace(name)}.{name}Response", func_str)
self.assertIn(f"-> {to_type_module_alias(name)}.{name}Response", func_str)
self.assertIn("class SubresourceUris", func_type_defs)
# self.assertIn('Required["SubresourceUris"]', func_type_defs)
48 changes: 46 additions & 2 deletions tests/test_generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import os
import shutil
import importlib.util
import tempfile
from unittest.mock import patch, MagicMock
from polyapi.utils import get_type_and_def, rewrite_reserved
from polyapi.generate import render_spec, create_empty_schemas_module, generate_functions, create_function
from polyapi.typedefs import SpecificationDto
from polyapi.utils import get_type_and_def, rewrite_reserved, to_type_module_alias
from polyapi.generate import render_spec, create_empty_schemas_module, generate_functions, create_function, add_function_file
from polyapi.poly_schemas import generate_schemas, create_schema
from polyapi.variables import generate_variables, create_variable

Expand Down Expand Up @@ -660,3 +662,45 @@ def failing_add_function_file(full_path, function_name, spec):
# The failed function directory should not exist
func_dir = os.path.join(context_dir, "failing_function")
self.assertFalse(os.path.exists(func_dir))

def test_add_function_file_aliases_pascal_case_type_module(self):
spec: SpecificationDto = {
"id": "test-func-id",
"name": "CreateAJsonPost",
"context": "test_context",
"type": "apiFunction",
"description": "Test function",
"language": "python",
"function": {
"arguments": [],
"returnType": {
"kind": "object",
"schema": {
"type": "object",
"properties": {
"id": {"type": "integer"},
},
"required": ["id"],
"title": "ReturnType",
},
},
},
}

with tempfile.TemporaryDirectory() as temp_dir:
add_function_file(temp_dir, spec["name"], spec)

init_path = os.path.join(temp_dir, "__init__.py")
with open(init_path, "r", encoding="utf-8") as file:
init_content = file.read()

type_module_alias = to_type_module_alias(spec["name"])
self.assertIn(
f"from . import {spec['name']} as {type_module_alias}",
init_content,
)
self.assertIn(f"def {spec['name']}(", init_content)
self.assertIn(
f"-> {type_module_alias}.{spec['name']}Response",
init_content,
)
6 changes: 3 additions & 3 deletions tests/test_server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest

from polyapi.utils import to_func_namespace
from polyapi.utils import to_type_module_alias

from .test_api import TWILIO
from polyapi.server import render_server_function
Expand Down Expand Up @@ -81,7 +81,7 @@ def test_render_function_twilio_server(self):
self.assertIn(TWILIO["id"], func_str)
self.assertIn("conversationSID: str", func_str)
self.assertIn("authToken: str", func_str)
self.assertIn(f"-> {to_func_namespace(name)}.ResponseType", func_str)
self.assertIn(f"-> {to_type_module_alias(name)}.ResponseType", func_str)

def test_render_function_get_products_count(self):
return_type = GET_PRODUCTS_COUNT["function"]["returnType"]
Expand Down Expand Up @@ -116,4 +116,4 @@ def test_render_function_list_recommendations(self):

# stay_date: Required[str]
# """ Required property """'''
# self.assertIn(expected_return_type, func_str)
# self.assertIn(expected_return_type, func_str)
Loading