Skip to content

Commit

Permalink
Merge pull request #585 from canonical/work/refactor-recipe
Browse files Browse the repository at this point in the history
* refactor: add _StandardRecipe base class

This class will hold the implementation that is valid for all non-snap recipe
types. For now it's just the previous CharmRecipe - the next commit will do
the generalization work.

* refactor: generalize _StandardRecipe

Subclasses must provide the name of the artifact (charm, rock, etc) and the
launchpad object that wraps the utility to manipulate recipes.

---------

Co-authored-by: Alex Lowe <[email protected]>
  • Loading branch information
lengau authored Dec 11, 2024
2 parents 46ee540 + 668b149 commit 8357448
Showing 1 changed file with 46 additions and 24 deletions.
70 changes: 46 additions & 24 deletions craft_application/launchpad/models/recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@

import enum
import time
from abc import abstractmethod
from collections.abc import Collection, Iterable
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, ClassVar, Literal

import lazr.restfulclient.errors # type: ignore[import-untyped]
from typing_extensions import Any, Self, TypedDict, override
Expand Down Expand Up @@ -326,11 +327,17 @@ def build(
return self._build(deadline, request_build_kwargs)


class CharmRecipe(_StoreRecipe):
"""A recipe for a charm.
class _StandardRecipe(_StoreRecipe):
"""A base class for recipes with common launchpad builds."""

https://api.launchpad.net/devel.html#charm_recipe
"""
ARTIFACT: ClassVar[str]
"""The artifact type this recipe creates: charm, rock, etc."""

@classmethod
@abstractmethod
def _get_lp_recipe(cls, lp: Launchpad) -> Any: # noqa: ANN401 (use of any)
"""Get the launchpad utility to manipulate recipes."""
raise NotImplementedError("Must be implemented by subclass.")

@classmethod
def new( # noqa: PLR0913
Expand All @@ -349,27 +356,25 @@ def new( # noqa: PLR0913
store_channels: Collection[str] = ("latest/edge",),
git_ref: str | None = None,
) -> Self:
"""Create a new charm recipe.
See: https://api.launchpad.net/devel.html#charm_recipes-new
"""Create a new recipe.
:param lp: The Launchpad client to use for this recipe.
:param name: The recipe name
:param owner: The username of the person or team who owns the recipe
:param project: The name of the project to which this recipe should be attached.
:param build_path: (Optional) The path to the directory containing
charmcraft.yaml (if it's not the root directory).
the project file (if it's not the root directory).
:param git_ref: A link to a git repository and branch from which to build.
Mutually exclusive with bzr_branch.
:param auto_build: Whether to automatically build on pushes to the branch.
(Defaults to False)
:param auto_build_channels: (Optional) A dictionary of channels to use for
snaps installed in the build environment.
:param store_name: (Optional) The name in the store to which to upload this
charm.
:param store_channels: (Optional) The channels onto which to publish the charm
asset.
:param store_channels: (Optional) The channels onto which to publish the asset
if uploaded.
:returns: The Charm recipe.
:returns: The recipe.
"""
kwargs: dict[str, Any] = {}
if auto_build:
Expand All @@ -383,51 +388,53 @@ def new( # noqa: PLR0913
)
cls._fill_repo_info(kwargs, git_ref=git_ref)

charm_entry = retry(
f"create charm recipe {name!r}",
created_entry = retry(
f"create {cls.ARTIFACT} recipe {name!r}",
lazr.restfulclient.errors.BadRequest,
lp.lp.charm_recipes.new,
cls._get_lp_recipe(lp).new,
name=name,
owner=util.get_person_link(owner),
project=f"/{project}",
auto_build=auto_build,
**kwargs,
)

if not charm_entry:
raise ValueError("Failed to create charm recipe")
if not created_entry:
raise ValueError(f"Failed to create {cls.ARTIFACT} recipe")

return cls(lp, charm_entry)
return cls(lp, created_entry)

@classmethod
def get( # pyright: ignore[reportIncompatibleMethodOverride]
cls, lp: Launchpad, name: str, owner: str, project: str | None = None
) -> Self:
"""Get a charm recipe."""
"""Get a recipe."""
try:
return cls(
lp,
retry(
f"get charm recipe {name!r}",
f"get {cls.ARTIFACT} recipe {name!r}",
lazr.restfulclient.errors.NotFound,
lp.lp.charm_recipes.getByName,
cls._get_lp_recipe(lp).getByName,
name=name,
owner=util.get_person_link(owner),
project=f"/{project}",
),
)
except lazr.restfulclient.errors.NotFound:
raise ValueError(
f"Could not find charm recipe {name!r} in project {project!r} with owner {owner!r}",
f"Could not find {cls.ARTIFACT} recipe {name!r} in project {project!r} with owner {owner!r}",
) from None

@classmethod
def find( # pyright: ignore[reportIncompatibleMethodOverride]
cls, lp: Launchpad, owner: str, *, name: str = ""
) -> Iterable[Self]:
"""Find a Charm recipe by the owner."""
"""Find a recipe by the owner."""
owner = util.get_person_link(owner)
lp_recipes = lp.lp.charm_recipes.findByOwner(owner=util.get_person_link(owner))
lp_recipes = cls._get_lp_recipe(lp).findByOwner(
owner=util.get_person_link(owner)
)
for recipe in lp_recipes:
if name and recipe.name != name:
continue
Expand All @@ -443,4 +450,19 @@ def build(
return self._build(deadline, kwargs)


class CharmRecipe(_StandardRecipe):
"""A recipe for a charm.
https://api.launchpad.net/devel.html#charm_recipe
"""

ARTIFACT: ClassVar[Literal["charm"]] = "charm" # type: ignore[reportIncompatibleVariableOverride]

@override
@classmethod
def _get_lp_recipe(cls, lp: Launchpad) -> Any:
"""https://api.launchpad.net/devel.html#charm_recipes."""
return lp.lp.charm_recipes


Recipe = SnapRecipe | CharmRecipe

0 comments on commit 8357448

Please sign in to comment.