Skip to content

Commit c1d69ca

Browse files
authored
Merge pull request #31 from DavidCEllis/fix_import_cycle
Fix import cycle - test for import cycle in lazy imports
2 parents 9719574 + 90eff17 commit c1d69ca

File tree

3 files changed

+69
-26
lines changed

3 files changed

+69
-26
lines changed

src/ducktools/env/_lazy_imports.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,5 @@
8383
"tomli",
8484
"tomllib",
8585
),
86-
87-
# Internal Imports
88-
FromImport(
89-
".bundle",
90-
"create_bundle"
91-
),
92-
FromImport(
93-
".scripts.get_pip",
94-
"retrieve_pip"
95-
),
96-
MultiFromImport(
97-
".scripts.get_uv",
98-
["retrieve_uv", "get_available_pythons", "install_uv_python"]
99-
),
100-
MultiFromImport(
101-
".scripts.create_zipapp",
102-
["build_env_folder", "build_zipapp"]
103-
),
10486
],
105-
globs=globals(),
10687
)

src/ducktools/env/manager.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import os.path
2626

2727
from ducktools.classbuilder.prefab import Prefab, attribute
28+
from ducktools.lazyimporter import LazyImporter, FromImport, MultiFromImport
2829

2930
from . import (
3031
FOLDER_ENVVAR,
@@ -52,6 +53,23 @@
5253
from ._logger import log
5354

5455

56+
_laz_internal = LazyImporter(
57+
[
58+
FromImport(".bundle", "create_bundle"),
59+
FromImport(".scripts.get_pip", "retrieve_pip"),
60+
MultiFromImport(
61+
".scripts.get_uv",
62+
["retrieve_uv", "get_available_pythons", "install_uv_python"]
63+
),
64+
MultiFromImport(
65+
".scripts.create_zipapp",
66+
["build_env_folder", "build_zipapp"]
67+
),
68+
],
69+
globs=globals(),
70+
)
71+
72+
5573
class Manager(Prefab):
5674
project_name: str = PROJECT_NAME
5775
config: Config = None
@@ -110,11 +128,11 @@ def install_outdated(self):
110128

111129
# Ducktools build commands
112130
def retrieve_pip(self) -> str:
113-
return _laz.retrieve_pip(paths=self.paths)
131+
return _laz_internal.retrieve_pip(paths=self.paths)
114132

115133
def retrieve_uv(self, required=False) -> str | None:
116134
if self.config.use_uv or required:
117-
uv_path = _laz.retrieve_uv(paths=self.paths)
135+
uv_path = _laz_internal.retrieve_uv(paths=self.paths)
118136
else:
119137
uv_path = None
120138

@@ -142,12 +160,12 @@ def _get_python_install(self, spec: EnvironmentSpec):
142160
else:
143161
# If no Python was matched try to install a matching python from UV
144162
if (uv_path := self.retrieve_uv()) and self.config.uv_install_python:
145-
uv_pythons = _laz.get_available_pythons(uv_path)
163+
uv_pythons = _laz_internal.get_available_pythons(uv_path)
146164
matched_python = False
147165
for ver in uv_pythons:
148166
if spec.details.requires_python_spec.contains(ver):
149167
# Install matching python
150-
_laz.install_uv_python(
168+
_laz_internal.install_uv_python(
151169
uv_path=uv_path,
152170
version_str=ver,
153171
)
@@ -182,7 +200,7 @@ def build_env_folder(self, clear_old_builds=True) -> None:
182200
# build-env-folder installs into a target directory
183201
# instead of using a venv
184202
base_command = [sys.executable, self.retrieve_pip(), "--disable-pip-version-check"]
185-
_laz.build_env_folder(
203+
_laz_internal.build_env_folder(
186204
paths=self.paths,
187205
install_base_command=base_command,
188206
clear_old_builds=clear_old_builds,
@@ -191,7 +209,7 @@ def build_env_folder(self, clear_old_builds=True) -> None:
191209
def build_zipapp(self, clear_old_builds=True) -> None:
192210
"""Build the ducktools.pyz zipapp"""
193211
base_command = [sys.executable, self.retrieve_pip(), "--disable-pip-version-check"]
194-
_laz.build_zipapp(
212+
_laz_internal.build_zipapp(
195213
paths=self.paths,
196214
install_base_command=base_command,
197215
clear_old_builds=clear_old_builds,
@@ -423,7 +441,7 @@ def create_bundle(
423441
generate_lock=generate_lock,
424442
)
425443

426-
_laz.create_bundle(
444+
_laz_internal.create_bundle(
427445
spec=spec,
428446
output_file=output_file,
429447
paths=self.paths,

tests/test_no_import_cycle.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# ducktools.env
2+
# MIT License
3+
#
4+
# Copyright (c) 2024 David C Ellis
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in all
14+
# copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
import os
24+
import sys
25+
26+
import subprocess
27+
28+
import ducktools.env
29+
30+
31+
def test_no_import_cycle():
32+
env_vars = os.environ.copy()
33+
env_vars["DUCKTOOLS_EAGER_IMPORT"] = "True"
34+
35+
# Just run the version command
36+
output = subprocess.run(
37+
[sys.executable, "-m", "ducktools.env", "--version"],
38+
capture_output=True,
39+
text=True,
40+
env=env_vars,
41+
check=True,
42+
)
43+
44+
assert output.stdout.strip() == ducktools.env.__version__.strip()

0 commit comments

Comments
 (0)