cwms-python 0.4.4__tar.gz → 0.6.0__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.
Files changed (37) hide show
  1. {cwms_python-0.4.4 → cwms_python-0.6.0}/PKG-INFO +16 -5
  2. {cwms_python-0.4.4 → cwms_python-0.6.0}/README.md +11 -1
  3. cwms_python-0.6.0/cwms/__init__.py +37 -0
  4. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/api.py +61 -11
  5. cwms_python-0.6.0/cwms/catalog/blobs.py +85 -0
  6. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/catalog/catalog.py +1 -1
  7. cwms_python-0.6.0/cwms/catalog/clobs.py +158 -0
  8. cwms_python-0.4.4/cwms/types.py → cwms_python-0.6.0/cwms/cwms_types.py +33 -12
  9. cwms_python-0.6.0/cwms/datafile_imports/shef_critfile_import.py +130 -0
  10. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/forecast/forecast_instance.py +1 -1
  11. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/forecast/forecast_spec.py +1 -1
  12. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/levels/location_levels.py +2 -2
  13. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/levels/specified_levels.py +1 -1
  14. cwms_python-0.6.0/cwms/locations/gate_changes.py +185 -0
  15. cwms_python-0.6.0/cwms/locations/location_groups.py +166 -0
  16. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/locations/physical_locations.py +29 -13
  17. cwms_python-0.6.0/cwms/outlets/outlets.py +195 -0
  18. cwms_python-0.6.0/cwms/outlets/virtual_outlets.py +164 -0
  19. cwms_python-0.6.0/cwms/projects/project_lock_rights.py +151 -0
  20. cwms_python-0.6.0/cwms/projects/project_locks.py +239 -0
  21. cwms_python-0.6.0/cwms/projects/projects.py +309 -0
  22. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/ratings/ratings.py +40 -5
  23. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/ratings/ratings_spec.py +51 -1
  24. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/ratings/ratings_template.py +1 -1
  25. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/standard_text/standard_text.py +1 -1
  26. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/timeseries/timeseries.py +142 -37
  27. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/timeseries/timeseries_bin.py +1 -13
  28. cwms_python-0.6.0/cwms/timeseries/timeseries_group.py +253 -0
  29. cwms_python-0.4.4/cwms/timeseries/timerseries_identifier.py → cwms_python-0.6.0/cwms/timeseries/timeseries_identifier.py +1 -1
  30. cwms_python-0.6.0/cwms/timeseries/timeseries_profile.py +166 -0
  31. cwms_python-0.6.0/cwms/timeseries/timeseries_profile_instance.py +237 -0
  32. cwms_python-0.6.0/cwms/timeseries/timeseries_profile_parser.py +210 -0
  33. {cwms_python-0.4.4 → cwms_python-0.6.0}/cwms/timeseries/timeseries_txt.py +1 -15
  34. cwms_python-0.6.0/cwms/turbines/turbines.py +242 -0
  35. {cwms_python-0.4.4 → cwms_python-0.6.0}/pyproject.toml +4 -3
  36. cwms_python-0.4.4/cwms/__init__.py +0 -22
  37. {cwms_python-0.4.4 → cwms_python-0.6.0}/LICENSE +0 -0
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: cwms-python
3
- Version: 0.4.4
3
+ Version: 0.6.0
4
4
  Summary: Corps water managerment systems (CWMS) REST API for Data Retrieval of USACE water data
5
5
  License: LICENSE
6
- Keywords: USACE,water data
6
+ Keywords: USACE,water data,CWMS
7
7
  Author: Eric Novotny
8
8
  Author-email: eric.v.novotny@usace.army.mil
9
9
  Requires-Python: >=3.9,<4.0
@@ -13,10 +13,11 @@ Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
- Requires-Dist: numpy (>=1.26.4,<2.0.0)
16
+ Classifier: Programming Language :: Python :: 3.13
17
17
  Requires-Dist: pandas (>=2.1.3,<3.0.0)
18
18
  Requires-Dist: requests (>=2.31.0,<3.0.0)
19
19
  Requires-Dist: requests-toolbelt (>=1.0.0,<2.0.0)
20
+ Project-URL: Repository, https://github.com/HydrologicEngineeringCenter/cwms-python
20
21
  Description-Content-Type: text/markdown
21
22
 
22
23
  # CWMSpy
@@ -49,7 +50,7 @@ from datetime import datetime, timedelta
49
50
 
50
51
  end = datetime.now()
51
52
  begin = end - timedelta(days = 10)
52
- data = cwms.get_timeseries(ts_id_='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , begin = begin, end = end)
53
+ data = cwms.get_timeseries(ts_id='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , begin = begin, end = end)
53
54
 
54
55
  #a cwms data object will be provided this object containes both the JSON as well
55
56
  #as the values converted into a dataframe
@@ -93,3 +94,13 @@ print(json)
93
94
  'version-date': None}
94
95
  ```
95
96
 
97
+ ## TimeSeries Profile API Compatibility Warning
98
+
99
+ Currently, the TimeSeries Profile API may not be fully supported
100
+ until a new version of cwms-data-access is released with the updated
101
+ endpoint implementation.
102
+
103
+ ## Contributing
104
+
105
+ Please view the contribution documentation here: [CONTRIBUTING.md]
106
+
@@ -28,7 +28,7 @@ from datetime import datetime, timedelta
28
28
 
29
29
  end = datetime.now()
30
30
  begin = end - timedelta(days = 10)
31
- data = cwms.get_timeseries(ts_id_='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , begin = begin, end = end)
31
+ data = cwms.get_timeseries(ts_id='Some.Fully.Qualified.Ts.Id',office_id='OFFICE1' , begin = begin, end = end)
32
32
 
33
33
  #a cwms data object will be provided this object containes both the JSON as well
34
34
  #as the values converted into a dataframe
@@ -71,3 +71,13 @@ print(json)
71
71
  ['2024-04-23T10:00:00', 86.57999999999997, 3]],
72
72
  'version-date': None}
73
73
  ```
74
+
75
+ ## TimeSeries Profile API Compatibility Warning
76
+
77
+ Currently, the TimeSeries Profile API may not be fully supported
78
+ until a new version of cwms-data-access is released with the updated
79
+ endpoint implementation.
80
+
81
+ ## Contributing
82
+
83
+ Please view the contribution documentation here: [CONTRIBUTING.md]
@@ -0,0 +1,37 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ from cwms.api import *
4
+ from cwms.catalog.blobs import *
5
+ from cwms.catalog.catalog import *
6
+ from cwms.catalog.clobs import *
7
+ from cwms.datafile_imports.shef_critfile_import import *
8
+ from cwms.forecast.forecast_instance import *
9
+ from cwms.forecast.forecast_spec import *
10
+ from cwms.levels.location_levels import *
11
+ from cwms.levels.specified_levels import *
12
+ from cwms.locations.gate_changes import *
13
+ from cwms.locations.location_groups import *
14
+ from cwms.locations.physical_locations import *
15
+ from cwms.outlets.outlets import *
16
+ from cwms.outlets.virtual_outlets import *
17
+ from cwms.projects.project_lock_rights import *
18
+ from cwms.projects.project_locks import *
19
+ from cwms.projects.projects import *
20
+ from cwms.ratings.ratings import *
21
+ from cwms.ratings.ratings_spec import *
22
+ from cwms.ratings.ratings_template import *
23
+ from cwms.standard_text.standard_text import *
24
+ from cwms.timeseries.timeseries import *
25
+ from cwms.timeseries.timeseries_bin import *
26
+ from cwms.timeseries.timeseries_group import *
27
+ from cwms.timeseries.timeseries_identifier import *
28
+ from cwms.timeseries.timeseries_profile import *
29
+ from cwms.timeseries.timeseries_profile_instance import *
30
+ from cwms.timeseries.timeseries_profile_parser import *
31
+ from cwms.timeseries.timeseries_txt import *
32
+ from cwms.turbines.turbines import *
33
+
34
+ try:
35
+ __version__ = version("cwms-python")
36
+ except PackageNotFoundError:
37
+ __version__ = "version-unknown"
@@ -1,4 +1,4 @@
1
- """ Session management and REST functions for CWMS Data API.
1
+ """Session management and REST functions for CWMS Data API.
2
2
 
3
3
  This module provides functions for making REST calls to the CWMS Data API (CDA). These
4
4
  functions should be used internally to interact with the API. The user should not have to
@@ -31,18 +31,20 @@ import logging
31
31
  from json import JSONDecodeError
32
32
  from typing import Any, Optional, cast
33
33
 
34
- from requests import Response
34
+ from requests import Response, adapters
35
35
  from requests_toolbelt import sessions # type: ignore
36
36
  from requests_toolbelt.sessions import BaseUrlSession # type: ignore
37
37
 
38
- from cwms.types import JSON, RequestParams
38
+ from cwms.cwms_types import JSON, RequestParams
39
39
 
40
40
  # Specify the default API root URL and version.
41
41
  API_ROOT = "https://cwms-data.usace.army.mil/cwms-data/"
42
42
  API_VERSION = 2
43
43
 
44
- # Initialize a non-authenticated session with the default root URL.
44
+ # Initialize a non-authenticated session with the default root URL and set default pool connections.
45
45
  SESSION = sessions.BaseUrlSession(base_url=API_ROOT)
46
+ adapter = adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)
47
+ SESSION.mount("https://", adapter)
46
48
 
47
49
 
48
50
  class InvalidVersion(Exception):
@@ -91,7 +93,10 @@ class ApiError(Exception):
91
93
 
92
94
 
93
95
  def init_session(
94
- *, api_root: Optional[str] = None, api_key: Optional[str] = None
96
+ *,
97
+ api_root: Optional[str] = None,
98
+ api_key: Optional[str] = None,
99
+ pool_connections: int = 100,
95
100
  ) -> BaseUrlSession:
96
101
  """Specify a root URL and authentication key for the CWMS Data API.
97
102
 
@@ -112,7 +117,10 @@ def init_session(
112
117
  if api_root:
113
118
  logging.debug(f"Initializing root URL: api_root={api_root}")
114
119
  SESSION = sessions.BaseUrlSession(base_url=api_root)
115
-
120
+ adapter = adapters.HTTPAdapter(
121
+ pool_connections=pool_connections, pool_maxsize=pool_connections
122
+ )
123
+ SESSION.mount("https://", adapter)
116
124
  if api_key:
117
125
  logging.debug(f"Setting authorization key: api_key={api_key}")
118
126
  SESSION.headers.update({"Authorization": api_key})
@@ -183,6 +191,7 @@ def get_xml(
183
191
 
184
192
  headers = {"Accept": api_version_text(api_version)}
185
193
  response = SESSION.get(endpoint, params=params, headers=headers)
194
+ response.close()
186
195
 
187
196
  if response.status_code < 200 or response.status_code >= 300:
188
197
  logging.error(f"CDA Error: response={response}")
@@ -220,7 +229,7 @@ def get(
220
229
 
221
230
  headers = {"Accept": api_version_text(api_version)}
222
231
  response = SESSION.get(endpoint, params=params, headers=headers)
223
-
232
+ response.close()
224
233
  if response.status_code < 200 or response.status_code >= 300:
225
234
  logging.error(f"CDA Error: response={response}")
226
235
  raise ApiError(response)
@@ -232,6 +241,46 @@ def get(
232
241
  return {}
233
242
 
234
243
 
244
+ def get_with_paging(
245
+ selector: str,
246
+ endpoint: str,
247
+ params: RequestParams,
248
+ *,
249
+ api_version: int = API_VERSION,
250
+ ) -> JSON:
251
+ """Make a GET request to the CWMS Data API with paging.
252
+
253
+ Args:
254
+ endpoint: The CDA endpoint for the record(s).
255
+ selector: The json key that will be merged though each page call
256
+ params (optional): Query parameters for the request.
257
+
258
+ Keyword Args:
259
+ api_version (optional): The CDA version to use for the request. If not specified,
260
+ the default API_VERSION will be used.
261
+
262
+ Returns:
263
+ The deserialized JSON response data.
264
+
265
+ Raises:
266
+ ApiError: If an error response is return by the API.
267
+ """
268
+
269
+ first_pass = True
270
+ while (params["page"] is not None) or first_pass:
271
+ temp = get(endpoint, params, api_version=api_version)
272
+ if first_pass:
273
+ response = temp
274
+ else:
275
+ response[selector] = response[selector] + temp[selector]
276
+ if "next-page" in temp.keys():
277
+ params["page"] = temp["next-page"]
278
+ else:
279
+ params["page"] = None
280
+ first_pass = False
281
+ return response
282
+
283
+
235
284
  def post(
236
285
  endpoint: str,
237
286
  data: Any,
@@ -260,10 +309,11 @@ def post(
260
309
  # post requires different headers than get for
261
310
  headers = {"accept": "*/*", "Content-Type": api_version_text(api_version)}
262
311
 
263
- if isinstance(data, dict):
312
+ if isinstance(data, dict) or isinstance(data, list):
264
313
  data = json.dumps(data)
265
314
 
266
315
  response = SESSION.post(endpoint, params=params, headers=headers, data=data)
316
+ response.close()
267
317
 
268
318
  if response.status_code < 200 or response.status_code >= 300:
269
319
  logging.error(f"CDA Error: response={response}")
@@ -299,10 +349,10 @@ def patch(
299
349
  if data is None:
300
350
  response = SESSION.patch(endpoint, params=params, headers=headers)
301
351
  else:
302
- if isinstance(data, dict):
352
+ if isinstance(data, dict) or isinstance(data, list):
303
353
  data = json.dumps(data)
304
354
  response = SESSION.patch(endpoint, params=params, headers=headers, data=data)
305
-
355
+ response.close()
306
356
  if response.status_code < 200 or response.status_code >= 300:
307
357
  logging.error(f"CDA Error: response={response}")
308
358
  raise ApiError(response)
@@ -330,7 +380,7 @@ def delete(
330
380
 
331
381
  headers = {"Accept": api_version_text(api_version)}
332
382
  response = SESSION.delete(endpoint, params=params, headers=headers)
333
-
383
+ response.close()
334
384
  if response.status_code < 200 or response.status_code >= 300:
335
385
  logging.error(f"CDA Error: response={response}")
336
386
  raise ApiError(response)
@@ -0,0 +1,85 @@
1
+ from typing import Optional
2
+
3
+ import cwms.api as api
4
+ from cwms.cwms_types import JSON, Data
5
+
6
+
7
+ def get_blob(blob_id: str, office_id: str) -> Data:
8
+ """Get a single clob.
9
+
10
+ Parameters
11
+ ----------
12
+ blob_id: string
13
+ Specifies the id of the blob
14
+ office_id: string
15
+ Specifies the office of the blob.
16
+
17
+
18
+ Returns
19
+ -------
20
+ cwms data type. data.json will return the JSON output and data.df will return a dataframe
21
+ """
22
+
23
+ endpoint = f"blobs/{blob_id}"
24
+ params = {"office": office_id}
25
+ response = api.get(endpoint, params, api_version=1)
26
+ return Data(response)
27
+
28
+
29
+ def get_blobs(
30
+ office_id: Optional[str] = None,
31
+ page_size: Optional[int] = 100,
32
+ blob_id_like: Optional[str] = None,
33
+ ) -> Data:
34
+ """Get a subset of Blobs
35
+
36
+ Parameters
37
+ ----------
38
+ office_id: Optional[string]
39
+ Specifies the office of the blob.
40
+ page_sie: Optional[Integer]
41
+ How many entries per page returned. Default 100.
42
+ blob_id_like: Optional[string]
43
+ Posix regular expression matching against the clob id
44
+
45
+ Returns
46
+ -------
47
+ cwms data type. data.json will return the JSON output and data.df will return a dataframe
48
+ """
49
+
50
+ endpoint = "blobs"
51
+ params = {"office": office_id, "page-size": page_size, "like": blob_id_like}
52
+
53
+ response = api.get(endpoint, params, api_version=1)
54
+ return Data(response, selector="blobs")
55
+
56
+
57
+ def store_blobs(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
58
+ """Create New Blob
59
+
60
+ Parameters
61
+ ----------
62
+ Data: JSON dictionary
63
+ JSON containing information of Blob to be updated
64
+ {
65
+ "office-id": "string",
66
+ "id": "string",
67
+ "description": "string",
68
+ "media-type-id": "string",
69
+ "value": "string"
70
+ }
71
+ fail_if_exists: Boolean
72
+ Create will fail if provided ID already exists. Default: true
73
+
74
+ Returns
75
+ -------
76
+ None
77
+ """
78
+
79
+ if not isinstance(data, dict):
80
+ raise ValueError("Cannot store a Blob without a JSON data dictionary")
81
+
82
+ endpoint = "blobs"
83
+ params = {"fail-if-exists": fail_if_exists}
84
+
85
+ return api.post(endpoint, data, params, api_version=1)
@@ -1,7 +1,7 @@
1
1
  from typing import Optional
2
2
 
3
3
  import cwms.api as api
4
- from cwms.types import Data
4
+ from cwms.cwms_types import Data
5
5
 
6
6
 
7
7
  def get_locations_catalog(
@@ -0,0 +1,158 @@
1
+ from typing import Optional
2
+
3
+ import cwms.api as api
4
+ from cwms.cwms_types import JSON, Data
5
+
6
+
7
+ def get_clob(clob_id: str, office_id: str, clob_id_query: Optional[str] = None) -> Data:
8
+ """Get a single clob.
9
+
10
+ Parameters
11
+ ----------
12
+ clob_id: string
13
+ Specifies the id of the clob
14
+ office_id: string
15
+ Specifies the office of the clob.
16
+ clob_id_query: string
17
+ If this query parameter is provided the id path parameter is ignored and the
18
+ value of the query parameter is used. Note: this query parameter is necessary
19
+ for id's that contain '/' or other special characters. Because of abuse even
20
+ properly escaped '/' in url paths are blocked. When using this query parameter
21
+ a valid path parameter must still be provided for the request to be properly
22
+ routed. If your clob id contains '/' you can't specify the clob-id query
23
+ parameter and also specify the id path parameter because firewall and/or server
24
+ rules will deny the request even though you are specifying this override. "ignored"
25
+ is suggested.
26
+
27
+
28
+ Returns
29
+ -------
30
+ cwms data type. data.json will return the JSON output and data.df will return a dataframe
31
+ """
32
+
33
+ endpoint = f"clobs/{clob_id}"
34
+ params = {
35
+ "office": office_id,
36
+ "clob-id-query": clob_id_query,
37
+ }
38
+ response = api.get(endpoint, params)
39
+ return Data(response)
40
+
41
+
42
+ def get_clobs(
43
+ office_id: Optional[str] = None,
44
+ page_size: Optional[int] = 100,
45
+ include_values: Optional[bool] = False,
46
+ clob_id_like: Optional[str] = None,
47
+ ) -> Data:
48
+ """Get a subset of Clobs
49
+
50
+ Parameters
51
+ ----------
52
+ office_id: Optional[string]
53
+ Specifies the office of the clob.
54
+ page_sie: Optional[Integer]
55
+ How many entries per page returned. Default 100.
56
+ include_values: Optional[Boolean]
57
+ Do you want the value associated with this particular clob (default: false)
58
+ clob_id_like: Optional[string]
59
+ Posix regular expression matching against the clob id
60
+
61
+ Returns
62
+ -------
63
+ cwms data type. data.json will return the JSON output and data.df will return a dataframe
64
+ """
65
+
66
+ endpoint = "clobs"
67
+ params = {
68
+ "office": office_id,
69
+ "page-size": page_size,
70
+ "include-values": include_values,
71
+ "like": clob_id_like,
72
+ }
73
+
74
+ response = api.get(endpoint, params)
75
+ return Data(response, selector="clobs")
76
+
77
+
78
+ def delete_clob(clob_id: str, office_id: str) -> None:
79
+ """Deletes requested clob
80
+
81
+ Parameters
82
+ ----------
83
+ clob_id: string
84
+ Specifies the id of the clob to be deleted
85
+ office_id: string
86
+ Specifies the office of the clob.
87
+
88
+ Returns
89
+ -------
90
+ None
91
+ """
92
+
93
+ endpoint = f"clobs/{clob_id}"
94
+ params = {"office": office_id}
95
+
96
+ return api.delete(endpoint, params=params, api_version=1)
97
+
98
+
99
+ def update_clob(data: JSON, clob_id: str, ignore_nulls: Optional[bool] = True) -> None:
100
+ """Updates clob
101
+
102
+ Parameters
103
+ ----------
104
+ Data: JSON dictionary
105
+ JSON containing information of Clob to be updated
106
+ {
107
+ "office-id": "string",
108
+ "id": "string",
109
+ "description": "string",
110
+ "value": "string"
111
+ }
112
+ clob_id: string
113
+ Specifies the id of the clob to be deleted
114
+ ignore_nulls: Boolean
115
+ If true, null and empty fields in the provided clob will be ignored and the existing value of those fields left in place. Default: true
116
+
117
+ Returns
118
+ -------
119
+ None
120
+ """
121
+
122
+ if not isinstance(data, dict):
123
+ raise ValueError("Cannot store a Clob without a JSON data dictionary")
124
+
125
+ endpoint = f"clobs/{clob_id}"
126
+ params = {"ignore-nulls": ignore_nulls}
127
+
128
+ return api.patch(endpoint, data, params, api_version=1)
129
+
130
+
131
+ def store_clobs(data: JSON, fail_if_exists: Optional[bool] = True) -> None:
132
+ """Create New Clob
133
+
134
+ Parameters
135
+ ----------
136
+ Data: JSON dictionary
137
+ JSON containing information of Clob to be updated
138
+ {
139
+ "office-id": "string",
140
+ "id": "string",
141
+ "description": "string",
142
+ "value": "string"
143
+ }
144
+ fail_if_exists: Boolean
145
+ Create will fail if provided ID already exists. Default: true
146
+
147
+ Returns
148
+ -------
149
+ None
150
+ """
151
+
152
+ if not isinstance(data, dict):
153
+ raise ValueError("Cannot store a Clob without a JSON data dictionary")
154
+
155
+ endpoint = "clobs"
156
+ params = {"fail-if-exists": fail_if_exists}
157
+
158
+ return api.post(endpoint, data, params, api_version=1)
@@ -2,7 +2,7 @@ from copy import deepcopy
2
2
  from enum import Enum, auto
3
3
  from typing import Any, Optional
4
4
 
5
- from pandas import DataFrame, Index, json_normalize, to_datetime
5
+ from pandas import DataFrame, Index, json_normalize, to_datetime, to_numeric
6
6
 
7
7
  # Describes generic JSON serializable data.
8
8
  JSON = dict[str, Any]
@@ -51,27 +51,48 @@ class Data:
51
51
  A data frame containing the data located
52
52
  """
53
53
 
54
- data = deepcopy(json)
55
-
56
- if selector:
54
+ def get_df_data(data: JSON, selector: str) -> JSON:
55
+ # get the data that will be stored in the dataframe using the selectors
57
56
  df_data = data
58
57
  for key in selector.split("."):
59
58
  if key in df_data.keys():
60
59
  df_data = df_data[key]
60
+ return df_data
61
+
62
+ def rating_type(data: JSON) -> DataFrame:
63
+ # grab the correct point values for a rating table
64
+ df = DataFrame(data["point"]) if data["point"] else DataFrame()
65
+ df = df.apply(to_numeric)
66
+ return df
67
+
68
+ def timeseries_type(orig_json: JSON, value_json: JSON) -> DataFrame:
69
+ # if timeseries values are present then grab the values and put into
70
+ # dataframe else create empty dataframe
71
+ columns = Index([sub["name"] for sub in orig_json["value-columns"]])
72
+ if value_json:
73
+ df = DataFrame(value_json)
74
+ df.columns = columns
75
+ else:
76
+ df = DataFrame(columns=columns)
77
+
78
+ if "date-time" in df.columns:
79
+ df["date-time"] = to_datetime(df["date-time"], unit="ms", utc=True)
80
+ return df
81
+
82
+ data = deepcopy(json)
83
+
84
+ if selector:
85
+ df_data = get_df_data(data, selector)
61
86
 
62
87
  # if the dataframe is for a rating table
63
88
  if ("rating-points" in selector) and ("point" in df_data.keys()):
64
- df = DataFrame(df_data["point"])
89
+ df = rating_type(df_data)
65
90
 
66
91
  elif selector == "values":
67
- df = DataFrame(df_data)
68
- # if timeseries values are present then grab the values and put into dataframe
69
- df.columns = Index([sub["name"] for sub in data["value-columns"]])
92
+ df = timeseries_type(data, df_data)
70
93
 
71
- if "date-time" in df.columns:
72
- df["date-time"] = to_datetime(df["date-time"], unit="ms", utc=True)
73
94
  else:
74
- df = json_normalize(df_data)
95
+ df = json_normalize(df_data) if df_data else DataFrame()
75
96
  else:
76
97
  df = json_normalize(data)
77
98
 
@@ -81,7 +102,7 @@ class Data:
81
102
  def df(self) -> DataFrame:
82
103
  """Return the data frame."""
83
104
 
84
- if type(self._df) != DataFrame:
105
+ if not isinstance(self._df, DataFrame):
85
106
  self._df = Data.to_df(self.json, self.selector)
86
107
 
87
108
  return self._df