polyapi-python 0.3.19.dev4__tar.gz → 0.3.20.dev1__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 (56) hide show
  1. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/PKG-INFO +1 -1
  2. polyapi_python-0.3.20.dev1/polyapi/ai_function.py +96 -0
  3. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/generate.py +12 -1
  4. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/typedefs.py +1 -1
  5. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/utils.py +22 -4
  6. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi_python.egg-info/PKG-INFO +1 -1
  7. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi_python.egg-info/SOURCES.txt +1 -0
  8. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/pyproject.toml +1 -1
  9. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_generate.py +193 -0
  10. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/LICENSE +0 -0
  11. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/README.md +0 -0
  12. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/__init__.py +0 -0
  13. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/__main__.py +0 -0
  14. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/api.py +0 -0
  15. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/auth.py +0 -0
  16. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/cli.py +0 -0
  17. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/cli_constants.py +0 -0
  18. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/client.py +0 -0
  19. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/config.py +0 -0
  20. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/constants.py +0 -0
  21. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/deployables.py +0 -0
  22. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/error_handler.py +0 -0
  23. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/exceptions.py +0 -0
  24. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/execute.py +0 -0
  25. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/function_cli.py +0 -0
  26. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/http_client.py +0 -0
  27. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/parser.py +0 -0
  28. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/poly_schemas.py +0 -0
  29. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/poly_tables.py +0 -0
  30. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/prepare.py +0 -0
  31. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/py.typed +0 -0
  32. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/rendered_spec.py +0 -0
  33. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/schema.py +0 -0
  34. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/server.py +0 -0
  35. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/sync.py +0 -0
  36. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/variables.py +0 -0
  37. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi/webhook.py +0 -0
  38. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi_python.egg-info/dependency_links.txt +0 -0
  39. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi_python.egg-info/requires.txt +0 -0
  40. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/polyapi_python.egg-info/top_level.txt +0 -0
  41. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/setup.cfg +0 -0
  42. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_api.py +0 -0
  43. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_async_proof.py +0 -0
  44. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_auth.py +0 -0
  45. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_cli.py +0 -0
  46. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_dependency_pins.py +0 -0
  47. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_deployables.py +0 -0
  48. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_parser.py +0 -0
  49. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_poly_custom.py +0 -0
  50. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_poly_schemas.py +0 -0
  51. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_rendered_spec.py +0 -0
  52. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_schema.py +0 -0
  53. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_server.py +0 -0
  54. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_tabi.py +0 -0
  55. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_utils.py +0 -0
  56. {polyapi_python-0.3.19.dev4 → polyapi_python-0.3.20.dev1}/tests/test_variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyapi-python
3
- Version: 0.3.19.dev4
3
+ Version: 0.3.20.dev1
4
4
  Summary: The Python SDK for the integration platform PolyAPI
5
5
  Author-email: PolyAPI <support@polyapi.io>
6
6
  License: MIT License
@@ -0,0 +1,96 @@
1
+ from typing import Any, Dict, List, Tuple, cast
2
+
3
+ from polyapi.typedefs import PropertySpecification, PropertyType
4
+ from polyapi.utils import add_type_import_path, parse_arguments, get_type_and_def, return_type_already_defined_in_args, rewrite_arg_name
5
+
6
+ AI_DEFS_TEMPLATE = """
7
+ from typing import List, Dict, Any, TypedDict, Callable
8
+ {args_def}
9
+ {return_type_def}
10
+ """
11
+
12
+ AI_FUNCTION_TEMPLATE = """
13
+ def {function_name}(
14
+ {args}
15
+ ) -> {return_type_name}:
16
+ \"""{function_description}
17
+
18
+ Function ID: {function_id}
19
+ \"""
20
+ resp = execute("{function_type}", "{function_id}", {data})
21
+ try:
22
+ return {return_action}
23
+ except:
24
+ return resp.text # type: ignore # fallback for debugging
25
+
26
+
27
+ async def {function_name}_async(
28
+ {args}
29
+ ) -> {return_type_name}:
30
+ \"""{function_description}
31
+
32
+ Function ID: {function_id}
33
+ \"""
34
+ resp = await execute_async("{function_type}", "{function_id}", {data})
35
+ try:
36
+ return {return_action}
37
+ except:
38
+ return resp.text # type: ignore # fallback for debugging
39
+
40
+
41
+ """
42
+
43
+
44
+ def render_ai_function(
45
+ function_type: str,
46
+ function_name: str,
47
+ function_id: str,
48
+ function_description: str,
49
+ arguments: List[PropertySpecification],
50
+ return_type: Dict[str, Any],
51
+ ) -> Tuple[str, str]:
52
+ arg_names = [a["name"] for a in arguments]
53
+ args, args_def = parse_arguments(function_name, arguments)
54
+ return_type_name, return_type_def = get_type_and_def(cast(PropertyType, return_type), "ReturnType")
55
+
56
+ if return_type_def and return_type_already_defined_in_args(return_type_name, args_def):
57
+ return_type_def = ""
58
+
59
+ data = "{" + ", ".join([f"'{arg}': {rewrite_arg_name(arg)}" for arg in arg_names]) + "}"
60
+ func_type_defs = AI_DEFS_TEMPLATE.format(
61
+ args_def=args_def,
62
+ return_type_def=return_type_def,
63
+ )
64
+ func_str = AI_FUNCTION_TEMPLATE.format(
65
+ return_type_name=_normalize_return_type_for_annotation(function_name, return_type_name),
66
+ function_type="ai",
67
+ function_name=function_name,
68
+ function_id=function_id,
69
+ function_description=function_description.replace('"', "'"),
70
+ args=args,
71
+ return_action=_get_ai_return_action(return_type_name),
72
+ data=data,
73
+ )
74
+ return func_str, func_type_defs
75
+
76
+
77
+ def _normalize_return_type_for_annotation(function_name: str, return_type_name: str) -> str:
78
+ if return_type_name == "ReturnType":
79
+ return "ReturnType"
80
+ return add_type_import_path(function_name, return_type_name)
81
+
82
+
83
+ def _get_ai_return_action(return_type_name: str) -> str:
84
+ normalized_type = return_type_name.replace(" ", "")
85
+
86
+ if normalized_type in {"str", "Optional[str]"}:
87
+ return_action = "resp.text"
88
+ elif "|" in normalized_type:
89
+ union_parts = {part for part in normalized_type.split("|") if part}
90
+ if union_parts == {"str", "None"}:
91
+ return_action = "resp.text"
92
+ else:
93
+ return_action = "resp.json()"
94
+ else:
95
+ return_action = "resp.json()"
96
+ return return_action
@@ -17,6 +17,7 @@ from .webhook import render_webhook_handle
17
17
  from .typedefs import PropertySpecification, SchemaSpecDto, SpecificationDto, VariableSpecDto, TableSpecDto
18
18
  from .api import render_api_function
19
19
  from .server import render_server_function
20
+ from .ai_function import render_ai_function
20
21
  from .utils import add_import_to_init, get_auth_headers, init_the_init, print_green, to_func_namespace, to_type_module_alias
21
22
  from .variables import generate_variables
22
23
  from .poly_tables import generate_tables
@@ -29,6 +30,7 @@ SUPPORTED_FUNCTION_TYPES = {
29
30
  "customFunction", # client function - this is badly named in /specs atm
30
31
  "serverFunction",
31
32
  "webhookHandle",
33
+ "aiFunction",
32
34
  }
33
35
 
34
36
  SUPPORTED_TYPES = SUPPORTED_FUNCTION_TYPES | {"serverVariable", "schema", "snippet", "table"}
@@ -98,7 +100,7 @@ def resolve_poly_refs(obj, schema_index):
98
100
  def replace_poly_refs_in_functions(specs: List[SpecificationDto], schema_index):
99
101
  spec_idxs_to_remove = []
100
102
  for idx, spec in enumerate(specs):
101
- if spec.get("type") in ("apiFunction", "customFunction", "serverFunction"):
103
+ if spec.get("type") in ("apiFunction", "customFunction", "serverFunction", "aiFunction"):
102
104
  func = spec.get("function")
103
105
  if func:
104
106
  try:
@@ -501,6 +503,15 @@ def render_spec(spec: SpecificationDto) -> Tuple[str, str]:
501
503
  arguments,
502
504
  return_type,
503
505
  )
506
+ elif function_type == "aiFunction":
507
+ func_str, func_type_defs = render_ai_function(
508
+ function_type,
509
+ function_name,
510
+ function_id,
511
+ function_description,
512
+ arguments,
513
+ return_type,
514
+ )
504
515
 
505
516
  if X_POLY_REF_WARNING in func_type_defs:
506
517
  # this indicates that jsonschema_gentypes has detected an x-poly-ref
@@ -35,7 +35,7 @@ class SpecificationDto(TypedDict):
35
35
  description: str
36
36
  # function is none (or function key not present) if this is actually VariableSpecDto
37
37
  function: NotRequired[FunctionSpecification | None]
38
- type: Literal['apiFunction', 'customFunction', 'serverFunction', 'authFunction', 'webhookHandle', 'serverVariable', 'table']
38
+ type: Literal['apiFunction', 'customFunction', 'serverFunction', 'authFunction', 'webhookHandle', 'serverVariable', 'table', 'aiFunction']
39
39
  code: NotRequired[str]
40
40
  language: str
41
41
 
@@ -1,7 +1,7 @@
1
1
  import keyword
2
2
  import re
3
3
  import os
4
- from urllib.parse import urlparse
4
+ from urllib.parse import unquote, urlparse
5
5
  from typing import Tuple, List, Optional, cast
6
6
  from colorama import Fore, Style
7
7
  from polyapi.constants import BASIC_PYTHON_TYPES
@@ -299,11 +299,29 @@ def _maybe_add_fallback_schema_name(a: PropertySpecification):
299
299
  # Handle cases where type might be missing
300
300
  if not a.get("type"):
301
301
  return
302
-
302
+
303
303
  if a["type"].get("kind") == "object" and a["type"].get("schema"):
304
304
  schema = a["type"].get("schema", {})
305
- if not schema.get("title") and not schema.get("name") and a.get("name"):
306
- schema["title"] = a["name"].title()
305
+ title = schema.get("title") or schema.get("name")
306
+ if not title and a.get("name"):
307
+ title = a["name"].title()
308
+ schema["title"] = title
309
+
310
+ reference = schema.get("$ref")
311
+ definitions = schema.get("definitions")
312
+ reference_prefix = "#/definitions/"
313
+ if not isinstance(title, str):
314
+ return
315
+ if not isinstance(reference, str) or not reference.startswith(reference_prefix):
316
+ return
317
+ if not isinstance(definitions, dict):
318
+ return
319
+
320
+ definition_name = unquote(reference[len(reference_prefix):])
321
+ definition_name = definition_name.replace("~1", "/").replace("~0", "~")
322
+ definition = definitions.get(definition_name)
323
+ if isinstance(definition, dict):
324
+ definition.setdefault("title", title)
307
325
 
308
326
 
309
327
  def _clean_description(text: str) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyapi-python
3
- Version: 0.3.19.dev4
3
+ Version: 0.3.20.dev1
4
4
  Summary: The Python SDK for the integration platform PolyAPI
5
5
  Author-email: PolyAPI <support@polyapi.io>
6
6
  License: MIT License
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  polyapi/__init__.py
5
5
  polyapi/__main__.py
6
+ polyapi/ai_function.py
6
7
  polyapi/api.py
7
8
  polyapi/auth.py
8
9
  polyapi/cli.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "polyapi-python"
7
- version = "0.3.19.dev4"
7
+ version = "0.3.20.dev1"
8
8
  description = "The Python SDK for the integration platform PolyAPI"
9
9
  authors = [{ name = "PolyAPI", email = "support@polyapi.io" }]
10
10
  dependencies = [
@@ -1,8 +1,12 @@
1
1
  import unittest
2
+ import copy
2
3
  import os
3
4
  import shutil
4
5
  import importlib.util
6
+ import subprocess
7
+ import sys
5
8
  import tempfile
9
+ from typing import cast
6
10
  from unittest.mock import patch, MagicMock
7
11
  from polyapi.typedefs import SpecificationDto
8
12
  from polyapi.utils import get_type_and_def, rewrite_reserved, to_type_module_alias
@@ -103,6 +107,144 @@ MINIMAL_FUNCTION_SPEC = {
103
107
  }
104
108
  }
105
109
 
110
+ MEWS_HEADERS_SPECS = [
111
+ (
112
+ {
113
+ "id": "6979c3e1-4d59-4867-91b7-4d394fe9e34a",
114
+ "type": "serverFunction",
115
+ "context": "gha.mews.v1",
116
+ "name": "generalEvents",
117
+ "description": "Sanitized Mews headers regression fixture",
118
+ "language": "javascript",
119
+ "function": {
120
+ "arguments": [
121
+ {
122
+ "name": "headers",
123
+ "description": "",
124
+ "required": True,
125
+ "type": {
126
+ "kind": "object",
127
+ "schema": {
128
+ "$ref": "#/definitions/Gha.Mews.V1.GeneralEvents%24Headers.Argument",
129
+ "$schema": "http://json-schema.org/draft-07/schema#",
130
+ "definitions": {
131
+ "Gha.Mews.V1.GeneralEvents$Headers.Argument": {
132
+ "type": "object",
133
+ "additionalProperties": {},
134
+ },
135
+ },
136
+ },
137
+ },
138
+ },
139
+ ],
140
+ "returnType": {"kind": "void"},
141
+ },
142
+ },
143
+ "Headers",
144
+ "Headers = dict[str, str | int | float | dict[str, Any] | list[Any] | bool | None]",
145
+ ),
146
+ (
147
+ {
148
+ "id": "d8230ba3-b1da-4d2f-8e47-97114d8a3f37",
149
+ "type": "serverFunction",
150
+ "context": "gha.mews.v1",
151
+ "name": "membersSearch",
152
+ "description": "Sanitized Mews headers regression fixture",
153
+ "language": "javascript",
154
+ "function": {
155
+ "arguments": [
156
+ {
157
+ "name": "_headers",
158
+ "description": "",
159
+ "required": True,
160
+ "type": {
161
+ "kind": "object",
162
+ "schema": {
163
+ "$ref": "#/definitions/Gha.Mews.V1.MembersSearch%24Headers.Argument",
164
+ "$schema": "http://json-schema.org/draft-07/schema#",
165
+ "definitions": {
166
+ "Gha.Mews.V1.MembersSearch$Headers.Argument": {
167
+ "type": "object",
168
+ "additionalProperties": {},
169
+ },
170
+ },
171
+ },
172
+ },
173
+ },
174
+ ],
175
+ "returnType": {"kind": "void"},
176
+ },
177
+ },
178
+ "_Headers",
179
+ "_Headers = dict[str, str | int | float | dict[str, Any] | list[Any] | bool | None]",
180
+ ),
181
+ (
182
+ {
183
+ "id": "3037a373-291f-4b87-9966-b3d125ffee26",
184
+ "type": "serverFunction",
185
+ "context": "gha.mews.v1",
186
+ "name": "membershipsCreate",
187
+ "description": "Sanitized Mews headers regression fixture",
188
+ "language": "javascript",
189
+ "function": {
190
+ "arguments": [
191
+ {
192
+ "name": "_headers",
193
+ "description": "",
194
+ "required": True,
195
+ "type": {
196
+ "kind": "object",
197
+ "schema": {
198
+ "$ref": "#/definitions/Gha.Mews.V1.MembershipsCreate%24Headers.Argument",
199
+ "$schema": "http://json-schema.org/draft-07/schema#",
200
+ "definitions": {
201
+ "Gha.Mews.V1.MembershipsCreate$Headers.Argument": {
202
+ "type": "object",
203
+ "additionalProperties": {"type": "string"},
204
+ },
205
+ },
206
+ },
207
+ },
208
+ },
209
+ ],
210
+ "returnType": {"kind": "void"},
211
+ },
212
+ },
213
+ "_Headers",
214
+ "_Headers = dict[str, str]",
215
+ ),
216
+ (
217
+ {
218
+ "id": "63c65574-26bf-4b10-a301-06ce02dad548",
219
+ "type": "serverFunction",
220
+ "context": "gha.mews.v1",
221
+ "name": "membershipsList",
222
+ "description": "Sanitized Mews headers regression fixture",
223
+ "language": "javascript",
224
+ "function": {
225
+ "arguments": [
226
+ {
227
+ "name": "_headers",
228
+ "description": "",
229
+ "required": True,
230
+ "type": {
231
+ "kind": "object",
232
+ "schema": {
233
+ "$schema": "http://json-schema.org/draft-07/schema#",
234
+ "type": "object",
235
+ "additionalProperties": {},
236
+ },
237
+ },
238
+ },
239
+ ],
240
+ "returnType": {"kind": "void"},
241
+ },
242
+ },
243
+ "_Headers",
244
+ "_Headers = dict[str, str | int | float | dict[str, Any] | list[Any] | bool | None]",
245
+ ),
246
+ ]
247
+
106
248
 
107
249
  class T(unittest.TestCase):
108
250
  def test_get_type_and_def(self):
@@ -704,3 +846,54 @@ def test_nested_function() -> schemas.api.v1.user.profile:
704
846
  f"-> {type_module_alias}.{spec['name']}Response",
705
847
  init_content,
706
848
  )
849
+
850
+ def test_mews_headers_schemas_generate_referenced_type_aliases(self):
851
+ for fixture, expected_symbol, expected_definition in MEWS_HEADERS_SPECS:
852
+ with self.subTest(function=fixture["name"]):
853
+ spec = copy.deepcopy(fixture)
854
+ func_str, func_type_defs = render_spec(cast(SpecificationDto, spec))
855
+ type_module_alias = to_type_module_alias(spec["name"])
856
+
857
+ self.assertIn(
858
+ f"{spec['function']['arguments'][0]['name']}: "
859
+ f"{type_module_alias}.{expected_symbol}",
860
+ func_str,
861
+ )
862
+ self.assertIn(expected_definition, func_type_defs)
863
+
864
+ def test_mews_headers_reference_preserves_explicit_definition_title(self):
865
+ spec = copy.deepcopy(MEWS_HEADERS_SPECS[1][0])
866
+ schema = spec["function"]["arguments"][0]["type"]["schema"]
867
+ definition = schema["definitions"]["Gha.Mews.V1.MembersSearch$Headers.Argument"]
868
+ definition["title"] = "ExistingTitle"
869
+
870
+ render_spec(cast(SpecificationDto, spec))
871
+
872
+ self.assertEqual(definition["title"], "ExistingTitle")
873
+
874
+ def test_mews_headers_generated_package_imports_in_fresh_process(self):
875
+ with tempfile.TemporaryDirectory() as temp_dir:
876
+ package_name = "generated_mews"
877
+ package_dir = os.path.join(temp_dir, package_name)
878
+ os.makedirs(package_dir)
879
+
880
+ for fixture, _, _ in MEWS_HEADERS_SPECS:
881
+ spec = copy.deepcopy(fixture)
882
+ add_function_file(
883
+ package_dir,
884
+ spec["name"],
885
+ cast(SpecificationDto, spec),
886
+ )
887
+
888
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
889
+ result = subprocess.run(
890
+ [sys.executable, "-c", f"import {package_name}"],
891
+ cwd=temp_dir,
892
+ env={
893
+ "PYTHONPATH": os.pathsep.join([temp_dir, repo_root]),
894
+ },
895
+ capture_output=True,
896
+ text=True,
897
+ )
898
+
899
+ self.assertEqual(result.returncode, 0, result.stderr)