Skip to content

Commit 2c3127a

Browse files
committed
Align API more consistently with appose-java
1 parent 6254e18 commit 2c3127a

7 files changed

Lines changed: 199 additions & 276 deletions

File tree

src/appose/__init__.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ def url(source: str) -> DynamicBuilder:
320320
return DynamicBuilder().url(source)
321321

322322

323-
def content(config_content: str) -> DynamicBuilder:
323+
def content(content: str) -> DynamicBuilder:
324324
"""
325325
Creates a DynamicBuilder from configuration content.
326326
The builder type will be auto-detected from content syntax.
@@ -331,7 +331,7 @@ def content(config_content: str) -> DynamicBuilder:
331331
Returns:
332332
A DynamicBuilder instance
333333
"""
334-
return DynamicBuilder().content(config_content)
334+
return DynamicBuilder().content(content)
335335

336336

337337
def wrap(env_dir: str | Path) -> Environment:
@@ -361,16 +361,6 @@ def wrap(env_dir: str | Path) -> Environment:
361361
return custom().wrap(env_path)
362362

363363

364-
def custom() -> SimpleBuilder:
365-
"""
366-
Creates a SimpleBuilder for custom environments without package management.
367-
368-
Returns:
369-
A SimpleBuilder instance
370-
"""
371-
return SimpleBuilder()
372-
373-
374364
def system(directory: str | Path = Path(".")) -> Environment:
375365
"""
376366
Creates a simple environment using system executables.
@@ -384,6 +374,16 @@ def system(directory: str | Path = Path(".")) -> Environment:
384374
return SimpleBuilder().base(directory).append_system_path().build()
385375

386376

377+
def custom() -> SimpleBuilder:
378+
"""
379+
Creates a SimpleBuilder for custom environments without package management.
380+
381+
Returns:
382+
A SimpleBuilder instance
383+
"""
384+
return SimpleBuilder()
385+
386+
387387
def _is_url(source: str) -> bool:
388388
"""
389389
Check if string appears to be a URL.

src/appose/builder/__init__.py

Lines changed: 94 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@
4545
from urllib.request import urlopen
4646

4747
from ..environment import Environment
48-
from ..scheme import Scheme, Schemes
48+
from ..scheme import Scheme
49+
from .. import scheme
50+
51+
52+
# Type alias for progress callback
53+
ProgressConsumer = Callable[[str, int, int], None]
4954

5055

5156
class BuildException(Exception):
@@ -67,19 +72,15 @@ def __init__(
6772
message: Error message
6873
cause: The underlying exception that caused the build to fail
6974
"""
70-
self.builder = builder
71-
if message is None and cause is not None:
75+
if message is None:
7276
noun = "build" if builder is None else f"{builder.name()} build"
7377
verb = "interrupted" if isinstance(cause, KeyboardInterrupt) else "failed"
7478
message = f"{noun} {verb}"
7579
super().__init__(message)
80+
self.builder = builder
7681
self.__cause__ = cause
7782

7883

79-
# Type alias for progress callback
80-
ProgressConsumer = Callable[[str, int, int], None]
81-
82-
8384
class Builder(Protocol):
8485
"""
8586
Base protocol for all Appose environment builders.
@@ -555,9 +556,9 @@ def _env_dir(self) -> Path:
555556
def _scheme(self) -> Scheme:
556557
"""Get the scheme, detecting from content if needed."""
557558
if self.scheme:
558-
return Schemes.from_name(self.scheme)
559+
return scheme.from_name(self.scheme)
559560
if self.source_content:
560-
return Schemes.from_content(self.source_content)
561+
return scheme.from_content(self.source_content)
561562
raise ValueError("Cannot determine scheme: neither scheme nor content is set")
562563

563564
def _create_env(
@@ -615,7 +616,7 @@ class SimpleBuilder(BaseBuilder):
615616

616617
def __init__(self):
617618
super().__init__()
618-
self.custom_bin_paths: list[str] = []
619+
self._custom_bin_paths: list[str] = []
619620

620621
def name(self) -> str:
621622
return "custom"
@@ -631,7 +632,7 @@ def bin_paths(self, *paths: str) -> SimpleBuilder:
631632
Returns:
632633
This builder instance
633634
"""
634-
self.custom_bin_paths.extend(paths)
635+
self._custom_bin_paths.extend(paths)
635636
return self
636637

637638
def append_system_path(self) -> SimpleBuilder:
@@ -642,7 +643,7 @@ def append_system_path(self) -> SimpleBuilder:
642643
This builder instance
643644
"""
644645
system_path = os.environ.get("PATH", "").split(os.pathsep)
645-
self.custom_bin_paths.extend(system_path)
646+
self._custom_bin_paths.extend(system_path)
646647
return self
647648

648649
def inherit_running_java(self) -> SimpleBuilder:
@@ -659,7 +660,7 @@ def inherit_running_java(self) -> SimpleBuilder:
659660
java_home_bin = Path(java_home) / "bin"
660661
if java_home_bin.is_dir():
661662
# Prepend to beginning of list for highest priority
662-
self.custom_bin_paths.insert(0, str(java_home_bin))
663+
self._custom_bin_paths.insert(0, str(java_home_bin))
663664
self.env_vars_dict["JAVA_HOME"] = java_home
664665
return self
665666

@@ -688,7 +689,7 @@ def build(self) -> Environment:
688689
bin_paths.append(str(bin_dir.absolute()))
689690

690691
# Add custom binary paths configured via builder methods
691-
bin_paths.extend(self.custom_bin_paths)
692+
bin_paths.extend(self._custom_bin_paths)
692693

693694
return self._create_env(base_path, bin_paths, launch_args)
694695

@@ -718,40 +719,6 @@ def _env_dir(self) -> Path:
718719
return self.env_dir if self.env_dir else Path(".")
719720

720721

721-
class SimpleBuilderFactory:
722-
"""
723-
Factory for creating SimpleBuilder instances.
724-
SimpleBuilder can wrap any directory as a fallback.
725-
"""
726-
727-
def create_builder(self) -> Builder:
728-
"""Create a SimpleBuilder instance."""
729-
return SimpleBuilder()
730-
731-
def name(self) -> str:
732-
"""Return the name of the builder."""
733-
return "custom"
734-
735-
def supports_scheme(self, scheme: str) -> bool:
736-
"""SimpleBuilder doesn't support any specific schemes."""
737-
return False
738-
739-
def priority(self) -> float:
740-
"""
741-
Lowest priority - SimpleBuilder is a fallback.
742-
Always tried last.
743-
"""
744-
return 0.0
745-
746-
def can_wrap(self, env_dir: str | Path) -> bool:
747-
"""
748-
SimpleBuilder can wrap any existing directory as a fallback.
749-
This ensures that any directory can be used as an environment.
750-
"""
751-
env_path = Path(env_dir) if isinstance(env_dir, str) else env_dir
752-
return env_path.exists() and env_path.is_dir()
753-
754-
755722
class DynamicBuilder(BaseBuilder):
756723
"""
757724
Dynamic builder that auto-detects the appropriate specific builder
@@ -838,93 +805,7 @@ def _create_builder(
838805
raise ValueError("Content and/or scheme must be provided for dynamic builder")
839806

840807

841-
# Builders utility class
842-
843-
844-
class Builders:
845-
"""
846-
Utility class for discovering and managing environment builder factories.
847-
848-
Uses entry points for plugin discovery instead of Java's ServiceLoader.
849-
"""
850-
851-
_ALL_FACTORIES: list[BuilderFactory] | None = None
852-
853-
@staticmethod
854-
def _discover_factories() -> list[BuilderFactory]:
855-
"""
856-
Discover all BuilderFactory implementations via entry points.
857-
858-
Returns:
859-
List of factories sorted by priority (highest first)
860-
"""
861-
if Builders._ALL_FACTORIES is not None:
862-
return Builders._ALL_FACTORIES
863-
864-
factories: list[BuilderFactory] = []
865-
866-
# Try modern importlib.metadata (Python 3.8+)
867-
try:
868-
from importlib.metadata import entry_points
869-
except ImportError:
870-
# Fall back to importlib_metadata for older Python
871-
try:
872-
from importlib_metadata import entry_points
873-
except ImportError:
874-
# If no entry point support, use hardcoded defaults
875-
from .pixi import PixiBuilderFactory
876-
from .mamba import MambaBuilderFactory
877-
from .uv import UvBuilderFactory
878-
879-
Builders._ALL_FACTORIES = sorted(
880-
[
881-
PixiBuilderFactory(),
882-
MambaBuilderFactory(),
883-
UvBuilderFactory(),
884-
SimpleBuilderFactory(),
885-
],
886-
key=lambda f: f.priority(),
887-
reverse=True,
888-
)
889-
return Builders._ALL_FACTORIES
890-
891-
# Load from entry points
892-
eps = entry_points()
893-
# Handle both old and new entry_points() API
894-
if hasattr(eps, "select"):
895-
# New API (Python 3.10+)
896-
builder_eps = eps.select(group="appose.builders")
897-
else:
898-
# Old API (Python 3.8-3.9)
899-
builder_eps = eps.get("appose.builders", [])
900-
901-
for ep in builder_eps:
902-
try:
903-
factory_class = ep.load()
904-
factories.append(factory_class())
905-
except Exception as e:
906-
print(
907-
f"Warning: Failed to load builder factory {ep.name}: {e}",
908-
file=sys.stderr,
909-
)
910-
911-
# If no entry points found, fall back to hardcoded defaults
912-
if not factories:
913-
from .pixi import PixiBuilderFactory
914-
from .mamba import MambaBuilderFactory
915-
from .uv import UvBuilderFactory
916-
917-
factories = [
918-
PixiBuilderFactory(),
919-
MambaBuilderFactory(),
920-
UvBuilderFactory(),
921-
SimpleBuilderFactory(),
922-
]
923-
924-
# Sort by priority (highest first)
925-
factories.sort(key=lambda f: f.priority(), reverse=True)
926-
Builders._ALL_FACTORIES = factories
927-
return factories
808+
_BUILDERS: list[BuilderFactory] | None = None
928809

929810

930811
def find_factory_by_name(name: str) -> BuilderFactory | None:
@@ -937,7 +818,7 @@ def find_factory_by_name(name: str) -> BuilderFactory | None:
937818
Returns:
938819
The factory with matching name, or None if not found
939820
"""
940-
factories = Builders._discover_factories()
821+
factories = _discover_factories()
941822
for factory in factories:
942823
if factory.name().lower() == name.lower():
943824
return factory
@@ -955,7 +836,7 @@ def find_factory_by_scheme(scheme: str) -> BuilderFactory | None:
955836
Returns:
956837
The first factory that supports the scheme, or None if none found
957838
"""
958-
factories = Builders._discover_factories()
839+
factories = _discover_factories()
959840
for factory in factories:
960841
if factory.supports_scheme(scheme):
961842
return factory
@@ -973,7 +854,7 @@ def find_factory_for_wrapping(env_dir: str | Path) -> BuilderFactory | None:
973854
Returns:
974855
The first factory that can wrap the directory, or None if none found
975856
"""
976-
factories = Builders._discover_factories()
857+
factories = _discover_factories()
977858
for factory in factories:
978859
if factory.can_wrap(env_dir):
979860
return factory
@@ -1005,3 +886,78 @@ def env_type(env_dir: str | Path) -> str | None:
1005886
"""
1006887
factory = find_factory_for_wrapping(env_dir)
1007888
return factory.name() if factory else None
889+
890+
891+
def _discover_factories() -> list[BuilderFactory]:
892+
"""
893+
Discover all BuilderFactory implementations via entry points.
894+
895+
Returns:
896+
List of factories sorted by priority (highest first)
897+
"""
898+
global _BUILDERS
899+
if _BUILDERS is not None:
900+
return _BUILDERS
901+
902+
factories: list[BuilderFactory] = []
903+
904+
# Try modern importlib.metadata (Python 3.8+)
905+
try:
906+
from importlib.metadata import entry_points
907+
except ImportError:
908+
# Fall back to importlib_metadata for older Python
909+
try:
910+
from importlib_metadata import entry_points
911+
except ImportError:
912+
# If no entry point support, use hardcoded defaults
913+
from .pixi import PixiBuilderFactory
914+
from .mamba import MambaBuilderFactory
915+
from .uv import UvBuilderFactory
916+
917+
_BUILDERS = sorted(
918+
[
919+
PixiBuilderFactory(),
920+
MambaBuilderFactory(),
921+
UvBuilderFactory(),
922+
],
923+
key=lambda f: f.priority(),
924+
reverse=True,
925+
)
926+
return _BUILDERS
927+
928+
# Load from entry points
929+
eps = entry_points()
930+
# Handle both old and new entry_points() API
931+
if hasattr(eps, "select"):
932+
# New API (Python 3.10+)
933+
builder_eps = eps.select(group="appose.builders")
934+
else:
935+
# Old API (Python 3.8-3.9)
936+
builder_eps = eps.get("appose.builders", [])
937+
938+
for ep in builder_eps:
939+
try:
940+
factory_class = ep.load()
941+
factories.append(factory_class())
942+
except Exception as e:
943+
print(
944+
f"Warning: Failed to load builder factory {ep.name}: {e}",
945+
file=sys.stderr,
946+
)
947+
948+
# If no entry points found, fall back to hardcoded defaults
949+
if not factories:
950+
from .pixi import PixiBuilderFactory
951+
from .mamba import MambaBuilderFactory
952+
from .uv import UvBuilderFactory
953+
954+
factories = [
955+
PixiBuilderFactory(),
956+
MambaBuilderFactory(),
957+
UvBuilderFactory(),
958+
]
959+
960+
# Sort by priority (highest first)
961+
factories.sort(key=lambda f: f.priority(), reverse=True)
962+
_BUILDERS = factories
963+
return factories

0 commit comments

Comments
 (0)