Skip to content

Commit

Permalink
Merge pull request #642 from TeskaLabs/Enhancement/Add-target-to-list
Browse files Browse the repository at this point in the history
Update item to hold target
  • Loading branch information
mithunbharadwaj authored Dec 13, 2024
2 parents 91efe34 + 212ae09 commit 2448800
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 25 deletions.
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 @@ -489,6 +489,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

0 comments on commit 2448800

Please sign in to comment.