From 184e1e01a2c50f0e8cbb8e14d649c225dbf99ed3 Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Tue, 31 Mar 2026 12:41:37 -0500 Subject: [PATCH 1/6] codex-authored fix for type checking --- MANIFEST.in | 1 + cfa/dataops/catalog.py | 109 ++++++++++++++++++++--------------------- cfa/dataops/py.typed | 1 + pyproject.toml | 3 ++ 4 files changed, 59 insertions(+), 55 deletions(-) create mode 100644 cfa/dataops/py.typed diff --git a/MANIFEST.in b/MANIFEST.in index a369d05..5263ae4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ recursive-include cfa/dataops/reporting/reports recursive-include cfa/dataops/etl/transform_templates include cfa/dataops/config.ini +include cfa/dataops/py.typed diff --git a/cfa/dataops/catalog.py b/cfa/dataops/catalog.py index 39e3c73..82d6387 100644 --- a/cfa/dataops/catalog.py +++ b/cfa/dataops/catalog.py @@ -7,7 +7,7 @@ from importlib import import_module from io import BytesIO from types import SimpleNamespace -from typing import Any, List, Sequence +from typing import TYPE_CHECKING, Any, List, Literal, Sequence, overload import pandas as pd import polars as pl @@ -51,9 +51,7 @@ def get_all_catalogs() -> list: catalog_nspace = _config.get("DEFAULT", "catalog_namespaces") try: catalog_pkg = import_module(catalog_nspace) - for module_finder, modname, ispkg in pkgutil.iter_modules( - catalog_pkg.__path__ - ): + for module_finder, modname, ispkg in pkgutil.iter_modules(catalog_pkg.__path__): if ispkg: catalogs.append((catalog_nspace, modname, module_finder.path)) except ModuleNotFoundError: @@ -74,9 +72,7 @@ def get_all_catalogs() -> list: report_mod = import_module(f"{cns}.{cat_name}.reports") all_dataset_ns_map.update(dataset_mod.dataset_ns_map) all_reports_ns_map.update(report_mod.report_ns_map) - with open( - os.path.join(cat_path, cat_name, "catalog_defaults.toml"), "rb" - ) as f: + with open(os.path.join(cat_path, cat_name, "catalog_defaults.toml"), "rb") as f: defaults = tomli.load(f) for k in dataset_mod.dataset_ns_map.keys(): all_defaults.update({k: defaults}) @@ -85,11 +81,40 @@ def get_all_catalogs() -> list: report_namespaces = get_dataset_dot_path(all_reports_ns_map) +class CatalogNamespace(SimpleNamespace): + """Recursive namespace wrapper for statically-typed catalog access. + + Runtime instances still behave like ``SimpleNamespace`` objects, but the + declared attributes let editors resolve endpoint methods on dynamic access + chains such as ``datacat.public.team.dataset.load``. + """ + + if TYPE_CHECKING: + load: "BlobEndpoint" + extract: "BlobEndpoint" + data: "BlobEndpoint" + _ledger_endpoint: "BlobEndpoint" + config: dict[str, Any] + __namespace_list__: list[str] + + def __getattr__(self, name: str) -> "CatalogNamespace": + raise AttributeError( + f"{type(self).__name__!s} object has no attribute {name!r}" + ) + + class DatasetEndpoint: """The DatasetEndpoint class for including in the datacat namespace. This ends the namespace branching at a config file and creates all the blob endpoints for each 'stage' of the config (e.g., extract, load, stage_01).""" + if TYPE_CHECKING: + config: dict[str, Any] + load: "BlobEndpoint" + extract: "BlobEndpoint" + data: "BlobEndpoint" + _ledger_endpoint: "BlobEndpoint" + def __init__(self, config_path: str, defaults: dict, ns: str): """Basic functionality to interact with datasets to be included via the datasets configs. @@ -109,13 +134,9 @@ def __init__(self, config_path: str, defaults: dict, ns: str): account = v.get("account", "") container = v.get("container", "") if account == "": - self.config[k]["account"] = self.defaults["storage"][ - "account" - ] + self.config[k]["account"] = self.defaults["storage"]["account"] if container == "": - self.config[k]["container"] = self.defaults["storage"][ - "container" - ] + self.config[k]["container"] = self.defaults["storage"]["container"] self.validate_dataset_config(config_path) self._ledger_location = { "account": self.defaults["storage"]["account"], @@ -142,8 +163,7 @@ def validate_dataset_config(self, config_path) -> None: config_models = {} for c_key, c_value in self.config.items(): if ( - c_key.startswith("stage_") - or c_key in ["load", "extract", "data"] + c_key.startswith("stage_") or c_key in ["load", "extract", "data"] ) and c_value is not None: config_models[c_key] = StorageEndpointValidation(**c_value) elif c_key == "properties": @@ -205,9 +225,7 @@ def write_blob( append (bool, optional): whether to append to existing file (only for single file writes). """ if auto_version and not append: - path_after_prefix = ( - f"{get_timestamp()}/{path_after_prefix.lstrip('/')}" - ) + path_after_prefix = f"{get_timestamp()}/{path_after_prefix.lstrip('/')}" path_after_prefix = path_after_prefix.lstrip("/") full_path = f"{self.prefix}/{path_after_prefix}" if isinstance(file_buffer, bytes): @@ -229,9 +247,7 @@ def write_blob( self.ledger_entry(action="write") # print(f"file written to: {full_path}") - def read_blobs( - self, version: str = "latest", newest: bool = True - ) -> List[bytes]: + def read_blobs(self, version: str = "latest", newest: bool = True) -> List[bytes]: """Read a blob in as bytes so it can be loaded into a dataframe Args: @@ -293,18 +309,16 @@ def get_file_ext(self, version: str = "latest") -> str: Returns: str: the file extension """ - return self._get_version_blobs(version=version, print_version=False)[ - 0 - ]["name"].split(".")[-1] + return self._get_version_blobs(version=version, print_version=False)[0][ + "name" + ].split(".")[-1] def _get_version_blobs( self, version: str = "latest", newest=True, print_version=True ) -> list: if not self.is_ledger: available_versions = self.get_versions() - version = version_matcher( - version, available_versions, newest=newest - ) + version = version_matcher(version, available_versions, newest=newest) if not version: raise ValueError( f"Version {version} not found in available versions: {available_versions}" @@ -451,9 +465,7 @@ def get_dataframe( return df elif file_ext == "jsonl": if output in ["pandas", "pd"]: - df = pd.concat( - [pd.read_json(blob, lines=True) for blob in blob_files] - ) + df = pd.concat([pd.read_json(blob, lines=True) for blob in blob_files]) df.reset_index(inplace=True, drop=True) else: df = pl.concat( @@ -465,9 +477,7 @@ def get_dataframe( return df elif file_ext == "parquet" or file_ext == "parq": if output in ["pandas", "pd"]: - df = pd.concat( - [pd.read_parquet(pq_file) for pq_file in blob_files] - ) + df = pd.concat([pd.read_parquet(pq_file) for pq_file in blob_files]) df.reset_index(inplace=True, drop=True) else: df = pl.concat( @@ -525,9 +535,7 @@ def save_dataframe( raise ValueError( f"File format {file_format} not supported. Use 'parquet', 'csv', 'json', or 'jsonl'." ) - if file_format in ["json", "jsonl"] and path_after_prefix.endswith( - ".json" - ): + if file_format in ["json", "jsonl"] and path_after_prefix.endswith(".json"): path_after_prefix = path_after_prefix[:-5] + ".jsonl" print("Changing file extension to .jsonl for line-delimited JSON.") if isinstance(df, pd.DataFrame): @@ -550,9 +558,7 @@ def save_dataframe( auto_version=auto_version, ) elif file_format in ["json", "jsonl"]: - json_bytes = df.to_json(orient="records", lines=True).encode( - "utf-8" - ) + json_bytes = df.to_json(orient="records", lines=True).encode("utf-8") self.write_blob( file_buffer=json_bytes, path_after_prefix=path_after_prefix @@ -644,7 +650,7 @@ def save_dir_to_blob( ) -def dict_to_sn(d: Any, defaults: dict = None, ns: str = "") -> SimpleNamespace: +def dict_to_sn(d: Any, defaults: dict | None = None, ns: str = "") -> CatalogNamespace: """Simple recursive namespace construction Args: @@ -653,9 +659,9 @@ def dict_to_sn(d: Any, defaults: dict = None, ns: str = "") -> SimpleNamespace: ns (str, optional): the current namespace path. Defaults to ''. Returns: - SimpleNamespace: namespace representation + CatalogNamespace: namespace representation """ - x = SimpleNamespace() + x = CatalogNamespace() ns_prefix = f"{ns}." if ns != "" else "" _ = [ setattr( @@ -694,19 +700,15 @@ def dict_to_sn(d: Any, defaults: dict = None, ns: str = "") -> SimpleNamespace: rc = [] for k in all_reports_ns_map.keys(): rc.append(report_dict_to_sn({k: all_reports_ns_map[k]})) -combined_reports_dict = { - key: value for ns in rc for key, value in vars(ns).items() -} +combined_reports_dict = {key: value for ns in rc for key, value in vars(ns).items()} -datacat = SimpleNamespace(**combined_dict) +datacat: CatalogNamespace = CatalogNamespace(**combined_dict) datacat.__setattr__("__namespace_list__", dataset_namespaces) -reportcat = SimpleNamespace(**combined_reports_dict) +reportcat: CatalogNamespace = CatalogNamespace(**combined_reports_dict) reportcat.__setattr__("__namespace_list__", report_namespaces) -def _attach_schema_mock_functions( - datacat: SimpleNamespace, catalogs: list -) -> None: +def _attach_schema_mock_functions(datacat: SimpleNamespace, catalogs: list) -> None: """Recursively walk the datacat namespace and attach mock_data functions to the extract and load BlobEndpoints of each DatasetEndpoint, sourced from a schema module co-located with the dataset. @@ -744,14 +746,11 @@ def _walk(ns: SimpleNamespace) -> None: # strip cat_name prefix -> "stf.nhsn_hrd_prelim" # then split into team ("stf") and dataset ("nhsn_hrd_prelim") # so the schema lives at: datasets.stf.schemas.nhsn_hrd_prelim - ns_within_datasets = val.__ns_str__.removeprefix( - f"{cat_name}." - ) + ns_within_datasets = val.__ns_str__.removeprefix(f"{cat_name}.") ns_parts = ns_within_datasets.rsplit(".", 1) team_path = ns_parts[0] if len(ns_parts) > 1 else "" schema_mod_path = ( - f"{cns}.{cat_name}.datasets" - f".{team_path}.schemas.{dataset_name}" + f"{cns}.{cat_name}.datasets.{team_path}.schemas.{dataset_name}" if team_path else f"{cns}.{cat_name}.datasets.schemas.{dataset_name}" ) diff --git a/cfa/dataops/py.typed b/cfa/dataops/py.typed new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/cfa/dataops/py.typed @@ -0,0 +1 @@ + diff --git a/pyproject.toml b/pyproject.toml index cbe86b5..988a4ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,9 @@ dependencies = [ [tool.poetry] packages = [{ include = "cfa" }] +include = [ + { path = "cfa/dataops/py.typed", format = ["sdist", "wheel"] }, +] [tool.poetry.group.dev.dependencies] From d5f5ed8d437fbf7c8a7fca1ae3285ffa94451cb6 Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Tue, 31 Mar 2026 12:57:28 -0500 Subject: [PATCH 2/6] ruff format --- cfa/dataops/catalog.py | 69 +++++++++++++++++++++++--------- cfa/dataops/reporting/catalog.py | 6 +-- cfa/dataops/soda.py | 6 +-- tests/test_create_catalog.py | 12 +++--- 4 files changed, 62 insertions(+), 31 deletions(-) diff --git a/cfa/dataops/catalog.py b/cfa/dataops/catalog.py index 82d6387..7478352 100644 --- a/cfa/dataops/catalog.py +++ b/cfa/dataops/catalog.py @@ -51,7 +51,9 @@ def get_all_catalogs() -> list: catalog_nspace = _config.get("DEFAULT", "catalog_namespaces") try: catalog_pkg = import_module(catalog_nspace) - for module_finder, modname, ispkg in pkgutil.iter_modules(catalog_pkg.__path__): + for module_finder, modname, ispkg in pkgutil.iter_modules( + catalog_pkg.__path__ + ): if ispkg: catalogs.append((catalog_nspace, modname, module_finder.path)) except ModuleNotFoundError: @@ -72,7 +74,9 @@ def get_all_catalogs() -> list: report_mod = import_module(f"{cns}.{cat_name}.reports") all_dataset_ns_map.update(dataset_mod.dataset_ns_map) all_reports_ns_map.update(report_mod.report_ns_map) - with open(os.path.join(cat_path, cat_name, "catalog_defaults.toml"), "rb") as f: + with open( + os.path.join(cat_path, cat_name, "catalog_defaults.toml"), "rb" + ) as f: defaults = tomli.load(f) for k in dataset_mod.dataset_ns_map.keys(): all_defaults.update({k: defaults}) @@ -134,9 +138,13 @@ def __init__(self, config_path: str, defaults: dict, ns: str): account = v.get("account", "") container = v.get("container", "") if account == "": - self.config[k]["account"] = self.defaults["storage"]["account"] + self.config[k]["account"] = self.defaults["storage"][ + "account" + ] if container == "": - self.config[k]["container"] = self.defaults["storage"]["container"] + self.config[k]["container"] = self.defaults["storage"][ + "container" + ] self.validate_dataset_config(config_path) self._ledger_location = { "account": self.defaults["storage"]["account"], @@ -163,7 +171,8 @@ def validate_dataset_config(self, config_path) -> None: config_models = {} for c_key, c_value in self.config.items(): if ( - c_key.startswith("stage_") or c_key in ["load", "extract", "data"] + c_key.startswith("stage_") + or c_key in ["load", "extract", "data"] ) and c_value is not None: config_models[c_key] = StorageEndpointValidation(**c_value) elif c_key == "properties": @@ -225,7 +234,9 @@ def write_blob( append (bool, optional): whether to append to existing file (only for single file writes). """ if auto_version and not append: - path_after_prefix = f"{get_timestamp()}/{path_after_prefix.lstrip('/')}" + path_after_prefix = ( + f"{get_timestamp()}/{path_after_prefix.lstrip('/')}" + ) path_after_prefix = path_after_prefix.lstrip("/") full_path = f"{self.prefix}/{path_after_prefix}" if isinstance(file_buffer, bytes): @@ -247,7 +258,9 @@ def write_blob( self.ledger_entry(action="write") # print(f"file written to: {full_path}") - def read_blobs(self, version: str = "latest", newest: bool = True) -> List[bytes]: + def read_blobs( + self, version: str = "latest", newest: bool = True + ) -> List[bytes]: """Read a blob in as bytes so it can be loaded into a dataframe Args: @@ -309,16 +322,18 @@ def get_file_ext(self, version: str = "latest") -> str: Returns: str: the file extension """ - return self._get_version_blobs(version=version, print_version=False)[0][ - "name" - ].split(".")[-1] + return self._get_version_blobs(version=version, print_version=False)[ + 0 + ]["name"].split(".")[-1] def _get_version_blobs( self, version: str = "latest", newest=True, print_version=True ) -> list: if not self.is_ledger: available_versions = self.get_versions() - version = version_matcher(version, available_versions, newest=newest) + version = version_matcher( + version, available_versions, newest=newest + ) if not version: raise ValueError( f"Version {version} not found in available versions: {available_versions}" @@ -465,7 +480,9 @@ def get_dataframe( return df elif file_ext == "jsonl": if output in ["pandas", "pd"]: - df = pd.concat([pd.read_json(blob, lines=True) for blob in blob_files]) + df = pd.concat( + [pd.read_json(blob, lines=True) for blob in blob_files] + ) df.reset_index(inplace=True, drop=True) else: df = pl.concat( @@ -477,7 +494,9 @@ def get_dataframe( return df elif file_ext == "parquet" or file_ext == "parq": if output in ["pandas", "pd"]: - df = pd.concat([pd.read_parquet(pq_file) for pq_file in blob_files]) + df = pd.concat( + [pd.read_parquet(pq_file) for pq_file in blob_files] + ) df.reset_index(inplace=True, drop=True) else: df = pl.concat( @@ -535,7 +554,9 @@ def save_dataframe( raise ValueError( f"File format {file_format} not supported. Use 'parquet', 'csv', 'json', or 'jsonl'." ) - if file_format in ["json", "jsonl"] and path_after_prefix.endswith(".json"): + if file_format in ["json", "jsonl"] and path_after_prefix.endswith( + ".json" + ): path_after_prefix = path_after_prefix[:-5] + ".jsonl" print("Changing file extension to .jsonl for line-delimited JSON.") if isinstance(df, pd.DataFrame): @@ -558,7 +579,9 @@ def save_dataframe( auto_version=auto_version, ) elif file_format in ["json", "jsonl"]: - json_bytes = df.to_json(orient="records", lines=True).encode("utf-8") + json_bytes = df.to_json(orient="records", lines=True).encode( + "utf-8" + ) self.write_blob( file_buffer=json_bytes, path_after_prefix=path_after_prefix @@ -650,7 +673,9 @@ def save_dir_to_blob( ) -def dict_to_sn(d: Any, defaults: dict | None = None, ns: str = "") -> CatalogNamespace: +def dict_to_sn( + d: Any, defaults: dict | None = None, ns: str = "" +) -> CatalogNamespace: """Simple recursive namespace construction Args: @@ -700,7 +725,9 @@ def dict_to_sn(d: Any, defaults: dict | None = None, ns: str = "") -> CatalogNam rc = [] for k in all_reports_ns_map.keys(): rc.append(report_dict_to_sn({k: all_reports_ns_map[k]})) -combined_reports_dict = {key: value for ns in rc for key, value in vars(ns).items()} +combined_reports_dict = { + key: value for ns in rc for key, value in vars(ns).items() +} datacat: CatalogNamespace = CatalogNamespace(**combined_dict) datacat.__setattr__("__namespace_list__", dataset_namespaces) @@ -708,7 +735,9 @@ def dict_to_sn(d: Any, defaults: dict | None = None, ns: str = "") -> CatalogNam reportcat.__setattr__("__namespace_list__", report_namespaces) -def _attach_schema_mock_functions(datacat: SimpleNamespace, catalogs: list) -> None: +def _attach_schema_mock_functions( + datacat: SimpleNamespace, catalogs: list +) -> None: """Recursively walk the datacat namespace and attach mock_data functions to the extract and load BlobEndpoints of each DatasetEndpoint, sourced from a schema module co-located with the dataset. @@ -746,7 +775,9 @@ def _walk(ns: SimpleNamespace) -> None: # strip cat_name prefix -> "stf.nhsn_hrd_prelim" # then split into team ("stf") and dataset ("nhsn_hrd_prelim") # so the schema lives at: datasets.stf.schemas.nhsn_hrd_prelim - ns_within_datasets = val.__ns_str__.removeprefix(f"{cat_name}.") + ns_within_datasets = val.__ns_str__.removeprefix( + f"{cat_name}." + ) ns_parts = ns_within_datasets.rsplit(".", 1) team_path = ns_parts[0] if len(ns_parts) > 1 else "" schema_mod_path = ( diff --git a/cfa/dataops/reporting/catalog.py b/cfa/dataops/reporting/catalog.py index c4c37d8..3bcd08d 100644 --- a/cfa/dataops/reporting/catalog.py +++ b/cfa/dataops/reporting/catalog.py @@ -123,9 +123,9 @@ def nb_to_html_file( Returns: None, saves the html content to a file. """ - assert html_out_path.endswith( - ".html" - ), "Output path must end with .html" + assert html_out_path.endswith(".html"), ( + "Output path must end with .html" + ) html_content = self.nb_to_html_str(nb_title=nb_title, **kwargs) os.makedirs(os.path.dirname(html_out_path), exist_ok=True) with open(html_out_path, "w", encoding="utf-8") as f: diff --git a/cfa/dataops/soda.py b/cfa/dataops/soda.py index 7dd0cd5..a4159bc 100644 --- a/cfa/dataops/soda.py +++ b/cfa/dataops/soda.py @@ -121,9 +121,9 @@ def n_rows(self) -> int: def _get_records(self, start: int, end: int) -> list[dict]: assert end >= start - assert ( - self.limit is None or end < self.limit - ), f"End index {end} is larger than limit {self.limit}." + assert self.limit is None or end < self.limit, ( + f"End index {end} is larger than limit {self.limit}." + ) n_rows = end - start + 1 return self._get_request( diff --git a/tests/test_create_catalog.py b/tests/test_create_catalog.py index 236b800..ac5a751 100644 --- a/tests/test_create_catalog.py +++ b/tests/test_create_catalog.py @@ -192,9 +192,9 @@ def test_create_expanded_catalog(catalog_parent): ] for file_name in cfa_files: - assert catalog_location.join(file_name).check( - file=True - ), f"Missing CFA file: {file_name}" + assert catalog_location.join(file_name).check(file=True), ( + f"Missing CFA file: {file_name}" + ) def test_unique_name_sanitization(catalog_parent): @@ -285,9 +285,9 @@ def test_datasets_directory_structure(catalog_parent): ] for example_file in example_files: - assert datasets_path.join(example_file).check( - file=True - ), f"Missing example file: {example_file}" + assert datasets_path.join(example_file).check(file=True), ( + f"Missing example file: {example_file}" + ) def test_reports_directory_structure(catalog_parent): From cdd4a1d525c342a3456d42c6b2b6f456ce6b8de9 Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Tue, 31 Mar 2026 13:06:51 -0500 Subject: [PATCH 3/6] pre-commit --- cfa/dataops/py.typed | 1 - cfa/dataops/reporting/catalog.py | 6 +++--- cfa/dataops/soda.py | 6 +++--- tests/test_create_catalog.py | 12 ++++++------ 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/cfa/dataops/py.typed b/cfa/dataops/py.typed index 8b13789..e69de29 100644 --- a/cfa/dataops/py.typed +++ b/cfa/dataops/py.typed @@ -1 +0,0 @@ - diff --git a/cfa/dataops/reporting/catalog.py b/cfa/dataops/reporting/catalog.py index 3bcd08d..c4c37d8 100644 --- a/cfa/dataops/reporting/catalog.py +++ b/cfa/dataops/reporting/catalog.py @@ -123,9 +123,9 @@ def nb_to_html_file( Returns: None, saves the html content to a file. """ - assert html_out_path.endswith(".html"), ( - "Output path must end with .html" - ) + assert html_out_path.endswith( + ".html" + ), "Output path must end with .html" html_content = self.nb_to_html_str(nb_title=nb_title, **kwargs) os.makedirs(os.path.dirname(html_out_path), exist_ok=True) with open(html_out_path, "w", encoding="utf-8") as f: diff --git a/cfa/dataops/soda.py b/cfa/dataops/soda.py index a4159bc..7dd0cd5 100644 --- a/cfa/dataops/soda.py +++ b/cfa/dataops/soda.py @@ -121,9 +121,9 @@ def n_rows(self) -> int: def _get_records(self, start: int, end: int) -> list[dict]: assert end >= start - assert self.limit is None or end < self.limit, ( - f"End index {end} is larger than limit {self.limit}." - ) + assert ( + self.limit is None or end < self.limit + ), f"End index {end} is larger than limit {self.limit}." n_rows = end - start + 1 return self._get_request( diff --git a/tests/test_create_catalog.py b/tests/test_create_catalog.py index ac5a751..236b800 100644 --- a/tests/test_create_catalog.py +++ b/tests/test_create_catalog.py @@ -192,9 +192,9 @@ def test_create_expanded_catalog(catalog_parent): ] for file_name in cfa_files: - assert catalog_location.join(file_name).check(file=True), ( - f"Missing CFA file: {file_name}" - ) + assert catalog_location.join(file_name).check( + file=True + ), f"Missing CFA file: {file_name}" def test_unique_name_sanitization(catalog_parent): @@ -285,9 +285,9 @@ def test_datasets_directory_structure(catalog_parent): ] for example_file in example_files: - assert datasets_path.join(example_file).check(file=True), ( - f"Missing example file: {example_file}" - ) + assert datasets_path.join(example_file).check( + file=True + ), f"Missing example file: {example_file}" def test_reports_directory_structure(catalog_parent): From 6f8f62a79afff94d849856514a9e1399326d0d1a Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Tue, 31 Mar 2026 13:12:55 -0500 Subject: [PATCH 4/6] pre-commit --- cfa/dataops/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfa/dataops/catalog.py b/cfa/dataops/catalog.py index 7478352..c0c9527 100644 --- a/cfa/dataops/catalog.py +++ b/cfa/dataops/catalog.py @@ -7,7 +7,7 @@ from importlib import import_module from io import BytesIO from types import SimpleNamespace -from typing import TYPE_CHECKING, Any, List, Literal, Sequence, overload +from typing import TYPE_CHECKING, Any, List, Sequence import pandas as pd import polars as pl From a691b63fb0ad1c22cca8c81890f6b659ce46f577 Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Tue, 21 Apr 2026 09:48:56 -0500 Subject: [PATCH 5/6] import literal --- cfa/dataops/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfa/dataops/catalog.py b/cfa/dataops/catalog.py index 7e9534c..54b8fa6 100644 --- a/cfa/dataops/catalog.py +++ b/cfa/dataops/catalog.py @@ -9,7 +9,7 @@ from pathlib import PurePosixPath from types import SimpleNamespace -from typing import TYPE_CHECKING, Any, List, Sequence, overload +from typing import TYPE_CHECKING, Any, List, Sequence, overload, Literal import pandas as pd import polars as pl From 9332f607296e249ffbafc0293ae68e77a0f48bb6 Mon Sep 17 00:00:00 2001 From: Damon Bayer Date: Tue, 28 Apr 2026 10:29:00 -0500 Subject: [PATCH 6/6] Update catalog.py --- cfa/dataops/catalog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/cfa/dataops/catalog.py b/cfa/dataops/catalog.py index 464e4aa..1485cdd 100644 --- a/cfa/dataops/catalog.py +++ b/cfa/dataops/catalog.py @@ -9,8 +9,7 @@ from io import BytesIO from pathlib import PurePosixPath from types import SimpleNamespace - -from typing import TYPE_CHECKING, Any, List, Sequence, overload, Literal +from typing import TYPE_CHECKING, Any, Literal, overload import pandas as pd import polars as pl @@ -715,9 +714,7 @@ def save_dir_to_blob( ) -def dict_to_sn( - d: Any, defaults: dict | None = None, ns: str = "" -) -> CatalogNamespace: +def dict_to_sn(d: Any, defaults: dict | None = None, ns: str = "") -> CatalogNamespace: """Simple recursive namespace construction Args: