cppython
Advanced tools
| """Git VCS plugin | ||
| """ | ||
| from pathlib import Path | ||
| from cppython_core.schema import VersionControl | ||
| from dulwich.errors import NotGitRepository | ||
| from dulwich.repo import Repo | ||
| class Git(VersionControl): | ||
| """Git implementation hooks""" | ||
| @staticmethod | ||
| def name() -> str: | ||
| """The VCS name | ||
| Returns: | ||
| The name | ||
| """ | ||
| return "git" | ||
| def is_repository(self, path: Path) -> bool: | ||
| """Queries repository status of a path | ||
| Args: | ||
| path: The input path to query | ||
| Returns: | ||
| Whether the given path is a repository root | ||
| """ | ||
| try: | ||
| Repo(str(path)) | ||
| return True | ||
| except NotGitRepository: | ||
| return False | ||
| def extract_version(self, path: Path) -> str: | ||
| """Extracts the system's version metadata | ||
| Args: | ||
| path: The repository path | ||
| Returns: | ||
| A version | ||
| """ | ||
| return "0.1.0" |
| """Project test fixtures for all cppython tests | ||
| """ | ||
| from typing import Any | ||
| import pytest | ||
| from cppython_core.schema import PEP621, CPPythonData, PyProject, ToolData | ||
| from pytest_cppython.fixtures import CPPythonFixtures | ||
| from pytest_cppython.mock import MockProviderData | ||
| class CPPythonProjectFixtures(CPPythonFixtures): | ||
| """Additional fixtures to help test projects""" | ||
| @pytest.fixture(name="tool", scope="session") | ||
| def fixture_tool(self, cppython: CPPythonData) -> ToolData: | ||
| """The tool data | ||
| Args: | ||
| cppython: The parameterized cppython table | ||
| Returns: | ||
| Wrapped CPPython data | ||
| """ | ||
| return ToolData(cppython=cppython) | ||
| @pytest.fixture(name="project", scope="session") | ||
| def fixture_project(self, tool: ToolData, pep621: PEP621) -> PyProject: | ||
| """Parameterized construction of PyProject data | ||
| Args: | ||
| tool: The tool table with internal cppython data | ||
| pep621: The project table | ||
| Returns: | ||
| All the data as one object | ||
| """ | ||
| return PyProject(project=pep621, tool=tool) | ||
| @pytest.fixture(name="mock_project", scope="session") | ||
| def fixture_mock_project(self, project: PyProject) -> dict[str, Any]: | ||
| """Extension of the 'project' fixture with mock data attached | ||
| Args: | ||
| project: The input project | ||
| Returns: | ||
| All the data as a dictionary | ||
| """ | ||
| mocked_pyproject = project.dict(by_alias=True) | ||
| mocked_pyproject["tool"]["cppython"]["mock"] = MockProviderData() | ||
| return mocked_pyproject |
| """Tests the cppython built-in VCS plugin | ||
| """ | ||
| from pytest_cppython.plugin import VersionControlIntegrationTests | ||
| from cppython.plugins.git import Git | ||
| class TestGitInterface(VersionControlIntegrationTests[Git]): | ||
| """Integration tests for the Git VCS plugin""" |
| """Unit tests for the cppython VCS plugin | ||
| """ | ||
| import pytest | ||
| from pytest_cppython.plugin import VersionControlUnitTests | ||
| from cppython.plugins.git import Git | ||
| class TestGitInterface(VersionControlUnitTests[Git]): | ||
| """Unit tests for the Git VCS plugin""" | ||
| @pytest.fixture(name="version_control_type") | ||
| def fixture_version_control_type(self) -> type[Git]: | ||
| """A required testing hook that allows type generation | ||
| Returns: | ||
| The VCS type | ||
| """ | ||
| return Git |
+114
-105
@@ -0,8 +1,8 @@ | ||
| """Everything needed to build a CPPython project | ||
| """ | ||
| TODO | ||
| """ | ||
| from collections.abc import Sequence | ||
| from importlib import metadata | ||
| from pathlib import Path | ||
| from typing import Type | ||
| from logging import Logger | ||
| from typing import Any | ||
@@ -12,7 +12,9 @@ from cppython_core.schema import ( | ||
| CPPythonDataResolved, | ||
| Generator, | ||
| GeneratorConfiguration, | ||
| PEP621Resolved, | ||
| PluginT, | ||
| Plugin, | ||
| ProjectConfiguration, | ||
| Provider, | ||
| ProviderConfiguration, | ||
| ProviderDataResolvedT, | ||
| ProviderDataT, | ||
| PyProject, | ||
@@ -23,33 +25,88 @@ ToolData, | ||
| from cppython.schema import CMakePresets, ConfigurePreset | ||
| from cppython.utility import read_json, write_json, write_model_json | ||
| class PluginBuilder: | ||
| """Collection of utilities to collect and build plugins""" | ||
| class Builder: | ||
| """ | ||
| TODO | ||
| """ | ||
| def __init__(self, group: str, logger: Logger) -> None: | ||
| self._group = group | ||
| self._logger = logger | ||
| def __init__(self, configuration: ProjectConfiguration) -> None: | ||
| self.configuration = configuration | ||
| def gather_entries(self) -> list[metadata.EntryPoint]: | ||
| """Gather all the available entry points for the grouping | ||
| def gather_plugins(self, plugin_type: Type[PluginT]) -> list[Type[PluginT]]: | ||
| Returns: | ||
| List of entries | ||
| """ | ||
| TODO | ||
| return list(metadata.entry_points(group=f"cppython.{self._group}")) | ||
| def load(self, entry_points: list[metadata.EntryPoint]) -> list[type[Plugin]]: | ||
| """Loads a set of entry points | ||
| Args: | ||
| entry_points: The entry points to load | ||
| Raises: | ||
| TypeError: If an entry point is not a subclass of the 'Plugin' type | ||
| Returns: | ||
| List of plugin types | ||
| """ | ||
| plugins = [] | ||
| entry_points = metadata.entry_points(group=f"cppython.{plugin_type.group()}") | ||
| for entry_point in entry_points: | ||
| loaded_plugin_type = entry_point.load() | ||
| if issubclass(loaded_plugin_type, plugin_type) & (loaded_plugin_type is not plugin_type): | ||
| plugins.append(loaded_plugin_type) | ||
| plugin = entry_point.load() | ||
| if not issubclass(plugin, Plugin): | ||
| raise TypeError("The CPPython plugin must be an instance of Plugin") | ||
| plugins.append(plugin) | ||
| return plugins | ||
| def generate_model(self, plugins: list[Type[Generator]]) -> Type[PyProject]: | ||
| class Builder: | ||
| """Helper class for building CPPython projects""" | ||
| def __init__(self, configuration: ProjectConfiguration, logger: Logger) -> None: | ||
| self.configuration = configuration | ||
| self.logger = logger | ||
| def discover_providers(self) -> list[type[Provider[Any, Any]]]: | ||
| """Discovers Provider plugin types | ||
| Raises: | ||
| TypeError: Raised if the Plugin type is not subclass of 'Provider' | ||
| Returns: | ||
| List of Provider types | ||
| """ | ||
| TODO: Proper return type hint | ||
| provider_builder = PluginBuilder(Provider.group(), self.logger) | ||
| # Gather provider entry points without any filtering | ||
| provider_entry_points = provider_builder.gather_entries() | ||
| provider_types = provider_builder.load(provider_entry_points) | ||
| plugins = [] | ||
| for provider_type in provider_types: | ||
| if not issubclass(provider_type, Provider): | ||
| raise TypeError("The CPPython plugin must be an instance of Plugin") | ||
| plugins.append(provider_type) | ||
| return plugins | ||
| def generate_model( | ||
| self, plugins: Sequence[type[Provider[ProviderDataT, ProviderDataResolvedT]]] | ||
| ) -> type[PyProject]: | ||
| """Constructs a dynamic type that contains plugin specific data requirements | ||
| Args: | ||
| plugins: List of Provider types | ||
| Returns: | ||
| An extended PyProject type containing dynamic plugin data requirements | ||
| """ | ||
| plugin_fields = {} | ||
| plugin_fields: dict[str, Any] = {} | ||
| for plugin_type in plugins: | ||
@@ -76,8 +133,15 @@ plugin_fields[plugin_type.name()] = (plugin_type.data_type(), ...) | ||
| def generate_resolved_cppython_model(self, plugins: list[Type[Generator]]) -> Type[CPPythonDataResolved]: | ||
| def generate_resolved_cppython_model( | ||
| self, plugins: Sequence[type[Provider[ProviderDataT, ProviderDataResolvedT]]] | ||
| ) -> type[CPPythonDataResolved]: | ||
| """Constructs a dynamic resolved type that contains plugin specific data requirements | ||
| Args: | ||
| plugins: List of Provider types | ||
| Returns: | ||
| An extended CPPython resolved type containing dynamic plugin data requirements | ||
| """ | ||
| TODO | ||
| """ | ||
| plugin_fields = {} | ||
| plugin_fields: dict[str, Any] = {} | ||
| for plugin_type in plugins: | ||
@@ -94,87 +158,32 @@ # The unresolved type is still appended to the CPPythonDataResolved type | ||
| def create_generators( | ||
| def create_providers( | ||
| self, | ||
| plugins: list[Type[Generator]], | ||
| plugins: Sequence[type[Provider[ProviderDataT, ProviderDataResolvedT]]], | ||
| project_configuration: ProjectConfiguration, | ||
| configuration: GeneratorConfiguration, | ||
| project: PEP621Resolved, | ||
| cppython: CPPythonDataResolved, | ||
| ) -> list[Generator]: | ||
| """ | ||
| TODO | ||
| """ | ||
| _generators = [] | ||
| for plugin_type in plugins: | ||
| name = plugin_type.name() | ||
| generator_data = getattr(cppython, name) | ||
| resolved_generator_data = generator_data.resolve(project_configuration) | ||
| _generators.append(plugin_type(configuration, project, cppython, resolved_generator_data)) | ||
| configuration: ProviderConfiguration, | ||
| static_resolved_project_data: tuple[PEP621Resolved, CPPythonDataResolved], | ||
| ) -> list[Provider[ProviderDataT, ProviderDataResolvedT]]: | ||
| """Creates Providers from input data | ||
| return _generators | ||
| Args: | ||
| plugins: List of Provider plugins to construct | ||
| project_configuration: Project configuration data | ||
| configuration: Provider configuration data | ||
| static_resolved_project_data: Resolved project data | ||
| def write_presets(self, path: Path, generator_output: list[tuple[str, ConfigurePreset]]) -> Path: | ||
| Returns: | ||
| List of constructed providers | ||
| """ | ||
| Write the cppython presets. | ||
| Returns the | ||
| """ | ||
| path.mkdir(parents=True, exist_ok=True) | ||
| project, cppython = static_resolved_project_data | ||
| def write_generator_presets(path: Path, generator_name: str, configure_preset: ConfigurePreset) -> Path: | ||
| """ | ||
| Write a generator preset. | ||
| @returns - The written json file | ||
| """ | ||
| generator_tool_path = path / generator_name | ||
| generator_tool_path.mkdir(parents=True, exist_ok=True) | ||
| _providers = [] | ||
| for plugin_type in plugins: | ||
| name = plugin_type.name() | ||
| provider_data = getattr(cppython, name) | ||
| resolved_provider_data = provider_data.resolve(project_configuration) | ||
| resolved_cppython_data = cppython.provider_resolve(plugin_type) | ||
| configure_preset.hidden = True | ||
| presets = CMakePresets(configurePresets=[configure_preset]) | ||
| _providers.append(plugin_type(configuration, project, resolved_cppython_data, resolved_provider_data)) | ||
| json_path = generator_tool_path / f"{generator_name}.json" | ||
| write_model_json(json_path, presets) | ||
| return json_path | ||
| names = [] | ||
| includes = [] | ||
| path = path / "cppython" | ||
| for generator_name, configure_preset in generator_output: | ||
| preset_file = write_generator_presets(path, generator_name, configure_preset) | ||
| relative_file = preset_file.relative_to(path) | ||
| names.append(generator_name) | ||
| includes.append(str(relative_file)) | ||
| configure_preset = ConfigurePreset(name="cppython", hidden=True, inherits=names) | ||
| presets = CMakePresets(configurePresets=[configure_preset], include=includes) | ||
| json_path = path / "cppython.json" | ||
| write_model_json(json_path, presets) | ||
| return json_path | ||
| def write_root_presets(self, path: Path): | ||
| """ | ||
| Read the top level json file and replace the include reference. | ||
| Receives a relative path to the tool cmake json file | ||
| """ | ||
| root_preset_path = self.configuration.pyproject_file.parent / "CMakePresets.json" | ||
| root_preset = read_json(root_preset_path) | ||
| root_model = CMakePresets.parse_obj(root_preset) | ||
| if root_model.include is not None: | ||
| for index, include_path in enumerate(root_model.include): | ||
| if Path(include_path).name == "cppython.json": | ||
| root_model.include[index] = "build/" + path.as_posix() | ||
| # 'dict.update' wont apply to nested types, manual replacement | ||
| root_preset["include"] = root_model.include | ||
| write_json(root_preset_path, root_preset) | ||
| return _providers |
@@ -0,20 +1,27 @@ | ||
| """A click CLI for CPPython interfacing | ||
| """ | ||
| A click CLI for CPPython interfacing | ||
| """ | ||
| from logging import getLogger | ||
| from pathlib import Path | ||
| from typing import Any, Type | ||
| import click | ||
| import tomlkit | ||
| from cppython_core.schema import GeneratorDataT, Interface, InterfaceConfiguration | ||
| from cppython_core.schema import ( | ||
| Interface, | ||
| InterfaceConfiguration, | ||
| ProjectConfiguration, | ||
| ProviderDataT, | ||
| VersionControl, | ||
| ) | ||
| from cppython.console.vcs.git import Git | ||
| from cppython.project import Project, ProjectConfiguration | ||
| from cppython.builder import PluginBuilder | ||
| from cppython.project import Project | ||
| def _find_pyproject_file() -> Path: | ||
| """Searches upward for a pyproject.toml file | ||
| Returns: | ||
| The found directory | ||
| """ | ||
| TODO | ||
| """ | ||
@@ -35,23 +42,9 @@ # Search for a path upward | ||
| def _create_pyproject(path: Path) -> dict[str, Any]: | ||
| """ | ||
| TODO | ||
| """ | ||
| class Configuration: | ||
| """Click configuration object""" | ||
| # Load file | ||
| data = tomlkit.loads(path.read_text(encoding="utf-8")) | ||
| # Interpret and validate data | ||
| return data | ||
| class Config: | ||
| """ | ||
| The data object that will be expanded alongside 'pass_obj' | ||
| """ | ||
| def __init__(self): | ||
| def __init__(self) -> None: | ||
| path = _find_pyproject_file() | ||
| file_path = path / "pyproject.toml" | ||
| self.pyproject_data = _create_pyproject(file_path) | ||
| self.pyproject_data = tomlkit.loads(file_path.read_text(encoding="utf-8")) | ||
@@ -61,24 +54,62 @@ configuration = InterfaceConfiguration() | ||
| # TODO: Don't assume git SCM. Implement importing and scm selection | ||
| plugin_builder = PluginBuilder("version_control", getLogger()) | ||
| version = Git().extract_version(path) | ||
| self.configuration = ProjectConfiguration(pyproject_file=file_path, version=version.base_version) | ||
| # Don't filter entries | ||
| entries = plugin_builder.gather_entries() | ||
| vcs_types = plugin_builder.load(entries) | ||
| plugins: list[type[VersionControl]] = [] | ||
| # Verify the plugin type | ||
| for vcs_type in vcs_types: | ||
| if not issubclass(vcs_type, VersionControl): | ||
| raise TypeError("The VCS plugin must be an instance of VersionControl") | ||
| plugins.append(vcs_type) | ||
| # Extract the first plugin that identifies the repository | ||
| plugin = None | ||
| for plugin_type in plugins: | ||
| plugin = plugin_type() | ||
| plugin.is_repository(path) | ||
| break | ||
| if plugin is None: | ||
| raise TypeError("No VCS plugin found") | ||
| version = plugin.extract_version(path) | ||
| self.configuration = ProjectConfiguration(pyproject_file=file_path, version=version) | ||
| def create_project(self) -> Project: | ||
| """Creates the project type from input data | ||
| Returns: | ||
| The project | ||
| """ | ||
| TODO | ||
| """ | ||
| return Project(self.configuration, self.interface, self.pyproject_data) | ||
| def query_vcs(self) -> str: | ||
| """Queries the VCS system for its version | ||
| pass_config = click.make_pass_decorator(Config, ensure=True) | ||
| Returns: | ||
| The version | ||
| """ | ||
| return "TODO" | ||
| # Attach our config object to click's hook | ||
| pass_config = click.make_pass_decorator(Configuration, ensure=True) | ||
| @click.group() | ||
| @click.option("-v", "--verbose", count=True, help="Print additional output") | ||
| @pass_config | ||
| def cli(config, verbose: int): | ||
| def cli(config: Configuration, verbose: int) -> None: | ||
| """entry_point group for the CLI commands | ||
| Args: | ||
| config: The CLI configuration object | ||
| verbose: The verbosity level | ||
| """ | ||
| entry_point group for the CLI commands | ||
| """ | ||
| config.configuration.verbosity = verbose | ||
@@ -89,6 +120,8 @@ | ||
| @pass_config | ||
| def info(config): | ||
| def info(config: Configuration) -> None: | ||
| """Prints project information | ||
| Args: | ||
| config: The CLI configuration object | ||
| """ | ||
| TODO | ||
| """ | ||
| config.create_project() | ||
@@ -99,6 +132,8 @@ | ||
| @pass_config | ||
| def install(config): | ||
| def install(config: Configuration) -> None: | ||
| """Install API call | ||
| Args: | ||
| config: The CLI configuration object | ||
| """ | ||
| TODO | ||
| """ | ||
| project = config.create_project() | ||
@@ -110,6 +145,8 @@ project.install() | ||
| @pass_config | ||
| def update(config): | ||
| def update(config: Configuration) -> None: | ||
| """Update API call | ||
| Args: | ||
| config: The CLI configuration object | ||
| """ | ||
| TODO | ||
| """ | ||
| project = config.create_project() | ||
@@ -119,33 +156,26 @@ project.update() | ||
| @cli.command() | ||
| @pass_config | ||
| def build(config): | ||
| """ | ||
| TODO | ||
| """ | ||
| project = config.create_project() | ||
| project.build() | ||
| class ConsoleInterface(Interface): | ||
| """ | ||
| Interface implementation to pass to the project | ||
| """ | ||
| """Interface implementation to pass to the project""" | ||
| def __init__(self, configuration: InterfaceConfiguration) -> None: | ||
| super().__init__(configuration) | ||
| @staticmethod | ||
| def name() -> str: | ||
| """Returns the name of the interface | ||
| Returns: | ||
| The name | ||
| """ | ||
| return "console" | ||
| def read_generator_data(self, generator_data_type: Type[GeneratorDataT]) -> GeneratorDataT: | ||
| def read_provider_data(self, provider_data_type: type[ProviderDataT]) -> ProviderDataT: | ||
| """Requests provider information | ||
| Args: | ||
| provider_data_type: The type to construct | ||
| Returns: | ||
| The constructed provider data type | ||
| """ | ||
| Requests generator information | ||
| """ | ||
| return generator_data_type() | ||
| return provider_data_type() | ||
| def write_pyproject(self) -> None: | ||
| """ | ||
| Write output | ||
| """ | ||
| """Write output""" |
+57
-99
@@ -0,5 +1,5 @@ | ||
| """Manages data flow to and from plugins | ||
| """ | ||
| The central delegation of the CPPython project | ||
| """ | ||
| import asyncio | ||
| import logging | ||
@@ -10,7 +10,6 @@ from typing import Any | ||
| CPPythonDataResolved, | ||
| Generator, | ||
| GeneratorConfiguration, | ||
| Interface, | ||
| PEP621Resolved, | ||
| ProjectConfiguration, | ||
| ProviderConfiguration, | ||
| ) | ||
@@ -23,5 +22,3 @@ | ||
| class Project(API): | ||
| """ | ||
| The object constructed at each entry_point | ||
| """ | ||
| """The object constructed at each entry_point""" | ||
@@ -32,10 +29,9 @@ def __init__( | ||
| self._enabled = False | ||
| self._configuration = configuration | ||
| # Default logging levels | ||
| levels = [logging.WARNING, logging.INFO, logging.DEBUG] | ||
| # Add default output stream | ||
| console_handler = logging.StreamHandler() | ||
| self.logger = logging.getLogger("cppython") | ||
| self.logger.addHandler(console_handler) | ||
| self.logger.addHandler(logging.StreamHandler()) | ||
| self.logger.setLevel(levels[configuration.verbosity]) | ||
@@ -45,16 +41,14 @@ | ||
| self._builder = Builder(self.configuration) | ||
| plugins = self._builder.gather_plugins(Generator) | ||
| builder = Builder(configuration, self.logger) | ||
| if not plugins: | ||
| self.logger.error("No generator plugin was found") | ||
| if not (plugins := builder.discover_providers()): | ||
| self.logger.error("No provider plugin was found") | ||
| return | ||
| for plugin in plugins: | ||
| self.logger.warning(f"Generator plugin found: {plugin.name()}") | ||
| self.logger.warning("Provider plugin found: %s", plugin.name()) | ||
| extended_pyproject_type = self._builder.generate_model(plugins) | ||
| pyproject = extended_pyproject_type(**pyproject_data) | ||
| extended_pyproject_type = builder.generate_model(plugins) | ||
| if pyproject is None: | ||
| if (pyproject := extended_pyproject_type(**pyproject_data)) is None: | ||
| self.logger.error("Data is not defined") | ||
@@ -75,11 +69,11 @@ return | ||
| resolved_cppython_model = self._builder.generate_resolved_cppython_model(plugins) | ||
| self._resolved_project_data = pyproject.project.resolve(self.configuration) | ||
| self._resolved_cppython_data = pyproject.tool.cppython.resolve(resolved_cppython_model, self.configuration) | ||
| resolved_cppython_model = builder.generate_resolved_cppython_model(plugins) | ||
| self._resolved_project_data = pyproject.project.resolve(configuration) | ||
| self._resolved_cppython_data = pyproject.tool.cppython.resolve(resolved_cppython_model, configuration) | ||
| self._interface = interface | ||
| generator_configuration = GeneratorConfiguration(root_directory=self.configuration.pyproject_file.parent) | ||
| self._generators = self._builder.create_generators( | ||
| plugins, self.configuration, generator_configuration, self.project, self.cppython | ||
| provider_configuration = ProviderConfiguration(root_directory=configuration.pyproject_file.parent) | ||
| self._providers = builder.create_providers( | ||
| plugins, configuration, provider_configuration, (self.project, self.cppython) | ||
| ) | ||
@@ -91,19 +85,16 @@ | ||
| def enabled(self) -> bool: | ||
| """Queries if the project was is initialized for full functionality | ||
| Returns: | ||
| The query result | ||
| """ | ||
| TODO | ||
| """ | ||
| return self._enabled | ||
| @property | ||
| def configuration(self) -> ProjectConfiguration: | ||
| """ | ||
| TODO | ||
| """ | ||
| return self._configuration | ||
| def project(self) -> PEP621Resolved: | ||
| """Resolved project data | ||
| @property | ||
| def project(self) -> PEP621Resolved: | ||
| Returns: | ||
| The resolved 'project' table | ||
| """ | ||
| The resolved pyproject project table | ||
| """ | ||
| return self._resolved_project_data | ||
@@ -113,13 +104,13 @@ | ||
| def cppython(self) -> CPPythonDataResolved: | ||
| """The resolved CPPython data | ||
| Returns: | ||
| Resolved 'cppython' table | ||
| """ | ||
| The resolved CPPython data | ||
| """ | ||
| return self._resolved_cppython_data | ||
| def download_generator_tools(self) -> None: | ||
| """ | ||
| Download the generator tooling if required | ||
| """ | ||
| async def download_provider_tools(self) -> None: | ||
| """Download the provider tooling if required""" | ||
| if not self._enabled: | ||
| self.logger.info("Skipping 'download_generator_tools' because the project is not enabled") | ||
| self.logger.info("Skipping 'download_provider_tools' because the project is not enabled") | ||
| return | ||
@@ -129,38 +120,22 @@ | ||
| for generator in self._generators: | ||
| path = base_path / generator.name() | ||
| for provider in self._providers: | ||
| path = base_path / provider.name() | ||
| path.mkdir(parents=True, exist_ok=True) | ||
| if not generator.generator_downloaded(path): | ||
| self.logger.warning(f"Downloading the {generator.name()} requirements to {path}") | ||
| if not provider.tooling_downloaded(path): | ||
| self.logger.warning("Downloading the %s requirements to %s", provider.name(), path) | ||
| # TODO: Make async with progress bar | ||
| generator.download_generator(path) | ||
| await provider.download_tooling(path) | ||
| self.logger.warning("Download complete") | ||
| else: | ||
| self.logger.info(f"The {generator.name()} generator is already downloaded") | ||
| self.logger.info("The %s provider is already downloaded", provider.name()) | ||
| def update_generator_tools(self) -> None: | ||
| """ | ||
| Update the generator tooling if available | ||
| """ | ||
| if not self._enabled: | ||
| self.logger.info("Skipping 'update_generator_tools' because the project is not enabled") | ||
| return | ||
| self.download_generator_tools() | ||
| base_path = self.cppython.install_path | ||
| for generator in self._generators: | ||
| path = base_path / generator.name() | ||
| generator.update_generator(path) | ||
| # API Contract | ||
| def install(self) -> None: | ||
| """Installs project dependencies | ||
| Raises: | ||
| Exception: Raised if failed | ||
| """ | ||
| TODO | ||
| """ | ||
| if not self._enabled: | ||
@@ -171,28 +146,21 @@ self.logger.info("Skipping install because the project is not enabled") | ||
| self.logger.info("Installing tools") | ||
| self.download_generator_tools() | ||
| asyncio.run(self.download_provider_tools()) | ||
| self.logger.info("Installing project") | ||
| preset_path = self.cppython.build_path | ||
| generator_output = [] | ||
| for provider in self._providers: | ||
| self.logger.info("Installing %s provider", provider.name()) | ||
| # TODO: Async | ||
| for generator in self._generators: | ||
| self.logger.info(f"Installing {generator.name()} generator") | ||
| try: | ||
| generator.install() | ||
| config_preset = generator.generate_cmake_config() | ||
| generator_output.append((generator.name(), config_preset)) | ||
| provider.install() | ||
| except Exception as exception: | ||
| self.logger.error(f"Generator {generator.name()} failed to install") | ||
| self.logger.error("Provider %s failed to install", provider.name()) | ||
| raise exception | ||
| project_presets = self._builder.write_presets(preset_path, generator_output) | ||
| self._builder.write_root_presets(project_presets.relative_to(preset_path)) | ||
| def update(self) -> None: | ||
| """Updates project dependencies | ||
| def update(self) -> None: | ||
| Raises: | ||
| Exception: Raised if failed | ||
| """ | ||
| TODO | ||
| """ | ||
| if not self._enabled: | ||
@@ -203,23 +171,13 @@ self.logger.info("Skipping update because the project is not enabled") | ||
| self.logger.info("Updating tools") | ||
| self.update_generator_tools() | ||
| asyncio.run(self.download_provider_tools()) | ||
| self.logger.info("Updating project") | ||
| preset_path = self.cppython.build_path | ||
| for provider in self._providers: | ||
| self.logger.info("Updating %s provider", provider.name()) | ||
| generator_output = [] | ||
| # TODO: Async | ||
| for generator in self._generators: | ||
| self.logger.info(f"Updating {generator.name()} generator") | ||
| try: | ||
| generator.update() | ||
| config_preset = generator.generate_cmake_config() | ||
| generator_output.append((generator.name(), config_preset)) | ||
| provider.update() | ||
| except Exception as exception: | ||
| self.logger.error(f"Generator {generator.name()} failed to update") | ||
| self.logger.error("Provider %s failed to update", provider.name()) | ||
| raise exception | ||
| project_presets = self._builder.write_presets(preset_path, generator_output) | ||
| self._builder.write_root_presets(project_presets.relative_to(preset_path)) |
+4
-73
@@ -0,80 +1,13 @@ | ||
| """Project schema specifications | ||
| """ | ||
| TODO | ||
| """ | ||
| from __future__ import annotations # Required for self-referenced pydantic types | ||
| from abc import abstractmethod | ||
| from pathlib import Path | ||
| from typing import Any, Optional | ||
| from cppython_core.schema import ConfigurePreset, CPPythonModel, Preset | ||
| from pydantic import Extra, Field, validator | ||
| class BuildPreset(Preset): | ||
| """ | ||
| Partial Build Preset specification | ||
| """ | ||
| configurePreset: Optional[str] = Field(default=None) | ||
| inheritConfigureEnvironment: Optional[bool] = Field(default=None) | ||
| class TestPreset(Preset): | ||
| """ | ||
| Partial Test Preset specification | ||
| """ | ||
| configurePreset: Optional[str] = Field(default=None) | ||
| inheritConfigureEnvironment: Optional[bool] = Field(default=None) | ||
| class CMakeVersion(CPPythonModel, extra=Extra.forbid): | ||
| """ | ||
| The version specification for CMake | ||
| """ | ||
| major: int = Field(default=3) | ||
| minor: int = Field(default=23) | ||
| patch: int = Field(default=1) | ||
| class CMakePresets(CPPythonModel, extra=Extra.forbid): | ||
| """ | ||
| The schema for the CMakePresets and CMakeUserPresets files | ||
| """ | ||
| version: int = Field(default=4, const=True) | ||
| cmakeMinimumRequired: CMakeVersion = Field(default=CMakeVersion()) # TODO: 'version' compatibility validation | ||
| include: Optional[list[str]] = Field(default=None) | ||
| vendor: Optional[Any] = Field(default=None) | ||
| configurePresets: Optional[list[ConfigurePreset]] = Field(default=None) | ||
| buildPresets: Optional[list[BuildPreset]] = Field(default=None) | ||
| testPresets: Optional[list[TestPreset]] = Field(default=None) | ||
| @validator("include") | ||
| def validate_path(cls, values): # pylint: disable=E0213 | ||
| """ | ||
| TODO | ||
| """ | ||
| if values is not None: | ||
| output = [] | ||
| for value in values: | ||
| output.append(Path(value).as_posix()) | ||
| return output | ||
| return None | ||
| class API: | ||
| """ | ||
| Project API | ||
| """ | ||
| """Project API specification""" | ||
| @abstractmethod | ||
| def install(self) -> None: | ||
| """ | ||
| TODO | ||
| """ | ||
| """Installs project dependencies""" | ||
| raise NotImplementedError() | ||
@@ -84,6 +17,4 @@ | ||
| def update(self) -> None: | ||
| """ | ||
| TODO | ||
| """ | ||
| """Updates project dependencies""" | ||
| raise NotImplementedError() |
+3
-3
| Metadata-Version: 2.1 | ||
| Name: cppython | ||
| Version: 0.7.1.dev8 | ||
| Summary: A Python package manager agnostic plugin integrating a transparent Conan and CMake workflow. | ||
| Version: 0.7.1.dev9 | ||
| Summary: A Python management solution for C++ dependencies | ||
| License: MIT | ||
@@ -13,3 +13,3 @@ Author-email: Synodic Software <contact@synodic.software> | ||
| # CPPython | ||
| A library for managing dependencies with CMake for C++ projects. | ||
| A Python management solution for C++ dependencies | ||
+58
-3
| [project] | ||
| description = " A Python package manager agnostic plugin integrating a transparent Conan and CMake workflow." | ||
| description = "A Python management solution for C++ dependencies" | ||
| name = "cppython" | ||
@@ -19,3 +19,3 @@ license-expression = "MIT" | ||
| ] | ||
| version = "0.7.1.dev8" | ||
| version = "0.7.1.dev9" | ||
@@ -31,2 +31,5 @@ [project.license-files] | ||
| [project.entry-points."cppython.version_control"] | ||
| cmake = "cppython.plugins.git:Git" | ||
| [project.scripts] | ||
@@ -43,2 +46,3 @@ cppython = "cppython.console.interface:cli" | ||
| "isort>=5.10.1", | ||
| "mypy>=0.971", | ||
| ] | ||
@@ -52,3 +56,27 @@ test = [ | ||
| [tool.pdm.scripts.analyze] | ||
| shell = "pylint --verbose cppython tests" | ||
| [tool.pdm.scripts.format] | ||
| shell = "black --check --verbose ." | ||
| [tool.pdm.scripts.lint] | ||
| composite = [ | ||
| "analyze", | ||
| "format", | ||
| "sort-imports", | ||
| "type-check", | ||
| ] | ||
| [tool.pdm.scripts.sort-imports] | ||
| shell = "isort --check-only --diff --verbose ." | ||
| [tool.pdm.scripts.test] | ||
| shell = "pytest --cov=cppython --verbose tests" | ||
| [tool.pdm.scripts.type-check] | ||
| shell = "mypy ." | ||
| [tool.pytest.ini_options] | ||
| log_cli = true | ||
| testpaths = [ | ||
@@ -64,5 +92,22 @@ "tests", | ||
| profile = "black" | ||
| skip_gitignore = true | ||
| [tool.mypy] | ||
| exclude = "__pypackages__" | ||
| plugins = [ | ||
| "pydantic.mypy", | ||
| ] | ||
| strict = true | ||
| [tool.pylint.MAIN] | ||
| load-plugins = [ | ||
| "pylint.extensions.code_style", | ||
| "pylint.extensions.typing", | ||
| "pylint.extensions.docstyle", | ||
| "pylint.extensions.docparams", | ||
| "pylint.extensions.private_import", | ||
| "pylint.extensions.bad_builtin", | ||
| ] | ||
| [tool.pylint.messages_control] | ||
| disable = "C0330, C0326, logging-fstring-interpolation" | ||
| extension-pkg-whitelist = "pydantic" | ||
@@ -73,2 +118,12 @@ | ||
| [tool.pylint.parameter_documentation] | ||
| accept-no-param-doc = false | ||
| accept-no-raise-doc = false | ||
| accept-no-return-doc = false | ||
| accept-no-yields-doc = false | ||
| default-docstring-type = "google" | ||
| [tool.coverage.report] | ||
| skip_empty = true | ||
| [build-system] | ||
@@ -75,0 +130,0 @@ build-backend = "pdm.pep517.api" |
+1
-1
| # CPPython | ||
| A library for managing dependencies with CMake for C++ projects. | ||
| A Python management solution for C++ dependencies |
@@ -0,4 +1,3 @@ | ||
| """Test the integrations related to the internal interface implementation and the 'Interface' interface itself | ||
| """ | ||
| Test the integrations related to the internal interface implementation and the 'Interface' interface itself | ||
| """ | ||
@@ -12,12 +11,15 @@ import pytest | ||
| class TestCLIInterface(InterfaceIntegrationTests): | ||
| """ | ||
| The tests for our CLI interface | ||
| """ | ||
| class TestCLIInterface(InterfaceIntegrationTests[ConsoleInterface]): | ||
| """The tests for our CLI interface""" | ||
| @pytest.fixture(name="interface") | ||
| def fixture_interface(self): | ||
| """ | ||
| Override of the plugin provided interface fixture. | ||
| def fixture_interface( | ||
| self, interface_type: type[ConsoleInterface], interface_configuration: InterfaceConfiguration | ||
| ) -> ConsoleInterface: | ||
| """Override of the plugin provided interface fixture. | ||
| Args: | ||
| interface_type: The input interface type | ||
| interface_configuration: Interface configuration for construction | ||
| Returns: | ||
@@ -24,0 +26,0 @@ ConsoleInterface -- The Interface object to use for the CPPython defined tests |
@@ -0,78 +1,35 @@ | ||
| """Test the functions related to the internal interface implementation and the 'Interface' interface itself | ||
| """ | ||
| Test the functions related to the internal interface implementation and the 'Interface' interface itself | ||
| """ | ||
| from typing import Type | ||
| import pytest | ||
| from click.testing import CliRunner | ||
| from cppython_core.schema import PEP621, CPPythonData, PyProject, TargetEnum, ToolData | ||
| from pytest_cppython.plugin import InterfaceUnitTests | ||
| from pytest_mock.plugin import MockerFixture | ||
| from cppython.console.interface import Config, ConsoleInterface, cli | ||
| from cppython.schema import API | ||
| from cppython.console.interface import Configuration, ConsoleInterface, cli | ||
| from tests.data.fixtures import CPPythonProjectFixtures | ||
| default_pep621 = PEP621(name="test_name", version="1.0") | ||
| default_cppython_data = CPPythonData(target=TargetEnum.EXE) | ||
| default_tool_data = ToolData(cppython=default_cppython_data) | ||
| default_pyproject = PyProject(project=default_pep621, tool=default_tool_data) | ||
| class TestCLIInterface(CPPythonProjectFixtures, InterfaceUnitTests[ConsoleInterface]): | ||
| """The tests for our CLI interface""" | ||
| class TestCLIInterface(InterfaceUnitTests): | ||
| """ | ||
| The tests for our CLI interface | ||
| """ | ||
| @pytest.fixture(name="interface_type") | ||
| def fixture_interface_type(self) -> type[ConsoleInterface]: | ||
| """A required testing hook that allows type generation | ||
| @pytest.fixture(name="interface_type") | ||
| def fixture_interface_type(self) -> Type[ConsoleInterface]: | ||
| Returns: | ||
| The interface type | ||
| """ | ||
| A required testing hook that allows type generation | ||
| """ | ||
| return ConsoleInterface | ||
| # Grab the API methods and parameterize them for automatic testing of the entry_points | ||
| method_list = [func for func in dir(API) if callable(getattr(API, func)) and not func.startswith("__")] | ||
| def test_config(self) -> None: | ||
| """Verify that the configuration object can be constructed""" | ||
| @pytest.mark.parametrize("command", method_list) | ||
| def test_command(self, command: str, mocker: MockerFixture): | ||
| """ | ||
| _summary_ | ||
| Configuration() | ||
| Arguments: | ||
| command {str} -- The CLI command with the same name as the CPPython API call | ||
| mocker {MockerFixture} -- pytest-mock fixture | ||
| """ | ||
| # Patch the project initialization | ||
| mocker.patch("cppython.project.Project.__init__", return_value=None) | ||
| def test_verbosity(self) -> None: | ||
| """Test that verbosity is passed through to the CLI""" | ||
| # Patch the reading of data | ||
| mocker.patch("cppython.console.interface._create_pyproject", return_value=default_pyproject) | ||
| config = Configuration() | ||
| config = Config() | ||
| # Patch out the implementation | ||
| mocked_command = mocker.patch(f"cppython.project.Project.{command}") | ||
| runner = CliRunner() | ||
| result = runner.invoke(cli, [command], obj=config, catch_exceptions=False) | ||
| assert result.exit_code == 0 | ||
| mocked_command.assert_called_once() | ||
| def test_config(self): | ||
| """ | ||
| TODO | ||
| """ | ||
| Config() | ||
| def test_verbosity(self): | ||
| """ | ||
| TODO | ||
| """ | ||
| config = Config() | ||
| runner = CliRunner() | ||
| result = runner.invoke(cli, "-v info", obj=config, catch_exceptions=False) | ||
@@ -79,0 +36,0 @@ |
+84
-162
@@ -0,94 +1,92 @@ | ||
| """Test the functions related to the internal interface implementation and the 'Interface' interface itself | ||
| """ | ||
| Test the functions related to the internal interface implementation and the 'Interface' interface itself | ||
| """ | ||
| from __future__ import annotations | ||
| from pathlib import Path | ||
| from logging import getLogger | ||
| from typing import Any | ||
| from cppython_core.schema import ( | ||
| PEP621, | ||
| ConfigurePreset, | ||
| CPPythonData, | ||
| CPPythonDataResolved, | ||
| Generator, | ||
| GeneratorConfiguration, | ||
| PEP621Resolved, | ||
| ProjectConfiguration, | ||
| ProviderConfiguration, | ||
| PyProject, | ||
| ToolData, | ||
| ) | ||
| from pytest_cppython.mock import MockGenerator, MockGeneratorData | ||
| from pytest_cppython.mock import MockProvider, MockProviderData | ||
| from pytest_mock import MockerFixture | ||
| from cppython.builder import Builder | ||
| from cppython.project import Project, ProjectConfiguration | ||
| from cppython.utility import read_json, write_json | ||
| from cppython.project import Project | ||
| from tests.data.fixtures import CPPythonProjectFixtures | ||
| default_pep621 = PEP621(name="test_name", version="1.0") | ||
| default_cppython_data = CPPythonData() | ||
| default_tool_data = ToolData(**{"cppython": default_cppython_data}) | ||
| default_pyproject = PyProject(**{"project": default_pep621, "tool": default_tool_data}) | ||
| mocked_pyproject = default_pyproject.dict(by_alias=True) | ||
| mocked_pyproject["tool"]["cppython"]["mock"] = MockGeneratorData() | ||
| class MockExtendedCPPython(CPPythonData): | ||
| """Mocked extended data for comparison verification""" | ||
| mock: MockProviderData | ||
| class ExtendedCPPython(CPPythonData): | ||
| """ | ||
| TODO | ||
| """ | ||
| mock: MockGeneratorData | ||
| class TestProject(CPPythonProjectFixtures): | ||
| """Grouping for Project class testing""" | ||
| def test_construction_without_plugins( | ||
| self, mocker: MockerFixture, project: PyProject, workspace: ProjectConfiguration | ||
| ) -> None: | ||
| """Verification that no error is thrown and output is gracefully handled if no provider plugins are found | ||
| class TestProject: | ||
| """ | ||
| TODO | ||
| """ | ||
| def test_construction_without_plugins(self, mocker: MockerFixture): | ||
| Args: | ||
| mocker: Mocking fixture for interface mocking | ||
| project: PyProject data to construct with | ||
| workspace: Temporary workspace for path resolution | ||
| """ | ||
| TODO | ||
| """ | ||
| interface_mock = mocker.MagicMock() | ||
| configuration = ProjectConfiguration(pyproject_file=Path("pyproject.toml"), version="1.0.0") | ||
| Project(configuration, interface_mock, default_pyproject.dict(by_alias=True)) | ||
| Project(workspace, interface_mock, project.dict(by_alias=True)) | ||
| def test_construction_with_plugins(self, mocker: MockerFixture): | ||
| def test_construction_with_plugins( | ||
| self, mocker: MockerFixture, workspace: ProjectConfiguration, mock_project: dict[str, Any] | ||
| ) -> None: | ||
| """Verification of full construction with mock provider plugin | ||
| Args: | ||
| mocker: Mocking fixture for interface mocking | ||
| workspace: Temporary workspace for path resolution | ||
| mock_project: PyProject data to construct with | ||
| """ | ||
| TODO | ||
| """ | ||
| mocked_plugin_list = [MockGenerator] | ||
| mocker.patch("cppython.builder.Builder.gather_plugins", return_value=mocked_plugin_list) | ||
| mocked_plugin_list = [MockProvider] | ||
| mocker.patch("cppython.builder.Builder.discover_providers", return_value=mocked_plugin_list) | ||
| interface_mock = mocker.MagicMock() | ||
| configuration = ProjectConfiguration(pyproject_file=Path("pyproject.toml"), version="1.0.0") | ||
| Project(configuration, interface_mock, mocked_pyproject) | ||
| Project(workspace, interface_mock, mock_project) | ||
| class TestBuilder: | ||
| """ | ||
| TODO | ||
| """ | ||
| class TestBuilder(CPPythonProjectFixtures): | ||
| """Tests of builder steps""" | ||
| def test_plugin_gather(self): | ||
| def test_plugin_gather(self, workspace: ProjectConfiguration) -> None: | ||
| """Verifies that provider discovery works with no results | ||
| Args: | ||
| workspace: Temporary workspace for path resolution | ||
| """ | ||
| TODO | ||
| """ | ||
| configuration = ProjectConfiguration(pyproject_file=Path("pyproject.toml"), version="1.0.0") | ||
| builder = Builder(configuration) | ||
| plugins = builder.gather_plugins(Generator) | ||
| builder = Builder(workspace, getLogger()) | ||
| plugins = builder.discover_providers() | ||
| assert len(plugins) == 0 | ||
| def test_generator_data_construction(self, mocker: MockerFixture): | ||
| def test_provider_data_construction( | ||
| self, mocker: MockerFixture, workspace: ProjectConfiguration, project: PyProject | ||
| ) -> None: | ||
| """Tests that the input data for providers can be constructed | ||
| Args: | ||
| mocker: Mocking fixture for interface mocking | ||
| workspace: Temporary workspace for path resolution | ||
| project: PyProject data to construct with | ||
| """ | ||
| TODO | ||
| """ | ||
| configuration = ProjectConfiguration(pyproject_file=Path("pyproject.toml"), version="1.0.0") | ||
| builder = Builder(configuration) | ||
| builder = Builder(workspace, getLogger()) | ||
| model_type = builder.generate_model([]) | ||
@@ -98,11 +96,11 @@ | ||
| generator_type = mocker.Mock() | ||
| generator_type.name.return_value = "mock" | ||
| generator_type.data_type.return_value = MockGeneratorData | ||
| provider_type = mocker.Mock() | ||
| provider_type.name.return_value = "mock" | ||
| provider_type.data_type.return_value = MockProviderData | ||
| model_type = builder.generate_model([generator_type]) | ||
| model_type = builder.generate_model([provider_type]) | ||
| project_data = default_pyproject.dict(by_alias=True) | ||
| project_data = project.dict(by_alias=True) | ||
| mock_data = MockGeneratorData() | ||
| mock_data = MockProviderData() | ||
| project_data["tool"]["cppython"]["mock"] = mock_data.dict(by_alias=True) | ||
@@ -113,115 +111,39 @@ result = model_type(**project_data) | ||
| assert result.tool.cppython is not None | ||
| assert result.tool.cppython.mock is not None | ||
| def test_generator_creation(self, mocker: MockerFixture): | ||
| def test_provider_creation( | ||
| self, mocker: MockerFixture, workspace: ProjectConfiguration, pep621: PEP621, cppython: CPPythonData | ||
| ) -> None: | ||
| """Test that providers can be created with the mock data available | ||
| Args: | ||
| mocker: Mocking fixture for interface mocking | ||
| workspace: Temporary workspace for path resolution | ||
| pep621: One of many parameterized Project data tables | ||
| cppython: One of many parameterized CPPython data tables | ||
| """ | ||
| TODO | ||
| """ | ||
| configuration = ProjectConfiguration(pyproject_file=Path("pyproject.toml"), version="1.0.0") | ||
| builder = Builder(configuration) | ||
| builder = Builder(workspace, getLogger()) | ||
| generator_configuration = GeneratorConfiguration(root_directory=configuration.pyproject_file.parent) | ||
| provider_configuration = ProviderConfiguration(root_directory=workspace.pyproject_file.parent) | ||
| resolved = builder.generate_resolved_cppython_model([]) | ||
| generators = builder.create_generators( | ||
| [], | ||
| configuration, | ||
| generator_configuration, | ||
| default_pep621.resolve(configuration), | ||
| default_cppython_data.resolve(resolved, configuration), | ||
| ) | ||
| assert not generators | ||
| provider_type = mocker.Mock() | ||
| provider_type.name.return_value = "mock" | ||
| provider_type.data_type.return_value = MockProviderData | ||
| generator_type = mocker.Mock() | ||
| generator_type.name.return_value = "mock" | ||
| generator_type.data_type.return_value = MockGeneratorData | ||
| mock_data = MockGeneratorData() | ||
| extended_cppython_dict = default_cppython_data.dict(exclude_defaults=True) | ||
| mock_data = MockProviderData() | ||
| extended_cppython_dict = cppython.dict(by_alias=True) | ||
| extended_cppython_dict["mock"] = mock_data | ||
| extended_cppython = ExtendedCPPython(**extended_cppython_dict) | ||
| extended_cppython = MockExtendedCPPython(**extended_cppython_dict) | ||
| resolved = builder.generate_resolved_cppython_model([generator_type]) | ||
| resolved = builder.generate_resolved_cppython_model([provider_type]) | ||
| generators = builder.create_generators( | ||
| [generator_type], | ||
| configuration, | ||
| generator_configuration, | ||
| default_pep621.resolve(configuration), | ||
| extended_cppython.resolve(resolved, configuration), | ||
| providers = builder.create_providers( | ||
| [provider_type], | ||
| workspace, | ||
| provider_configuration, | ||
| (pep621.resolve(workspace), extended_cppython.resolve(resolved, workspace)), | ||
| ) | ||
| assert len(generators) == 1 | ||
| def test_presets(self, tmp_path: Path): | ||
| """ | ||
| TODO | ||
| """ | ||
| # Write a dummy file for the config | ||
| test_file = tmp_path / "pyproject.toml" | ||
| test_file.write_text("Test File", encoding="utf-8") | ||
| configuration = ProjectConfiguration(pyproject_file=test_file, version="1.0.0") | ||
| builder = Builder(configuration) | ||
| input_toolchain = tmp_path / "input.cmake" | ||
| with open(input_toolchain, "w", encoding="utf8") as file: | ||
| file.write("") | ||
| configure_preset = ConfigurePreset(name="test_preset", toolchainFile=str(input_toolchain)) | ||
| generator_output = [("test", configure_preset)] | ||
| builder.write_presets(tmp_path, generator_output) | ||
| cppython_tool = tmp_path / "cppython" | ||
| assert cppython_tool.exists() | ||
| cppython_file = cppython_tool / "cppython.json" | ||
| assert cppython_file.exists() | ||
| test_tool = cppython_tool / "test" | ||
| assert test_tool.exists() | ||
| test_file = test_tool / "test.json" | ||
| assert test_file.exists() | ||
| def test_root_unmodified(self, tmp_path: Path): | ||
| """ | ||
| TODO | ||
| """ | ||
| # Write a dummy file for the config | ||
| test_file = tmp_path / "pyproject.toml" | ||
| test_file.write_text("Test File", encoding="utf-8") | ||
| configuration = ProjectConfiguration(pyproject_file=test_file, version="1.0.0") | ||
| builder = Builder(configuration) | ||
| # TODO: Translate into reuseable testing data | ||
| output = { | ||
| "version": 4, | ||
| "cmakeMinimumRequired": {"major": 3, "minor": 23, "patch": 1}, | ||
| "include": ["should/be/replaced/cppython.json"], | ||
| "configurePresets": [ | ||
| { | ||
| "name": "default", | ||
| "inherits": ["cppython"], | ||
| "hidden": True, | ||
| "description": "Tests that generator isn't removed", | ||
| "generator": "Should exist", | ||
| }, | ||
| ], | ||
| } | ||
| input_preset = tmp_path / "CMakePresets.json" | ||
| write_json(input_preset, output) | ||
| builder.write_root_presets(tmp_path / "test_location") | ||
| data = read_json(input_preset) | ||
| # TODO: Assert the differences affect nothing but what is written by the builder | ||
| assert len(providers) == 1 |
| """ | ||
| TODO | ||
| """ | ||
| from abc import ABC, abstractmethod | ||
| from pathlib import Path | ||
| from packaging.version import Version | ||
| class VCS(ABC): | ||
| """ | ||
| Base class for version control systems | ||
| """ | ||
| subclasses = [] | ||
| def __init_subclass__(cls, **kwargs): | ||
| super().__init_subclass__(**kwargs) | ||
| cls.subclasses.append(cls) | ||
| @abstractmethod | ||
| def is_repository(self, path: Path) -> bool: | ||
| """ | ||
| TODO | ||
| """ | ||
| raise NotImplementedError() | ||
| @abstractmethod | ||
| def extract_version(self, path: Path) -> Version: | ||
| """ | ||
| TODO | ||
| """ | ||
| raise NotImplementedError() |
| """ | ||
| TODO | ||
| """ | ||
| from pathlib import Path | ||
| from dulwich.porcelain import tag_list | ||
| from dulwich.repo import Repo | ||
| from packaging.version import Version | ||
| from cppython.console.vcs.base import VCS | ||
| class Git(VCS): | ||
| """ | ||
| Git implementation hooks | ||
| """ | ||
| def is_repository(self, path: Path) -> bool: | ||
| """ | ||
| TODO | ||
| """ | ||
| try: | ||
| Repo(str(path)) | ||
| return True | ||
| except Exception: | ||
| return False | ||
| def extract_version(self, path: Path) -> Version: | ||
| """ | ||
| TODO | ||
| """ | ||
| repo = Repo(str(path)) | ||
| tags = tag_list(repo) | ||
| try: | ||
| tag = tags[-1].decode("utf-8") | ||
| except Exception: | ||
| tag = "v0.1.0" | ||
| return Version(tag) |
| """ | ||
| TODO | ||
| """ | ||
| import json | ||
| from pathlib import Path | ||
| from typing import Any, Type | ||
| from cppython_core.schema import ModelT | ||
| from pydantic import BaseModel | ||
| def read_model_json(path: Path, model: Type[ModelT]) -> ModelT: | ||
| """ | ||
| Reading routine. Only keeps Model data | ||
| """ | ||
| return model.parse_file(path=path) | ||
| def read_json(path: Path) -> Any: | ||
| """ | ||
| Reading routine | ||
| """ | ||
| with open(path, "r", encoding="utf-8") as file: | ||
| return json.load(file) | ||
| def write_model_json(path: Path, model: BaseModel) -> None: | ||
| """ | ||
| Writing routine. Only writes model data | ||
| """ | ||
| serialized = json.loads(model.json(exclude_none=True)) | ||
| with open(path, "w", encoding="utf8") as file: | ||
| json.dump(serialized, file, ensure_ascii=False, indent=4) | ||
| def write_json(path: Path, data: Any) -> None: | ||
| """ | ||
| Writing routine | ||
| """ | ||
| with open(path, "w", encoding="utf-8") as file: | ||
| json.dump(data, file, ensure_ascii=False, indent=4) |
| """ | ||
| TODO | ||
| """ | ||
| from pathlib import Path | ||
| from cppython_core.schema import CPPythonModel | ||
| from cppython.utility import read_model_json, write_model_json | ||
| class TestBuilder: | ||
| """ | ||
| TODO | ||
| """ | ||
| class ModelTest(CPPythonModel): | ||
| """ | ||
| TODO | ||
| """ | ||
| test_path: Path | ||
| test_int: int | ||
| def test_model_read_write(self, tmpdir): | ||
| """ | ||
| TODO | ||
| """ | ||
| test_model = TestBuilder.ModelTest(test_path=Path(), test_int=3) | ||
| temporary_directory = Path(tmpdir) | ||
| json_path = temporary_directory / "test.json" | ||
| write_model_json(json_path, test_model) | ||
| output = read_model_json(json_path, TestBuilder.ModelTest) | ||
| assert test_model == output |
| """ | ||
| TODO | ||
| """ | ||
| from pathlib import Path | ||
| from cppython.console.vcs.git import Git | ||
| class TestGit: | ||
| """ | ||
| TODO | ||
| """ | ||
| def test_version(self): | ||
| """ | ||
| TODO | ||
| """ | ||
| directory = Path() | ||
| git = Git() | ||
| result = git.extract_version(directory) | ||
| assert result != "" |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
23
4.55%30505
-10.99%653
-23.45%