Skip to content

Commit f8b7a8c

Browse files
authored
ProjectBuilder + Direct Data (#36)
1 parent c6e57f2 commit f8b7a8c

File tree

5 files changed

+301
-177
lines changed

5 files changed

+301
-177
lines changed

cppython/console.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
"""
44

55
from pathlib import Path
6-
from typing import Type
6+
from typing import Any, Type
77
from xmlrpc.client import Boolean
88

99
import click
1010
import tomlkit
11-
from cppython_core.schema import GeneratorDataType, Interface, PyProject
11+
from cppython_core.schema import GeneratorDataType, Interface
1212

1313
from cppython.project import Project, ProjectConfiguration
1414

1515

16-
def _create_pyproject():
16+
def _create_pyproject() -> dict[str, Any]:
1717

1818
# Search for a path upward
1919
path = Path.cwd()
@@ -30,7 +30,7 @@ def _create_pyproject():
3030
data = tomlkit.loads(path.read_text(encoding="utf-8"))
3131

3232
# Interpret and validate data
33-
return PyProject(**data)
33+
return data
3434

3535

3636
class Config:
@@ -39,15 +39,15 @@ class Config:
3939
"""
4040

4141
def __init__(self):
42-
self.pyproject = _create_pyproject()
42+
self.pyproject_data = _create_pyproject()
4343
self.interface = ConsoleInterface()
4444
self.configuration = ProjectConfiguration()
4545

4646
def create_project(self) -> Project:
4747
"""
4848
TODO
4949
"""
50-
return Project(self.configuration, self.interface, self.pyproject)
50+
return Project(self.configuration, self.interface, self.pyproject_data)
5151

5252

5353
pass_config = click.make_pass_decorator(Config, ensure=True)

cppython/project.py

Lines changed: 101 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44

55
from dataclasses import dataclass
66
from importlib import metadata
7-
from typing import Callable, Optional, Type, TypeVar
7+
from typing import Any, Type, TypeVar
88
from xmlrpc.client import Boolean
99

10-
from cppython_core.exceptions import ConfigError
11-
from cppython_core.schema import API, Generator, Interface, Plugin, PyProject
10+
from cppython_core.schema import (
11+
API,
12+
CPPythonData,
13+
Generator,
14+
Interface,
15+
Plugin,
16+
PyProject,
17+
ToolData,
18+
)
19+
from pydantic import create_model
1220

1321

1422
@dataclass
@@ -20,89 +28,138 @@ class ProjectConfiguration:
2028
verbose: Boolean = False
2129

2230

31+
class ProjectBuilder:
32+
"""
33+
TODO
34+
"""
35+
36+
def __init__(self, configuration: ProjectConfiguration) -> None:
37+
self.configuration = configuration
38+
39+
def gather_plugins(self, plugin_type: Type[Plugin]) -> list[Type[Plugin]]:
40+
"""
41+
TODO
42+
"""
43+
plugins = []
44+
entry_points = metadata.entry_points(group=f"cppython.{plugin_type.plugin_group()}")
45+
46+
for entry_point in entry_points:
47+
loaded_plugin_type = entry_point.load()
48+
if issubclass(loaded_plugin_type, plugin_type) & (loaded_plugin_type is not plugin_type):
49+
plugins.append(loaded_plugin_type)
50+
51+
return plugins
52+
53+
def generate_model(self, plugins: list[Type[Plugin]]) -> Type[PyProject]:
54+
"""
55+
TODO
56+
"""
57+
plugin_fields = {}
58+
for plugin_type in plugins:
59+
plugin_fields[plugin_type.name()] = plugin_type.data_type()
60+
61+
ExtendedCPPythonData = create_model(
62+
"ExtendedCPPythonData",
63+
**plugin_fields,
64+
__base__=CPPythonData,
65+
)
66+
67+
ExtendedToolData = create_model(
68+
"ToolData",
69+
cppython=ExtendedCPPythonData,
70+
__base__=ToolData,
71+
)
72+
73+
return create_model(
74+
"PyProject",
75+
tool=ExtendedToolData,
76+
__base__=PyProject,
77+
)
78+
79+
2380
class Project(API):
2481
"""
2582
The object constructed at each entry_point
2683
"""
2784

28-
def __init__(self, configuration: ProjectConfiguration, interface: Interface, pyproject: PyProject) -> None:
85+
def __init__(
86+
self, configuration: ProjectConfiguration, interface: Interface, pyproject_data: dict[str, Any]
87+
) -> None:
2988

3089
self.enabled = False
31-
self.verbose = configuration.verbose
90+
self.configuration = configuration
3291

33-
if self.verbose:
92+
if self.configuration.verbose:
3493
interface.print("Starting CPPython project initialization")
3594

95+
builder = ProjectBuilder(self.configuration)
96+
plugins = builder.gather_plugins(Generator)
97+
98+
if not plugins:
99+
if self.configuration.verbose:
100+
interface.print("No generator plugin was found.")
101+
return
102+
103+
ExtendedPyProject = builder.generate_model(plugins)
104+
pyproject = ExtendedPyProject(**pyproject_data)
105+
36106
if pyproject.tool is None:
37-
if self.verbose:
107+
if self.configuration.verbose:
38108
interface.print("Table [tool] is not defined")
39109
return
40110

41111
if pyproject.tool.cppython is None:
42-
if self.verbose:
112+
if self.configuration.verbose:
43113
interface.print("Table [tool.cppython] is not defined")
44114
return
45115

46116
self.enabled = True
47117

48118
self._interface = interface
49119

50-
PluginType = TypeVar("PluginType", bound=Type[Plugin])
51-
52-
def find_plugin_type(plugin_type: PluginType, condition: Callable[[str], bool]) -> Optional[PluginType]:
53-
"""
54-
Finds the first plugin that satisfies the given condition
55-
"""
56-
57-
entry_points = metadata.entry_points(group=f"cppython.{plugin_type.plugin_group()}")
58-
59-
for entry_point in entry_points:
60-
loaded_plugin_type = entry_point.load()
61-
if issubclass(loaded_plugin_type, plugin_type) & (loaded_plugin_type is not plugin_type):
62-
if condition(loaded_plugin_type.name()):
63-
return loaded_plugin_type
120+
self._generators = []
121+
for plugin_type in plugins:
122+
self._generators.append(plugin_type(pyproject))
64123

65-
return None
66-
67-
plugin_type = find_plugin_type(Generator, lambda name: name == pyproject.tool.cppython.generator)
68-
69-
if plugin_type is None:
70-
raise ConfigError(f"No generator plugin with the name '{pyproject.tool.cppython.generator}' was found.")
71-
72-
generator_data = interface.read_generator_data(plugin_type.data_type())
73-
self._generator = plugin_type(pyproject, generator_data)
74-
75-
if self.verbose:
124+
if self.configuration.verbose:
76125
interface.print("CPPython project initialized")
77126

78127
def download(self):
79128
"""
80129
Download the generator tooling if required
81130
"""
82-
if not self._generator.generator_downloaded():
83-
self._interface.print(f"Downloading the {self._generator.name()} tool")
131+
for generator in self._generators:
84132

85-
# TODO: Make async with progress bar
86-
self._generator.download_generator()
87-
self._interface.print("Download complete")
133+
if not generator.generator_downloaded():
134+
self._interface.print(f"Downloading the {generator.name()} tool")
135+
136+
# TODO: Make async with progress bar
137+
generator.download_generator()
138+
self._interface.print("Download complete")
88139

89140
# API Contract
90141

91142
def install(self) -> None:
92143
if self.enabled:
93-
if self.verbose:
144+
if self.configuration.verbose:
94145
self._interface.print("CPPython: Installing...")
95146
self.download()
96-
self._generator.install()
147+
148+
for generator in self._generators:
149+
generator.install()
97150

98151
def update(self) -> None:
99152
if self.enabled:
100-
if self.verbose:
153+
if self.configuration.verbose:
101154
self._interface.print("CPPython: Updating...")
102-
self._generator.update()
155+
156+
for generator in self._generators:
157+
generator.update()
103158

104159
def build(self) -> None:
105160
if self.enabled:
106-
if self.verbose:
161+
if self.configuration.verbose:
107162
self._interface.print("CPPython: Building...")
108-
self._generator.build()
163+
164+
for generator in self._generators:
165+
generator.build()

0 commit comments

Comments
 (0)