-
Notifications
You must be signed in to change notification settings - Fork 95
Implementation of an Ownership factory #3072
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
cc50b87
f5a9e80
37fc786
5991123
37e29e9
7c21f78
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
from __future__ import annotations | ||
import logging | ||
from abc import ABC, abstractmethod | ||
from collections.abc import Callable, Iterable, Sequence | ||
from dataclasses import dataclass | ||
from datetime import timedelta | ||
from functools import cached_property | ||
from typing import Generic, TypeVar, final | ||
from typing import Generic, TypeVar, final, Any | ||
|
||
from databricks.labs.blueprint.paths import WorkspacePath | ||
from databricks.sdk import WorkspaceClient | ||
|
@@ -169,8 +171,21 @@ def get_workspace_administrator(self) -> str: | |
class Ownership(ABC, Generic[Record]): | ||
"""Determine an owner for a given type of object.""" | ||
|
||
_ownerships: set[Ownership] = set() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the type annotation here is quite correct: it needs to be |
||
|
||
@classmethod | ||
def for_record(cls, record: Any) -> Ownership[Record]: | ||
for ownership in cls._ownerships: | ||
if ownership.is_applicable_to(record): | ||
return ownership | ||
raise ValueError(f"Ownership not implemented or not registered for {type(record).__name__}") | ||
Comment on lines
+176
to
+181
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Elsewhere I've commented on this API, and whether it should take the type of the record instead of an instance. I'd really like this method to have a docstring. The current design allows for some ambiguous corner cases like multiple registered instances supporting a given record. (This remains even if we switch to type-based lookups.) |
||
|
||
def __init__(self, administrator_locator: AdministratorLocator) -> None: | ||
self._administrator_locator = administrator_locator | ||
self._ownerships.add(self) | ||
|
||
@abstractmethod | ||
def is_applicable_to(self, record: Any) -> bool: ... | ||
Comment on lines
+187
to
+188
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From a design perspective, I think I'd prefer this to take the type of the record as an argument rather than an instance thereof. The reason for this is that it would then encourage the caller to get the encoder and re-use it for a collection, which is the usual use-case. As it stands the current API encourages the lookup for each record within a loop over a collection, and the lookups are reasonably expensive due to the iteration required. I'm on the fence about whether it should be a class method or not, although leaning towards a class method being more accurate. (For singletons, which these are, I agree that the distinction is more academic than practical.) Assuming the current API, I think a default implementation is possible (and preferable) here:
(If we switch to accepting the type rather than an instance then the details obviously change.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I missed this comment indicating that we want to allow instances to support multiple types. That said, I don't fully follow the reasoning: type-based lookups can also support multiple instance types ( |
||
|
||
@final | ||
def owner_of(self, record: Record) -> str: | ||
|
@@ -201,6 +216,9 @@ def __init__(self, administrator_locator: AdministratorLocator, ws: WorkspaceCli | |
super().__init__(administrator_locator) | ||
self._ws = ws | ||
|
||
def is_applicable_to(self, record: Any) -> bool: | ||
return isinstance(record, WorkspacePath) | ||
|
||
def owner_of_path(self, path: str) -> str: | ||
return self.owner_of(WorkspacePath(self._ws, path)) | ||
|
||
|
@@ -242,14 +260,22 @@ def _infer_from_first_can_manage(object_permissions): | |
return None | ||
|
||
|
||
class LegacyQueryOwnership(Ownership[str]): | ||
@dataclass | ||
class LegacyQueryPath: | ||
path: str | ||
|
||
|
||
class LegacyQueryOwnership(Ownership[LegacyQueryPath]): | ||
def __init__(self, administrator_locator: AdministratorLocator, workspace_client: WorkspaceClient) -> None: | ||
super().__init__(administrator_locator) | ||
self._workspace_client = workspace_client | ||
|
||
def _maybe_direct_owner(self, record: str) -> str | None: | ||
def is_applicable_to(self, record: Any) -> bool: | ||
return isinstance(record, LegacyQueryPath) | ||
|
||
def _maybe_direct_owner(self, record: LegacyQueryPath) -> str | None: | ||
try: | ||
legacy_query = self._workspace_client.queries.get(record) | ||
legacy_query = self._workspace_client.queries.get(record.path) | ||
return legacy_query.owner_user_name | ||
except NotFound: | ||
return 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.
As things stand during tests we re-create the application context for each test that needs one, and this is going to lead to the registry accumulating instances for each test that hits this property.
For this reason (and others) in the past we've preferred to avoid global singletons (which the class-hosted registry is), and move the cache to be owned and managed outside of it so that the lifecycle of the cache is tied to the application context.