Skip to content
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

chore!: restructure plugin models #775

Merged
merged 11 commits into from
Jul 12, 2024
2 changes: 1 addition & 1 deletion craft_parts/parts.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def __init__(
project_dirs = ProjectDirs(partitions=partitions)

if not plugin_properties:
plugin_properties = PluginProperties()
plugin_properties = PluginProperties.unmarshal(data)

plugin_name: str = data.get("plugin", "")

Expand Down
6 changes: 2 additions & 4 deletions craft_parts/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@

"""Craft Parts plugins subsystem."""

from .base import Plugin, PluginModel, extract_plugin_properties
from .base import Plugin
from .plugins import (
PluginProperties,
extract_part_properties,
get_plugin,
get_plugin_class,
Expand All @@ -27,15 +26,14 @@
unregister,
unregister_all,
)
from .properties import PluginProperties
from .validator import PluginEnvironmentValidator

__all__ = [
"Plugin",
"PluginEnvironmentValidator",
"PluginModel",
"PluginProperties",
"extract_part_properties",
"extract_plugin_properties",
"get_plugin",
"get_plugin_class",
"get_registered_plugins",
Expand Down
26 changes: 6 additions & 20 deletions craft_parts/plugins/ant_plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2022 Canonical Ltd.
# Copyright 2022,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -20,45 +20,31 @@
import os
import shlex
from collections.abc import Iterator
from typing import Any, cast
from typing import Literal, cast
from urllib.parse import urlsplit

from overrides import override

from craft_parts import errors

from . import validator
from .base import JavaPlugin, PluginModel, extract_plugin_properties
from .base import JavaPlugin
from .properties import PluginProperties

logger = logging.getLogger(__name__)


class AntPluginProperties(PluginProperties, PluginModel):
class AntPluginProperties(PluginProperties, frozen=True):
"""The part properties used by the Ant plugin."""

plugin: Literal["ant"] = "ant"

ant_build_targets: list[str] = []
ant_build_file: str | None = None
ant_properties: dict[str, str] = {}

source: str

@classmethod
@override
def unmarshal(cls, data: dict[str, Any]) -> "AntPluginProperties":
"""Populate make properties from the part specification.

:param data: A dictionary containing part properties.

:return: The populated plugin properties data object.

:raise pydantic.ValidationError: If validation fails.
"""
plugin_data = extract_plugin_properties(
data, plugin_name="ant", required=["source"]
)
return cls(**plugin_data)


class AntPluginEnvironmentValidator(validator.PluginEnvironmentValidator):
"""Check the execution environment for the Ant plugin.
Expand Down
26 changes: 6 additions & 20 deletions craft_parts/plugins/autotools_plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2020 Canonical Ltd.
# Copyright 2020,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -16,39 +16,25 @@

"""The autotools plugin implementation."""

from typing import Any, cast
from typing import Literal, cast

from overrides import override

from .base import Plugin, PluginModel, extract_plugin_properties
from .base import Plugin
from .properties import PluginProperties


class AutotoolsPluginProperties(PluginProperties, PluginModel):
class AutotoolsPluginProperties(PluginProperties, frozen=True):
"""The part properties used by the autotools plugin."""

plugin: Literal["autotools"] = "autotools"

autotools_configure_parameters: list[str] = []
autotools_bootstrap_parameters: list[str] = []

# part properties required by the plugin
source: str

@classmethod
@override
def unmarshal(cls, data: dict[str, Any]) -> "AutotoolsPluginProperties":
"""Populate autotools properties from the part specification.

:param data: A dictionary containing part properties.

:return: The populated plugin properties data object.

:raise pydantic.ValidationError: If validation fails.
"""
plugin_data = extract_plugin_properties(
data, plugin_name="autotools", required=["source"]
)
return cls(**plugin_data)


class AutotoolsPlugin(Plugin):
"""The autotools plugin is used for autotools-based parts.
Expand Down
44 changes: 6 additions & 38 deletions craft_parts/plugins/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2020-2023 Canonical Ltd.
# Copyright 2020-2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -16,13 +16,15 @@

"""Plugin base class and definitions."""

from __future__ import annotations

import abc
from copy import deepcopy
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING

from craft_parts.actions import ActionProperties

from .properties import PluginProperties, PluginPropertiesModel
from .properties import PluginProperties
from .validator import PluginEnvironmentValidator

if TYPE_CHECKING:
Expand All @@ -47,7 +49,7 @@ class Plugin(abc.ABC):
"""Plugins that can run in 'strict' mode must set this classvar to True."""

def __init__(
self, *, properties: PluginProperties, part_info: "infos.PartInfo"
self, *, properties: PluginProperties, part_info: infos.PartInfo
) -> None:
self._options = properties
self._part_info = part_info
Expand Down Expand Up @@ -120,37 +122,3 @@ def _get_java_post_build_commands(self) -> list[str]:
# pylint: enable=line-too-long

return link_java + link_jars


class PluginModel(PluginPropertiesModel):
"""Model for plugins using pydantic validation.

Plugins with configuration properties can use pydantic validation to unmarshal
data from part specs. In this case, extract plugin-specific properties using
the :func:`extract_plugin_properties` helper.
"""


def extract_plugin_properties(
data: dict[str, Any], *, plugin_name: str, required: list[str] | None = None
) -> dict[str, Any]:
"""Obtain plugin-specific entries from part properties.

Plugin-specific properties must be prefixed with the name of the plugin.

:param data: A dictionary containing all part properties.
:plugin_name: The name of the plugin.

:return: A dictionary with plugin properties.
"""
if required is None:
required = []

plugin_data: dict[str, Any] = {}
prefix = f"{plugin_name}-"

for key, value in data.items():
if key.startswith(prefix) or key in required:
plugin_data[key] = value

return plugin_data
26 changes: 6 additions & 20 deletions craft_parts/plugins/cmake_plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2020-2022 Canonical Ltd.
# Copyright 2020-2022,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -16,39 +16,25 @@

"""The cmake plugin."""

from typing import Any, cast
from typing import Literal, cast

from overrides import override

from .base import Plugin, PluginModel, extract_plugin_properties
from .base import Plugin
from .properties import PluginProperties


class CMakePluginProperties(PluginProperties, PluginModel):
class CMakePluginProperties(PluginProperties, frozen=True):
"""The part properties used by the cmake plugin."""

plugin: Literal["cmake"] = "cmake"

cmake_parameters: list[str] = []
cmake_generator: str = "Unix Makefiles"

# part properties required by the plugin
source: str

@classmethod
@override
def unmarshal(cls, data: dict[str, Any]) -> "CMakePluginProperties":
"""Populate class attributes from the part specification.

:param data: A dictionary containing part properties.

:return: The populated plugin properties data object.

:raise pydantic.ValidationError: If validation fails.
"""
plugin_data = extract_plugin_properties(
data, plugin_name="cmake", required=["source"]
)
return cls(**plugin_data)


class CMakePlugin(Plugin):
"""The cmake plugin is useful for building cmake based parts.
Expand Down
26 changes: 6 additions & 20 deletions craft_parts/plugins/dotnet_plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2021 Canonical Ltd.
# Copyright 2021,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -17,42 +17,28 @@
"""The Dotnet plugin."""

import logging
from typing import Any, cast
from typing import Literal, cast

from overrides import override

from . import validator
from .base import Plugin, PluginModel, extract_plugin_properties
from .base import Plugin
from .properties import PluginProperties

logger = logging.getLogger(__name__)


class DotnetPluginProperties(PluginProperties, PluginModel):
class DotnetPluginProperties(PluginProperties, frozen=True):
"""The part properties used by the Dotnet plugin."""

plugin: Literal["dotnet"] = "dotnet"

dotnet_build_configuration: str = "Release"
dotnet_self_contained_runtime_identifier: str | None = None

# part properties required by the plugin
source: str

@classmethod
@override
def unmarshal(cls, data: dict[str, Any]) -> "DotnetPluginProperties":
"""Populate make properties from the part specification.

:param data: A dictionary containing part properties.

:return: The populated plugin properties data object.

:raise pydantic.ValidationError: If validation fails.
"""
plugin_data = extract_plugin_properties(
data, plugin_name="dotnet", required=["source"]
)
return cls(**plugin_data)


class DotPluginEnvironmentValidator(validator.PluginEnvironmentValidator):
"""Check the execution environment for the Dotnet plugin.
Expand Down
24 changes: 5 additions & 19 deletions craft_parts/plugins/dump_plugin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2020 Canonical Ltd.
# Copyright 2020,2024 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -19,33 +19,19 @@
This plugin just dumps the content from a specified part source.
"""

from typing import Any
from typing import Literal

from overrides import override

from .base import Plugin
from .properties import PluginProperties


class DumpPluginProperties(PluginProperties):
class DumpPluginProperties(PluginProperties, frozen=True):
"""The part properties used by the dump plugin."""

@classmethod
@override
def unmarshal(cls, data: dict[str, Any]) -> "DumpPluginProperties":
"""Populate dump properties from the part specification.

'source' is a required part property.

:param data: A dictionary containing part properties.

:return: The populated plugin properties data object.

:raise ValueError: If a required property is not found.
"""
if "source" not in data:
raise ValueError("'source' is required by the dump plugin")
return cls()
plugin: Literal["dump"] = "dump"
source: str


class DumpPlugin(Plugin):
Expand Down
Loading
Loading