Skip to content

Commit

Permalink
Merge pull request #35 from eilmiv/more_upload_functions
Browse files Browse the repository at this point in the history
More upload functions
  • Loading branch information
nitrosx authored Jan 31, 2023
2 parents 98fa114 + 336fc87 commit 03e0628
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 11 deletions.
14 changes: 14 additions & 0 deletions docs/source/howto/ingest.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ from pyscicat.model import (
Datablock,
DataFile,
Dataset,
Sample,
Ownable
)

Expand Down Expand Up @@ -61,6 +62,19 @@ Note that we store the provided dataset_id in a variable for later use.

Also note the `sourceFolder`. This is a folder on the file system that SciCat has access to, and will contain the files for this `Dataset`.

Proposals and instruments have to be created by an administrator. A sample with `sampleId="gargleblaster"` can be created like this:
```python
sample = Sample(
sampleId="gargleblaster",
owner="Chamber of Commerce",
description="A legendary drink.",
sampleCharacteristics={"Flavour": "Unknown, but potent"},
isPublished=False,
**ownable.dict()
)
sample_id = client.upload_sample(sample) # sample_id == "gargleblaster"
```

## Upload a Datablock

```python
Expand Down
223 changes: 219 additions & 4 deletions pyscicat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
Attachment,
Datablock,
Dataset,
DerivedDataset,
Instrument,
OrigDatablock,
Proposal,
RawDataset,
DerivedDataset,
Sample,
)

logger = logging.getLogger("splash_ingest")
Expand Down Expand Up @@ -208,7 +211,7 @@ def datasets_raw_replace(self, dataset: Dataset) -> str:
This function was renamed.
It is still accessible with the original name for backward compatibility
The original names were repalce_raw_dataset and upload_raw_dataset
THis function is obsolete and it will be removed in future releases
This function is obsolete and it will be removed in future releases
Parameters
----------
Expand Down Expand Up @@ -269,8 +272,11 @@ def datasets_derived_replace(self, dataset: Dataset) -> str:
operation="datasets_derived_replace",
).get("pid")

def update_dataset(self, dataset: Dataset, pid: str) -> str:
def datasets_update(self, dataset: Dataset, pid: str) -> str:
"""Updates an existing dataset
This function was renamed.
It is still accessible with the original name for backward compatibility
The original name was update_dataset.
Parameters
----------
Expand All @@ -293,9 +299,15 @@ def update_dataset(self, dataset: Dataset, pid: str) -> str:
cmd="patch",
endpoint=f"Datasets/{quote_plus(pid)}",
data=dataset,
operation="update_dataset",
operation="datasets_update",
).get("pid")

"""
Update a dataset
Original name, kept for for backward compatibility
"""
update_dataset = datasets_update

def datasets_datablock_create(
self, datablock: Datablock, datasetType: str = "RawDatasets"
) -> dict:
Expand Down Expand Up @@ -413,6 +425,209 @@ def datasets_attachment_create(
upload_attachment = datasets_attachment_create
create_dataset_attachment = datasets_attachment_create

def samples_create(self, sample: Sample) -> str:
"""
Create a new sample.
An error is raised when a sample with the same sampleId already exists.
This function is also accessible as upload_sample.
Parameters
----------
sample : Sample
Sample to upload
Returns
-------
str
ID of the newly created sample
Raises
------
ScicatCommError
Raises if a non-20x message is returned
"""
return self._call_endpoint(
cmd="post",
endpoint="Samples",
data=sample,
operation="samples_create",
).get("sampleId")

upload_sample = samples_create

def samples_update(self, sample: Sample, sampleId: str = None) -> str:
"""Updates an existing sample
Parameters
----------
sample : Sample
Sample to update
sampleId
ID of sample being updated. By default, ID is taken from sample parameter.
Returns
-------
str
ID of the sample
Raises
------
ScicatCommError
Raises if a non-20x message is returned
AssertionError
Raises if no ID is provided
"""
if sampleId is None:
assert sample.sampleId is not None, "sampleId should not be None"
sampleId = sample.sampleId
sample.sampleId = None
return self._call_endpoint(
cmd="patch",
endpoint=f"Samples/{quote_plus(sampleId)}",
data=sample,
operation="samples_update",
).get("sampleId")

def instruments_create(self, instrument: Instrument):
"""
Create a new instrument.
Note that in SciCat admin rights are required to upload instruments.
An error is raised when an instrument with the same pid already exists.
This function is also accessible as upload_instrument.
Parameters
----------
instrument : Instrument
Instrument to upload
Returns
-------
str
pid (or unique identifier) of the newly created instrument
Raises
------
ScicatCommError
Raises if a non-20x message is returned
"""
return self._call_endpoint(
cmd="post",
endpoint="Instruments",
data=instrument,
operation="instruments_create",
).get("pid")

upload_instrument = instruments_create

def instruments_update(self, instrument: Instrument, pid: str = None) -> str:
"""Updates an existing instrument.
Note that in SciCat admin rights are required to upload instruments.
Parameters
----------
instrument : Instrument
Instrument to update
pid
pid (or unique identifier) of instrument being updated.
By default, pid is taken from instrument parameter.
Returns
-------
str
ID of the instrument
Raises
------
ScicatCommError
Raises if a non-20x message is returned
AssertionError
Raises if no ID is provided
"""
if pid is None:
assert instrument.pid is not None, "pid should not be None"
pid = instrument.pid
instrument.pid = None
return self._call_endpoint(
cmd="patch",
endpoint=f"Instruments/{quote_plus(pid)}",
data=instrument,
operation="instruments_update",
).get("pid")

def proposals_create(self, proposal: Proposal):
"""
Create a new proposal.
Note that in SciCat admin rights are required to upload proposals.
An error is raised when a proposal with the same proposalId already exists.
This function is also accessible as upload_proposal.
Parameters
----------
proposal : Proposal
Proposal to upload
Returns
-------
str
ID of the newly created proposal
Raises
------
ScicatCommError
Raises if a non-20x message is returned
"""
return self._call_endpoint(
cmd="post",
endpoint="Proposals",
data=proposal,
operation="proposals_create",
).get("proposalId")

upload_proposal = proposals_create

def proposals_update(self, proposal: Proposal, proposalId: str = None) -> str:
"""Updates an existing proposal.
Note that in SciCat admin rights are required to upload proposals.
Parameters
----------
proposal : Proposal
Proposal to update
proposalId
ID of proposal being updated. By default, this is taken from proposal parameter.
Returns
-------
str
ID of the proposal
Raises
------
ScicatCommError
Raises if a non-20x message is returned
AssertionError
Raises if no ID is provided
"""
if proposalId is None:
assert proposal.proposalId is not None, "proposalId should not be None"
proposalId = proposal.proposalId
proposal.proposalId = None
return self._call_endpoint(
cmd="patch",
endpoint=f"Proposals/{quote_plus(proposalId)}",
data=proposal,
operation="proposals_update",
).get("proposalId")

def datasets_find(
self, skip: int = 0, limit: int = 25, query_fields: Optional[dict] = None
) -> Optional[dict]:
Expand Down
11 changes: 5 additions & 6 deletions pyscicat/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class Ownable(MongoQueryable):
"""Many objects in SciCat are ownable"""

ownerGroup: str
accessGroups: List[str]
accessGroups: Optional[List[str]]
instrumentGroup: Optional[str]


class User(BaseModel):
Expand All @@ -45,15 +46,14 @@ class Proposal(Ownable):
Defines the purpose of an experiment and links an experiment to principal investigator and main proposer
"""

# TODO: find out which of these are not optional and update
proposalId: Optional[str]
proposalId: str
pi_email: Optional[str]
pi_firstname: Optional[str]
pi_lastname: Optional[str]
email: Optional[str]
email: str
firstname: Optional[str]
lastname: Optional[str]
title: Optional[str]
title: Optional[str] # required in next backend version
abstract: Optional[str]
startTime: Optional[str]
endTime: Optional[str]
Expand All @@ -68,7 +68,6 @@ class Sample(Ownable):
Raw datasets should be linked to such sample definitions.
"""

# TODO: find out which of these are not optional and update
sampleId: Optional[str]
owner: Optional[str]
description: Optional[str]
Expand Down
46 changes: 45 additions & 1 deletion pyscicat/tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
Attachment,
Datablock,
DataFile,
Instrument,
Proposal,
RawDataset,
Sample,
Ownable,
)

Expand All @@ -28,7 +31,18 @@ def add_mock_requests(mock_request):
local_url + "Users/login",
json={"id": "a_token"},
)
mock_request.post(local_url + "Samples", json={"sampleId": "dataset_id"})

mock_request.post(local_url + "Instruments", json={"pid": "earth"})
mock_request.post(local_url + "Proposals", json={"proposalId": "deepthought"})
mock_request.post(local_url + "Samples", json={"sampleId": "gargleblaster"})
mock_request.patch(local_url + "Instruments/earth", json={"pid": "earth"})
mock_request.patch(
local_url + "Proposals/deepthought", json={"proposalId": "deepthought"}
)
mock_request.patch(
local_url + "Samples/gargleblaster", json={"sampleId": "gargleblaster"}
)

mock_request.post(local_url + "RawDatasets/replaceOrCreate", json={"pid": "42"})
mock_request.patch(
local_url + "Datasets/42",
Expand Down Expand Up @@ -66,6 +80,36 @@ def test_scicat_ingest():
size = get_file_size(thumb_path)
assert size is not None

# Instrument
instrument = Instrument(
pid="earth", name="Earth", customMetadata={"a": "field"}
)
assert scicat.upload_instrument(instrument) == "earth"
assert scicat.instruments_create(instrument) == "earth"
assert scicat.instruments_update(instrument) == "earth"

# Proposal
proposal = Proposal(
proposalId="deepthought",
title="Deepthought",
email="deepthought@viltvodle.com",
**ownable.dict()
)
assert scicat.upload_proposal(proposal) == "deepthought"
assert scicat.proposals_create(proposal) == "deepthought"
assert scicat.proposals_update(proposal) == "deepthought"

# Sample
sample = Sample(
sampleId="gargleblaster",
description="Gargleblaster",
sampleCharacteristics={"a": "field"},
**ownable.dict()
)
assert scicat.upload_sample(sample) == "gargleblaster"
assert scicat.samples_create(sample) == "gargleblaster"
assert scicat.samples_update(sample) == "gargleblaster"

# RawDataset
dataset = RawDataset(
path="/foo/bar",
Expand Down

0 comments on commit 03e0628

Please sign in to comment.