cppython
Advanced tools
| """Defines the post-construction data management for CPPython""" | ||
| from dataclasses import dataclass | ||
| from logging import Logger | ||
| from cppython_core.exceptions import PluginError | ||
| from cppython_core.plugin_schema.generator import Generator | ||
| from cppython_core.plugin_schema.provider import Provider | ||
| from cppython_core.plugin_schema.scm import SCM | ||
| from cppython_core.schema import CoreData | ||
| @dataclass | ||
| class Plugins: | ||
| """The plugin data for CPPython""" | ||
| generator: Generator | ||
| provider: Provider | ||
| scm: SCM | ||
| class Data: | ||
| """Contains and manages the project data""" | ||
| def __init__(self, core_data: CoreData, plugins: Plugins, logger: Logger) -> None: | ||
| self._core_data = core_data | ||
| self._plugins = plugins | ||
| self.logger = logger | ||
| @property | ||
| def plugins(self) -> Plugins: | ||
| """The plugin data for CPPython""" | ||
| return self._plugins | ||
| def sync(self) -> None: | ||
| """Gathers sync information from providers and passes it to the generator | ||
| Raises: | ||
| PluginError: Plugin error | ||
| """ | ||
| if (sync_data := self.plugins.provider.sync_data(self.plugins.generator)) is None: | ||
| raise PluginError("The provider doesn't support the generator") | ||
| self.plugins.generator.sync(sync_data) | ||
| async def download_provider_tools(self) -> None: | ||
| """Download the provider tooling if required""" | ||
| base_path = self._core_data.cppython_data.install_path | ||
| path = base_path / self.plugins.provider.name() | ||
| path.mkdir(parents=True, exist_ok=True) | ||
| self.logger.warning("Downloading the %s requirements to %s", self.plugins.provider.name(), path) | ||
| await self.plugins.provider.download_tooling(path) |
| """Tests the Builder and Resolver types""" | ||
| import logging | ||
| import pytest_cppython | ||
| from cppython_core.schema import ( | ||
| CPPythonLocalConfiguration, | ||
| PEP621Configuration, | ||
| ProjectConfiguration, | ||
| ProjectData, | ||
| ) | ||
| from cppython.builder import Builder, Resolver | ||
| class TestBuilder: | ||
| """Various tests for the Builder type""" | ||
| def test_build( | ||
| self, | ||
| project_configuration: ProjectConfiguration, | ||
| pep621_configuration: PEP621Configuration, | ||
| cppython_local_configuration: CPPythonLocalConfiguration, | ||
| ) -> None: | ||
| """Verifies that the builder can build a project with all test variants | ||
| Args: | ||
| project_configuration: Variant fixture for the project configuration | ||
| pep621_configuration: Variant fixture for PEP 621 configuration | ||
| cppython_local_configuration: Variant fixture for cppython configuration | ||
| """ | ||
| logger = logging.getLogger() | ||
| builder = Builder(project_configuration, logger) | ||
| assert builder.build(pep621_configuration, cppython_local_configuration) | ||
| class TestResolver: | ||
| """Various tests for the Resolver type""" | ||
| def test_generate_plugins( | ||
| self, | ||
| project_configuration: ProjectConfiguration, | ||
| cppython_local_configuration: CPPythonLocalConfiguration, | ||
| project_data: ProjectData, | ||
| ) -> None: | ||
| """Verifies that the resolver can generate plugins | ||
| Args: | ||
| project_configuration: Variant fixture for the project configuration | ||
| cppython_local_configuration: Variant fixture for cppython configuration | ||
| project_data: Variant fixture for the project data | ||
| """ | ||
| logger = logging.getLogger() | ||
| resolver = Resolver(project_configuration, logger) | ||
| assert resolver.generate_plugins(cppython_local_configuration, project_data) |
| """Tests the Data type""" | ||
| import logging | ||
| import pytest | ||
| import pytest_cppython | ||
| from cppython_core.resolution import PluginBuildData | ||
| from cppython_core.schema import ( | ||
| CPPythonLocalConfiguration, | ||
| PEP621Configuration, | ||
| ProjectConfiguration, | ||
| ) | ||
| from pytest_cppython.mock.generator import MockGenerator | ||
| from pytest_cppython.mock.provider import MockProvider | ||
| from pytest_cppython.mock.scm import MockSCM | ||
| from cppython.builder import Builder | ||
| from cppython.data import Data | ||
| class TestData: | ||
| """Various tests for the Data type""" | ||
| @pytest.fixture( | ||
| name="data", | ||
| scope="session", | ||
| ) | ||
| def fixture_data( | ||
| self, | ||
| project_configuration: ProjectConfiguration, | ||
| pep621_configuration: PEP621Configuration, | ||
| cppython_local_configuration: CPPythonLocalConfiguration, | ||
| ) -> Data: | ||
| """Creates a mock plugins fixture. We want all the plugins to use the same data variants at the same time, so we have to resolve data inside the fixture instead of using other data fixtures | ||
| Args: | ||
| project_configuration: Variant fixture for the project configuration | ||
| pep621_configuration: Variant fixture for PEP 621 configuration | ||
| cppython_local_configuration: Variant fixture for cppython configuration | ||
| Returns: | ||
| The mock plugins fixture | ||
| """ | ||
| logger = logging.getLogger() | ||
| builder = Builder(project_configuration, logger) | ||
| plugin_build_data = PluginBuildData(generator_type=MockGenerator, provider_type=MockProvider, scm_type=MockSCM) | ||
| return builder.build(pep621_configuration, cppython_local_configuration, plugin_build_data) | ||
| def test_sync(self, data: Data) -> None: | ||
| """Verifies that the sync method executes without error | ||
| Args: | ||
| data: Fixture for the mocked data class | ||
| """ | ||
| data.sync() |
+231
-156
@@ -1,3 +0,2 @@ | ||
| """Everything needed to build a CPPython project | ||
| """ | ||
| """Defines the data and routines for building a CPPython project type""" | ||
@@ -16,6 +15,6 @@ import logging | ||
| PluginBuildData, | ||
| PluginCPPythonData, | ||
| resolve_cppython, | ||
| resolve_cppython_plugin, | ||
| resolve_generator, | ||
| resolve_name, | ||
| resolve_pep621, | ||
@@ -30,57 +29,38 @@ resolve_project_configuration, | ||
| CPPythonGlobalConfiguration, | ||
| DataPluginT, | ||
| CPPythonLocalConfiguration, | ||
| DataPlugin, | ||
| PEP621Configuration, | ||
| PEP621Data, | ||
| ProjectConfiguration, | ||
| ProjectData, | ||
| PyProject, | ||
| ) | ||
| from cppython.data import Data, Plugins | ||
| class Builder: | ||
| """Helper class for building CPPython projects""" | ||
| def __init__(self, logger: Logger) -> None: | ||
| self.logger = logger | ||
| class Resolver: | ||
| """The resolution of data sources for the builder""" | ||
| def setup_logger(self, project_configuration: ProjectConfiguration) -> None: | ||
| """_summary_ | ||
| def __init__(self, project_configuration: ProjectConfiguration, logger: Logger) -> None: | ||
| Args: | ||
| project_configuration: _description_ | ||
| """ | ||
| # Default logging levels | ||
| levels = [logging.WARNING, logging.INFO, logging.DEBUG] | ||
| self._project_configuration = project_configuration | ||
| self._logger = logger | ||
| # Add default output stream | ||
| self.logger.addHandler(logging.StreamHandler()) | ||
| self.logger.setLevel(levels[project_configuration.verbosity]) | ||
| def generate_plugins( | ||
| self, cppython_local_configuration: CPPythonLocalConfiguration, project_data: ProjectData | ||
| ) -> PluginBuildData: | ||
| """Generates the plugin data from the local configuration and project data | ||
| self.logger.info("Logging setup complete") | ||
| def generate_project_data(self, project_configuration: ProjectConfiguration) -> ProjectData: | ||
| """_summary_ | ||
| Args: | ||
| project_configuration: _description_ | ||
| cppython_local_configuration: The local configuration | ||
| project_data: The project data | ||
| Returns: | ||
| _description_ | ||
| The resolved plugin data | ||
| """ | ||
| return resolve_project_configuration(project_configuration) | ||
| def generate_data_plugins(self, pyproject: PyProject) -> PluginBuildData: | ||
| """_summary_ | ||
| Args: | ||
| pyproject: _description_ | ||
| Returns: | ||
| _description_ | ||
| """ | ||
| raw_generator_plugins = self.find_generators() | ||
| generator_plugins = self.filter_plugins( | ||
| raw_generator_plugins, | ||
| pyproject.tool.cppython.generator_name, | ||
| cppython_local_configuration.generator_name, | ||
| "Generator", | ||
@@ -92,62 +72,63 @@ ) | ||
| raw_provider_plugins, | ||
| pyproject.tool.cppython.provider_name, | ||
| cppython_local_configuration.provider_name, | ||
| "Provider", | ||
| ) | ||
| scm_plugins = self.find_source_managers() | ||
| scm_type = self.select_scm(scm_plugins, project_data) | ||
| # Solve the messy interactions between plugins | ||
| generator_type, provider_type = self.solve(generator_plugins, provider_plugins) | ||
| return PluginBuildData(generator_type=generator_type, provider_type=provider_type) | ||
| return PluginBuildData(generator_type=generator_type, provider_type=provider_type, scm_type=scm_type) | ||
| def generate_pep621_data( | ||
| self, pyproject: PyProject, project_configuration: ProjectConfiguration, scm: SCM | None | ||
| ) -> PEP621Data: | ||
| """_summary_ | ||
| def generate_cppython_plugin_data(self, plugin_build_data: PluginBuildData) -> PluginCPPythonData: | ||
| """Generates the CPPython plugin data from the resolved plugins | ||
| Args: | ||
| pyproject: _description_ | ||
| project_configuration: _description_ | ||
| scm: _description_ | ||
| plugin_build_data: The resolved plugin data | ||
| Returns: | ||
| _description_ | ||
| The plugin data used by CPPython | ||
| """ | ||
| return resolve_pep621(pyproject.project, project_configuration, scm) | ||
| def generate_core_data( | ||
| self, | ||
| project_data: ProjectData, | ||
| pyproject: PyProject, | ||
| pep621_data: PEP621Data, | ||
| plugin_build_date: PluginBuildData, | ||
| ) -> CoreData: | ||
| """Parses and returns resolved data from all configuration sources | ||
| return PluginCPPythonData( | ||
| generator_name=plugin_build_data.generator_type.name(), | ||
| provider_name=plugin_build_data.provider_type.name(), | ||
| scm_name=plugin_build_data.scm_type.name(), | ||
| ) | ||
| def generate_pep621_data( | ||
| self, pep621_configuration: PEP621Configuration, project_configuration: ProjectConfiguration, scm: SCM | None | ||
| ) -> PEP621Data: | ||
| """Generates the PEP621 data from configuration sources | ||
| Args: | ||
| project_data: Project data | ||
| pyproject: TODO | ||
| pep621_data: TODO | ||
| plugin_build_date: TODO | ||
| pep621_configuration: The PEP621 configuration | ||
| project_configuration: The project configuration | ||
| scm: The source control manager, if any | ||
| Raises: | ||
| ConfigError: Raised if data cannot be parsed | ||
| Returns: | ||
| The resolved core object | ||
| The resolved PEP621 data | ||
| """ | ||
| return resolve_pep621(pep621_configuration, project_configuration, scm) | ||
| global_configuration = CPPythonGlobalConfiguration() | ||
| def resolve_global_config(self) -> CPPythonGlobalConfiguration: | ||
| """Generates the global configuration object | ||
| cppython_data = resolve_cppython(pyproject.tool.cppython, global_configuration, project_data, plugin_build_date) | ||
| Returns: | ||
| The global configuration object | ||
| """ | ||
| return CoreData(project_data=project_data, pep621_data=pep621_data, cppython_data=cppython_data) | ||
| return CPPythonGlobalConfiguration() | ||
| def find_generators(self) -> list[type[Generator]]: | ||
| """_summary_ | ||
| """Extracts the generator plugins from the package's entry points | ||
| Raises: | ||
| PluginError: _description_ | ||
| PluginError: Raised if no plugins can be found | ||
| Returns: | ||
| _description_ | ||
| The list of generator plugin types | ||
| """ | ||
@@ -162,10 +143,8 @@ | ||
| if not issubclass(loaded_type, Generator): | ||
| self.logger.warning( | ||
| f"Found incompatible plugin. The '{resolve_name(loaded_type)}' plugin must be an instance of" | ||
| self._logger.warning( | ||
| f"Found incompatible plugin. The '{loaded_type.name()}' plugin must be an instance of" | ||
| f" '{group_name}'" | ||
| ) | ||
| else: | ||
| self.logger.warning( | ||
| f"{group_name} plugin found: {resolve_name(loaded_type)} from {getmodule(loaded_type)}" | ||
| ) | ||
| self._logger.warning(f"{group_name} plugin found: {loaded_type.name()} from {getmodule(loaded_type)}") | ||
| plugin_types.append(loaded_type) | ||
@@ -179,9 +158,9 @@ | ||
| def find_providers(self) -> list[type[Provider]]: | ||
| """_summary_ | ||
| """Extracts the provider plugins from the package's entry points | ||
| Raises: | ||
| PluginError: _description_ | ||
| PluginError: Raised if no plugins can be found | ||
| Returns: | ||
| _description_ | ||
| The list of provider plugin types | ||
| """ | ||
@@ -196,10 +175,38 @@ | ||
| if not issubclass(loaded_type, Provider): | ||
| self.logger.warning( | ||
| f"Found incompatible plugin. The '{resolve_name(loaded_type)}' plugin must be an instance of" | ||
| self._logger.warning( | ||
| f"Found incompatible plugin. The '{loaded_type.name()}' plugin must be an instance of" | ||
| f" '{group_name}'" | ||
| ) | ||
| else: | ||
| self.logger.warning( | ||
| f"{group_name} plugin found: {resolve_name(loaded_type)} from {getmodule(loaded_type)}" | ||
| self._logger.warning(f"{group_name} plugin found: {loaded_type.name()} from {getmodule(loaded_type)}") | ||
| plugin_types.append(loaded_type) | ||
| if not plugin_types: | ||
| raise PluginError(f"No {group_name} plugin was found") | ||
| return plugin_types | ||
| def find_source_managers(self) -> list[type[SCM]]: | ||
| """Extracts the source control manager plugins from the package's entry points | ||
| Raises: | ||
| PluginError: Raised if no plugins can be found | ||
| Returns: | ||
| The list of source control manager plugin types | ||
| """ | ||
| group_name = "scm" | ||
| plugin_types: list[type[SCM]] = [] | ||
| # Filter entries by type | ||
| for entry_point in list(metadata.entry_points(group=f"cppython.{group_name}")): | ||
| loaded_type = entry_point.load() | ||
| if not issubclass(loaded_type, SCM): | ||
| self._logger.warning( | ||
| f"Found incompatible plugin. The '{loaded_type.name()}' plugin must be an instance of" | ||
| f" '{group_name}'" | ||
| ) | ||
| else: | ||
| self._logger.warning(f"{group_name} plugin found: {loaded_type.name()} from {getmodule(loaded_type)}") | ||
| plugin_types.append(loaded_type) | ||
@@ -212,5 +219,5 @@ | ||
| def filter_plugins( | ||
| self, plugin_types: list[type[DataPluginT]], pinned_name: str | None, group_name: str | ||
| ) -> list[type[DataPluginT]]: | ||
| def filter_plugins[ | ||
| T: DataPlugin | ||
| ](self, plugin_types: list[type[T]], pinned_name: str | None, group_name: str) -> list[type[T]]: | ||
| """Finds and filters data plugins | ||
@@ -233,16 +240,16 @@ | ||
| for loaded_type in plugin_types: | ||
| if resolve_name(loaded_type) == pinned_name: | ||
| self.logger.warning( | ||
| f"Using {group_name} plugin: {resolve_name(loaded_type)} from {getmodule(loaded_type)}" | ||
| if loaded_type.name() == pinned_name: | ||
| self._logger.warning( | ||
| f"Using {group_name} plugin: {loaded_type.name()} from {getmodule(loaded_type)}" | ||
| ) | ||
| return [loaded_type] | ||
| self.logger.warning(f"'{group_name}_name' was empty. Trying to deduce {group_name}s") | ||
| self._logger.warning(f"'{group_name}_name' was empty. Trying to deduce {group_name}s") | ||
| supported_types: list[type[DataPluginT]] = [] | ||
| supported_types: list[type[T]] = [] | ||
| # Deduce types | ||
| for loaded_type in plugin_types: | ||
| self.logger.warning( | ||
| f"A {group_name} plugin is supported: {resolve_name(loaded_type)} from {getmodule(loaded_type)}" | ||
| self._logger.warning( | ||
| f"A {group_name} plugin is supported: {loaded_type.name()} from {getmodule(loaded_type)}" | ||
| ) | ||
@@ -257,16 +264,36 @@ supported_types.append(loaded_type) | ||
| def select_scm(self, scm_plugins: list[type[SCM]], project_data: ProjectData) -> type[SCM]: | ||
| """Given data constraints, selects the SCM plugin to use | ||
| Args: | ||
| scm_plugins: The list of SCM plugin types | ||
| project_data: The project data | ||
| Raises: | ||
| PluginError: Raised if no SCM plugin was found that supports the given data | ||
| Returns: | ||
| The selected SCM plugin type | ||
| """ | ||
| for scm_type in scm_plugins: | ||
| if scm_type.features(project_data.pyproject_file.parent).repository: | ||
| return scm_type | ||
| raise PluginError("No SCM plugin was found that supports the given path") | ||
| def solve( | ||
| self, generator_types: list[type[Generator]], provider_types: list[type[Provider]] | ||
| ) -> tuple[type[Generator], type[Provider]]: | ||
| """_summary_ | ||
| """Selects the first generator and provider that can work together | ||
| Args: | ||
| generator_types: _description_ | ||
| provider_types: _description_ | ||
| generator_types: The list of generator plugin types | ||
| provider_types: The list of provider plugin types | ||
| Raises: | ||
| PluginError: _description_ | ||
| PluginError: Raised if no provider that supports a given generator could be deduced | ||
| Returns: | ||
| _description_ | ||
| A tuple of the selected generator and provider plugin types | ||
| """ | ||
@@ -291,49 +318,28 @@ | ||
| self, | ||
| project_data: ProjectData, | ||
| ) -> SCM | None: | ||
| """_summary_ | ||
| core_data: CoreData, | ||
| scm_type: type[SCM], | ||
| ) -> SCM: | ||
| """Creates a source control manager from input configuration | ||
| Args: | ||
| project_data: _description_ | ||
| core_data: The resolved configuration data | ||
| scm_type: The plugin type | ||
| Raises: | ||
| PluginError: Ya | ||
| Returns: | ||
| _description_ | ||
| The constructed source control manager | ||
| """ | ||
| group = "scm" | ||
| path = project_data.pyproject_file.parent | ||
| scm_types: list[type[SCM]] = [] | ||
| cppython_plugin_data = resolve_cppython_plugin(core_data.cppython_data, scm_type) | ||
| scm_data = resolve_scm(core_data.project_data, cppython_plugin_data) | ||
| if not (entries := list(metadata.entry_points(group=f"cppython.{group}"))): | ||
| raise PluginError("No SCM plugin found") | ||
| plugin = scm_type(scm_data) | ||
| # Filter entries | ||
| for entry_point in entries: | ||
| plugin_type = entry_point.load() | ||
| if not issubclass(plugin_type, SCM): | ||
| self.logger.warning( | ||
| f"Found incompatible plugin. The '{resolve_name(plugin_type)}' plugin must be an instance of" | ||
| f" '{group}'" | ||
| ) | ||
| else: | ||
| scm_types.append(plugin_type) | ||
| # Deduce the SCM repository | ||
| plugin = None | ||
| for scm_type in scm_types: | ||
| if scm_type.features(path).repository: | ||
| scm_data = resolve_scm(project_data) | ||
| plugin = scm_type(scm_data) | ||
| break | ||
| if not plugin: | ||
| self.logger.error("No applicable SCM plugin found for the given path") | ||
| return plugin | ||
| def create_generator( | ||
| self, core_data: CoreData, generator_configuration: dict[str, Any], generator_type: type[Generator] | ||
| self, | ||
| core_data: CoreData, | ||
| pep621_data: PEP621Data, | ||
| generator_configuration: dict[str, Any], | ||
| generator_type: type[Generator], | ||
| ) -> Generator: | ||
@@ -344,8 +350,6 @@ """Creates a generator from input configuration | ||
| core_data: The resolved configuration data | ||
| pep621_data: The PEP621 data | ||
| generator_configuration: The generator table of the CPPython configuration data | ||
| generator_type: The plugin type | ||
| Raises: | ||
| PluginError: Raised if no viable generator plugin was found | ||
| Returns: | ||
@@ -355,20 +359,25 @@ The constructed generator | ||
| generator_data = resolve_generator(core_data.project_data) | ||
| cppython_plugin_data = resolve_cppython_plugin(core_data.cppython_data, generator_type) | ||
| generator_data = resolve_generator(core_data.project_data, cppython_plugin_data) | ||
| if not generator_configuration: | ||
| self._logger.error( | ||
| "The pyproject.toml table 'tool.cppython.generator' does not exist. Sending generator empty data", | ||
| ) | ||
| core_plugin_data = CorePluginData( | ||
| project_data=core_data.project_data, | ||
| pep621_data=core_data.pep621_data, | ||
| pep621_data=pep621_data, | ||
| cppython_data=cppython_plugin_data, | ||
| ) | ||
| if not generator_configuration: | ||
| self.logger.error( | ||
| "The pyproject.toml table 'tool.cppython.generator' does not exist. Sending generator empty data", | ||
| ) | ||
| return generator_type(generator_data, core_plugin_data, generator_configuration) | ||
| def create_provider( | ||
| self, core_data: CoreData, provider_configuration: dict[str, Any], provider_type: type[Provider] | ||
| self, | ||
| core_data: CoreData, | ||
| pep621_data: PEP621Data, | ||
| provider_configuration: dict[str, Any], | ||
| provider_type: type[Provider], | ||
| ) -> Provider: | ||
@@ -379,8 +388,6 @@ """Creates Providers from input data | ||
| core_data: The resolved configuration data | ||
| pep621_data: The PEP621 data | ||
| provider_configuration: The provider data table | ||
| provider_type: The type to instantiate | ||
| Raises: | ||
| PluginError: Raised if no viable provider plugin was found | ||
| Returns: | ||
@@ -390,16 +397,84 @@ A constructed provider plugins | ||
| provider_data = resolve_provider(core_data.project_data) | ||
| cppython_plugin_data = resolve_cppython_plugin(core_data.cppython_data, provider_type) | ||
| provider_data = resolve_provider(core_data.project_data, cppython_plugin_data) | ||
| if not provider_configuration: | ||
| self._logger.error( | ||
| "The pyproject.toml table 'tool.cppython.provider' does not exist. Sending provider empty data", | ||
| ) | ||
| core_plugin_data = CorePluginData( | ||
| project_data=core_data.project_data, | ||
| pep621_data=core_data.pep621_data, | ||
| pep621_data=pep621_data, | ||
| cppython_data=cppython_plugin_data, | ||
| ) | ||
| if not provider_configuration: | ||
| self.logger.error( | ||
| "The pyproject.toml table 'tool.cppython.provider' does not exist. Sending provider empty data", | ||
| ) | ||
| return provider_type(provider_data, core_plugin_data, provider_configuration) | ||
| return provider_type(provider_data, core_plugin_data, provider_configuration) | ||
| class Builder: | ||
| """Helper class for building CPPython projects""" | ||
| def __init__(self, project_configuration: ProjectConfiguration, logger: Logger) -> None: | ||
| self._project_configuration = project_configuration | ||
| self._logger = logger | ||
| # Default logging levels | ||
| levels = [logging.WARNING, logging.INFO, logging.DEBUG] | ||
| # Add default output stream | ||
| self._logger.addHandler(logging.StreamHandler()) | ||
| self._logger.setLevel(levels[project_configuration.verbosity]) | ||
| self._logger.info("Logging setup complete") | ||
| self._resolver = Resolver(self._project_configuration, self._logger) | ||
| def build( | ||
| self, | ||
| pep621_configuration: PEP621Configuration, | ||
| cppython_local_configuration: CPPythonLocalConfiguration, | ||
| plugin_build_data: PluginBuildData | None = None, | ||
| ) -> Data: | ||
| """Builds the project data | ||
| Args: | ||
| pep621_configuration: The PEP621 configuration | ||
| cppython_local_configuration: The local configuration | ||
| plugin_build_data: Plugin override data. If it exists, the build will use the given types instead of resolving them | ||
| Returns: | ||
| The built data object | ||
| """ | ||
| project_data = resolve_project_configuration(self._project_configuration) | ||
| if plugin_build_data is None: | ||
| plugin_build_data = self._resolver.generate_plugins(cppython_local_configuration, project_data) | ||
| plugin_cppython_data = self._resolver.generate_cppython_plugin_data(plugin_build_data) | ||
| global_configuration = self._resolver.resolve_global_config() | ||
| cppython_data = resolve_cppython( | ||
| cppython_local_configuration, global_configuration, project_data, plugin_cppython_data | ||
| ) | ||
| core_data = CoreData(project_data=project_data, cppython_data=cppython_data) | ||
| scm = self._resolver.create_scm(core_data, plugin_build_data.scm_type) | ||
| pep621_data = self._resolver.generate_pep621_data(pep621_configuration, self._project_configuration, scm) | ||
| # Create the chosen plugins | ||
| generator = self._resolver.create_generator( | ||
| core_data, pep621_data, cppython_local_configuration.generator, plugin_build_data.generator_type | ||
| ) | ||
| provider = self._resolver.create_provider( | ||
| core_data, pep621_data, cppython_local_configuration.provider, plugin_build_data.provider_type | ||
| ) | ||
| plugins = Plugins(generator=generator, provider=provider, scm=scm) | ||
| return Data(core_data, plugins, self._logger) |
@@ -1,3 +0,2 @@ | ||
| """A click CLI for CPPython interfacing | ||
| """ | ||
| """A click CLI for CPPython interfacing""" | ||
@@ -64,3 +63,4 @@ from logging import getLogger | ||
| pyproject_data = tomlkit.loads(self.configuration.pyproject_file.read_text(encoding="utf-8")) | ||
| path: Path = self.configuration.pyproject_file | ||
| pyproject_data = tomlkit.loads(path.read_text(encoding="utf-8")) | ||
@@ -67,0 +67,0 @@ return Project(self.configuration, self.interface, pyproject_data) |
+26
-100
@@ -1,3 +0,2 @@ | ||
| """Manages data flow to and from plugins | ||
| """ | ||
| """Manages data flow to and from plugins""" | ||
@@ -8,7 +7,5 @@ import asyncio | ||
| from cppython_core.exceptions import ConfigError, PluginError | ||
| from cppython_core.plugin_schema.scm import SCM | ||
| from cppython_core.resolution import resolve_name | ||
| from cppython_core.schema import CoreData, Interface, ProjectConfiguration, PyProject | ||
| from pydantic import ValidationError | ||
| from cppython_core.exceptions import ConfigException | ||
| from cppython_core.resolution import resolve_model | ||
| from cppython_core.schema import Interface, ProjectConfiguration, PyProject | ||
@@ -20,3 +17,3 @@ from cppython.builder import Builder | ||
| class Project(API): | ||
| """The object constructed at each entry_point""" | ||
| """The object that should be constructed at each entry_point""" | ||
@@ -30,40 +27,18 @@ def __init__( | ||
| try: | ||
| builder = Builder(self.logger) | ||
| builder.setup_logger(project_configuration) | ||
| builder = Builder(project_configuration, self.logger) | ||
| self.logger.info("Initializing project") | ||
| self.logger.info("Initializing project") | ||
| project_data = builder.generate_project_data(project_configuration) | ||
| self._scm = builder.create_scm(project_data) | ||
| try: | ||
| pyproject = resolve_model(PyProject, pyproject_data) | ||
| except ConfigException as error: | ||
| self.logger.error(error, exc_info=True) | ||
| return | ||
| pyproject = PyProject(**pyproject_data) | ||
| if not pyproject.tool or not pyproject.tool.cppython: | ||
| self.logger.warning("The pyproject.toml file doesn't contain the `tool.cppython` table") | ||
| return | ||
| plugin_build_data = builder.generate_data_plugins(pyproject) | ||
| self._data = builder.build(pyproject.project, pyproject.tool.cppython) | ||
| # Once the plugins are resolved, the core data is complete and can be generated | ||
| pep621_data = builder.generate_pep621_data(pyproject, project_configuration, self._scm) | ||
| self._core_data = builder.generate_core_data( | ||
| project_data, | ||
| pyproject, | ||
| pep621_data, | ||
| plugin_build_data, | ||
| ) | ||
| # Create the chosen plugins | ||
| self._generator = builder.create_generator( | ||
| self._core_data, pyproject.tool.cppython.generator, plugin_build_data.generator_type | ||
| ) | ||
| self._provider = builder.create_provider( | ||
| self._core_data, pyproject.tool.cppython.provider, plugin_build_data.provider_type | ||
| ) | ||
| except ConfigError: | ||
| logging.exception("Unhandled configuration. CPPython will process no further") | ||
| return | ||
| except ValidationError as error: | ||
| logging.error(error) | ||
| return | ||
| self._enabled = True | ||
@@ -82,49 +57,2 @@ | ||
| @property | ||
| def core_data(self) -> CoreData | None: | ||
| """Core data | ||
| Returns: | ||
| Core data, if enabled | ||
| """ | ||
| return self._core_data if self._enabled else None | ||
| @property | ||
| def scm(self) -> SCM | None: | ||
| """SCM | ||
| Returns: | ||
| SCM, if enabled | ||
| """ | ||
| return self._scm if self._enabled else None | ||
| async def download_provider_tools(self) -> None: | ||
| """Download the provider tooling if required""" | ||
| if not self._enabled: | ||
| self.logger.info("Skipping 'download_provider_tools' because the project is not enabled") | ||
| return | ||
| name = resolve_name(type(self._provider)) | ||
| base_path = self._core_data.cppython_data.install_path | ||
| path = base_path / name | ||
| path.mkdir(parents=True, exist_ok=True) | ||
| self.logger.warning("Downloading the %s requirements to %s", name, path) | ||
| await self._provider.download_tooling(path) | ||
| def sync(self) -> None: | ||
| """Gathers sync information from providers and passes it to the generator | ||
| Raises: | ||
| PluginError: Plugin error | ||
| """ | ||
| if (sync_data := self._provider.sync_data(self._generator)) is None: | ||
| raise PluginError("The provider doesn't support the generator") | ||
| self._generator.sync(sync_data) | ||
| # API Contract | ||
| def install(self) -> None: | ||
@@ -141,15 +69,14 @@ """Installs project dependencies | ||
| self.logger.info("Installing tools") | ||
| asyncio.run(self.download_provider_tools()) | ||
| asyncio.run(self._data.download_provider_tools()) | ||
| self.logger.info("Installing project") | ||
| name = resolve_name(type(self._provider)) | ||
| self.logger.info("Installing %s provider", name) | ||
| self.logger.info("Installing %s provider", self._data.plugins.provider.name()) | ||
| try: | ||
| self._provider.install() | ||
| self._data.plugins.provider.install() | ||
| except Exception as exception: | ||
| self.logger.error("Provider %s failed to install", name) | ||
| self.logger.error("Provider %s failed to install", self._data.plugins.provider.name()) | ||
| raise exception | ||
| self.sync() | ||
| self._data.sync() | ||
@@ -167,14 +94,13 @@ def update(self) -> None: | ||
| self.logger.info("Updating tools") | ||
| asyncio.run(self.download_provider_tools()) | ||
| asyncio.run(self._data.download_provider_tools()) | ||
| self.logger.info("Updating project") | ||
| name = resolve_name(type(self._provider)) | ||
| self.logger.info("Updating %s provider", name) | ||
| self.logger.info("Updating %s provider", self._data.plugins.provider.name()) | ||
| try: | ||
| self._provider.update() | ||
| self._data.plugins.provider.update() | ||
| except Exception as exception: | ||
| self.logger.error("Provider %s failed to update", name) | ||
| self.logger.error("Provider %s failed to update", self._data.plugins.provider.name()) | ||
| raise exception | ||
| self.sync() | ||
| self._data.sync() |
@@ -1,8 +0,8 @@ | ||
| """Project schema specifications | ||
| """ | ||
| """Project schema specifications""" | ||
| from abc import abstractmethod | ||
| from typing import Protocol | ||
| class API: | ||
| class API(Protocol): | ||
| """Project API specification""" | ||
@@ -9,0 +9,0 @@ |
+11
-6
| Metadata-Version: 2.1 | ||
| Name: cppython | ||
| Version: 0.7.1.dev34 | ||
| Version: 0.7.1.dev35 | ||
| Summary: A Python management solution for C++ dependencies | ||
| Home-page: https://github.com/Synodic-Software/CPPython | ||
| Author-Email: Synodic Software <contact@synodic.software> | ||
| License: MIT | ||
| Author-email: Synodic Software <contact@synodic.software> | ||
| Requires-Python: >=3.11 | ||
| Project-URL: homepage, https://github.com/Synodic-Software/CPPython | ||
| Project-URL: repository, https://github.com/Synodic-Software/CPPython | ||
| Project-URL: Homepage, https://github.com/Synodic-Software/CPPython | ||
| Project-URL: Repository, https://github.com/Synodic-Software/CPPython | ||
| Requires-Python: >=3.12 | ||
| Requires-Dist: click>=8.1.3 | ||
| Requires-Dist: tomlkit>=0.12.4 | ||
| Requires-Dist: cppython-core>=0.4.1.dev19 | ||
| Requires-Dist: pydantic>=2.6.3 | ||
| Requires-Dist: packaging>=21.3 | ||
| Description-Content-Type: text/markdown | ||
@@ -14,2 +20,1 @@ | ||
| A Python management solution for C++ dependencies | ||
+19
-18
| [project] | ||
| description = "A Python management solution for C++ dependencies" | ||
| name = "cppython" | ||
| license = "MIT" | ||
| authors = [ | ||
@@ -10,12 +9,15 @@ { name = "Synodic Software", email = "contact@synodic.software" }, | ||
| dynamic = [] | ||
| requires-python = ">=3.11" | ||
| requires-python = ">=3.12" | ||
| dependencies = [ | ||
| "click>=8.1.3", | ||
| "tomlkit>=0.11.4", | ||
| "tomlkit>=0.12.4", | ||
| "cppython-core>=0.4.1.dev19", | ||
| "pydantic>=2.0a4", | ||
| "pydantic>=2.6.3", | ||
| "packaging>=21.3", | ||
| ] | ||
| version = "0.7.1.dev34" | ||
| version = "0.7.1.dev35" | ||
| [project.license] | ||
| text = "MIT" | ||
| [project.license-files] | ||
@@ -30,19 +32,22 @@ paths = [ | ||
| [project.optional-dependencies] | ||
| [project.scripts] | ||
| cppython = "cppython.console.interface:cli" | ||
| [tool.pdm.options] | ||
| update = [ | ||
| "--update-all", | ||
| ] | ||
| [tool.pdm.version] | ||
| use_scm = true | ||
| source = "scm" | ||
| [tool.pdm.dev-dependencies] | ||
| lint = [ | ||
| "black>=22.6.0", | ||
| "pylint>=2.14.5", | ||
| "black>=24.2.0", | ||
| "pylint>=3.0.0", | ||
| "isort>=5.10.1", | ||
| "mypy>=1.2", | ||
| "mypy>=1.9", | ||
| ] | ||
| test = [ | ||
| "pytest>=7.1.2", | ||
| "pytest>=8.0.2", | ||
| "pytest-cov>=3.0.0", | ||
@@ -96,3 +101,2 @@ "pytest-click>=1.1", | ||
| ] | ||
| show_error_codes = true | ||
| strict = true | ||
@@ -110,5 +114,2 @@ | ||
| [tool.pylint.typecheck] | ||
| ignored-classes = "FieldInfo" | ||
| [tool.pylint.format] | ||
@@ -128,5 +129,5 @@ max-line-length = "120" | ||
| [build-system] | ||
| build-backend = "pdm.pep517.api" | ||
| build-backend = "pdm.backend" | ||
| requires = [ | ||
| "pdm-pep517", | ||
| "pdm.backend", | ||
| ] |
| """Tests the Project type""" | ||
| from pathlib import Path | ||
| import tomlkit | ||
| from cppython_core.schema import ProjectConfiguration | ||
| from cppython_core.schema import ( | ||
| CPPythonLocalConfiguration, | ||
| PEP621Configuration, | ||
| ProjectConfiguration, | ||
| PyProject, | ||
| ToolData, | ||
| ) | ||
| from pytest import FixtureRequest | ||
@@ -13,9 +18,10 @@ from pytest_cppython.mock.interface import MockInterface | ||
| pep621 = PEP621Configuration(name="test-project", version="0.1.0") | ||
| class TestProject: | ||
| """Various tests for the project object""" | ||
| def test_default_construction(self, request: FixtureRequest) -> None: | ||
| """The project type should be constructable without pyproject.toml support. | ||
| The CPPython project uses a working pyproject.toml file, and this file is used as the test data | ||
| def test_self_construction(self, request: FixtureRequest) -> None: | ||
| """The project type should be constructable with this projects configuration | ||
@@ -34,6 +40,7 @@ Args: | ||
| assert project | ||
| # Doesn't have the cppython table | ||
| assert not project.enabled | ||
| def test_missing_project_table(self, tmp_path: Path) -> None: | ||
| """The project type should be constructable without the top level table | ||
| def test_missing_tool_table(self, tmp_path: Path) -> None: | ||
| """The project type should be constructable without the tool table | ||
@@ -52,4 +59,48 @@ Args: | ||
| project = Project(project_configuration, interface, {}) | ||
| pyproject = PyProject(project=pep621) | ||
| project = Project(project_configuration, interface, pyproject.model_dump(by_alias=True)) | ||
| assert project | ||
| assert not project.enabled | ||
| def test_missing_cppython_table(self, tmp_path: Path) -> None: | ||
| """The project type should be constructable without the cppython table | ||
| Args: | ||
| tmp_path: Temporary directory for dummy data | ||
| """ | ||
| file_path = tmp_path / "pyproject.toml" | ||
| with open(file_path, "a", encoding="utf8") as file: | ||
| file.write("") | ||
| project_configuration = ProjectConfiguration(pyproject_file=file_path, version=None) | ||
| interface = MockInterface() | ||
| tool_data = ToolData() | ||
| pyproject = PyProject(project=pep621, tool=tool_data) | ||
| project = Project(project_configuration, interface, pyproject.model_dump(by_alias=True)) | ||
| assert not project.enabled | ||
| def test_default_cppython_table(self, tmp_path: Path) -> None: | ||
| """The project type should be constructable with the default cppython table | ||
| Args: | ||
| tmp_path: Temporary directory for dummy data | ||
| """ | ||
| file_path = tmp_path / "pyproject.toml" | ||
| with open(file_path, "a", encoding="utf8") as file: | ||
| file.write("") | ||
| project_configuration = ProjectConfiguration(pyproject_file=file_path, version=None) | ||
| interface = MockInterface() | ||
| cppython_config = CPPythonLocalConfiguration() | ||
| tool_data = ToolData(cppython=cppython_config) | ||
| pyproject = PyProject(project=pep621, tool=tool_data) | ||
| project = Project(project_configuration, interface, pyproject.model_dump(by_alias=True)) | ||
| assert project.enabled |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
37581
31.3%19
18.75%770
25.61%