p2f-client-py 0.0.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: p2f-client-py
3
+ Version: 0.0.11
4
+ Summary: API Client library for the Past to Future project.
5
+ Author-email: Garrett Speed <g.t.speed@uu.nl>
6
+ Project-URL: Homepage, https://github.com/Past-to-Future-EU-Horizon/p2f-client-py
7
+ Requires-Python: >=3.13
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: furl>=2.1.4
10
+ Requires-Dist: p2f-pydantic
11
+ Requires-Dist: pandas>=2.3.3
12
+ Requires-Dist: pyjwt>=2.10.1
13
+ Requires-Dist: python-dotenv>=1.1.0
14
+ Requires-Dist: requests>=2.32.3
15
+
16
+ # p2f-client-py
17
+ Python client library for the Past to Future projects Portal. Past to Future is an EU Horizon funded project.
18
+
19
+ ## Installation
20
+
21
+ pip install git+https://github.com/Past-to-Future-EU-Horizon/p2f-client-py
22
+
23
+
24
+ ## Usage
25
+
26
+ Create a client
27
+
28
+ from p2f_client import P2F_Client
29
+
30
+ client = P2F_Client("p2f-api.uu.nl", 8000)
@@ -0,0 +1,15 @@
1
+ # p2f-client-py
2
+ Python client library for the Past to Future projects Portal. Past to Future is an EU Horizon funded project.
3
+
4
+ ## Installation
5
+
6
+ pip install git+https://github.com/Past-to-Future-EU-Horizon/p2f-client-py
7
+
8
+
9
+ ## Usage
10
+
11
+ Create a client
12
+
13
+ from p2f_client import P2F_Client
14
+
15
+ client = P2F_Client("p2f-api.uu.nl", 8000)
File without changes
@@ -0,0 +1,32 @@
1
+ import requests
2
+ from furl import furl
3
+
4
+ ### We're having an issue with the API needing a connection
5
+ ### open to actually get data into the portal. Here we're
6
+ ### making a decorator function to probe the health-check
7
+ ### endpoint first, opening the connection to the API.
8
+
9
+ def health_probe(base_url):
10
+ def health_probe_func_level(func):
11
+ base_url = furl(base_url)
12
+ prefix = "health-check"
13
+ healthcheckurl = base_url / f"{prefix}/"
14
+ r = requests.get(healthcheckurl)
15
+ if r.ok:
16
+ # Run the decorated function
17
+ def innerfunc(*args, **kwargs):
18
+ return func(*args, **kwargs)
19
+ return innerfunc
20
+ else:
21
+ # Due to above commented issue, if we can't get a GET, then raise an error
22
+ raise requests.exceptions.HTTPError("The client could not connect to the API server.")
23
+ return health_probe_func_level
24
+
25
+ def health_check(base_url: furl):
26
+ prefix = "health-check"
27
+ healthcheck_url = base_url / f"{prefix}/"
28
+ r = requests.get(healthcheck_url)
29
+ if r.ok:
30
+ return True
31
+ else:
32
+ return False
@@ -0,0 +1,66 @@
1
+ # Local libraries
2
+ from p2f_pydantic.datasets import Datasets
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ from furl import furl
7
+ # Batteries included libraries
8
+ from uuid import UUID
9
+ from typing import Optional, List
10
+
11
+ class datasets:
12
+ def __init__(self, p2fclient):
13
+ self.p2fclient = p2fclient
14
+ self.base_url = p2fclient.base_url
15
+ self.prefix = "datasets/"
16
+ self.dataset_url = self.base_url / self.prefix
17
+ self.upload_queue = []
18
+ def add_dataset(self, dataset: Datasets):
19
+ self.upload_queue.append(dataset)
20
+ def upload_datasets(self):
21
+ uploaded_datasets = []
22
+ if health_check(self.base_url):
23
+ for dataset in self.upload_queue:
24
+ r = requests.post(self.dataset_url,
25
+ data=self.p2fclient.json_serialize_with_auth("dataset", dataset.model_dump_json(exclude_unset=True)),
26
+ headers={"Content-Type": "application/json"})
27
+ uploaded_datasets.append(Datasets(**r.json()))
28
+ # self.uploaded_datasets = uploaded_datasets
29
+ return uploaded_datasets
30
+ def upload_dataset(self, dataset: Datasets):
31
+ if health_check(self.base_url):
32
+ r = requests.post(self.dataset_url,
33
+ data=self.p2fclient.json_serialize_with_auth("dataset", dataset.model_dump_json(exclude_unset=True)),
34
+ headers={"Content-Type": "application/json"})
35
+ return Datasets(**r.json())
36
+ def list_remote_datasets(self,
37
+ is_new_p2f: Optional[bool]=None,
38
+ is_sub_dataset: Optional[bool]=None,
39
+ doi: Optional[str]=None) -> List[Datasets]:
40
+ list_url = self.dataset_url
41
+ # data = {}
42
+ if is_new_p2f is not None:
43
+ list_url.args["is_new_p2f"] = is_new_p2f
44
+ # data["is_new_p2f"] = is_new_p2f
45
+ if is_sub_dataset is not None:
46
+ list_url.args["is_sub_dataset"] = is_sub_dataset
47
+ # data["is_sub_dataset"] = is_sub_dataset
48
+ if doi is not None:
49
+ list_url.args["doi"] = doi
50
+ # data["doi"] = doi
51
+ if health_check(self.base_url):
52
+ r = requests.get(list_url, data=self.p2fclient.json_serialize_with_auth(),
53
+ headers={"Content-Type": "application/json"})
54
+ # self.datasets = [Datasets(**x) for x in r.json()]
55
+ return [Datasets(**x) for x in r.json()]
56
+ def get_remote_dataset(self, dataset_id):
57
+ get_url = self.dataset_url / str(dataset_id)
58
+ if health_check(self.base_url):
59
+ r = requests.get(get_url, data=self.p2fclient.json_serialize_with_auth(),
60
+ headers={"Content-Type": "application/json"})
61
+ return Datasets(**r.json())
62
+ def delete_remote_dataset(self, dataset_id):
63
+ delete_url = self.dataset_url / str(dataset_id)
64
+ if health_check(self.base_url):
65
+ r = requests.delete(delete_url, data=self.p2fclient.json_serialize_with_auth(),
66
+ headers={"Content-Type": "application/json"})
@@ -0,0 +1,38 @@
1
+ import furl
2
+
3
+ class doi:
4
+ def __init__(self, doi_input: str):
5
+ if doi_input.startswith("10"):
6
+ doi_split = doi_input.split("/")
7
+ print(doi_split)
8
+ if len(doi_split) == 2:
9
+ self.prefix, self.suffix = doi_split
10
+ else:
11
+ raise ValueError("The provided doi object does not conform to the prefix and suffix format.")
12
+ elif doi_input.startswith("http"):
13
+ url = furl.furl(doi_input)
14
+ match url.host:
15
+ case "doi.org":
16
+ print(url.path.segments)
17
+ self.prefix = [x for x in url.path.segments if x.startswith("10")][0]
18
+ self.suffix = url.path.segments[url.path.segments.index(self.prefix) + 1]
19
+ case "doi.pangaea.de":
20
+ print(url.path.segments)
21
+ self.prefix = [x for x in url.path.segments if x.startswith("10")][0]
22
+ self.suffix = url.path.segments[url.path.segments.index(self.prefix) + 1]
23
+ elif doi_input.startswith("doi:"):
24
+ doi_input = doi_input[4:]
25
+ doi_split = doi_input.split("/")
26
+ print(doi_split)
27
+ if len(doi_split) == 2:
28
+ self.prefix, self.suffix = doi_split
29
+ else:
30
+ raise ValueError("The provided doi object does not conform to the prefix and suffix format.")
31
+ else:
32
+ raise ValueError("Unable to recognize the format of this doi")
33
+ self.url = furl.furl(f"https://doi.org/{self.prefix}/{self.suffix}")
34
+ self.string = f"{self.prefix}/{self.suffix}"
35
+ def __str__(self):
36
+ return self.string
37
+ def __repr__(self):
38
+ return f"<doi object prefix={self.prefix} suffix={self.suffix}>"
@@ -0,0 +1,65 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_data_record import HARM_Data_Record
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ from furl import furl
7
+ # Batteries included libraries
8
+ from uuid import UUID
9
+ from typing import Optional, List
10
+ import hashlib
11
+ from datetime import datetime
12
+ from zoneinfo import ZoneInfo
13
+
14
+ class harm_data_records:
15
+ def __init__(self, p2fclient):
16
+ self.p2fclient = p2fclient
17
+ self.base_url = p2fclient.base_url
18
+ self.prefix = "harm-data-records/"
19
+ self.hdr_url = self.base_url / self.prefix
20
+ self.harm_data_records_queue = []
21
+ def add_data_record(self, data_record: HARM_Data_Record):
22
+ self.harm_data_records_queue.append(data_record)
23
+ def upload_data_records(self):
24
+ uploaded_records = []
25
+ if health_check(self.base_url):
26
+ for record in self.harm_data_records_queue:
27
+ r = requests.post(self.hdr_url,
28
+ data=self.p2fclient.json_serialize_with_auth("new_data_record", record.model_dump_json(exclude_unset=True)),
29
+ headers={"Content-Type": "application/json"})
30
+ record.append(HARM_Data_Record(**r.json()))
31
+ self.uploaded_records = uploaded_records
32
+ return uploaded_records
33
+ def upload_data_record(self, data_record: HARM_Data_Record):
34
+ if health_check(self.base_url):
35
+ r = requests.post(self.hdr_url,
36
+ data=self.p2fclient.json_serialize_with_auth("new_data_record", data_record.model_dump_json(exclude_unset=True)),
37
+ headers={"Content-Type": "application/json"})
38
+ return HARM_Data_Record(**r.json())
39
+ def list_remote_records(self,
40
+ dataset: Optional[str]=None,
41
+ data_type: Optional[str]=None):
42
+ params = {"dataset": dataset,
43
+ "data_type": data_type}
44
+ params = {x:y for x, y in params.items() if y != None}
45
+ if health_check(self.base_url):
46
+ r = requests.get(self.hdr_url,
47
+ params=params, data=self.p2fclient.json_serialize_with_auth(),
48
+ headers={"Content-Type": "application/json"})
49
+ return [HARM_Data_Record(**x) for x in r.json()]
50
+ def get_remote_record(self, record_hash: str):
51
+ if health_check(self.base_url):
52
+ r = requests.get(self.hdr_url / record_hash, data=self.p2fclient.json_serialize_with_auth(),
53
+ headers={"Content-Type": "application/json"})
54
+ return HARM_Data_Record(**r.json())
55
+ def delete_remote_dataset(self, record_hash: str):
56
+ if health_check(self.base_url):
57
+ r = requests.delete(self.hdr_url / record_hash, data=self.p2fclient.json_serialize_with_auth(),
58
+ headers={"Content-Type": "application/json"})
59
+ def calculate_hash(self, dataset_id, row_number, debugging=False):
60
+ hasher = hashlib.md5()
61
+ hasher.update(str(dataset_id).encode("utf8"))
62
+ hasher.update(str(row_number).encode("utf8"))
63
+ if debugging == True:
64
+ hasher.update(str(datetime.now(tz=ZoneInfo("UTC")).isoformat(sep="T")).encode("utf8"))
65
+ return str(hasher.hexdigest())
@@ -0,0 +1,58 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_data_types import HARM_Data_Type
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ from furl import furl
7
+ # Batteries included libraries
8
+ from uuid import UUID
9
+ from typing import Optional, List
10
+
11
+ class harm_data_type:
12
+ def __init__(self, p2fclient):
13
+ self.p2fclient = p2fclient
14
+ self.base_url = p2fclient.base_url
15
+ self.prefix = "harm-data-types/"
16
+ self.hdt_url = self.base_url / self.prefix
17
+ self.harm_data_types_queue = []
18
+ def add_harm_data_type(self, new_data_type: HARM_Data_Type):
19
+ self.harm_data_types_queue.append(new_data_type)
20
+ def upload_data_types(self):
21
+ if health_check(self.base_url):
22
+ for datatype in self.harm_data_types_queue:
23
+ r = requests.post(self.hdt_url,
24
+ data=self.p2fclient.json_serialize_with_auth("new_harm_data_type", datatype.model_dump_json(exclude_unset=True)),
25
+ headers={"Content-Type": "application/json"})
26
+ def upload_data_type(self, new_data_type: HARM_Data_Type):
27
+ if health_check(self.base_url):
28
+ r = requests.post(self.hdt_url,
29
+ data=self.p2fclient.json_serialize_with_auth("new_harm_data_type", new_data_type.model_dump_json(exclude_unset=True)),
30
+ headers={"Content-Type": "application/json"})
31
+ return HARM_Data_Type(**r.json())
32
+ def list_data_types(self,
33
+ measure: Optional[str]=None,
34
+ unit_of_measure: Optional[str]=None,
35
+ method: Optional[str]=None,
36
+ dataset_id: Optional[UUID]=None):
37
+ params = {"measure": measure,
38
+ "unit_of_measure": unit_of_measure,
39
+ "method": method,
40
+ "dataset_id": dataset_id}
41
+ params = {x:y for x, y in params.items() if y != None}
42
+ if health_check(self.base_url):
43
+ r = requests.get(self.hdt_url,
44
+ params=params,
45
+ data=self.p2fclient.json_serialize_with_auth(),
46
+ headers={"Content-Type": "application/json"})
47
+ return [HARM_Data_Type(**x) for x in r.json()]
48
+ def get_data_type(self, datatype_id: UUID):
49
+ if health_check(self.base_url):
50
+ r = requests.get(self.hdt_url / datatype_id,
51
+ data=self.p2fclient.json_serialize_with_auth(),
52
+ headers={"Content-Type": "application/json"})
53
+ return HARM_Data_Type(**r.json())
54
+ def delete_data_type(self, datatype_id: UUID):
55
+ if health_check(self.base_url):
56
+ r = requests.delete(self.hdt_url / datatype_id,
57
+ data=self.p2fclient.json_serialize_with_auth(),
58
+ headers={"Content-Type": "application/json"})
@@ -0,0 +1,77 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_data_metadata import HARM_Bounding_Box, HARM_Location
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ from furl import furl
7
+ # Batteries included libraries
8
+ from uuid import UUID
9
+ from typing import Optional, List, Union
10
+
11
+ class harm_location:
12
+ def __init__(self, p2fclient):
13
+ self.p2fclient = p2fclient
14
+ self.base_url = p2fclient.base_url
15
+ self.prefix = "harm-data-locations/"
16
+ self.hdl_url = self.base_url / self.prefix
17
+ self.harmonized_location_queue = []
18
+ def add_harm_location(self, new_location: HARM_Location):
19
+ self.harmonized_location_queue.append(new_location)
20
+ def upload_harm_locations(self):
21
+ inserted_locations = []
22
+ if health_check(self.base_url):
23
+ for location in self.harmonized_location_queue:
24
+ r = requests.post(self.hdl_url,
25
+ data=self.p2fclient.json_serialize_with_auth("new_location", location.model_dump_json(exclude_unset=True)),
26
+ headers={"Content-Type": "application/json"})
27
+ inserted_locations.append(HARM_Location(**r.json()))
28
+ return inserted_locations
29
+ def upload_harm_location(self, new_location: HARM_Location):
30
+ if health_check(self.base_url):
31
+ r = requests.post(self.hdl_url,
32
+ data=self.p2fclient.json_serialize_with_auth("new_location", new_location.model_dump_json(exclude_unset=True)),
33
+ headers={"Content-Type": "application/json"})
34
+ return HARM_Location(**r.json())
35
+ def list_harm_locations(self,
36
+ bounding_box: Optional[HARM_Bounding_Box]=None,
37
+ location_name: Optional[str]=None,
38
+ location_code: Optional[str]=None,
39
+ minimum_elevation: Optional[float]=None,
40
+ maximum_elevation: Optional[float]=None,
41
+ min_location_age: Optional[float]=None,
42
+ max_location_age: Optional[float]=None,
43
+ dataset_id: Optional[UUID]=None):
44
+ params = {
45
+ "bounding_box": bounding_box,
46
+ "location_name": location_name,
47
+ "location_code": location_code,
48
+ "minimum_elevation": minimum_elevation,
49
+ "maximum_elevation": maximum_elevation,
50
+ "min_location_age": min_location_age,
51
+ "max_location_age": max_location_age,
52
+ "dataset_id": dataset_id
53
+ }
54
+ params = {x: y for x, y in params.items() if y != None}
55
+ if health_check(self.base_url):
56
+ r = requests.get(self.hdl_url,
57
+ params=params, data=self.p2fclient.json_serialize_with_auth(),
58
+ headers={"Content-Type": "application/json"})
59
+ return [HARM_Location(**x) for x in r.json()]
60
+ def get_harm_location(self, location_identifier: UUID):
61
+ if health_check(self.base_url):
62
+ r = requests.get(self.hdl_url/str(location_identifier), data=self.p2fclient.json_serialize_with_auth(),
63
+ headers={"Content-Type": "application/json"})
64
+ return HARM_Location(**r.json())
65
+ def delete_harm_location(self, location_identifier: UUID):
66
+ if health_check(self.base_url):
67
+ r = requests.delete(self.hdl_url/str(location_identifier), data=self.p2fclient.json_serialize_with_auth(),
68
+ headers={"Content-Type": "application/json"})
69
+ def assign_location_to_record(self, location_identifier: UUID, record_hash: str):
70
+ # params = {"location_identifier": str(location_identifier),
71
+ # "record_hash": record_hash}
72
+ assign_url = self.hdl_url / "assign"
73
+ assign_url.args["location_identifier"] = str(location_identifier)
74
+ assign_url.args["record_hash"] = record_hash
75
+ if health_check(self.base_url):
76
+ r = requests.post(assign_url, data=self.p2fclient.json_serialize_with_auth(),
77
+ headers={"Content-Type": "application/json"})
@@ -0,0 +1,84 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_data_numerical import HARM_Float, HARM_Float_Confidence
3
+ from p2f_pydantic.harm_data_numerical import HARM_Int, HARM_Int_Confidence
4
+ from p2f_pydantic.harm_data_numerical import Insert_HARM_Numerical, Return_HARM_Numerical
5
+ from .conn import health_check
6
+ # Third Party Libraries
7
+ import requests
8
+ from furl import furl
9
+ # Batteries included libraries
10
+ from uuid import UUID
11
+ from typing import Optional, List, Union, Literal
12
+
13
+ Harm_numerical_union = Union[HARM_Int, HARM_Int_Confidence, HARM_Float, HARM_Float_Confidence]
14
+
15
+ class harm_numerical:
16
+ def __init__(self, p2fclient):
17
+ self.p2fclient = p2fclient
18
+ self.base_url = p2fclient.base_url
19
+ self.prefix = "harm-numerical/"
20
+ self.hdn_url = self.base_url / self.prefix
21
+ self.harmonized_numerical_records_queue = []
22
+ def add_harm_numerical(self, new_numerical_record: Insert_HARM_Numerical):
23
+ self.harmonized_numerical_records_queue.append(new_numerical_record)
24
+ def upload_harm_numericals(self):
25
+ inserted_numericals = []
26
+ if health_check(self.base_url):
27
+ for nummer in self.harmonized_numerical_records_queue:
28
+ r = requests.post(self.hdn_url,
29
+ data=self.p2fclient.json_serialize_with_auth("new_numeric", nummer.model_dump_json(exclude_unset=True)),
30
+ headers={"Content-Type": "application/json"})
31
+ inserted_numericals.append(self.identify_numeric_object(r.json(), nummer.model_dump_json(exclude_unset=True)))
32
+ return inserted_numericals
33
+ def upload_harm_numerical(self, new_record: Insert_HARM_Numerical):
34
+ if health_check(self.base_url):
35
+ r = requests.post(self.hdn_url, data=self.p2fclient.json_serialize_with_auth("new_numeric", new_record.model_dump_json(exclude_unset=True)),
36
+ headers={"Content-Type": "application/json"})
37
+ return self.identify_numeric_object(r.json(), new_record.model_dump(exclude_unset=True))
38
+ def list_harm_numericals(self,
39
+ record_hash: Optional[str]=None,
40
+ numeric_type: Optional[Literal["float_confidence",
41
+ "float",
42
+ "int_confidence",
43
+ "int"]]=None,
44
+ data_type: Optional[UUID]=None,
45
+ dataset_id: Optional[UUID]=None):
46
+ params = {
47
+ "record_hash": record_hash,
48
+ "numeric_type": numeric_type,
49
+ "data_type": data_type,
50
+ "dataset_id": dataset_id
51
+ }
52
+ params = {x:y for x, y in params.items() if y != None}
53
+ if health_check(self.base_url):
54
+ r = requests.get(self.hdn_url,
55
+ params=params, data=self.p2fclient.json_serialize_with_auth(),
56
+ headers={"Content-Type": "application/json"})
57
+ return Return_HARM_Numerical(**r.json())
58
+ def identify_numeric_object(self,
59
+ incoming_json,
60
+ original: Optional[Insert_HARM_Numerical]=None) -> Harm_numerical_union:
61
+ if original:
62
+ number_type = original["numerical_type"]
63
+ else:
64
+ incoming_value = incoming_json["value"]
65
+ if "." in str(incoming_value):
66
+ number_type = "FLOAT"
67
+ else:
68
+ number_type = "INT"
69
+ if "upper_conf_value" in incoming_json.keys():
70
+ number_type += "_CONFIDENCE"
71
+ match number_type:
72
+ case "INT":
73
+ rv = HARM_Int(**incoming_json)
74
+ case "INT_CONFIDENCE":
75
+ rv = HARM_Int_Confidence(**incoming_json)
76
+ case "FLOAT":
77
+ rv = HARM_Float(**incoming_json)
78
+ case "FLOAT_CONFIDENCE":
79
+ rv = HARM_Float_Confidence(**incoming_json)
80
+ return rv
81
+
82
+
83
+
84
+
@@ -0,0 +1,68 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_reference import HARM_Reference
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ # Batteries included libraries
7
+ from typing import Optional, List
8
+ from uuid import UUID
9
+
10
+ class harm_reference:
11
+ def __init__(self, p2fclient):
12
+ self.p2fclient = p2fclient
13
+ self.base_url = p2fclient.base_url
14
+ self.prefix = "harm-reference/"
15
+ self.hr_url = self.base_url / self.prefix
16
+ self.harmonized_reference_queue = []
17
+ def add_harm_reference(self, new_reference: HARM_Reference):
18
+ self.harmonized_reference_queue.append(new_reference)
19
+ def upload_harm_reference_queue(self) -> List[HARM_Reference]:
20
+ inserted_harm_references = []
21
+ if health_check(self.base_url):
22
+ for ref in self.harmonized_reference_queue:
23
+ r = requests.post(self.hr_url, data=self.p2fclient.json_serialize_with_auth("new_reference", ref.model_dump_json(exclude_unset=True)),
24
+ headers={"Content-Type": "application/json"})
25
+ inserted_harm_references.append(HARM_Reference(**r.json()))
26
+ return inserted_harm_references
27
+ def upload_harm_reference(self, new_reference: HARM_Reference) -> HARM_Reference:
28
+ if health_check(self.base_url):
29
+ r = requests.post(self.hr_url, data=self.p2fclient.json_serialize_with_auth("new_reference", new_reference.model_dump_json(exclude_unset=True)),
30
+ headers={"Content-Type": "application/json"})
31
+ if r.ok:
32
+ return (HARM_Reference(**r.json()))
33
+ def list_harm_references(self) -> List[HARM_Reference]:
34
+ if health_check(self.base_url):
35
+ r = requests.get(self.hr_url, data=self.p2fclient.json_serialize_with_auth(),
36
+ headers={"Content-Type": "application/json"})
37
+ if r.ok:
38
+ return [HARM_Reference(**x) for x in r.json()]
39
+ else:
40
+ return []
41
+ def get_harm_reference(self, reference_id: UUID) -> HARM_Reference:
42
+ if health_check(self.base_url):
43
+ r = requests.get(self.hr_url / reference_id, data=self.p2fclient.json_serialize_with_auth(),
44
+ headers={"Content-Type": "application/json"})
45
+ if r.ok:
46
+ return HARM_Reference(**r.json())
47
+ def delete_harm_reference(self, reference_id: UUID):
48
+ if health_check(self.base_url):
49
+ r = requests.delete(self, reference_id, data=self.p2fclient.json_serialize_with_auth(),
50
+ headers={"Content-Type": "application/json"})
51
+ def assign_harm_reference(self,
52
+ reference_id: UUID,
53
+ record_hash: str):
54
+ assign_url = self.hr_url
55
+ assign_url.args["reference_id"] = reference_id
56
+ assign_url.args["record_hash"] = record_hash
57
+ if health_check(self.base_url):
58
+ r = requests.post(assign_url, data=self.p2fclient.json_serialize_with_auth(),
59
+ headers={"Content-Type": "application/json"})
60
+ def remove_harm_reference(self,
61
+ reference_id: UUID,
62
+ record_hash: str):
63
+ assign_url = self.hr_url
64
+ assign_url.args["reference_id"] = reference_id
65
+ assign_url.args["record_hash"] = record_hash
66
+ if health_check(self.base_url):
67
+ r = requests.delete(assign_url, data=self.p2fclient.json_serialize_with_auth(),
68
+ headers={"Content-Type": "application/json"})
@@ -0,0 +1,76 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_data_metadata import HARM_Data_Species
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ # Batteries included libraries
7
+ from typing import Optional, List
8
+ from uuid import UUID
9
+
10
+ class harm_species:
11
+ def __init__(self, p2fclient):
12
+ self.p2fclient = p2fclient
13
+ self.base_url = p2fclient.base_url
14
+ self.prefix = "harm-data-species/"
15
+ self.hds_url = self.base_url / self.prefix
16
+ self.harmonized_species_queue = []
17
+ def add_harm_species(self, new_species: HARM_Data_Species):
18
+ self.harmonized_species_queue.append(new_species)
19
+ def upload_harm_species_queue(self) -> List[HARM_Data_Species]:
20
+ inserted_species = []
21
+ if health_check(self.base_url):
22
+ for record in self.harmonized_species_queue:
23
+ r = requests.post(self.hds_url,
24
+ data=self.p2fclient.json_serialize_with_auth("new_species", record.model_dump_json(exclude_unset=True)),
25
+ headers={"Content-Type": "application/json"})
26
+ inserted_species.append(HARM_Data_Species(**r.json()))
27
+ return inserted_species
28
+ def upload_harm_species(self, new_species: HARM_Data_Species):
29
+ if health_check(self.base_url):
30
+ r = requests.post(self.hds_url,
31
+ data=self.p2fclient.json_serialize_with_auth("new_species", new_species.model_dump_json(exclude_unset=True)),
32
+ headers={"Content-Type": "application/json"})
33
+ return HARM_Data_Species(**r.json())
34
+ def list_harm_species(self,
35
+ tax_domain: Optional[str]=None,
36
+ tax_kingdom: Optional[str]=None,
37
+ tax_subkingdom: Optional[str]=None,
38
+ tax_infrakingdom: Optional[str]=None,
39
+ tax_phylum: Optional[str]=None,
40
+ tax_class: Optional[str]=None,
41
+ tax_subclass: Optional[str]=None,
42
+ tax_order: Optional[str]=None,
43
+ tax_suborder: Optional[str]=None,
44
+ tax_superfamily: Optional[str]=None,
45
+ tax_family: Optional[str]=None,
46
+ tax_subfamily: Optional[str]=None,
47
+ tax_genus: Optional[str]=None,
48
+ tax_species: Optional[str]=None,
49
+ tax_subspecies: Optional[str]=None,
50
+ common_name: Optional[str]=None,
51
+ display_species: Optional[str]=None,) -> HARM_Data_Species:
52
+ params = {x: y for x, y in locals().items() if x.startswith("tax_")}
53
+ params["common_name"] = common_name
54
+ params["display_species"] = display_species
55
+ params = {x: y for x, y in params.items() if y != None}
56
+ if health_check(self.base_url):
57
+ r = requests.get(self.hds_url,
58
+ params=params, data=self.p2fclient.json_serialize_with_auth(),
59
+ headers={"Content-Type": "application/json"})
60
+ return [HARM_Data_Species(**x) for x in r.json()]
61
+ def get_harm_species(self, species_identifier: UUID):
62
+ if health_check(self.base_url):
63
+ r = requests.get(self.hds_url/species_identifier, data=self.p2fclient.json_serialize_with_auth(),
64
+ headers={"Content-Type": "application/json"})
65
+ return HARM_Data_Species(**r.json())
66
+ def delete_harm_species(self, species_identifier: UUID):
67
+ if health_check(self.base_url):
68
+ r = requests.delete(self.hds_url/species_identifier, data=self.p2fclient.json_serialize_with_auth(),
69
+ headers={"Content-Type": "application/json"})
70
+ def assign_species_to_record(self, species_identifier: UUID, record_hash: str):
71
+ assign_url = self.hds_url / "assign"
72
+ assign_url.args["species_id"] = species_identifier
73
+ assign_url.args["record_hash"] = record_hash
74
+ if health_check(self.base_url):
75
+ r = requests.post(assign_url, data=self.p2fclient.json_serialize_with_auth(),
76
+ headers={"Content-Type": "application/json"})
@@ -0,0 +1,75 @@
1
+ # Local libraries
2
+ from p2f_pydantic.harm_timeslices import HARM_Timeslice
3
+ from .conn import health_check
4
+ # Third Party Libraries
5
+ import requests
6
+ # Batteries included libraries
7
+ from uuid import UUID
8
+ from typing import Optional, List
9
+
10
+ class harm_timeslice:
11
+ def __init__(self, p2fclient):
12
+ self.p2fclient = p2fclient
13
+ self.base_url = p2fclient.base_url
14
+ self.prefix = "harm-timeslice/"
15
+ self.ht_url = self.base_url / self.prefix
16
+ self.harmonized_timeslice_queue = []
17
+ def add_timeslice(self, new_timeslice: HARM_Timeslice):
18
+ self.harmonized_timeslice_queue.append(new_timeslice)
19
+ def upload_timeslice_queue(self):
20
+ inserted_timeslice_list = []
21
+ if health_check(self.base_url):
22
+ for timeslice in self.harmonized_timeslice_queue:
23
+ r = requests.post(self.ht_url,
24
+ data=self.p2fclient.json_serialize_with_auth("new_harm_timeslice", timeslice.model_dump_json(exclude_unset=True)),
25
+ headers={"Content-Type": "application/json"})
26
+ inserted_timeslice_list.append(HARM_Timeslice(**r.json()))
27
+ return inserted_timeslice_list
28
+ def upload_timeslice(self, new_timeslice: HARM_Timeslice) -> HARM_Timeslice:
29
+ if health_check(self.base_url):
30
+ r = requests.post(self.ht_url,
31
+ data=self.p2fclient.json_serialize_with_auth("new_harm_timeslice", new_timeslice.model_dump_json(exclude_unset=True)),
32
+ headers={"Content-Type": "application/json"})
33
+ return HARM_Timeslice(**r.json())
34
+ def list_timeslices(self,
35
+ named_time_period: Optional[str]=None,
36
+ older_search_age: Optional[int]=None,
37
+ recent_search_age: Optional[int]=None,) -> List[HARM_Timeslice]:
38
+ params = {"named_time_period": named_time_period,
39
+ "older_search_age": older_search_age,
40
+ "recent_search_age": recent_search_age}
41
+ params = {x: y for x, y in params.items() if y != None}
42
+ if health_check(self.base_url):
43
+ r = requests.get(self.ht_url,
44
+ params=params, data=self.p2fclient.json_serialize_with_auth(),
45
+ headers={"Content-Type": "application/json"})
46
+ return [HARM_Timeslice(**x) for x in r.json()]
47
+ def get_timeslice(self,
48
+ timeslice_id: UUID) -> HARM_Timeslice:
49
+ if health_check(self.base_url):
50
+ r = requests.get(self.ht_url / timeslice_id, data=self.p2fclient.json_serialize_with_auth(),
51
+ headers={"Content-Type": "application/json"})
52
+ return HARM_Timeslice(**r.json())
53
+ def delete_timeslice(self,
54
+ timeslice_id: UUID) -> HARM_Timeslice:
55
+ if health_check(self.base_url):
56
+ r = requests.delete(self.ht_url / timeslice_id, data=self.p2fclient.json_serialize_with_auth(),
57
+ headers={"Content-Type": "application/json"})
58
+ def assign_timeslice(self,
59
+ timeslice_id: UUID,
60
+ record_hash: str):
61
+ assign_url = self.ht_url / "assign"
62
+ assign_url.args["timeslice_id"] = timeslice_id
63
+ assign_url.args["record_hash"] = record_hash
64
+ if health_check(self.base_url):
65
+ r = requests.post(assign_url, data=self.p2fclient.json_serialize_with_auth(),
66
+ headers={"Content-Type": "application/json"})
67
+ def remove_timeslice(self,
68
+ timeslice_id: UUID,
69
+ record_hash: str):
70
+ remove_url = self.ht_url / "remove"
71
+ remove_url.args["timeslice_id"] = timeslice_id
72
+ remove_url.args["record_hash"] = record_hash
73
+ if health_check(self.base_url):
74
+ r = requests.delete(remove_url, data=self.p2fclient.json_serialize_with_auth(),
75
+ headers={"Content-Type": "application/json"})
@@ -0,0 +1,84 @@
1
+ # Local libraries
2
+ from .datasets import datasets
3
+ from .harm_data_record import harm_data_records
4
+ from .harm_data_types import harm_data_type
5
+ from .harm_numerical import harm_numerical
6
+ from .harm_location import harm_location
7
+ from .harm_species import harm_species
8
+ from .harm_timeslice import harm_timeslice
9
+ from .harm_reference import harm_reference
10
+ from .conn import health_check
11
+ from p2f_pydantic.temp_accounts import Temp_Account
12
+ # Third Party Libraries
13
+ import requests
14
+ import furl
15
+ # Batteries included libraries
16
+ from datetime import datetime, timedelta
17
+ from zoneinfo import ZoneInfo
18
+ from typing import Optional
19
+
20
+
21
+ class P2F_Client:
22
+ def __init__(self,
23
+ hostname: str,
24
+ port: int=443,
25
+ https: bool=True,
26
+ email: Optional[str]=None,
27
+ token: Optional[str] = None,
28
+ token_expiration: Optional[datetime]=None):
29
+ self.version = (0, 0, 10) # turn this into a real named tuple one day
30
+ self.hostname = hostname
31
+ self.port = port
32
+ if https:
33
+ self.protocol = "https"
34
+ else:
35
+ self.protocol = "http"
36
+ self.host_url = f"{self.protocol}://{self.hostname}:{self.port}"
37
+ self.base_url = furl.furl(self.host_url)
38
+ self.email= email
39
+ self.token = token
40
+ if self.token is not None:
41
+ if token_expiration is None:
42
+ # It's not true, but it does inform us that the token could possibly last till tomorrow
43
+ self.TOKEN_EXPIRATION = datetime.now(tz=ZoneInfo("UTC")) + timedelta(hours=24)
44
+ raise UserWarning("A generic token expiration time was used, the token could expire sooner than the currently set token expiration time")
45
+ else:
46
+ self.TOKEN_EXPIRATION = token_expiration
47
+ if self.email is not None:
48
+ self.temp_account = Temp_Account(email=self.email, token=self.token)
49
+ self.child_class_loading()
50
+ def child_class_loading(self):
51
+ # Separated this out so we can reload it later.
52
+ self.datasets = datasets(self)
53
+ self.harm_data_records = harm_data_records(self)
54
+ self.harm_data_type = harm_data_type(self)
55
+ self.harm_numerical = harm_numerical(self)
56
+ self.harm_location = harm_location(self)
57
+ self.harm_species = harm_species(self)
58
+ self.harm_timeslice = harm_timeslice(self)
59
+ self.harm_reference = harm_reference(self)
60
+ def request_token(self):
61
+ # self.email = email
62
+ self.token_url = self.base_url / "token"
63
+ self.token_request_url = self.token_url / "request"
64
+ token_request_model = Temp_Account(email=self.email)
65
+ # calculate the datetime of the token before making the request
66
+ # so that our expiration time is just before actual expiration.
67
+ self.TOKEN_EXPIRATION = datetime.now(tz=ZoneInfo("UTC")) + timedelta(hours=24)
68
+ if health_check(self.base_url):
69
+ r = requests.post(self.token_request_url,
70
+ data=token_request_model.model_dump_json(exclude_unset=True),
71
+ headers={"Content-Type": "application/json"})
72
+ print(r.json())
73
+ def set_token(self, token: str):
74
+ self.token = token
75
+ self.temp_account = Temp_Account(email=self.email, token=self.token)
76
+ # reload the child classes so they will have the token
77
+ self.child_class_loading()
78
+ def json_serialize_with_auth(self,
79
+ label: Optional[str]=None,
80
+ JSON_str: Optional[str]=None):
81
+ if label is not None:
82
+ return f"""{{"auth":{self.temp_account.model_dump_json(exclude_unset=True)},"{label}":{JSON_str}}}"""
83
+ if label is None:
84
+ return self.temp_account.model_dump_json(exclude_unset=True)
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: p2f-client-py
3
+ Version: 0.0.11
4
+ Summary: API Client library for the Past to Future project.
5
+ Author-email: Garrett Speed <g.t.speed@uu.nl>
6
+ Project-URL: Homepage, https://github.com/Past-to-Future-EU-Horizon/p2f-client-py
7
+ Requires-Python: >=3.13
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: furl>=2.1.4
10
+ Requires-Dist: p2f-pydantic
11
+ Requires-Dist: pandas>=2.3.3
12
+ Requires-Dist: pyjwt>=2.10.1
13
+ Requires-Dist: python-dotenv>=1.1.0
14
+ Requires-Dist: requests>=2.32.3
15
+
16
+ # p2f-client-py
17
+ Python client library for the Past to Future projects Portal. Past to Future is an EU Horizon funded project.
18
+
19
+ ## Installation
20
+
21
+ pip install git+https://github.com/Past-to-Future-EU-Horizon/p2f-client-py
22
+
23
+
24
+ ## Usage
25
+
26
+ Create a client
27
+
28
+ from p2f_client import P2F_Client
29
+
30
+ client = P2F_Client("p2f-api.uu.nl", 8000)
@@ -0,0 +1,19 @@
1
+ README.md
2
+ pyproject.toml
3
+ p2f_client/__init__.py
4
+ p2f_client/conn.py
5
+ p2f_client/datasets.py
6
+ p2f_client/doi.py
7
+ p2f_client/harm_data_record.py
8
+ p2f_client/harm_data_types.py
9
+ p2f_client/harm_location.py
10
+ p2f_client/harm_numerical.py
11
+ p2f_client/harm_reference.py
12
+ p2f_client/harm_species.py
13
+ p2f_client/harm_timeslice.py
14
+ p2f_client/p2f_client.py
15
+ p2f_client_py.egg-info/PKG-INFO
16
+ p2f_client_py.egg-info/SOURCES.txt
17
+ p2f_client_py.egg-info/dependency_links.txt
18
+ p2f_client_py.egg-info/requires.txt
19
+ p2f_client_py.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ furl>=2.1.4
2
+ p2f-pydantic
3
+ pandas>=2.3.3
4
+ pyjwt>=2.10.1
5
+ python-dotenv>=1.1.0
6
+ requests>=2.32.3
@@ -0,0 +1,2 @@
1
+ dist
2
+ p2f_client
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "p2f-client-py"
3
+ version = "0.0.11"
4
+ description = "API Client library for the Past to Future project."
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "furl>=2.1.4",
9
+ "p2f-pydantic",
10
+ "pandas>=2.3.3",
11
+ "pyjwt>=2.10.1",
12
+ "python-dotenv>=1.1.0",
13
+ "requests>=2.32.3",
14
+ ]
15
+ authors = [
16
+ { name="Garrett Speed", email="g.t.speed@uu.nl"}
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/Past-to-Future-EU-Horizon/p2f-client-py"
21
+
22
+ [tool.setuptools.packages.find]
23
+ exclude = ["Pliocene_SSTs"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+