flowfabricpy 0.1.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.
- flowfabricpy-0.1.0/LICENSE +21 -0
- flowfabricpy-0.1.0/PKG-INFO +87 -0
- flowfabricpy-0.1.0/README.md +75 -0
- flowfabricpy-0.1.0/README.rst +104 -0
- flowfabricpy-0.1.0/pyproject.toml +13 -0
- flowfabricpy-0.1.0/setup.cfg +4 -0
- flowfabricpy-0.1.0/src/__init__.py +0 -0
- flowfabricpy-0.1.0/src/flowfabricpy/__init__.py +34 -0
- flowfabricpy-0.1.0/src/flowfabricpy/auth.py +176 -0
- flowfabricpy-0.1.0/src/flowfabricpy/catalog_utils.py +70 -0
- flowfabricpy-0.1.0/src/flowfabricpy/client.py +489 -0
- flowfabricpy-0.1.0/src/flowfabricpy/flowfabric_http.py +76 -0
- flowfabricpy-0.1.0/src/flowfabricpy.egg-info/PKG-INFO +87 -0
- flowfabricpy-0.1.0/src/flowfabricpy.egg-info/SOURCES.txt +19 -0
- flowfabricpy-0.1.0/src/flowfabricpy.egg-info/dependency_links.txt +1 -0
- flowfabricpy-0.1.0/src/flowfabricpy.egg-info/requires.txt +1 -0
- flowfabricpy-0.1.0/src/flowfabricpy.egg-info/top_level.txt +2 -0
- flowfabricpy-0.1.0/tests/test_auth.py +11 -0
- flowfabricpy-0.1.0/tests/test_catalog_utils.py +13 -0
- flowfabricpy-0.1.0/tests/test_client.py +83 -0
- flowfabricpy-0.1.0/tests/test_http.py +29 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lynker
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flowfabricpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for Flowfabric
|
|
5
|
+
Author-email: Paul Linza <plinza@lynker.com>, Mike Johnson <mjohnson@lynker.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: requests
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# flowfabricpy: Effortless Python Access to FlowFabric API
|
|
14
|
+
|
|
15
|
+
`flowfabricpy` is a powerful Python client for the FlowFabric API, providing seamless access to hydrologic forecasts, reanalysis, rating curves, and datasets. With robust authentication and automatic token caching, you can focus on data science, not on data munging plumbing.
|
|
16
|
+
|
|
17
|
+
## Key Features
|
|
18
|
+
- **Evolving catalog** of harmonized data from multiple models
|
|
19
|
+
- **Single method access** to all models - both retrospective and forecast
|
|
20
|
+
- **One-time authentication**: Log in once, and your token is cached for future use.
|
|
21
|
+
- **Arrow IPC support**: Fast, memory-efficient data transfer
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
```py
|
|
25
|
+
# Install from GitHub:
|
|
26
|
+
pip install git+https://github.com/lynker-spatial/flowfabric-py#egg=flowfabricpy
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
```py
|
|
31
|
+
# 1. List available datasets
|
|
32
|
+
datasets = flowfabric_list_datasets()
|
|
33
|
+
print(datasets)
|
|
34
|
+
|
|
35
|
+
# 2. Query streamflow forecast (first call prompts login, then caches token)
|
|
36
|
+
# More on atuhentication below ...
|
|
37
|
+
tbl = flowfabric_streamflow_query(
|
|
38
|
+
dataset_id = "nws_owp_nwm_analysis",
|
|
39
|
+
feature_ids = ["101", "1001"],
|
|
40
|
+
issue_time = "latest"
|
|
41
|
+
)
|
|
42
|
+
print(tbl)
|
|
43
|
+
|
|
44
|
+
# 3. Query streamflow reanalysis data
|
|
45
|
+
tbl_re = flowfabric_streamflow_query(
|
|
46
|
+
"nws_owp_nwm_reanalysis_3_0",
|
|
47
|
+
feature_ids = ["101", "1001"],
|
|
48
|
+
start_time = "2018-01-01",
|
|
49
|
+
end_time = "2018-01-31"
|
|
50
|
+
)
|
|
51
|
+
print(tbl_re)
|
|
52
|
+
|
|
53
|
+
# 4. Query ratings
|
|
54
|
+
ratings = flowfabric_ratings_query(
|
|
55
|
+
feature_ids = ["101", "1001"],
|
|
56
|
+
type = "rem"
|
|
57
|
+
)
|
|
58
|
+
print(ratings)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Authentication & Token Caching
|
|
62
|
+
|
|
63
|
+
Using this platform requires authentication via the Lynker Spatial Portal. Accounts are free to set up and use. If use exceeds costs we can burden we will reach out to power users to better understand how we can help.
|
|
64
|
+
|
|
65
|
+
To get access, users can create an account here: https://proxy.lynker-spatial.com/
|
|
66
|
+
|
|
67
|
+
The first API call will prompt you to log in via browser. Your token is then cached and reused for all future calls—no repeated browser prompts!
|
|
68
|
+
|
|
69
|
+
**Manual token refresh:**
|
|
70
|
+
```py
|
|
71
|
+
flowfabric_refresh_token() # Forces re-authentication and updates cached token
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Advanced:**
|
|
75
|
+
You can always pass a token explicitly:
|
|
76
|
+
```py
|
|
77
|
+
token = flowfabric_get_token()['id_token']
|
|
78
|
+
healthz = flowfabric_healthz(token = token)
|
|
79
|
+
```
|
|
80
|
+
## Troubleshooting
|
|
81
|
+
- If you see repeated browser prompts, call `flowfabric_refresh_token()` once, then retry your queries.
|
|
82
|
+
- If you switch users, manually refresh the token.
|
|
83
|
+
- Use `verbose = TRUE` in any endpoint for detailed debug output.
|
|
84
|
+
|
|
85
|
+
## Learn More
|
|
86
|
+
- See the vignettes for advanced usage, authentication, and custom queries.
|
|
87
|
+
- All API responses are Arrow tables for high-performance analytics.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# flowfabricpy: Effortless Python Access to FlowFabric API
|
|
2
|
+
|
|
3
|
+
`flowfabricpy` is a powerful Python client for the FlowFabric API, providing seamless access to hydrologic forecasts, reanalysis, rating curves, and datasets. With robust authentication and automatic token caching, you can focus on data science, not on data munging plumbing.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
- **Evolving catalog** of harmonized data from multiple models
|
|
7
|
+
- **Single method access** to all models - both retrospective and forecast
|
|
8
|
+
- **One-time authentication**: Log in once, and your token is cached for future use.
|
|
9
|
+
- **Arrow IPC support**: Fast, memory-efficient data transfer
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
```py
|
|
13
|
+
# Install from GitHub:
|
|
14
|
+
pip install git+https://github.com/lynker-spatial/flowfabric-py#egg=flowfabricpy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
```py
|
|
19
|
+
# 1. List available datasets
|
|
20
|
+
datasets = flowfabric_list_datasets()
|
|
21
|
+
print(datasets)
|
|
22
|
+
|
|
23
|
+
# 2. Query streamflow forecast (first call prompts login, then caches token)
|
|
24
|
+
# More on atuhentication below ...
|
|
25
|
+
tbl = flowfabric_streamflow_query(
|
|
26
|
+
dataset_id = "nws_owp_nwm_analysis",
|
|
27
|
+
feature_ids = ["101", "1001"],
|
|
28
|
+
issue_time = "latest"
|
|
29
|
+
)
|
|
30
|
+
print(tbl)
|
|
31
|
+
|
|
32
|
+
# 3. Query streamflow reanalysis data
|
|
33
|
+
tbl_re = flowfabric_streamflow_query(
|
|
34
|
+
"nws_owp_nwm_reanalysis_3_0",
|
|
35
|
+
feature_ids = ["101", "1001"],
|
|
36
|
+
start_time = "2018-01-01",
|
|
37
|
+
end_time = "2018-01-31"
|
|
38
|
+
)
|
|
39
|
+
print(tbl_re)
|
|
40
|
+
|
|
41
|
+
# 4. Query ratings
|
|
42
|
+
ratings = flowfabric_ratings_query(
|
|
43
|
+
feature_ids = ["101", "1001"],
|
|
44
|
+
type = "rem"
|
|
45
|
+
)
|
|
46
|
+
print(ratings)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Authentication & Token Caching
|
|
50
|
+
|
|
51
|
+
Using this platform requires authentication via the Lynker Spatial Portal. Accounts are free to set up and use. If use exceeds costs we can burden we will reach out to power users to better understand how we can help.
|
|
52
|
+
|
|
53
|
+
To get access, users can create an account here: https://proxy.lynker-spatial.com/
|
|
54
|
+
|
|
55
|
+
The first API call will prompt you to log in via browser. Your token is then cached and reused for all future calls—no repeated browser prompts!
|
|
56
|
+
|
|
57
|
+
**Manual token refresh:**
|
|
58
|
+
```py
|
|
59
|
+
flowfabric_refresh_token() # Forces re-authentication and updates cached token
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Advanced:**
|
|
63
|
+
You can always pass a token explicitly:
|
|
64
|
+
```py
|
|
65
|
+
token = flowfabric_get_token()['id_token']
|
|
66
|
+
healthz = flowfabric_healthz(token = token)
|
|
67
|
+
```
|
|
68
|
+
## Troubleshooting
|
|
69
|
+
- If you see repeated browser prompts, call `flowfabric_refresh_token()` once, then retry your queries.
|
|
70
|
+
- If you switch users, manually refresh the token.
|
|
71
|
+
- Use `verbose = TRUE` in any endpoint for detailed debug output.
|
|
72
|
+
|
|
73
|
+
## Learn More
|
|
74
|
+
- See the vignettes for advanced usage, authentication, and custom queries.
|
|
75
|
+
- All API responses are Arrow tables for high-performance analytics.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
.. flowfabricpy documentation master file, created by
|
|
2
|
+
sphinx-quickstart on Wed Feb 11 13:17:49 2026.
|
|
3
|
+
You can adapt this file completely to your liking, but it should at least
|
|
4
|
+
contain the root `toctree` directive.
|
|
5
|
+
|
|
6
|
+
flowfabricpy: Effortless Python Access to FlowFabric API
|
|
7
|
+
========================================================
|
|
8
|
+
|
|
9
|
+
**flowfabricpy** is a powerful Python client for the FlowFabric API, providing seamless access to hydrologic forecasts, reanalysis, rating curves, and datasets. With robust authentication and automatic token caching, you can focus on data science, not on data munging plumbing.
|
|
10
|
+
|
|
11
|
+
Key Features
|
|
12
|
+
------------
|
|
13
|
+
- **Evolving catalog** of harmonized data from multiple models
|
|
14
|
+
- **Single method access** to all models - both retrospective and forecast
|
|
15
|
+
- **One-time authentication**: Log in once, and your token is cached for future use.
|
|
16
|
+
- **Arrow IPC support**: Fast, memory-efficient data transfer
|
|
17
|
+
|
|
18
|
+
Installation
|
|
19
|
+
------------
|
|
20
|
+
.. code-block:: python
|
|
21
|
+
|
|
22
|
+
# Install from GitHub:
|
|
23
|
+
pip install git+https://github.com/lynker-spatial/flowfabric-py#egg=flowfabricpy
|
|
24
|
+
|
|
25
|
+
Quick Start
|
|
26
|
+
-----------
|
|
27
|
+
.. code-block:: python
|
|
28
|
+
|
|
29
|
+
# 1. List available datasets
|
|
30
|
+
datasets = flowfabric_list_datasets()
|
|
31
|
+
print(datasets)
|
|
32
|
+
|
|
33
|
+
# 2. Query streamflow forecast (first call prompts login, then caches token)
|
|
34
|
+
# More on atuhentication below ...
|
|
35
|
+
tbl = flowfabric_streamflow_query(
|
|
36
|
+
dataset_id = "nws_owp_nwm_analysis",
|
|
37
|
+
feature_ids = ["101", "1001"],
|
|
38
|
+
issue_time = "latest"
|
|
39
|
+
)
|
|
40
|
+
print(tbl)
|
|
41
|
+
|
|
42
|
+
# 3. Query streamflow reanalysis data
|
|
43
|
+
tbl_re = flowfabric_streamflow_query(
|
|
44
|
+
"nws_owp_nwm_reanalysis_3_0",
|
|
45
|
+
feature_ids = ["101", "1001"],
|
|
46
|
+
start_time = "2018-01-01",
|
|
47
|
+
end_time = "2018-01-31"
|
|
48
|
+
)
|
|
49
|
+
print(tbl_re)
|
|
50
|
+
|
|
51
|
+
# 4. Query ratings
|
|
52
|
+
ratings = flowfabric_ratings_query(
|
|
53
|
+
feature_ids = ["101", "1001"],
|
|
54
|
+
type = "rem"
|
|
55
|
+
)
|
|
56
|
+
print(ratings)
|
|
57
|
+
|
|
58
|
+
Authentication & Token Caching
|
|
59
|
+
------------------------------
|
|
60
|
+
|
|
61
|
+
Using this platform requires authentication via the Lynker Spatial Portal. Accounts are free to set up and use. If use exceeds costs we can burden we will reach out to power users to better understand how we can help.
|
|
62
|
+
|
|
63
|
+
To get access, users can create an account here: https://proxy.lynker-spatial.com/
|
|
64
|
+
|
|
65
|
+
The first API call will prompt you to log in via browser. Your token is then cached and reused for all future calls—no repeated browser prompts!
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
**Manual token refresh:**
|
|
69
|
+
|
|
70
|
+
.. code-block:: python
|
|
71
|
+
|
|
72
|
+
flowfabric_refresh_token() # Forces re-authentication and updates cached token
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
**Advanced:**
|
|
76
|
+
You can always pass a token explicitly:
|
|
77
|
+
|
|
78
|
+
.. code-block:: python
|
|
79
|
+
|
|
80
|
+
token = flowfabric_get_token()['id_token']
|
|
81
|
+
healthz = flowfabric_healthz(token = token)
|
|
82
|
+
|
|
83
|
+
Troubleshooting
|
|
84
|
+
---------------
|
|
85
|
+
- If you see repeated browser prompts, call `flowfabric_refresh_token()` once, then retry your queries.
|
|
86
|
+
- If you switch users, manually refresh the token.
|
|
87
|
+
- Use `verbose = TRUE` in any endpoint for detailed debug output.
|
|
88
|
+
|
|
89
|
+
Learn More
|
|
90
|
+
----------
|
|
91
|
+
- See the vignettes for advanced usage, authentication, and custom queries.
|
|
92
|
+
- All API responses are Arrow tables for high-performance analytics.
|
|
93
|
+
|
|
94
|
+
.. toctree::
|
|
95
|
+
:maxdepth: 2
|
|
96
|
+
:caption: Contents:
|
|
97
|
+
|
|
98
|
+
Contents
|
|
99
|
+
--------
|
|
100
|
+
.. toctree::
|
|
101
|
+
getting-started
|
|
102
|
+
authentication
|
|
103
|
+
advanced
|
|
104
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "flowfabricpy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.8"
|
|
9
|
+
dependencies = ["requests"]
|
|
10
|
+
description = "Python client for Flowfabric"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
authors = [{name = "Paul Linza", email = "plinza@lynker.com"}, {name = "Mike Johnson", email = "mjohnson@lynker.com"}]
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
from .auth import flowfabric_get_token, flowfabric_refresh_token
|
|
3
|
+
from .catalog_utils import auto_streamflow_params
|
|
4
|
+
from .flowfabric_http import flowfabric_get, flowfabric_post
|
|
5
|
+
from .client import (
|
|
6
|
+
flowfabric_list_datasets,
|
|
7
|
+
flowfabric_get_dataset,
|
|
8
|
+
flowfabric_get_latest_run,
|
|
9
|
+
flowfabric_streamflow_query,
|
|
10
|
+
flowfabric_streamflow_estimate,
|
|
11
|
+
flowfabric_ratings_query,
|
|
12
|
+
flowfabric_ratings_estimate,
|
|
13
|
+
flowfabric_stage_query,
|
|
14
|
+
flowfabric_healthz,
|
|
15
|
+
flowfabric_inundation_ids,
|
|
16
|
+
get_bearer_token
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = ['flowfabric_get_token',
|
|
20
|
+
'flowfabric_refresh_token',
|
|
21
|
+
'auto_streamflow_params',
|
|
22
|
+
'flowfabric_list_datasets',
|
|
23
|
+
'flowfabric_get',
|
|
24
|
+
'flowfabric_post',
|
|
25
|
+
'flowfabric_get_dataset',
|
|
26
|
+
'flowfabric_get_latest_run',
|
|
27
|
+
'flowfabric_streamflow_query',
|
|
28
|
+
'flowfabric_streamflow_estimate',
|
|
29
|
+
'flowfabric_ratings_query',
|
|
30
|
+
'flowfabric_ratings_estimate',
|
|
31
|
+
'flowfabric_stage_query',
|
|
32
|
+
'flowfabric_healthz',
|
|
33
|
+
'flowfabric_inundation_ids',
|
|
34
|
+
'get_bearer_token']
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# auth.py
|
|
2
|
+
"""
|
|
3
|
+
Functions for getting an authentication token
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
import webbrowser
|
|
9
|
+
import time
|
|
10
|
+
from threading import Lock
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
|
|
14
|
+
token_file = Path.home() / ".flowfabric_token.json"
|
|
15
|
+
|
|
16
|
+
import base64
|
|
17
|
+
import hashlib
|
|
18
|
+
import secrets
|
|
19
|
+
import requests
|
|
20
|
+
from requests_oauthlib import OAuth2Session
|
|
21
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
22
|
+
from urllib.parse import urlparse, parse_qs
|
|
23
|
+
|
|
24
|
+
# token lock
|
|
25
|
+
token_lock = Lock()
|
|
26
|
+
|
|
27
|
+
# helper function to load token
|
|
28
|
+
def load_token():
|
|
29
|
+
"""
|
|
30
|
+
Helper function to load a token. Do not call directly.
|
|
31
|
+
:return:
|
|
32
|
+
"""
|
|
33
|
+
if not token_file.exists():
|
|
34
|
+
return None
|
|
35
|
+
try:
|
|
36
|
+
with token_file.open("r") as f:
|
|
37
|
+
return json.load(f)
|
|
38
|
+
except Exception:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
# helper function to save the token
|
|
42
|
+
def save_token(token):
|
|
43
|
+
"""
|
|
44
|
+
Helper function to save a token. Do not call directly.
|
|
45
|
+
:param token: Token
|
|
46
|
+
:type token: dict
|
|
47
|
+
:return:
|
|
48
|
+
"""
|
|
49
|
+
token_file.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
with token_file.open("w") as f:
|
|
51
|
+
json.dump(token, f)
|
|
52
|
+
|
|
53
|
+
# helper function
|
|
54
|
+
# only returns True if both the expiration time & token exist & are not expired
|
|
55
|
+
def token_is_valid(token):
|
|
56
|
+
"""
|
|
57
|
+
Helper function to determine the validity of a token. Do not call directly.
|
|
58
|
+
:return: Bool representing validity
|
|
59
|
+
"""
|
|
60
|
+
if not token:
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
expires_at = token.get("expires_at")
|
|
64
|
+
if not expires_at:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
return expires_at - 60 > time.time() # extra time as a buffer
|
|
68
|
+
|
|
69
|
+
# handle the OAuth callback
|
|
70
|
+
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
71
|
+
"""
|
|
72
|
+
Class to handle the OAuth callback
|
|
73
|
+
"""
|
|
74
|
+
auth_code = None
|
|
75
|
+
auth_state = None
|
|
76
|
+
|
|
77
|
+
def do_GET(self):
|
|
78
|
+
query = parse_qs(urlparse(self.path).query)
|
|
79
|
+
OAuthCallbackHandler.auth_code = query.get("code", [None])[0]
|
|
80
|
+
OAuthCallbackHandler.auth_state = query.get("state", [None])[0]
|
|
81
|
+
|
|
82
|
+
self.send_response(200)
|
|
83
|
+
self.end_headers()
|
|
84
|
+
self.wfile.write(b"Authentication complete. You may close this window.")
|
|
85
|
+
|
|
86
|
+
threading.Thread(target=self.server.shutdown).start()
|
|
87
|
+
|
|
88
|
+
# login and get a token
|
|
89
|
+
def flowfabric_get_token(force_refresh=False):
|
|
90
|
+
"""
|
|
91
|
+
Get and cache an authentication token
|
|
92
|
+
|
|
93
|
+
:param force_refresh: (Optional) force refresh token
|
|
94
|
+
:type force_refresh: bool or None
|
|
95
|
+
|
|
96
|
+
:return: The cached authentication token
|
|
97
|
+
"""
|
|
98
|
+
# dumps the cached token
|
|
99
|
+
if force_refresh:
|
|
100
|
+
token_file.unlink(missing_ok=True)
|
|
101
|
+
|
|
102
|
+
# return current token if it is still valid
|
|
103
|
+
token = load_token()
|
|
104
|
+
if token_is_valid(token):
|
|
105
|
+
return token
|
|
106
|
+
|
|
107
|
+
# otherwise generate a new token
|
|
108
|
+
with token_lock:
|
|
109
|
+
provider = requests.get(
|
|
110
|
+
"https://cognito-idp.us-west-2.amazonaws.com/us-west-2_em0hAPqnS/.well-known/openid-configuration"
|
|
111
|
+
)
|
|
112
|
+
json_prov = provider.json()
|
|
113
|
+
|
|
114
|
+
client_id = "1he6ti5109b9t6r1ifd4brecpl"
|
|
115
|
+
redirect_uri = "http://localhost:57777"
|
|
116
|
+
scope = ["openid", "profile", "email", "phone"]
|
|
117
|
+
|
|
118
|
+
# PKCE
|
|
119
|
+
code_verifier = secrets.token_urlsafe(64)
|
|
120
|
+
code_challenge = base64.urlsafe_b64encode(
|
|
121
|
+
hashlib.sha256(code_verifier.encode()).digest()
|
|
122
|
+
).decode().rstrip("=")
|
|
123
|
+
|
|
124
|
+
oauth = OAuth2Session(
|
|
125
|
+
client_id=client_id,
|
|
126
|
+
redirect_uri=redirect_uri,
|
|
127
|
+
scope=scope,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
authorization_url, state = oauth.authorization_url(
|
|
131
|
+
json_prov["authorization_endpoint"],
|
|
132
|
+
code_challenge=code_challenge,
|
|
133
|
+
code_challenge_method="S256",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Start callback server
|
|
137
|
+
server = HTTPServer(("localhost", 57777), OAuthCallbackHandler)
|
|
138
|
+
thread = threading.Thread(target=server.serve_forever)
|
|
139
|
+
thread.daemon = True
|
|
140
|
+
thread.start()
|
|
141
|
+
|
|
142
|
+
webbrowser.open(authorization_url)
|
|
143
|
+
|
|
144
|
+
timeout = time.time() + 60
|
|
145
|
+
# Wait for redirect
|
|
146
|
+
while OAuthCallbackHandler.auth_code is None:
|
|
147
|
+
if time.time() > timeout:
|
|
148
|
+
server.shutdown()
|
|
149
|
+
thread.join()
|
|
150
|
+
server.server_close()
|
|
151
|
+
time.sleep(0.1)
|
|
152
|
+
|
|
153
|
+
server.shutdown()
|
|
154
|
+
thread.join()
|
|
155
|
+
server.server_close()
|
|
156
|
+
|
|
157
|
+
token = oauth.fetch_token(
|
|
158
|
+
json_prov["token_endpoint"],
|
|
159
|
+
code=OAuthCallbackHandler.auth_code,
|
|
160
|
+
code_verifier=code_verifier,
|
|
161
|
+
include_client_id=True,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
provider.close()
|
|
165
|
+
save_token(token)
|
|
166
|
+
return token
|
|
167
|
+
|
|
168
|
+
# refresh token
|
|
169
|
+
def flowfabric_refresh_token():
|
|
170
|
+
"""
|
|
171
|
+
Force refresh the cached token
|
|
172
|
+
|
|
173
|
+
:return: The new token
|
|
174
|
+
"""
|
|
175
|
+
token = flowfabric_get_token(force_refresh=True)
|
|
176
|
+
return token
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# catalog_utils.py
|
|
2
|
+
"""
|
|
3
|
+
Contains a function to get recommended streamflow parameters.
|
|
4
|
+
"""
|
|
5
|
+
import requests
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
# autofill streamflow parameters
|
|
9
|
+
def auto_streamflow_params(dataset_id):
|
|
10
|
+
"""
|
|
11
|
+
Auto-generate streamflow parameters for a given dataset
|
|
12
|
+
|
|
13
|
+
:param dataset_id: The id of the dataset
|
|
14
|
+
:type dataset_id: str
|
|
15
|
+
|
|
16
|
+
:return: Dictionary containing the recommended streamflow parameters
|
|
17
|
+
"""
|
|
18
|
+
catalog = requests.get("https://flowfabric.lynker-spatial.com/catalog")
|
|
19
|
+
json_data = catalog.json()['provider_groups']
|
|
20
|
+
df = pd.json_normalize(json_data, record_path="datasets")
|
|
21
|
+
datasets = df.to_dict(orient='records')
|
|
22
|
+
data = next((dataset for dataset in datasets if dataset.get("id") == dataset_id), None)
|
|
23
|
+
|
|
24
|
+
if data is None:
|
|
25
|
+
print("[auto_streamflow_params] Error: dataset not found")
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
# determine if it is a reanalysis or not
|
|
29
|
+
is_reanalysis = False
|
|
30
|
+
if 'query_mode' in data and data['query_mode'] == 'absolute':
|
|
31
|
+
is_reanalysis = True
|
|
32
|
+
if 'configuration' in data and 'reanalysis' in data['configuration']:
|
|
33
|
+
is_reanalysis = True
|
|
34
|
+
|
|
35
|
+
if is_reanalysis:
|
|
36
|
+
if 'default_start_time' in data:
|
|
37
|
+
start_time = data['default_start_time']
|
|
38
|
+
elif 'min_time' in data:
|
|
39
|
+
start_time = data['min_time']
|
|
40
|
+
else:
|
|
41
|
+
start_time = "2018-01-01T00:00:00Z"
|
|
42
|
+
|
|
43
|
+
if 'default_end_time' in data:
|
|
44
|
+
end_time = data['default_end_time']
|
|
45
|
+
elif 'max_time' in data:
|
|
46
|
+
end_time = data['max_time']
|
|
47
|
+
else:
|
|
48
|
+
end_time = "2018-01-31T23:59:59Z"
|
|
49
|
+
|
|
50
|
+
params = {
|
|
51
|
+
'query_mode': 'absolute',
|
|
52
|
+
'start_time': start_time,
|
|
53
|
+
'end_time': end_time,
|
|
54
|
+
'scope': data['default_scope'] if 'default_scope' in data else 'all',
|
|
55
|
+
'format': data['default_format'] if 'default_format' in data else 'json',
|
|
56
|
+
'mode': data['default_mode'] if 'default_mode' in data else 'sync',
|
|
57
|
+
}
|
|
58
|
+
else:
|
|
59
|
+
params = {
|
|
60
|
+
'query_mode': 'run',
|
|
61
|
+
'issue_time': data['issue_time'] if 'issue_time' in data else 'latest',
|
|
62
|
+
'scope': data['default_scope'] if 'default_scope' in data else 'all',
|
|
63
|
+
'lead_start': data['lead_start'] if 'lead_start' in data else 0,
|
|
64
|
+
'lead_end': data['lead_end'] if 'lead_end' in data else 0,
|
|
65
|
+
'format': data['default_format'] if 'default_format' in data else 'json',
|
|
66
|
+
'mode': data['default_mode'] if 'default_mode' in data else 'sync',
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
catalog.close()
|
|
70
|
+
return params
|