Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

cppython

Package Overview
Dependencies
Maintainers
1
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cppython - pypi Package Compare versions

Comparing version
0.7.1.dev34
to
0.7.1.dev35
+56
cppython/data.py
"""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)

@@ -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 @@

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
[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