-
Notifications
You must be signed in to change notification settings - Fork 13
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
feat!(services): add a project service #646
Conversation
3736290
to
5c62503
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cmatsuoka this is the bulk of the actual change to the business logic. It (and its related test) is already reviewable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good so far.
This makes the ServiceFactory no longer a dataclass. It's only a dataclass for historical reasons around the old way of registering services, but this is no longer necessary.
We don't always have that call at the beginning of the interactions.
Removes the ability to use secrets. This can be added to applications if necessary using the prototype secrets service.
40d2762
to
9502d62
Compare
docs/reference/services/index.rst
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be a bit much infrastructure for now, but I'd rather have it than need to make forwards later.
self._project_dir = project_dir | ||
self._project_model = None | ||
|
||
def resolve_project_file_path(self) -> pathlib.Path: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please review these docstrings both as-is and in the context of the autodocs.
|
||
|
||
class ProjectService(base.AppService): | ||
"""A service for handling access to the project.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this to be scant but as I tried to write something up, I could not think of anything meaningful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's why I didn't include more. Every time I tried to add something it seemed redundant to the documentation page.
Maybe a reference to the documentation page is what I should add?
The application must use the provided parameters rather than service state | ||
when applying ``build_on``, ``build_for`` or ``platform`` information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs rewording, or expanding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might need removing (and replacing). My first implementation of the ProjectService had those three as instance attributes as well, but this ended up complicating the implementation. I'll rewrite to include some real-world use cases.
docs/reference/services/project.rst
Outdated
--------------- | ||
|
||
The project service is responsible for loading, validating and rendering the project | ||
file to a ``Project`` model. While the service can render a project as though it is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you reference Project even if it is the base model?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed it, though the rendered version won't change because we don't include the class's reference in docs. I've created #662 for that.
arch=build_on, | ||
target_arch=str(build_for), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why one needs str()
and another one does not ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
build_for
used to be a DebianArchitecture
or the string all
. The type change is no longer needed.
raise RuntimeError( | ||
"Somehow the host architecture is all Debian architectures. " | ||
"My universe is shattered; I give up." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if this is very unlikely, we should have a dedicated error for that, that Host architecture couldn't be resolved, so we can't build for "all".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need a dedicated error, because this is an error in the application. The application would need to provide a version of craft_platforms
that only has a single value in DebianArchitecture
that matches. I'll rewrite the message to be less prototype-y though.
@@ -257,6 +256,8 @@ def get(self, service: Literal["package"]) -> services.PackageService: ... | |||
@overload | |||
def get(self, service: Literal["lifecycle"]) -> services.LifecycleService: ... | |||
@overload | |||
def get(self, service: Literal["project"]) -> ProjectService: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not services.ProjectService
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eventually I want to remove the individual services from the services
module directly so we don't even have to load built-in services if we don't use them. ProjectService
only gets imported here in type-checking mode or when actually requesting it.
# Workaround until we implement CRAFT-4159 | ||
app._bootstrap_services() # We need to access the project service. | ||
app.services.get("project").render_once() | ||
if date.today() < date(2025, 3, 1): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a good idea to put here 2 weeks deadline 😄 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll have to extend it if I don't finish the build plan service by the end of the pulse, but I'd rather extend it than have it accidentally go into main.
@@ -591,6 +592,7 @@ def test_lifecycle_package_repositories( | |||
platform=None, | |||
build_plan=fake_build_plan, | |||
) | |||
service.setup() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we need to setup it now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because setup
gets the project.
@@ -251,8 +251,7 @@ def test_get_unregistered_service(factory): | |||
|
|||
|
|||
def test_get_project_service_error(factory): | |||
factory.project = None | |||
with pytest.raises(ValueError, match="LifecycleService requires a project"): | |||
with pytest.raises(RuntimeError, match="Project not rendered yet."): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a good error ? It is suggesting that it will render at some point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what better wording would be appropriate. Maybe "attempted to get the project before it was rendered"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea and how it looks like.
The comments I left are mostly to clarify certain aspects and to see if I understand it correctly.
From what I can see, it shouldn't affect how the commands that conditionally require project works and for the others change is rather transparent (even if non backward-compatible)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great :) the review script is specially appreciated! I think this is overall "there" but I had some questions before approving the PR
thanks!
craft_application/application.py
Outdated
@@ -300,6 +301,17 @@ def cache_dir(self) -> pathlib.Path: | |||
f"Unable to create/access cache directory: {err.strerror}" | |||
) from err | |||
|
|||
def _bootstrap_services(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to suggest _configure_early_services()
, so that the difference between it and _configure_services
is more evident
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea!
craft_application/application.py
Outdated
@@ -332,6 +344,9 @@ def _resolve_project_path(self, project_dir: pathlib.Path | None) -> pathlib.Pat | |||
directory. Applications may wish to override this if the project file could be | |||
in multiple places within the project directory. | |||
""" | |||
warnings.warn( | |||
DeprecationWarning("NEEDS REMOVING BEFORE MERGE TO MAIN"), stacklevel=1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this being removed on a later task?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this! At first I thought it would need to stay until a future task, but it looks like I have indeed removed all usages.
project_service = self.services.get("project") | ||
# This branch always runs, except during testing. | ||
if not project_service.is_rendered: | ||
project_service.render_once() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about the platform
and build_for
parameters? How do they end up in the service in this specific code path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, thanks!
) | ||
|
||
@final | ||
def get_platforms(self) -> dict[str, craft_platforms.PlatformDict]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how come this method is public? Who, do you figure, will need to use this? Other than tests I mean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The BuildPlanService uses it to generate the build plan. This is also the primary reason for having _app_render_legacy_platforms()
- the BuildPlanService expects to get a platforms
entry that craft-platforms can use to generate the exhaustive build plan.
@@ -337,21 +337,6 @@ def _configure_services(self, provider_name: str | None) -> None: | |||
session_policy=self._fetch_service_policy, | |||
) | |||
|
|||
def _resolve_project_path(self, project_dir: pathlib.Path | None) -> pathlib.Path: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to double-check: is snapcraft expected to just drop the override
on their version? Or are there more changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Snapcraft will need to move that override to the ProjectService's `resolve_project_file_path()'. Should work roughly the same though (just has to use the class attribute for project dir rather than a method arg)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My concern is that Snapcraft calls that one extremely early, before even _configure_early_services()
has been called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is worth sweating too much just now; since this is going to a feature branch we'll have time to assess the changes in Snapcraft before setting anything in stone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
Regarding the changelog: Since there are more changes incoming, do you want to draft the changes for the project service now, or do a unified changelog entry once the feature-branch is ready to be merged to main? |
@tigarmo to your point about the changelog: I'll be requesting a full documentation review before merge to main, so I'm not requesting it right now on the feature branch. However, I have added the relevant changes so I don't forget them. Thanks! |
make lint && make test
?docs/reference/changelog.rst
)?This creates a ProjectService for handling projects. There are a few tests that this causes issues with because it messes with build plans, so those are being skipped until I implement the BuildPlanService.
This is pretty huge, so I'm providing a review methodology. I'd recommend marking each file as viewed as you complete it, even if you have comments, because there's a "draw the rest of the owl" step at the end.
CRAFT-4158