Skip to content

ProjectBuilder + Direct Data #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions cppython/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
"""

from pathlib import Path
from typing import Type
from typing import Any, Type
from xmlrpc.client import Boolean

import click
import tomlkit
from cppython_core.schema import GeneratorDataType, Interface, PyProject
from cppython_core.schema import GeneratorDataType, Interface

from cppython.project import Project, ProjectConfiguration


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

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

# Interpret and validate data
return PyProject(**data)
return data


class Config:
Expand All @@ -39,15 +39,15 @@ class Config:
"""

def __init__(self):
self.pyproject = _create_pyproject()
self.pyproject_data = _create_pyproject()
self.interface = ConsoleInterface()
self.configuration = ProjectConfiguration()

def create_project(self) -> Project:
"""
TODO
"""
return Project(self.configuration, self.interface, self.pyproject)
return Project(self.configuration, self.interface, self.pyproject_data)


pass_config = click.make_pass_decorator(Config, ensure=True)
Expand Down
145 changes: 101 additions & 44 deletions cppython/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@

from dataclasses import dataclass
from importlib import metadata
from typing import Callable, Optional, Type, TypeVar
from typing import Any, Type, TypeVar
from xmlrpc.client import Boolean

from cppython_core.exceptions import ConfigError
from cppython_core.schema import API, Generator, Interface, Plugin, PyProject
from cppython_core.schema import (
API,
CPPythonData,
Generator,
Interface,
Plugin,
PyProject,
ToolData,
)
from pydantic import create_model


@dataclass
Expand All @@ -20,89 +28,138 @@ class ProjectConfiguration:
verbose: Boolean = False


class ProjectBuilder:
"""
TODO
"""

def __init__(self, configuration: ProjectConfiguration) -> None:
self.configuration = configuration

def gather_plugins(self, plugin_type: Type[Plugin]) -> list[Type[Plugin]]:
"""
TODO
"""
plugins = []
entry_points = metadata.entry_points(group=f"cppython.{plugin_type.plugin_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)

return plugins

def generate_model(self, plugins: list[Type[Plugin]]) -> Type[PyProject]:
"""
TODO
"""
plugin_fields = {}
for plugin_type in plugins:
plugin_fields[plugin_type.name()] = plugin_type.data_type()

ExtendedCPPythonData = create_model(
"ExtendedCPPythonData",
**plugin_fields,
__base__=CPPythonData,
)

ExtendedToolData = create_model(
"ToolData",
cppython=ExtendedCPPythonData,
__base__=ToolData,
)

return create_model(
"PyProject",
tool=ExtendedToolData,
__base__=PyProject,
)


class Project(API):
"""
The object constructed at each entry_point
"""

def __init__(self, configuration: ProjectConfiguration, interface: Interface, pyproject: PyProject) -> None:
def __init__(
self, configuration: ProjectConfiguration, interface: Interface, pyproject_data: dict[str, Any]
) -> None:

self.enabled = False
self.verbose = configuration.verbose
self.configuration = configuration

if self.verbose:
if self.configuration.verbose:
interface.print("Starting CPPython project initialization")

builder = ProjectBuilder(self.configuration)
plugins = builder.gather_plugins(Generator)

if not plugins:
if self.configuration.verbose:
interface.print("No generator plugin was found.")
return

ExtendedPyProject = builder.generate_model(plugins)
pyproject = ExtendedPyProject(**pyproject_data)

if pyproject.tool is None:
if self.verbose:
if self.configuration.verbose:
interface.print("Table [tool] is not defined")
return

if pyproject.tool.cppython is None:
if self.verbose:
if self.configuration.verbose:
interface.print("Table [tool.cppython] is not defined")
return

self.enabled = True

self._interface = interface

PluginType = TypeVar("PluginType", bound=Type[Plugin])

def find_plugin_type(plugin_type: PluginType, condition: Callable[[str], bool]) -> Optional[PluginType]:
"""
Finds the first plugin that satisfies the given condition
"""

entry_points = metadata.entry_points(group=f"cppython.{plugin_type.plugin_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):
if condition(loaded_plugin_type.name()):
return loaded_plugin_type
self._generators = []
for plugin_type in plugins:
self._generators.append(plugin_type(pyproject))

return None

plugin_type = find_plugin_type(Generator, lambda name: name == pyproject.tool.cppython.generator)

if plugin_type is None:
raise ConfigError(f"No generator plugin with the name '{pyproject.tool.cppython.generator}' was found.")

generator_data = interface.read_generator_data(plugin_type.data_type())
self._generator = plugin_type(pyproject, generator_data)

if self.verbose:
if self.configuration.verbose:
interface.print("CPPython project initialized")

def download(self):
"""
Download the generator tooling if required
"""
if not self._generator.generator_downloaded():
self._interface.print(f"Downloading the {self._generator.name()} tool")
for generator in self._generators:

# TODO: Make async with progress bar
self._generator.download_generator()
self._interface.print("Download complete")
if not generator.generator_downloaded():
self._interface.print(f"Downloading the {generator.name()} tool")

# TODO: Make async with progress bar
generator.download_generator()
self._interface.print("Download complete")

# API Contract

def install(self) -> None:
if self.enabled:
if self.verbose:
if self.configuration.verbose:
self._interface.print("CPPython: Installing...")
self.download()
self._generator.install()

for generator in self._generators:
generator.install()

def update(self) -> None:
if self.enabled:
if self.verbose:
if self.configuration.verbose:
self._interface.print("CPPython: Updating...")
self._generator.update()

for generator in self._generators:
generator.update()

def build(self) -> None:
if self.enabled:
if self.verbose:
if self.configuration.verbose:
self._interface.print("CPPython: Building...")
self._generator.build()

for generator in self._generators:
generator.build()
Loading