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

Update item to hold target #642

Merged
merged 7 commits into from
Dec 13, 2024
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
35 changes: 18 additions & 17 deletions asab/library/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@

@dataclasses.dataclass
class LibraryItem:
"""
The data class that contains the info about a specific item in the library.
"""
The data class that contains the info about a specific item in the library.

Attributes:
name (str): The absolute path of the Item. It can be directly fed into `LibraryService.read(...)`.
type (str): Can be either `dir` if the Item is a directory or `item` if Item is of any other type.
layer (int): The number of highest layer in which this Item is found. The higher the number, the lower the layer is.
providers (list): List of `LibraryProvider` objects containing this Item.
disabled (bool): `True` if the Item is disabled, `False` otherwise. If the Item is disabled, `LibraryService.read(...)` will return `None`.
override (int): If `True`, this item is marked as an override for the providers with the same Item name.
"""

name: str
type: str
layer: int
providers: list
disabled: bool = False
override: int = 0 # Default value for override is False
Attributes:
name (str): The absolute path of the Item. It can be directly fed into `LibraryService.read(...)`.
type (str): Can be either `dir` if the Item is a directory or `item` if Item is of any other type.
layer (int): The number of highest layer in which this Item is found. The higher the number, the lower the layer is.
providers (list): List of `LibraryProvider` objects containing this Item.
disabled (bool): `True` if the Item is disabled, `False` otherwise. If the Item is disabled, `LibraryService.read(...)` will return `None`.
override (int): If `True`, this item is marked as an override for the providers with the same Item name.
target (str): Specifies the target context, e.g., "tenant" or "global". Defaults to "global".
"""
name: str
type: str
layer: int
providers: list
disabled: bool = False
override: int = 0
target: str = "global" # Default to "global" if not tenant-specific
25 changes: 17 additions & 8 deletions asab/library/providers/zookeeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ async def list(self, path: str) -> list:
tenant_node_path = self.build_path(path, tenant_specific=True)
if tenant_node_path != global_node_path:
tenant_nodes = await self.Zookeeper.get_children(tenant_node_path) or []
tenant_items = await self.process_nodes(tenant_nodes, path)
tenant_items = await self.process_nodes(tenant_nodes, path, target="tenant")
else:
tenant_items = []
# Combine items, with tenant items taking precedence over global ones
Expand All @@ -295,32 +295,41 @@ async def list(self, path: str) -> list:

return list(combined_items.values())

async def process_nodes(self, nodes, base_path):
async def process_nodes(self, nodes, base_path, target="global"):
"""
Processes a list of nodes and creates corresponding LibraryItem objects.

Args:
nodes (list): List of node names to process.
base_path (str): The base path for the nodes.
target (str): Specifies the target context, e.g., "tenant" or "global".

Returns:
list: A list of LibraryItem objects.
"""
items = []
for node in nodes:
# Remove any component that starts with '.'
startswithdot = functools.reduce(lambda x, y: x or y.startswith('.'), node.split(os.path.sep), False)
if startswithdot:
continue
# Extract the last 5 characters of the node name
last_five_chars = node[-5:]

# Check if there is a period in the last five characters,
# We detect files in Zookeeper by the presence of a dot in the filename,
# but exclude filenames ending with '.io' or '.d' (e.g., 'logman.io', server_https.d)
# from being considered as files.
# Determine if this is a file or directory
last_five_chars = node[-5:]
if '.' in last_five_chars and not node.endswith(('.io', '.d')):
fname = base_path + node
ftype = "item"
else:
fname = base_path + node + '/'
ftype = "dir"

# Add the item with the specified target
items.append(LibraryItem(
name=fname,
type=ftype,
layer=self.Layer,
providers=[self],
target=target
))

return items
Expand Down
53 changes: 53 additions & 0 deletions asab/library/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,59 @@ def check_disabled(self, path: str) -> bool:

return False

async def get_item_metadata(self, path: str) -> typing.Optional[dict]:
"""
Retrieve metadata for a specific file in the library, including its `target`.

Args:
path (str): The absolute path of the file to retrieve metadata for.
Must start with '/' and include a filename with an extension.

Returns:
dict: Metadata for the specified file, including `target`, or None if not found.
"""
# Validate the path format
_validate_path_item(path)

# Split into directory and filename
directory, filename = os.path.split(path)

if not directory or not filename:
L.warning("Invalid path '{}': missing directory or filename.".format(path))
return None
# Ensure directory ends with '/'
if not directory.endswith('/'):
directory += '/'

try:
# Fetch all items in the directory
items = await self.list(directory)
except Exception as e:
L.warning("Failed to list items in directory '{}': {}".format(directory, e))
return None

# Use dictionary for faster lookup
items_dict = {item.name: item for item in items}

# Retrieve the item by path
item = items_dict.get(path)
if item and item.type == "item":
# Match found; return metadata including `target`
return {
"name": item.name,
"type": item.type,
"layer": item.layer,
"providers": item.providers,
"disabled": item.disabled,
"override": item.override,
"target": item.target, # Include the target in the metadata
}

# Item not found
L.info("Item '{}' not found in directory '{}'.".format(filename, directory))
return None


async def export(self, path: str = "/", remove_path: bool = False) -> typing.IO:
"""
Return a file-like stream containing a gzipped tar archive of the library contents of the path.
Expand Down
Loading