Skip to content

Commit a83fd2d

Browse files
Merge pull request #118 from jverswijver/add_components
Add components
2 parents fb5344b + f10c6fe commit a83fd2d

File tree

6 files changed

+125
-14
lines changed

6 files changed

+125
-14
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
44

5+
## [0.4.0] - 2022-03-18
6+
### Fixed
7+
- Bug with `order_by` not applying from fetch args PR #117
8+
9+
### Added
10+
- Support for new `slider` and `dropdown-query` components PR #118
11+
- Numpy parser for `component_interface.py` to remove numpy types for json serialization PR #118
12+
- Support for loginless mode PR #118
13+
514
## [0.3.0] - 2022-01-21
615
### Changed
716
- Hot-reload mechanism to use `otumat watch` PR #116
@@ -102,6 +111,7 @@ Observes [Semantic Versioning](https://semver.org/spec/v2.0.0.html) standard and
102111
- Support for DataJoint attribute types: `varchar`, `int`, `float`, `datetime`, `date`, `time`, `decimal`, `uuid`.
103112
- Check dependency utility to determine child table references.
104113

114+
[0.4.0]: https://github.com/datajoint/pharus/compare/0.3.0...0.4.0
105115
[0.3.0]: https://github.com/datajoint/pharus/compare/0.2.3...0.3.0
106116
[0.2.3]: https://github.com/datajoint/pharus/compare/0.2.2...0.2.3
107117
[0.2.2]: https://github.com/datajoint/pharus/compare/0.2.1...0.2.2

pharus/component_interface.py

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,44 @@
44
import datajoint as dj
55
import re
66
import inspect
7-
from datetime import datetime
7+
from datetime import date, datetime
88
from flask import request, send_file
99
from .interface import _DJConnector
1010
import os
1111
from pathlib import Path
1212
import types
1313
import io
14+
import numpy as np
15+
16+
17+
class NumpyEncoder(json.JSONEncoder):
18+
"""teach json to dump datetimes, etc"""
19+
20+
npmap = {
21+
np.bool_: bool,
22+
np.uint8: int,
23+
np.uint16: int,
24+
np.uint32: int,
25+
np.uint64: int,
26+
np.int8: int,
27+
np.int16: int,
28+
np.int32: int,
29+
np.int64: int,
30+
np.float32: float,
31+
np.float64: float,
32+
np.ndarray: list,
33+
}
34+
35+
def default(self, o):
36+
if type(o) in self.npmap:
37+
return self.npmap[type(o)](o)
38+
if type(o) in (datetime, date):
39+
return o.isoformat()
40+
return json.JSONEncoder.default(self, o)
41+
42+
@classmethod
43+
def dumps(cls, obj):
44+
return json.dumps(obj, cls=cls)
1445

1546

1647
class QueryComponent:
@@ -70,9 +101,9 @@ def restriction(self):
70101
self.dj_restriction(),
71102
{
72103
k: (
73-
datetime.fromtimestamp(float(v))
104+
datetime.fromtimestamp(float(v)).isoformat()
74105
if re.match(
75-
r"^datetime.*$",
106+
r"^date.*$",
76107
self.fetch_metadata["query"].heading.attributes[k].type,
77108
)
78109
else v
@@ -226,8 +257,42 @@ def __init__(self, *args, **kwargs):
226257

227258
def dj_query_route(self):
228259
fetch_metadata = self.fetch_metadata
229-
return (fetch_metadata["query"] & self.restriction).fetch1(
230-
*fetch_metadata["fetch_args"]
260+
return NumpyEncoder.dumps(
261+
(fetch_metadata["query"] & self.restriction).fetch1(
262+
*fetch_metadata["fetch_args"]
263+
)
264+
)
265+
266+
267+
class BasicQuery(QueryComponent):
268+
def __init__(self, *args, **kwargs):
269+
super().__init__(*args, **kwargs)
270+
self.frontend_map = {
271+
"source": "sci-viz/src/Components/Plots/FullPlotly.tsx",
272+
"target": "FullPlotly",
273+
}
274+
self.response_examples = {
275+
"dj_query_route": {
276+
"recordHeader": ["subject_uuid", "session_start_time", "session_uuid"],
277+
"records": [
278+
[
279+
"00778394-c956-408d-8a6c-ca3b05a611d5",
280+
1565436299.0,
281+
"fb9bdf18-76be-452b-ac4e-21d5de3a6f9f",
282+
]
283+
],
284+
"totalCount": 1,
285+
},
286+
}
287+
288+
def dj_query_route(self):
289+
fetch_metadata = self.fetch_metadata
290+
record_header, table_records, total_count = _DJConnector._fetch_records(
291+
query=fetch_metadata["query"] & self.restriction,
292+
fetch_args=fetch_metadata["fetch_args"],
293+
)
294+
return dict(
295+
recordHeader=record_header, records=table_records, totalCount=total_count
231296
)
232297

233298

@@ -254,8 +319,11 @@ def dj_query_route(self):
254319

255320

256321
type_map = {
322+
"basicquery": BasicQuery,
257323
"plot:plotly:stored_json": PlotPlotlyStoredjsonComponent,
258324
"table": TableComponent,
259325
"metadata": MetadataComponent,
260326
"file:image:attach": FileImageAttachComponent,
327+
"slider": BasicQuery,
328+
"dropdown-query": BasicQuery,
261329
}

pharus/dynamic_api_gen.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def populate_api():
1616
from datetime import datetime
1717
import inspect
1818
import traceback
19+
import os
1920
try:
2021
from .component_interface_override import type_map
2122
except (ModuleNotFoundError, ImportError):
@@ -37,13 +38,35 @@ def {method_name}(jwt_payload: dict) -> dict:
3738
except Exception as e:
3839
return traceback.format_exc(), 500
3940
"""
41+
route_template_nologin = """
42+
43+
@app.route('{route}', methods=['GET'])
44+
def {method_name}() -> dict:
45+
if request.method in {{'GET'}}:
46+
jwt_payload = dict(
47+
databaseAddress=os.environ["PHARUS_HOST"],
48+
username=os.environ["PHARUS_USER"],
49+
password=os.environ["PHARUS_PASSWORD"],
50+
)
51+
try:
52+
component_instance = type_map['{component_type}'](name='{component_name}',
53+
component_config={component},
54+
static_config={static_config},
55+
jwt_payload=jwt_payload)
56+
return component_instance.{method_name_type}()
57+
except Exception as e:
58+
return traceback.format_exc(), 500
59+
"""
4060

4161
pharus_root = f"{pkg_resources.get_distribution('pharus').module_path}/pharus"
4262
api_path = f"{pharus_root}/dynamic_api.py"
4363
spec_path = os.environ.get("PHARUS_SPEC_PATH")
4464
values_yaml = EnvYAML(Path(spec_path))
4565
with open(Path(api_path), "w") as f:
4666
f.write(header_template)
67+
active_route_template = (
68+
route_template if values_yaml["SciViz"]["auth"] else route_template_nologin
69+
)
4770
if (
4871
"component_interface" in values_yaml["SciViz"]
4972
and "override" in values_yaml["SciViz"]["component_interface"]
@@ -74,10 +97,10 @@ def {method_name}(jwt_payload: dict) -> dict:
7497
for grid in page["grids"].values():
7598
if grid["type"] == "dynamic":
7699
f.write(
77-
route_template.format(
100+
(active_route_template).format(
78101
route=grid["route"],
79102
method_name=grid["route"].replace("/", ""),
80-
component_type="table",
103+
component_type="basicquery",
81104
component_name="dynamicgrid",
82105
component=json.dumps(grid),
83106
static_config=static_config,
@@ -90,9 +113,12 @@ def {method_name}(jwt_payload: dict) -> dict:
90113
if "component_templates" in grid
91114
else grid["components"]
92115
).items():
93-
if re.match(r"^(table|metadata|plot|file).*$", comp["type"]):
116+
if re.match(
117+
r"^(table|metadata|plot|file|slider|dropdown-query).*$",
118+
comp["type"],
119+
):
94120
f.write(
95-
route_template.format(
121+
(active_route_template).format(
96122
route=comp["route"],
97123
method_name=comp["route"].replace("/", ""),
98124
component_type=comp["type"],
@@ -107,7 +133,7 @@ def {method_name}(jwt_payload: dict) -> dict:
107133
comp["type"]
108134
].attributes_route_format.format(route=comp["route"])
109135
f.write(
110-
route_template.format(
136+
(active_route_template).format(
111137
route=attributes_route,
112138
method_name=attributes_route.replace("/", ""),
113139
component_type=comp["type"],

pharus/interface.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def _fetch_records(
113113
restriction: list = [],
114114
limit: int = 1000,
115115
page: int = 1,
116-
order=["KEY ASC"],
116+
order=None,
117117
fetch_blobs=False,
118118
fetch_args=[],
119119
) -> tuple:
@@ -150,19 +150,25 @@ def _fetch_records(
150150
]
151151
)
152152

153+
order_by = (
154+
fetch_args.pop("order_by") if "order_by" in fetch_args else ["KEY ASC"]
155+
)
156+
order_by = order if order else order_by
157+
158+
limit = fetch_args.pop("limit") if "limit" in fetch_args else limit
159+
153160
if fetch_blobs and not fetch_args:
154161
fetch_args = [*query.heading.attributes]
155162
elif not fetch_args:
156163
fetch_args = query.heading.non_blobs
157164
else:
158165
attributes = {k: v for k, v in attributes.items() if k in fetch_args}
159-
160166
non_blobs_rows = query_restricted.fetch(
161167
*fetch_args,
162168
as_dict=True,
163169
limit=limit,
164170
offset=(page - 1) * limit,
165-
order_by=order,
171+
order_by=order_by,
166172
)
167173

168174
# Buffer list to be return

pharus/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""Package metadata."""
2-
__version__ = "0.3.0"
2+
__version__ = "0.4.0"

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ datajoint_connection_hub
66
pyyaml
77
envyaml
88
otumat
9+
numpy

0 commit comments

Comments
 (0)