Skip to content
Open
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
41 changes: 39 additions & 2 deletions src/simdb/database/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from simdb.config import Config
from simdb.query import QueryType, query_compare
from simdb.remote.models import SimulationReference

from .models import Base
from .models.file import File
Expand Down Expand Up @@ -226,8 +227,8 @@ def _find_simulation(self, sim_ref: str) -> "Simulation":
)
except SQLAlchemyError:
simulation = None
if not simulation:
raise DatabaseError(f"Simulation {sim_ref} not found.") from None
if not simulation:
raise DatabaseError(f"Simulation {sim_ref} not found.") from None
return simulation

def remove(self):
Expand Down Expand Up @@ -571,6 +572,24 @@ def get_simulation_parents(self, simulation: "Simulation") -> List[dict]:
)
return [{"uuid": r.uuid, "alias": r.alias} for r in query.all()]

def get_simulation_parents_ref(
self, simulation: "Simulation"
) -> List[SimulationReference]:
subquery = (
self.session.query(File.checksum)
.filter(File.checksum != "")
.filter(File.input_for.contains(simulation))
.subquery()
)
query = (
self.session.query(Simulation.uuid, Simulation.alias)
.join(Simulation.outputs)
.filter(File.checksum.in_(subquery))
.filter(Simulation.alias != simulation.alias)
.distinct()
)
return [SimulationReference(uuid=r.uuid, alias=r.alias) for r in query.all()]

def get_simulation_children(self, simulation: "Simulation") -> List[dict]:
subquery = (
self.session.query(File.checksum)
Expand All @@ -587,6 +606,24 @@ def get_simulation_children(self, simulation: "Simulation") -> List[dict]:
)
return [{"uuid": r.uuid, "alias": r.alias} for r in query.all()]

def get_simulation_children_ref(
self, simulation: "Simulation"
) -> List[SimulationReference]:
subquery = (
self.session.query(File.checksum)
.filter(File.checksum != "")
.filter(File.output_of.contains(simulation))
.subquery()
)
query = (
self.session.query(Simulation.uuid, Simulation.alias)
.join(Simulation.inputs)
.filter(File.checksum.in_(subquery))
.filter(Simulation.alias != simulation.alias)
.distinct()
)
return [SimulationReference(uuid=r.uuid, alias=r.alias) for r in query.all()]

def get_file(self, file_uuid_str: str) -> "File":
"""
Get the specified file from the database.
Expand Down
58 changes: 57 additions & 1 deletion src/simdb/database/models/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from simdb.config.config import Config
from simdb.docstrings import inherit_docstrings
from simdb.imas.checksum import checksum as imas_checksum
from simdb.imas.utils import imas_timestamp
from simdb.imas.utils import imas_files, imas_timestamp
from simdb.remote.models import FileData, FileGetDataResponse, FileInfo
from simdb.uda.checksum import checksum as uda_checksum

from .base import Base
Expand Down Expand Up @@ -125,6 +126,23 @@ def from_data(cls, data: Dict) -> "File":
file.datetime = date_parser.parse(checked_get(data, "datetime", str))
return file

@classmethod
def from_data_model(cls, data: FileData) -> "File":
data_type = data.type
uri = data.uri
file = File(
DataObject.Type[data_type], urilib.URI(uri), perform_integrity_check=False
)
file.uuid = data.uuid
file.usage = data.usage
file.checksum = data.checksum
file.purpose = data.purpose
file.sensitivity = data.sensitivity
file.access = data.access
file.embargo = data.embargo
file.datetime = data.datetime
return file

def data(self, recurse: bool = False) -> Dict[str, str]:
data = {
"uuid": self.uuid,
Expand All @@ -139,3 +157,41 @@ def data(self, recurse: bool = False) -> Dict[str, str]:
"datetime": self.datetime.isoformat(),
}
return data

def to_model(self) -> FileData:
return FileData(
type=self.type.name,
uri=str(self.uri),
uuid=self.uuid,
checksum=self.checksum,
datetime=self.datetime,
usage=self.usage,
purpose=self.purpose,
sensitivity=self.sensitivity,
access=self.access,
embargo=self.embargo,
)

def to_model_with_path(self) -> FileGetDataResponse:
if self.type.name == "FILE":
if self.uri.path is None:
raise ValueError("File path not set")
files = [FileInfo(path=self.uri.path, checksum=self.checksum)]
else:
files = [
FileInfo(path=path, checksum=sha1_checksum(URI(f"file:{path}")))
for path in imas_files(self.uri)
]
return FileGetDataResponse(
type=self.type.name,
uri=str(self.uri),
uuid=self.uuid,
checksum=self.checksum,
datetime=self.datetime,
usage=self.usage,
purpose=self.purpose,
sensitivity=self.sensitivity,
access=self.access,
embargo=self.embargo,
files=files,
)
9 changes: 9 additions & 0 deletions src/simdb/database/models/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sqlalchemy import types as sql_types

from simdb.docstrings import inherit_docstrings
from simdb.remote.models import MetadataData

from .base import Base

Expand Down Expand Up @@ -32,12 +33,20 @@ def from_data(cls, data: Dict) -> "MetaData":
meta = MetaData(data["element"], data["value"])
return meta

@classmethod
def from_data_model(cls, data: MetadataData) -> "MetaData":
meta = MetaData(data.element, data.value)
return meta

def data(self, recurse: bool = False) -> Dict[str, str]:
data = {
"element": self.element,
"value": self.value,
}
return data

def to_model(self) -> MetadataData:
return MetadataData(element=self.element, value=self.value)


Index("metadata_index", MetaData.sim_id, MetaData.element, unique=True)
90 changes: 90 additions & 0 deletions src/simdb/database/models/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Union

from simdb.remote.models import (
FileDataList,
MetadataDataList,
SimulationData,
SimulationDataResponse,
SimulationTraceData,
)

if sys.version_info < (3, 11):
from backports.datetime_fromisoformat import MonkeyPatch

Expand Down Expand Up @@ -336,6 +344,17 @@ def from_data(cls, data: Dict[str, Union[str, Dict, List]]) -> "Simulation":
simulation.meta.append(MetaData.from_data(el))
return simulation

@classmethod
def from_data_model(cls, data: SimulationData) -> "Simulation":
simulation = Simulation(None)
simulation.uuid = data.uuid
simulation.alias = data.alias
simulation.datetime = data.datetime
simulation.inputs = [File.from_data_model(el) for el in data.inputs.root]
simulation.outputs = [File.from_data_model(el) for el in data.outputs.root]
simulation.meta = [MetaData.from_data_model(el) for el in data.metadata.root]
return simulation

def data(
self, recurse: bool = False, meta_keys: Optional[List[str]] = None
) -> Dict[str, Union[str, List]]:
Expand All @@ -354,6 +373,77 @@ def data(
]
return data

def to_model(
self, recurse: bool = False, meta_keys: Optional[List[str]] = None
) -> SimulationData:
inputs = FileDataList()
outputs = FileDataList()
metadata = MetadataDataList()
if recurse:
inputs = FileDataList([f.to_model() for f in self.inputs])
outputs = FileDataList([f.to_model() for f in self.outputs])
metadata = MetadataDataList([m.to_model() for m in self.meta])
elif meta_keys:
metadata = MetadataDataList(
[m.to_model() for m in self.meta if m.element in meta_keys]
)
return SimulationData(
uuid=self.uuid,
alias=self.alias,
datetime=self.datetime,
inputs=inputs,
outputs=outputs,
metadata=metadata,
)

def to_model_with_refs(
self, recurse: bool = False, meta_keys: Optional[List[str]] = None
) -> SimulationDataResponse:
inputs = FileDataList()
outputs = FileDataList()
metadata = MetadataDataList()
if recurse:
inputs = FileDataList([f.to_model() for f in self.inputs])
outputs = FileDataList([f.to_model() for f in self.outputs])
metadata = MetadataDataList([m.to_model() for m in self.meta])
elif meta_keys:
metadata = MetadataDataList(
[m.to_model() for m in self.meta if m.element in meta_keys]
)
return SimulationDataResponse(
uuid=self.uuid,
alias=self.alias,
datetime=self.datetime,
inputs=inputs,
outputs=outputs,
metadata=metadata,
parents=[],
children=[],
)

def to_model_trace(
self, recurse: bool = False, meta_keys: Optional[List[str]] = None
) -> SimulationTraceData:
inputs = FileDataList()
outputs = FileDataList()
metadata = MetadataDataList()
if recurse:
inputs = FileDataList([f.to_model() for f in self.inputs])
outputs = FileDataList([f.to_model() for f in self.outputs])
metadata = MetadataDataList([m.to_model() for m in self.meta])
elif meta_keys:
metadata = MetadataDataList(
[m.to_model() for m in self.meta if m.element in meta_keys]
)
return SimulationTraceData(
uuid=self.uuid,
alias=self.alias,
datetime=self.datetime,
inputs=inputs,
outputs=outputs,
metadata=metadata,
)

def meta_dict(self) -> Dict[str, Union[Dict, Any]]:
meta = {m.element: m.value for m in self.meta}
return unflatten_dict(meta)
4 changes: 4 additions & 0 deletions src/simdb/database/models/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from simdb.docstrings import inherit_docstrings
from simdb.notifications import Notification
from simdb.remote.models import WatcherData

from .base import Base
from .types import ChoiceType
Expand Down Expand Up @@ -59,3 +60,6 @@ def data(self, recurse: bool = False) -> Dict[str, str]:
"notification": str(self.notification),
}
return data

def to_model(self) -> WatcherData:
return WatcherData.model_validate(self.data())
32 changes: 10 additions & 22 deletions src/simdb/remote/apis/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import json
import uuid
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, cast
from typing import Dict, Iterable, List, Optional

import magic
from flask import Response, jsonify, request, send_file, stream_with_context
Expand All @@ -18,7 +18,9 @@
from simdb.remote.core.auth import User, requires_auth
from simdb.remote.core.errors import error
from simdb.remote.core.path import find_common_root, secure_path
from simdb.remote.core.pydantic_utils import pydantic_validate
from simdb.remote.core.typing import current_app
from simdb.remote.models import FileDataList, FileGetDataResponse
from simdb.uri import URI

api = Namespace("files", path="/")
Expand Down Expand Up @@ -170,9 +172,10 @@ def _handle_file_upload() -> Response:
@api.route("/files")
class FileList(Resource):
@requires_auth()
def get(self, user: User):
@pydantic_validate(api)
def get(self, user: User) -> FileDataList:
files = current_app.db.list_files()
return jsonify([file.data() for file in files])
return FileDataList.model_validate([file.to_model() for file in files])

@requires_auth()
def post(self, user: User):
Expand All @@ -189,25 +192,10 @@ def post(self, user: User):
@api.route("/file/<string:file_uuid>")
class File(Resource):
@requires_auth()
def get(self, file_uuid: str, user: Optional[User] = None):
try:
file = current_app.db.get_file(file_uuid)
data = cast(Dict[str, Any], file.data(recurse=True))
if file.type == DataObject.Type.FILE:
data["files"] = [
{
"path": str(file.uri.path),
"checksum": file.checksum,
}
]
else:
data["files"] = [
{"path": str(path), "checksum": sha1_checksum(URI(f"file:{path}"))}
for path in imas_files(file.uri)
]
return jsonify(data)
except DatabaseError as err:
return error(str(err))
@pydantic_validate(api)
def get(self, file_uuid: str, user: Optional[User] = None) -> FileGetDataResponse:
file = current_app.db.get_file(file_uuid)
return file.to_model_with_path()


@api.route("/file/download/<string:file_uuid>")
Expand Down
Loading
Loading