Skip to content

Commit 097d618

Browse files
authored
Ap66-add_cfasodpy_and_respnet_etl (#58)
* refactoring namespace for multiple groups * changes for namespace refactor * updates to fix tests after refactor * adding and updating soda code * respnet wip * added json raw reading * including get_data in easy importing path * new respnet etl * all items complete * removed personal app token * version bump * Update coverage badge * casting weekly_rate to float * Update coverage badge * left out one important change worth mentioning * Update coverage badge * forgot to add this one __init__.py * Update coverage badge * updated docs to reflect changes in refactor * Update coverage badge --------- Co-authored-by: cdc-ap66 <cdc-ap66@users.noreply.github.com>
1 parent 38519f4 commit 097d618

84 files changed

Lines changed: 656 additions & 218 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ To use this repository:
3333
```
3434

3535
To add a new dataset:
36-
1. Create a new TOML configuration file in `cfa/scenarios/dataops/datasets/`
37-
2. Create a new ETL script in `cfa/scenarios/dataops/etl/`
38-
3. Add SQL transformation templates in `cfa/scenarios/dataops/etl/transform_templates/`
36+
1. Create a new TOML configuration file in `cfa/scenarios/dataops/datasets/{team_dir}/`
37+
2. Create a new ETL script in `cfa/scenarios/dataops/etl/{team_dir}/`
38+
3. Add SQL transformation templates in `cfa/scenarios/dataops/etl/transform_templates/{team_dir}/` (is using SQL for transforms). These are [Mako templates](https://www.makotemplates.org/)
3939

4040
## Accessing Datasets
4141

@@ -45,13 +45,13 @@ When the ETL pipelines are run, the data sources (raw and/or transformed) are st
4545
- type: either 'raw' or 'transformed'. Default is 'transformed'.
4646
- output: the type of dataframe to output, either 'pandas' or 'polars'. Default is 'pandas'.
4747

48-
The available datasets can be found by running `list_datasets()`, which can be found in the `cfa.scenarios.dataops.datasets.catalog` submodule.
48+
The available datasets can be found by running `list_datasets()`, which can be found in the `cfa.scenarios.dataops.catalog` submodule.
4949

5050
An example for getting the polars dataframes for the latest raw versions of the covid19vax_trends and seroprevalence datasets is below:
5151
```python
52-
from cfa.scenarios.dataops.datasets.catalog import get_data
53-
vax_df = get_data("covid19vax_trends", type = "transformed", output = "polars")
54-
sero_df = get_data("seroprevalence", type = "transformed", output = "polars")
52+
from cfa.scenarios.dataops import get_data
53+
vax_df = get_data("scenarios.covid19vax_trends", type = "transformed", output = "polars")
54+
sero_df = get_data("scenarios.seroprevalence", type = "transformed", output = "polars")
5555
```
5656

5757
## Running Workflows

cfa/scenarios/dataops/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Simplified importing of most commonly used objects
2+
3+
All data configurations should reside in this directory and be in the toml
4+
format. Use the existing configuration files as a starting point. Validations
5+
will run on all configurations.
6+
"""
7+
8+
from .catalog import datacat, get_data
9+
10+
__all__ = [datacat, get_data]
Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,27 @@
1818
from .config_validator import validate_dataset_config, verify_no_repeats
1919

2020
_here_dir = os.path.split(os.path.abspath(__file__))[0]
21-
_dataset_config_paths = glob.glob(os.path.join(_here_dir, "*.toml"))
21+
_dataset_config_paths = glob.glob(
22+
os.path.join(_here_dir, "datasets", "**", "*.toml")
23+
)
2224

23-
dataset_configs = []
25+
name_paths = []
26+
dataset_configs = {}
2427
for cp_i in _dataset_config_paths:
28+
ns_pre = cp_i.split(f"datasets{os.sep}")[-1].split(os.sep)[:-1]
2529
with open(cp_i, "rb") as f:
2630
config = tomli.load(f)
2731
config["_metadata"] = dict(filename=os.path.split(cp_i)[1])
2832
validate_dataset_config(config)
29-
dataset_configs.append(config)
33+
current = dataset_configs
34+
for part in ns_pre:
35+
if part not in current:
36+
current[part] = {}
37+
current = current[part]
38+
current[config["properties"]["name"]] = config
39+
name_paths.append(".".join(ns_pre + [config["properties"]["name"]]))
3040

31-
verify_no_repeats(dataset_configs)
41+
verify_no_repeats(name_paths)
3242

3343

3444
class BlobEndpoint:
@@ -169,10 +179,7 @@ def dict_to_sn(d: Any) -> SimpleNamespace:
169179
return x
170180

171181

172-
datasets = SimpleNamespace()
173-
174-
for i in dataset_configs:
175-
setattr(datasets, i["properties"]["name"], dict_to_sn(i))
182+
datacat = dict_to_sn(dataset_configs)
176183

177184

178185
def get_data(
@@ -194,11 +201,13 @@ def get_data(
194201
pd.DataFrame | pl.DataFrame: pandas or polars dataframe
195202
"""
196203
# check data exists
197-
available_datasets = [x for x in datasets.__dict__.keys()]
198-
if name not in available_datasets:
199-
print(f"{name} not in available datasets.")
200-
print("Available datasets:", available_datasets)
201-
raise ValueError(f"{name} not in available datasets.")
204+
try:
205+
config = eval(f"datacat.{name}")
206+
except AttributeError as e:
207+
raise ValueError(
208+
f"{name} not in available datasets."
209+
f" Available datasets: {list_datasets()}"
210+
) from e
202211

203212
# validate type, raise error if not raw or transformed
204213
if type not in ["raw", "transformed"]:
@@ -213,7 +222,7 @@ def get_data(
213222
# continue workflow based on raw or transformed
214223
if type == "raw":
215224
# get the BlobEndpoint for the raw data
216-
blob_endpoint = datasets.__dict__[name].extract
225+
blob_endpoint = config.extract
217226
# check version exists, raise error if not
218227
if version != "latest":
219228
v_list = blob_endpoint.get_versions()
@@ -225,17 +234,29 @@ def get_data(
225234
f"Version {version} not in available versions."
226235
)
227236
# get blobs and convert to correct df
228-
blobs = blob_endpoint.read_blobs()
229-
if output in ["pandas", "pd"]:
230-
df = pd.concat([pd.read_csv(blob) for blob in blobs])
231-
else:
232-
df = pl.concat(
233-
[pl.read_csv(blob.content_as_bytes()) for blob in blobs],
234-
how="vertical_relaxed",
235-
)
236-
return df
237+
if config.extract.get_file_ext() == "csv":
238+
blobs = blob_endpoint.read_blobs()
239+
if output in ["pandas", "pd"]:
240+
df = pd.concat([pd.read_csv(blob) for blob in blobs])
241+
else:
242+
df = pl.concat(
243+
[pl.read_csv(blob.content_as_bytes()) for blob in blobs],
244+
how="vertical_relaxed",
245+
)
246+
return df
247+
elif config.extract.get_file_ext() == "json":
248+
blobs = blob_endpoint.read_blobs()
249+
if output in ["pandas", "pd"]:
250+
df = pd.concat(
251+
[pd.read_json(blob.content_as_bytes()) for blob in blobs]
252+
)
253+
else:
254+
df = pl.concat(
255+
[pl.read_json(blob.content_as_bytes()) for blob in blobs],
256+
)
257+
return df
237258
else:
238-
blob_endpoint = datasets.__dict__[name].load
259+
blob_endpoint = config.load
239260
# check version exists, raise error if not
240261
if version != "latest":
241262
v_list = blob_endpoint.get_versions()
@@ -268,8 +289,8 @@ def list_datasets() -> list[str]:
268289
269290
Examples:
270291
>>> datasets = list_datasets()
271-
>>> 'covid19vax_trends' in datasets
292+
>>> 'scenarios.covid19vax_trends' in datasets
272293
True
273294
274295
"""
275-
return [x for x in datasets.__dict__.keys()]
296+
return name_paths

cfa/scenarios/dataops/datasets/config_validator.py renamed to cfa/scenarios/dataops/config_validator.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,10 @@ def verify_no_repeats(configs: List[dict]) -> None:
6464
Raises:
6565
AttributeError: When the names repeat.
6666
"""
67-
name_counts = Counter([i["properties"]["name"] for i in configs])
67+
name_counts = Counter(configs)
6868
repeats = [k for k, v in name_counts.items() if v > 1]
6969
if repeats:
70-
names_in_files = []
71-
for n_i in set(repeats):
72-
names_in_files += [
73-
f' - {n_i} in {config["_metadata"]["filename"]}'
74-
for config in configs
75-
if config["properties"]["name"] == n_i
76-
]
77-
error_file_strings = ("\n").join(names_in_files)
7870
raise AttributeError(
79-
"More than one config shares a the same name property: "
80-
f"{error_file_strings}"
71+
"More than one config shares a the same name. Here are the repeats: "
72+
f"{', '.join(repeats)}"
8173
)
Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +0,0 @@
1-
"""Simplified importing of most commonly used objects
2-
3-
All data configurations should reside in this directory and be in the toml
4-
format. Use the existing configuration files as a starting point. Validations
5-
will run on all configurations.
6-
"""
7-
8-
from .catalog import datasets
9-
10-
__all__ = [datasets]

cfa/scenarios/dataops/datasets/mcmv/__init__.py

Whitespace-only changes.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# This dataset is static, and thus only needs to be extracted in it's raw form
2+
# once and saved to blob storage. For a human viewable we format, see:
3+
# https://data.cdc.gov/Vaccinations/COVID-19-Vaccination-Age-and-Sex-Trends-in-the-Uni/5i5k-6cmh/data_preview
4+
# Raw (extracted) and transformed (loaded) data will be stored in azure blob storage
5+
# which requires the account, container and path for access
6+
7+
[properties]
8+
9+
name = "respnet"
10+
automate = false
11+
transform_template = ""
12+
schema = ""
13+
14+
[source]
15+
16+
domain = "data.cdc.gov"
17+
id = "kvib-3txy"
18+
limit = 10000
19+
20+
[extract]
21+
22+
account = "cfadatalakeprd"
23+
container = "cfapredict"
24+
prefix = "dataops/mcmv/raw/respnet"
25+
26+
[load]
27+
28+
account = "cfadatalakeprd"
29+
container = "cfapredict"
30+
prefix = "dataops/mcmv/transformed/respnet"
31+
32+
# TODO: add some data schema validation

cfa/scenarios/dataops/datasets/scenarios/__init__.py

Whitespace-only changes.

cfa/scenarios/dataops/datasets/covid19_hospitalizations.toml renamed to cfa/scenarios/dataops/datasets/scenarios/covid19_hospitalizations.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
[properties]
88
name = "covid19hospitalizations"
99
automate = false
10-
transform_template = "covid19hospitalizations.sql"
11-
schema = "datasets/schemas/covid19hospitalizations.py"
10+
transform_template = "scenarios/covid19hospitalizations.sql"
11+
schema = "datasets/scenarios/schemas/covid19hospitalizations.py"
1212

1313
[source]
1414
url = "https://healthdata.gov/resource/sgxm-t72h.csv"

cfa/scenarios/dataops/datasets/covid19_vaccination_trends.toml renamed to cfa/scenarios/dataops/datasets/scenarios/covid19_vaccination_trends.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
name = "covid19vax_trends"
1010
automate = false
11-
transform_template = "covid19vax_trends.sql"
12-
schema = "datasets/schemas/covid19vax_trends.py"
11+
transform_template = "scenarios/covid19vax_trends.sql"
12+
schema = "datasets/scenarios/schemas/covid19vax_trends.py"
1313

1414
[source]
1515

0 commit comments

Comments
 (0)