` syntax and RSpace will turn these into formatted links\n","metadata":{}},{"cell_type":"code","source":"new_doc = api.create_document(name=f\"Analysis of dataset {raw_data_file_id}\")\ncontent = f\"\"\"\nAnalysis of temperature dataset from our standard locations.\n
No variation between locations:\nRaw data: \n\nStatistical summary: \n\nLocation vs temperature: \n\"\"\"\n\nupdated_doc = api.append_content(new_doc['id'], content)\n\n## a simple utility function so you can get a link to view the updated contents in a browser.\ndef api_to_browser(link):\n return '/globalId/SD'.join(link.split('/api/v1/documents/'))\n\nprint(f\"You can view this in a browser at {api_to_browser(updated_doc['_links'][0]['link'])}\")\n","metadata":{"execution":{"iopub.status.busy":"2021-12-02T09:30:33.035126Z","iopub.execute_input":"2021-12-02T09:30:33.036313Z","iopub.status.idle":"2021-12-02T09:30:38.342404Z","shell.execute_reply.started":"2021-12-02T09:30:33.036246Z","shell.execute_reply":"2021-12-02T09:30:38.341291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"If you're in a group, you can now share this with your group. You can get your groups' IDs: ","metadata":{}},{"cell_type":"code","source":"groups = api.get_groups()\nfor gp in groups:\n print(f\"{gp['name']:30}{gp['id']}\")\nchosen_group = None\n#chosen_group = input(\"please enter a group ID to share with\")\nchosen_group = chosen_group or groups[0]['id'] ## if not running interactively, choose 1st group","metadata":{"execution":{"iopub.status.busy":"2021-12-02T09:48:52.764733Z","iopub.execute_input":"2021-12-02T09:48:52.765029Z","iopub.status.idle":"2021-12-02T09:48:53.294518Z","shell.execute_reply.started":"2021-12-02T09:48:52.764997Z","shell.execute_reply":"2021-12-02T09:48:53.293245Z"},"trusted":true},"execution_count":53,"outputs":[]},{"cell_type":"code","source":"api.shareDocuments([new_doc['id']], chosen_group, permission=\"EDIT\")","metadata":{"execution":{"iopub.status.busy":"2021-12-02T09:31:28.045633Z","iopub.execute_input":"2021-12-02T09:31:28.045924Z","iopub.status.idle":"2021-12-02T09:31:29.469595Z","shell.execute_reply.started":"2021-12-02T09:31:28.045892Z","shell.execute_reply":"2021-12-02T09:31:29.468475Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"### tidy up - remove output files\noutfile_dir=\"/kaggle/working\"\nfor root,dirs,files in os.walk(outfile_dir):\n for f in files:\n os.remove(f)\nprint (\"output files removed\")","metadata":{"execution":{"iopub.status.busy":"2021-12-02T09:32:58.850987Z","iopub.execute_input":"2021-12-02T09:32:58.851459Z","iopub.status.idle":"2021-12-02T09:32:58.859278Z","shell.execute_reply.started":"2021-12-02T09:32:58.851405Z","shell.execute_reply":"2021-12-02T09:32:58.858292Z"},"trusted":true},"execution_count":null,"outputs":[]}]}
--------------------------------------------------------------------------------
/rspace_client/tests/data/sample_by_id.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 7,
3 | "globalId": "SA7",
4 | "name": "Basic Sample",
5 | "description": null,
6 | "created": "2025-02-17T09:33:37.000Z",
7 | "createdBy": "user2b",
8 | "lastModified": "2025-02-17T09:33:37.277Z",
9 | "modifiedBy": "user2b",
10 | "modifiedByFullName": "user two",
11 | "canBeDeleted": false,
12 | "deleted": false,
13 | "deletedDate": null,
14 | "iconId": -1,
15 | "quantity": {
16 | "numericValue": 1,
17 | "unitId": 3
18 | },
19 | "tags": [],
20 | "type": "SAMPLE",
21 | "attachments": [
22 | {
23 | "id": 32772,
24 | "globalId": "IF32772",
25 | "name": "test.pdf",
26 | "parentGlobalId": "SS4",
27 | "mediaFileGlobalId": null,
28 | "type": "GENERAL",
29 | "contentMimeType": "application/pdf",
30 | "extension": "pdf",
31 | "size": 3190,
32 | "created": "2025-02-17T13:48:28.000Z",
33 | "createdBy": "user1a",
34 | "deleted": false,
35 | "_links": [
36 | {
37 | "link": "http://localhost:8080/api/inventory/v1/files/32772",
38 | "rel": "self"
39 | },
40 | {
41 | "link": "http://localhost:8080/api/inventory/v1/files/32772/file",
42 | "rel": "enclosure"
43 | }
44 | ]
45 | }
46 | ],
47 | "barcodes": [],
48 | "identifiers": [],
49 | "owner": {
50 | "id": -3,
51 | "username": "user2b",
52 | "email": "u2@researchspace.com",
53 | "firstName": "user",
54 | "lastName": "two",
55 | "homeFolderId": 165,
56 | "workbenchId": null,
57 | "hasPiRole": false,
58 | "hasSysAdminRole": false,
59 | "_links": []
60 | },
61 | "permittedActions": ["READ", "UPDATE", "CHANGE_OWNER"],
62 | "sharingMode": "OWNER_GROUPS",
63 | "templateId": null,
64 | "templateVersion": null,
65 | "template": false,
66 | "revisionId": null,
67 | "version": 1,
68 | "historicalVersion": false,
69 | "subSampleAlias": {
70 | "alias": "aliquot",
71 | "plural": "aliquots"
72 | },
73 | "subSamplesCount": 1,
74 | "storageTempMin": null,
75 | "storageTempMax": null,
76 | "sampleSource": "LAB_CREATED",
77 | "expiryDate": null,
78 | "sharedWith": [
79 | {
80 | "group": {
81 | "id": 1,
82 | "globalId": "GP1",
83 | "name": "userGroup",
84 | "uniqueName": "userGroupKHqD",
85 | "_links": []
86 | },
87 | "shared": false,
88 | "itemOwnerGroup": true
89 | }
90 | ],
91 | "fields": [],
92 | "extraFields": [],
93 | "subSamples": [
94 | {
95 | "id": 7,
96 | "globalId": "SS7",
97 | "name": "Basic Sample.01",
98 | "description": null,
99 | "created": "2025-02-17T09:33:37.000Z",
100 | "createdBy": "user2b",
101 | "lastModified": "2025-02-17T09:33:37.277Z",
102 | "modifiedBy": "user2b",
103 | "modifiedByFullName": "user two",
104 | "deleted": false,
105 | "deletedDate": null,
106 | "iconId": -1,
107 | "tags": [],
108 | "attachments": [],
109 | "barcodes": [],
110 | "identifiers": [],
111 | "owner": {
112 | "id": -3,
113 | "username": "user2b",
114 | "email": "u2@researchspace.com",
115 | "firstName": "user",
116 | "lastName": "two",
117 | "homeFolderId": 165,
118 | "workbenchId": null,
119 | "hasPiRole": false,
120 | "hasSysAdminRole": false,
121 | "_links": []
122 | },
123 | "permittedActions": ["READ", "UPDATE", "CHANGE_OWNER"],
124 | "sharingMode": "OWNER_GROUPS",
125 | "quantity": {
126 | "numericValue": 1,
127 | "unitId": 3
128 | },
129 | "type": "SUBSAMPLE",
130 | "parentContainers": [
131 | {
132 | "id": 12,
133 | "globalId": "IC12",
134 | "name": "box #1 (list container)",
135 | "description": null,
136 | "created": "2025-02-17T09:33:37.000Z",
137 | "createdBy": "user2b",
138 | "lastModified": "2025-02-17T09:33:37.251Z",
139 | "modifiedBy": "user2b",
140 | "modifiedByFullName": null,
141 | "deleted": false,
142 | "deletedDate": null,
143 | "iconId": -1,
144 | "quantity": null,
145 | "tags": [],
146 | "type": "CONTAINER",
147 | "attachments": [],
148 | "barcodes": [],
149 | "identifiers": [],
150 | "owner": {
151 | "id": -3,
152 | "username": "user2b",
153 | "email": "u2@researchspace.com",
154 | "firstName": "user",
155 | "lastName": "two",
156 | "homeFolderId": 165,
157 | "workbenchId": null,
158 | "hasPiRole": false,
159 | "hasSysAdminRole": false,
160 | "_links": []
161 | },
162 | "permittedActions": [],
163 | "sharingMode": "OWNER_GROUPS",
164 | "parentContainers": [
165 | {
166 | "id": 11,
167 | "globalId": "IC11",
168 | "name": "storage shelf #1 (list container)",
169 | "description": null,
170 | "created": "2025-02-17T09:33:37.000Z",
171 | "createdBy": "user2b",
172 | "lastModified": "2025-02-17T09:33:37.251Z",
173 | "modifiedBy": "user2b",
174 | "modifiedByFullName": null,
175 | "deleted": false,
176 | "deletedDate": null,
177 | "iconId": -1,
178 | "quantity": null,
179 | "tags": [],
180 | "type": "CONTAINER",
181 | "attachments": [],
182 | "barcodes": [],
183 | "identifiers": [],
184 | "owner": {
185 | "id": -3,
186 | "username": "user2b",
187 | "email": "u2@researchspace.com",
188 | "firstName": "user",
189 | "lastName": "two",
190 | "homeFolderId": 165,
191 | "workbenchId": null,
192 | "hasPiRole": false,
193 | "hasSysAdminRole": false,
194 | "_links": []
195 | },
196 | "permittedActions": [],
197 | "sharingMode": "OWNER_GROUPS",
198 | "parentContainers": [],
199 | "parentLocation": null,
200 | "lastNonWorkbenchParent": null,
201 | "lastMoveDate": null,
202 | "locationsCount": null,
203 | "contentSummary": {
204 | "totalCount": 3,
205 | "subSampleCount": 0,
206 | "containerCount": 3
207 | },
208 | "cType": "LIST",
209 | "gridLayout": null,
210 | "canStoreSamples": true,
211 | "canStoreContainers": true,
212 | "_links": []
213 | }
214 | ],
215 | "parentLocation": {
216 | "id": 14,
217 | "coordX": 1,
218 | "coordY": 1
219 | },
220 | "lastNonWorkbenchParent": null,
221 | "lastMoveDate": "2025-02-17T09:33:37.000Z",
222 | "locationsCount": null,
223 | "contentSummary": {
224 | "totalCount": 4,
225 | "subSampleCount": 1,
226 | "containerCount": 3
227 | },
228 | "cType": "LIST",
229 | "gridLayout": null,
230 | "canStoreSamples": true,
231 | "canStoreContainers": true,
232 | "_links": []
233 | },
234 | {
235 | "id": 11,
236 | "globalId": "IC11",
237 | "name": "storage shelf #1 (list container)",
238 | "description": null,
239 | "created": "2025-02-17T09:33:37.000Z",
240 | "createdBy": "user2b",
241 | "lastModified": "2025-02-17T09:33:37.251Z",
242 | "modifiedBy": "user2b",
243 | "modifiedByFullName": null,
244 | "deleted": false,
245 | "deletedDate": null,
246 | "iconId": -1,
247 | "quantity": null,
248 | "tags": [],
249 | "type": "CONTAINER",
250 | "attachments": [],
251 | "barcodes": [],
252 | "identifiers": [],
253 | "owner": {
254 | "id": -3,
255 | "username": "user2b",
256 | "email": "u2@researchspace.com",
257 | "firstName": "user",
258 | "lastName": "two",
259 | "homeFolderId": 165,
260 | "workbenchId": null,
261 | "hasPiRole": false,
262 | "hasSysAdminRole": false,
263 | "_links": []
264 | },
265 | "permittedActions": [],
266 | "sharingMode": "OWNER_GROUPS",
267 | "parentContainers": [],
268 | "parentLocation": null,
269 | "lastNonWorkbenchParent": null,
270 | "lastMoveDate": null,
271 | "locationsCount": null,
272 | "contentSummary": {
273 | "totalCount": 3,
274 | "subSampleCount": 0,
275 | "containerCount": 3
276 | },
277 | "cType": "LIST",
278 | "gridLayout": null,
279 | "canStoreSamples": true,
280 | "canStoreContainers": true,
281 | "_links": []
282 | }
283 | ],
284 | "parentLocation": {
285 | "id": 20,
286 | "coordX": 4,
287 | "coordY": 1
288 | },
289 | "lastNonWorkbenchParent": null,
290 | "lastMoveDate": "2025-02-17T09:33:37.000Z",
291 | "revisionId": null,
292 | "deletedOnSampleDeletion": false,
293 | "storedInContainer": true,
294 | "_links": [
295 | {
296 | "link": "http://localhost:8080/api/inventory/v1/subSamples/7",
297 | "rel": "self"
298 | },
299 | {
300 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/7/icon/-1",
301 | "rel": "image"
302 | },
303 | {
304 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/7/icon/-1",
305 | "rel": "thumbnail"
306 | }
307 | ]
308 | }
309 | ],
310 | "_links": [
311 | {
312 | "link": "http://localhost:8080/api/inventory/v1/samples/7",
313 | "rel": "self"
314 | },
315 | {
316 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/7/icon/-1",
317 | "rel": "icon"
318 | },
319 | {
320 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/7/icon/-1",
321 | "rel": "thumbnail"
322 | },
323 | {
324 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/7/icon/-1",
325 | "rel": "image"
326 | }
327 | ]
328 | }
329 |
--------------------------------------------------------------------------------
/rspace_client/client_base.py:
--------------------------------------------------------------------------------
1 | import re
2 | import requests
3 | import sys
4 |
5 |
6 | class Pagination:
7 | """
8 | For setting page size, number and orderby/ sort fields of listings
9 | """
10 |
11 | def __init__(
12 | self,
13 | page_number: int = 0,
14 | page_size: int = 10,
15 | order_by: str = None,
16 | sort_order: str = "asc",
17 | ):
18 | self.data = {
19 | "pageNumber": page_number,
20 | "pageSize": page_size,
21 | }
22 | if order_by is not None:
23 | self.data["orderBy"] = f"{order_by} {sort_order}"
24 |
25 |
26 | class ClientBase:
27 | """Base class of common methods for all API clients"""
28 |
29 | def __init__(self, rspace_url, api_key):
30 | """
31 | Initializes RSpace client.
32 | :param api_key: RSpace API key of a user can be found on 'My Profile' page
33 | """
34 | self.rspace_url = rspace_url.rstrip('/')
35 | self.api_key = api_key
36 |
37 | def _get_headers(self, content_type="application/json"):
38 | return {"apiKey": self.api_key, "Accept": content_type}
39 |
40 | @staticmethod
41 | def _get_numeric_record_id(global_id):
42 | """
43 | Gets numeric part of a global ID.
44 | :param global_id: global ID (for example, SD123 or FM12)
45 | :return: numeric record id
46 | """
47 | if re.match(r"[a-zA-Z]{2}\d+$", str(global_id)) is not None:
48 | return int(global_id[2:])
49 | elif re.match(r"\d+$", str(global_id)) is not None:
50 | return int(global_id)
51 | else:
52 | raise ValueError("{} is not a valid global ID".format(global_id))
53 |
54 | @staticmethod
55 | def _get_formated_error_message(json_error):
56 | return "error message: {}, errors: {}".format(
57 | json_error.get("message", ""),
58 | ", ".join(json_error.get("errors", [])) or "no error list",
59 | )
60 |
61 | @staticmethod
62 | def _responseContainsJson(response):
63 | return (
64 | "Content-Type" in response.headers
65 | and "application/json" in response.headers["Content-Type"]
66 | )
67 |
68 | @staticmethod
69 | def _handle_response(response):
70 | # Check whether response includes UNAUTHORIZED response code
71 | # print("status: {}, header: {}".format(response.headers, response.status_code))
72 | if response.status_code == 401:
73 | raise ClientBase.AuthenticationError(response.json()["message"])
74 |
75 | try:
76 | response.raise_for_status()
77 |
78 | if ClientBase._responseContainsJson(response):
79 | return response.json()
80 | elif response.text:
81 | return response.text
82 | else:
83 | return response
84 | except:
85 | if "application/json" in response.headers["Content-Type"]:
86 | error = "Error code: {}, {}".format(
87 | response.status_code,
88 | ClientBase._get_formated_error_message(response.json()),
89 | )
90 | else:
91 | error = "Error code: {}, error message: {}".format(
92 | response.status_code, response.text
93 | )
94 | raise ClientBase.ApiError(error, response_status_code=response.status_code)
95 |
96 | def doDelete(self, path, resource_id):
97 | """
98 | Performs a delete operation for a given resource
99 | """
100 | numeric_id = self._get_numeric_record_id(resource_id)
101 | return self.retrieve_api_results(
102 | "/{}/{}".format(path, numeric_id),
103 | content_type=None,
104 | request_type="DELETE",
105 | )
106 |
107 | def retrieve_api_results(
108 | self, endpoint, params=None, content_type="application/json", request_type="GET"
109 | ):
110 | """
111 | Makes the requested API call and returns either an exception or a parsed JSON response as a dictionary.
112 | Authentication header is automatically added. In most cases, a specialised method can be used instead.
113 | :endpoint url: API endpoint
114 | :param request_type: 'GET', 'POST', 'PUT', 'DELETE'
115 | :param params: arguments to be added to the API request
116 | :param content_type: content type
117 | :return: parsed JSON response as a dictionary
118 | """
119 | url = endpoint
120 | if not endpoint.startswith(self._get_api_url()):
121 | url = self._get_api_url() + endpoint
122 |
123 | headers = self._get_headers(content_type)
124 | try:
125 | if request_type == "GET":
126 | response = requests.get(url, params=params, headers=headers)
127 | elif (
128 | request_type == "PUT"
129 | or request_type == "POST"
130 | or request_type == "DELETE"
131 | ):
132 | response = requests.request(
133 | request_type, url, json=params, headers=headers
134 | )
135 | else:
136 | raise ValueError(
137 | "Expected GET / PUT / POST / DELETE request type, received {} instead".format(
138 | request_type
139 | )
140 | )
141 |
142 | return self._handle_response(response)
143 | except requests.exceptions.ConnectionError as e:
144 | raise ClientBase.ConnectionError(e)
145 |
146 | @staticmethod
147 | def _get_links(response):
148 | """
149 | Finds links part of the response. Most responses contain links section with URLs that might be useful to query
150 | for further information.
151 | :param response: response from the API server
152 | :return: links section of the response
153 | """
154 | try:
155 | return response["_links"]
156 | except KeyError:
157 | raise ClientBase.NoSuchLinkRel("There are no links!")
158 |
159 | def get_link_contents(self, response, link_rel):
160 | """
161 | Finds a link with rel attribute equal to link_rel and retrieves its contents.
162 | :param response: response from the API server
163 | :param link_rel: rel attribute value to look for
164 | :return: parsed response from the found URL
165 | """
166 | return self.retrieve_api_results(self.get_link(response, link_rel))
167 |
168 | def get_link(self, response, link_rel):
169 | """
170 | Finds a link with rel attribute equal to link_rel.
171 | :param response: response from the API server.
172 | :param link_rel: rel attribute value to look for
173 | :return: string URL
174 | """
175 | for link in self._get_links(response):
176 | if link["rel"] == link_rel:
177 | return link["link"]
178 | raise ClientBase.NoSuchLinkRel(
179 | 'Requested link rel "{}", available rel(s): {}'.format(
180 | link_rel, (", ".join(x["rel"] for x in self._get_links(response)))
181 | )
182 | )
183 |
184 | def download_link_to_file(self, url, filename, chunk_size=128):
185 | """
186 | Downloads a file from the API server.
187 | :param url: URL of the file to be downloaded
188 | :param filename: file path to save the file to or an already opened file object
189 | :param chunk_size: size of the chunks to download at a time, default is 128
190 | """
191 | headers = {"apiKey": self.api_key, "Accept": "application/octet-stream"}
192 | if isinstance(filename, str):
193 | with open(filename, "wb") as fd:
194 | for chunk in requests.get(url, headers=headers).iter_content(
195 | chunk_size=chunk_size
196 | ):
197 | fd.write(chunk)
198 | else:
199 | for chunk in requests.get(url, headers=headers).iter_content(
200 | chunk_size=chunk_size
201 | ):
202 | filename.write(chunk)
203 |
204 | def link_exists(self, response, link_rel):
205 | """
206 | Checks whether there is a link with rel attribute equal to link_rel in the links section of the response.
207 | :param response: response from the API server
208 | :param link_rel: rel attribute value to look for
209 | :return: True, if the link exists
210 | """
211 | return link_rel in [x["rel"] for x in self._get_links(response)]
212 |
213 | def serr(self, msg: str):
214 | print(msg, file=sys.stderr)
215 |
216 | def _stream(
217 | self,
218 | endpoint: str,
219 | pagination: Pagination = Pagination(),
220 | ):
221 | """
222 | Yields items, making paginated requests to the server as each page
223 | is consumed by the calling code.
224 |
225 | Note this method assumes that the name of the collection of items in the
226 | response matches the endpoint name. For example 'samples' returns a response
227 | with a dictionary entry 'samples'.
228 | Parameters
229 | ----------
230 | endpoint : str
231 | An endpoint with a GET request that makes paginated listings
232 | pagination : Pagination, optional
233 | The pagination control. The default is Pagination().
234 | : Pagination
235 |
236 | Yields
237 | ------
238 | item : A stream of items, depending on the endpoint called
239 | """
240 |
241 | urlStr = f"{self._get_api_url()}/{endpoint}"
242 |
243 | next_link = requests.Request(url=urlStr, params=pagination.data).prepare().url
244 | while True:
245 | if next_link is not None:
246 | items = self.retrieve_api_results(next_link)
247 | for item in items[endpoint]:
248 | yield item
249 | if self.link_exists(items, "next"):
250 | next_link = self.get_link(items, "next")
251 | else:
252 | break
253 |
254 | class ConnectionError(Exception):
255 | pass
256 |
257 | class AuthenticationError(Exception):
258 | pass
259 |
260 | class NoSuchLinkRel(Exception):
261 | pass
262 |
263 | class ApiError(Exception):
264 | def __init__(self, error_message, response_status_code=None):
265 | Exception.__init__(self, error_message)
266 | self.response_status_code = response_status_code
267 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/rspace_client/tests/data/subsample_by_id.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 4,
3 | "globalId": "SS4",
4 | "name": "Complex Sample #1.01",
5 | "description": null,
6 | "created": "2025-02-17T09:33:36.000Z",
7 | "createdBy": "user1a",
8 | "lastModified": "2025-02-17T13:35:02.730Z",
9 | "modifiedBy": "user1a",
10 | "modifiedByFullName": "user user",
11 | "deleted": false,
12 | "deletedDate": null,
13 | "iconId": -1,
14 | "quantity": {
15 | "numericValue": 1,
16 | "unitId": 3
17 | },
18 | "tags": [],
19 | "type": "SUBSAMPLE",
20 | "attachments": [
21 | {
22 | "id": 32772,
23 | "globalId": "IF32772",
24 | "name": "test.pdf",
25 | "parentGlobalId": "SS4",
26 | "mediaFileGlobalId": null,
27 | "type": "GENERAL",
28 | "contentMimeType": "application/pdf",
29 | "extension": "pdf",
30 | "size": 3190,
31 | "created": "2025-02-17T13:48:28.000Z",
32 | "createdBy": "user1a",
33 | "deleted": false,
34 | "_links": [
35 | {
36 | "link": "http://localhost:8080/api/inventory/v1/files/32772",
37 | "rel": "self"
38 | },
39 | {
40 | "link": "http://localhost:8080/api/inventory/v1/files/32772/file",
41 | "rel": "enclosure"
42 | }
43 | ]
44 | }
45 | ],
46 | "barcodes": [
47 | {
48 | "id": 1,
49 | "data": "test",
50 | "format": null,
51 | "description": "Scanned Unknown: test",
52 | "created": "2025-02-17T13:35:02.000Z",
53 | "createdBy": "user1a",
54 | "_links": [
55 | {
56 | "link": "http://localhost:8080/api/inventory/v1/barcodes%3Fcontent=test&barcodeType=QR",
57 | "rel": "enclosure"
58 | }
59 | ]
60 | }
61 | ],
62 | "identifiers": [],
63 | "owner": {
64 | "id": -1,
65 | "username": "user1a",
66 | "email": "user@user.com",
67 | "firstName": "user",
68 | "lastName": "user",
69 | "homeFolderId": 124,
70 | "workbenchId": null,
71 | "hasPiRole": true,
72 | "hasSysAdminRole": false,
73 | "_links": []
74 | },
75 | "permittedActions": ["READ", "UPDATE", "CHANGE_OWNER"],
76 | "sharingMode": "OWNER_GROUPS",
77 | "parentContainers": [
78 | {
79 | "id": 1,
80 | "globalId": "BE1",
81 | "name": "WB user1a",
82 | "description": null,
83 | "created": "2025-02-17T09:33:36.000Z",
84 | "createdBy": "user1a",
85 | "lastModified": "2025-02-17T09:33:36.543Z",
86 | "modifiedBy": "user1a",
87 | "modifiedByFullName": null,
88 | "deleted": false,
89 | "deletedDate": null,
90 | "iconId": -1,
91 | "quantity": null,
92 | "tags": [],
93 | "type": "CONTAINER",
94 | "attachments": [],
95 | "barcodes": [],
96 | "identifiers": [],
97 | "owner": {
98 | "id": -1,
99 | "username": "user1a",
100 | "email": "user@user.com",
101 | "firstName": "user",
102 | "lastName": "user",
103 | "homeFolderId": 124,
104 | "workbenchId": null,
105 | "hasPiRole": true,
106 | "hasSysAdminRole": false,
107 | "_links": []
108 | },
109 | "permittedActions": [],
110 | "sharingMode": "OWNER_GROUPS",
111 | "parentContainers": [],
112 | "parentLocation": null,
113 | "lastNonWorkbenchParent": null,
114 | "lastMoveDate": null,
115 | "locationsCount": null,
116 | "contentSummary": {
117 | "totalCount": 1,
118 | "subSampleCount": 1,
119 | "containerCount": 0
120 | },
121 | "cType": "WORKBENCH",
122 | "gridLayout": null,
123 | "canStoreSamples": true,
124 | "canStoreContainers": true,
125 | "_links": []
126 | }
127 | ],
128 | "parentLocation": {
129 | "id": 1,
130 | "coordX": 1,
131 | "coordY": 1
132 | },
133 | "lastNonWorkbenchParent": null,
134 | "lastMoveDate": null,
135 | "deletedOnSampleDeletion": false,
136 | "revisionId": null,
137 | "sample": {
138 | "id": 4,
139 | "globalId": "SA4",
140 | "name": "Complex Sample #1",
141 | "description": null,
142 | "created": "2025-02-17T09:33:36.000Z",
143 | "createdBy": "user1a",
144 | "lastModified": "2025-02-17T09:33:36.540Z",
145 | "modifiedBy": "user1a",
146 | "modifiedByFullName": null,
147 | "deleted": false,
148 | "deletedDate": null,
149 | "iconId": 3,
150 | "quantity": {
151 | "numericValue": 1,
152 | "unitId": 3
153 | },
154 | "tags": [],
155 | "type": "SAMPLE",
156 | "attachments": [],
157 | "barcodes": [],
158 | "identifiers": [],
159 | "owner": {
160 | "id": -1,
161 | "username": "user1a",
162 | "email": "user@user.com",
163 | "firstName": "user",
164 | "lastName": "user",
165 | "homeFolderId": 124,
166 | "workbenchId": null,
167 | "hasPiRole": true,
168 | "hasSysAdminRole": false,
169 | "_links": []
170 | },
171 | "permittedActions": [],
172 | "sharingMode": "OWNER_GROUPS",
173 | "templateId": 2,
174 | "templateVersion": 1,
175 | "template": false,
176 | "revisionId": null,
177 | "version": 1,
178 | "historicalVersion": false,
179 | "subSampleAlias": {
180 | "alias": "aliquot",
181 | "plural": "aliquots"
182 | },
183 | "subSamplesCount": 1,
184 | "storageTempMin": {
185 | "numericValue": 3,
186 | "unitId": 8
187 | },
188 | "storageTempMax": {
189 | "numericValue": 10,
190 | "unitId": 8
191 | },
192 | "sampleSource": "LAB_CREATED",
193 | "expiryDate": null,
194 | "sharedWith": [],
195 | "fields": [
196 | {
197 | "id": 21,
198 | "globalId": "SF21",
199 | "name": "MyNumber",
200 | "type": "number",
201 | "mandatory": true,
202 | "content": "23",
203 | "lastModified": "2025-02-17T09:33:36.540Z",
204 | "columnIndex": 1,
205 | "definition": null,
206 | "selectedOptions": null,
207 | "attachment": null,
208 | "_links": []
209 | },
210 | {
211 | "id": 22,
212 | "globalId": "SF22",
213 | "name": "MyDate",
214 | "type": "date",
215 | "mandatory": false,
216 | "content": "2020-10-01",
217 | "lastModified": "2025-02-17T09:33:36.540Z",
218 | "columnIndex": 2,
219 | "definition": null,
220 | "selectedOptions": null,
221 | "attachment": null,
222 | "_links": []
223 | },
224 | {
225 | "id": 23,
226 | "globalId": "SF23",
227 | "name": "MyString",
228 | "type": "string",
229 | "mandatory": false,
230 | "content": "Default string value",
231 | "lastModified": "2025-02-17T09:33:36.540Z",
232 | "columnIndex": 3,
233 | "definition": null,
234 | "selectedOptions": null,
235 | "attachment": null,
236 | "_links": []
237 | },
238 | {
239 | "id": 24,
240 | "globalId": "SF24",
241 | "name": "MyText",
242 | "type": "text",
243 | "mandatory": false,
244 | "content": "Default text value",
245 | "lastModified": "2025-02-17T09:33:36.540Z",
246 | "columnIndex": 4,
247 | "definition": null,
248 | "selectedOptions": null,
249 | "attachment": null,
250 | "_links": []
251 | },
252 | {
253 | "id": 25,
254 | "globalId": "SF25",
255 | "name": "MyURL",
256 | "type": "uri",
257 | "mandatory": false,
258 | "content": "https://www.google.com",
259 | "lastModified": "2025-02-17T09:33:36.540Z",
260 | "columnIndex": 5,
261 | "definition": null,
262 | "selectedOptions": null,
263 | "attachment": null,
264 | "_links": []
265 | },
266 | {
267 | "id": 26,
268 | "globalId": "SF26",
269 | "name": "My reference",
270 | "type": "reference",
271 | "mandatory": false,
272 | "content": null,
273 | "lastModified": "2025-02-17T09:33:36.540Z",
274 | "columnIndex": 6,
275 | "definition": null,
276 | "selectedOptions": null,
277 | "attachment": null,
278 | "_links": []
279 | },
280 | {
281 | "id": 27,
282 | "globalId": "SF27",
283 | "name": "MyAttachment",
284 | "type": "attachment",
285 | "mandatory": false,
286 | "content": null,
287 | "lastModified": "2025-02-17T09:33:36.540Z",
288 | "columnIndex": 7,
289 | "definition": null,
290 | "selectedOptions": null,
291 | "attachment": {
292 | "id": 1,
293 | "globalId": "IF1",
294 | "name": "loremIpsem20para.txt",
295 | "parentGlobalId": "SF27",
296 | "mediaFileGlobalId": null,
297 | "type": "GENERAL",
298 | "contentMimeType": "text/plain",
299 | "extension": "txt",
300 | "size": 10295,
301 | "created": "2025-02-17T09:33:36.000Z",
302 | "createdBy": "user1a",
303 | "deleted": false,
304 | "_links": [
305 | {
306 | "link": "http://localhost:8080/api/inventory/v1/files/1",
307 | "rel": "self"
308 | },
309 | {
310 | "link": "http://localhost:8080/api/inventory/v1/files/1/file",
311 | "rel": "enclosure"
312 | }
313 | ]
314 | },
315 | "_links": []
316 | },
317 | {
318 | "id": 28,
319 | "globalId": "SF28",
320 | "name": "MyTime",
321 | "type": "time",
322 | "mandatory": false,
323 | "content": "00:00",
324 | "lastModified": "2025-02-17T09:33:36.540Z",
325 | "columnIndex": 8,
326 | "definition": null,
327 | "selectedOptions": null,
328 | "attachment": null,
329 | "_links": []
330 | },
331 | {
332 | "id": 29,
333 | "globalId": "SF29",
334 | "name": "radioField",
335 | "type": "radio",
336 | "mandatory": false,
337 | "content": null,
338 | "lastModified": "2025-02-17T09:33:36.540Z",
339 | "columnIndex": 9,
340 | "definition": {
341 | "options": ["option1", "option2"],
342 | "multiple": false
343 | },
344 | "selectedOptions": [],
345 | "attachment": null,
346 | "_links": []
347 | },
348 | {
349 | "id": 30,
350 | "globalId": "SF30",
351 | "name": "choiceField",
352 | "type": "choice",
353 | "mandatory": false,
354 | "content": null,
355 | "lastModified": "2025-02-17T09:33:36.540Z",
356 | "columnIndex": 10,
357 | "definition": {
358 | "options": ["optionA", "optionB"],
359 | "multiple": false
360 | },
361 | "selectedOptions": [],
362 | "attachment": null,
363 | "_links": []
364 | }
365 | ],
366 | "extraFields": [
367 | {
368 | "id": 1,
369 | "globalId": "EF1",
370 | "name": "My extra text",
371 | "lastModified": "2025-02-17T09:33:36.540Z",
372 | "modifiedBy": "user1a",
373 | "type": "text",
374 | "content": "Lorem ipsum dolor sit amet...",
375 | "parentGlobalId": "SA4",
376 | "_links": [],
377 | "deleted": false
378 | }
379 | ],
380 | "_links": [
381 | {
382 | "link": "http://localhost:8080/api/inventory/v1/files/image/6f2904706397452a3c3772fadd2ca6e1fbcc084f68abc6535f4f26c5e953471d",
383 | "rel": "image"
384 | },
385 | {
386 | "link": "http://localhost:8080/api/inventory/v1/files/image/3c4b7a3d823a128ea5325e8228324d6a5740af6977acb45861b47e8e23fc109d",
387 | "rel": "thumbnail"
388 | },
389 | {
390 | "link": "http://localhost:8080/api/inventory/v1/samples/4",
391 | "rel": "self"
392 | },
393 | {
394 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/4/icon/3",
395 | "rel": "icon"
396 | },
397 | {
398 | "link": "http://localhost:8080/api/inventory/v1/files/image/6f2904706397452a3c3772fadd2ca6e1fbcc084f68abc6535f4f26c5e953471d",
399 | "rel": "image"
400 | },
401 | {
402 | "link": "http://localhost:8080/api/inventory/v1/files/image/3c4b7a3d823a128ea5325e8228324d6a5740af6977acb45861b47e8e23fc109d",
403 | "rel": "thumbnail"
404 | },
405 | {
406 | "link": "http://localhost:8080/api/inventory/v1/samples/4",
407 | "rel": "self"
408 | },
409 | {
410 | "link": "http://localhost:8080/api/inventory/v1/sampleTemplates/4/icon/3",
411 | "rel": "icon"
412 | }
413 | ]
414 | },
415 | "extraFields": [
416 | {
417 | "id": 2,
418 | "globalId": "EF2",
419 | "name": "My extra number",
420 | "lastModified": "2025-02-17T09:33:36.540Z",
421 | "modifiedBy": "user1a",
422 | "type": "number",
423 | "content": "3.141592",
424 | "parentGlobalId": "SS4",
425 | "_links": [],
426 | "deleted": false
427 | }
428 | ],
429 | "notes": [],
430 | "_links": [
431 | {
432 | "link": "http://localhost:8080/api/inventory/v1/subSamples/4",
433 | "rel": "self"
434 | },
435 | {
436 | "link": "http://localhost:8080/api/inventory/v1/files/image/6f2904706397452a3c3772fadd2ca6e1fbcc084f68abc6535f4f26c5e953471d",
437 | "rel": "image"
438 | },
439 | {
440 | "link": "http://localhost:8080/api/inventory/v1/files/image/3c4b7a3d823a128ea5325e8228324d6a5740af6977acb45861b47e8e23fc109d",
441 | "rel": "thumbnail"
442 | }
443 | ],
444 | "storedInContainer": false
445 | }
446 |
--------------------------------------------------------------------------------