diff --git a/hypha/VERSION b/hypha/VERSION index f0e3ef88..bd0e11c2 100644 --- a/hypha/VERSION +++ b/hypha/VERSION @@ -1,3 +1,3 @@ { - "version": "0.20.39.post8" + "version": "0.20.39.post9" } diff --git a/hypha/artifact.py b/hypha/artifact.py index b927d067..897073b5 100644 --- a/hypha/artifact.py +++ b/hypha/artifact.py @@ -166,7 +166,10 @@ async def get_artifact( session = await self._get_session(read_only=True) async with session.begin(): # Fetch artifact along with parent - artifact, _ = await self._get_artifact_with_permission( + ( + artifact, + parent_artifact, + ) = await self._get_artifact_with_permission( user_info, artifact_id, "read", session ) @@ -174,7 +177,9 @@ async def get_artifact( if version_index == self._get_version_index(artifact, None): # Prepare artifact representation - artifact_data = self._generate_artifact_data(artifact) + artifact_data = self._generate_artifact_data( + artifact, parent_artifact + ) else: s3_config = self._get_s3_config(artifact) artifact_data = await self._load_version_from_s3( @@ -252,7 +257,10 @@ async def list_children( children = result.scalars().all() # Return children data, excluding secrets - return [self._generate_artifact_data(child) for child in children] + return [ + self._generate_artifact_data(child, parent_artifact) + for child in children + ] except KeyError: raise HTTPException(status_code=404, detail="Parent artifact not found") @@ -432,8 +440,12 @@ async def _get_artifact_with_parent(self, session, artifact_id): parent_artifact = parent_result.scalar_one_or_none() return artifact, parent_artifact - def _generate_artifact_data(self, artifact): + def _generate_artifact_data(self, artifact, parent_artifact=None): artifact_data = model_to_dict(artifact) + if parent_artifact: + artifact_data[ + "parent_id" + ] = f"{parent_artifact.workspace}/{parent_artifact.alias}" artifact_data["id"] = f"{artifact.workspace}/{artifact.alias}" artifact_data["_id"] = artifact.id # Exclude 'secrets' from artifact_data to prevent exposure @@ -1067,7 +1079,7 @@ async def create( logger.info( f"Created artifact with ID: {id} (staged), alias: {alias}, parent: {parent_id}" ) - return self._generate_artifact_data(new_artifact) + return self._generate_artifact_data(new_artifact, parent_artifact) except Exception as e: raise e finally: @@ -1205,7 +1217,7 @@ async def edit( artifact, self._get_s3_config(artifact, parent_artifact), ) - return self._generate_artifact_data(artifact) + return self._generate_artifact_data(artifact, parent_artifact) except Exception as e: raise e finally: @@ -1226,7 +1238,7 @@ async def read( session = await self._get_session() try: async with session.begin(): - artifact, _ = await self._get_artifact_with_permission( + artifact, parent_artifact = await self._get_artifact_with_permission( user_info, artifact_id, "read", session ) if not silent: @@ -1234,7 +1246,9 @@ async def read( version_index = self._get_version_index(artifact, version) if version_index == self._get_version_index(artifact, None): - artifact_data = self._generate_artifact_data(artifact) + artifact_data = self._generate_artifact_data( + artifact, parent_artifact + ) else: s3_config = self._get_s3_config(artifact) artifact_data = await self._load_version_from_s3( @@ -1300,7 +1314,10 @@ async def commit( raise FileNotFoundError( f"File '{file_info['path']}' does not exist in the artifact." ) - if file_info.get("download_weight") is not None: + if ( + file_info.get("download_weight") is not None + and file_info["download_weight"] > 0 + ): download_weights[file_info["path"]] = file_info[ "download_weight" ] @@ -1367,7 +1384,7 @@ async def commit( logger.info( f"Committed artifact with ID: {artifact_id}, alias: {artifact.alias}, version: {version}" ) - return self._generate_artifact_data(artifact) + return self._generate_artifact_data(artifact, parent_artifact) except Exception as e: raise e finally: @@ -1765,6 +1782,7 @@ async def put_file( artifact_id = self._validate_artifact_id(artifact_id, context) user_info = UserInfo.model_validate(context["user"]) session = await self._get_session() + assert download_weight >= 0, "Download weight must be a non-negative number." try: async with session.begin(): artifact, parent_artifact = await self._get_artifact_with_permission( @@ -2195,7 +2213,9 @@ async def list_children( # Compile summary results for each artifact results = [] for artifact in artifacts: - _artifact_data = self._generate_artifact_data(artifact) + _artifact_data = self._generate_artifact_data( + artifact, parent_artifact + ) results.append(_artifact_data) # Increment view count for parent artifact if not in stage mode diff --git a/hypha/templates/ws/index.html b/hypha/templates/ws/index.html index b280490b..30c11994 100644 --- a/hypha/templates/ws/index.html +++ b/hypha/templates/ws/index.html @@ -569,7 +569,7 @@

Generated Token:

isCollection ? "fa-folder" : "fa-file-alt" } mr-2`} > -
+

{artifact.manifest?.name}

{artifact.manifest?.description}

Type: {artifact.type}

@@ -867,7 +867,7 @@

Files:

event.preventDefault(); if (confirm(`Are you sure you want to delete the artifact with ID: ${artifactId}?`)) { try { - await am.delete(artifactId); // Delete the artifact + await am.delete(artifactId, {delete_files: true, recursive: true, _rkwargs: true}); // Delete the artifact if (parentId) { // If the artifact is a child, refresh the parent's children diff --git a/hypha/utils/zenodo.py b/hypha/utils/zenodo.py index c0bf7e79..e306af00 100644 --- a/hypha/utils/zenodo.py +++ b/hypha/utils/zenodo.py @@ -5,6 +5,9 @@ from pathlib import PurePosixPath +ZENODO_TIMEOUT = 30 # seconds + + class ZenodoClient: def __init__( self, access_token: str, zenodo_server: str = "https://sandbox.zenodo.org" @@ -13,7 +16,9 @@ def __init__( self.zenodo_server = zenodo_server self.headers = {"Content-Type": "application/json"} self.params = {"access_token": self.access_token} - self.client = httpx.AsyncClient(headers={"Connection": "close"}) + self.client = httpx.AsyncClient( + headers={"Connection": "close"}, timeout=ZENODO_TIMEOUT + ) async def create_deposition(self) -> Dict[str, Any]: """Creates a new empty deposition and returns its info.""" @@ -97,7 +102,9 @@ async def file_chunk_reader(file_path: str, chunk_size: int = 1024): async def import_file(self, deposition_info, name, target_url): bucket_url = deposition_info["links"]["bucket"] - async with httpx.AsyncClient(headers={"Connection": "close"}) as client: + async with httpx.AsyncClient( + headers={"Connection": "close"}, timeout=ZENODO_TIMEOUT + ) as client: async with client.stream("GET", target_url) as response: async def s3_response_chunk_reader(response, chunk_size: int = 2048): diff --git a/tests/test_artifact.py b/tests/test_artifact.py index 777e1709..2c9875fa 100644 --- a/tests/test_artifact.py +++ b/tests/test_artifact.py @@ -517,6 +517,7 @@ async def test_artifact_permissions( parent_id=collection.id, manifest=dataset_manifest, ) + assert dataset["parent_id"] == f"{api.config.workspace}/{collection.alias}" await artifact_manager_anonymous.read(artifact_id=dataset.id)