From 6ef06701a280cfe9460ab51e0c070d0f75e32c57 Mon Sep 17 00:00:00 2001 From: Jack Rosacker <18175209+jackrosacker@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:05:03 -0400 Subject: [PATCH 1/6] rename objects --- dcpy/lifecycle/package/shapefiles.py | 21 ++++++++++--------- .../test/lifecycle/package/test_shapefiles.py | 14 ++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/dcpy/lifecycle/package/shapefiles.py b/dcpy/lifecycle/package/shapefiles.py index 44555833cb..7efb201974 100644 --- a/dcpy/lifecycle/package/shapefiles.py +++ b/dcpy/lifecycle/package/shapefiles.py @@ -121,8 +121,9 @@ def parse_shapefile_metadata(file_path: Path) -> Metadata: app = typer.Typer() +# TODO - delete? @app.command("to_metadata") -def _write_metadata( +def _write_shapefile_metadata( shp_xml_path: Path, output_path: Path = typer.Option( None, @@ -137,11 +138,11 @@ def _write_metadata( @app.command("write_metadata") -def _write_shapefile_xml_metadata( +def _write_metadata( product_name: str, dataset_name: str, path: Path, - shp_name: str, + layer: str, org_md_path: Path | None = typer.Option( None, "--org-md-path", @@ -153,22 +154,22 @@ def _write_shapefile_xml_metadata( help="Directory structure within zip file, if relevant", ), ): - write_shapefile_xml_metadata( + write_metadata( product_name=product_name, dataset_name=dataset_name, path=path, - shp_name=shp_name, + layer=layer, zip_subdir=zip_subdir, org_md=org_md_path, ) - logger.info(f"Wrote metadata to {shp_name} in {path}") + logger.info(f"Wrote metadata to {layer} in {path}") -def write_shapefile_xml_metadata( +def write_metadata( product_name: str, dataset_name: str, path: Path, - shp_name: str, + layer: str, zip_subdir: str | None, org_md: Path | OrgMetadata | None, # Allow passing OrgMetadata for testing purposes ): @@ -179,7 +180,7 @@ def write_shapefile_xml_metadata( product_name (str): Name of product. e.g. "lion" dataset_name (str): Name of dataset within a product. e.g. "pseudo-lots" path (Path): Path to parent directory or zip file containing shapefile. - shp_name (str): Shapefile name, ending in ".shp". e.g. "shapefile_name.shp" + layer (str): Shapefile name, ending in ".shp". e.g. "shapefile_name.shp" zip_subdir (str | None): Internal path, if shp is nested within a zip file. org_md (Path | OrgMetadata | None): Metadata reference used to populate shapefile metadata. """ @@ -206,7 +207,7 @@ def write_shapefile_xml_metadata( _create_attr_metadata(column) for column in product_md.columns ] - shp = Shapefile(path=path, shp_name=shp_name, zip_subdir=zip_subdir) + shp = Shapefile(path=path, shp_name=layer, zip_subdir=zip_subdir) shp.write_metadata(metadata, overwrite=True) diff --git a/dcpy/test/lifecycle/package/test_shapefiles.py b/dcpy/test/lifecycle/package/test_shapefiles.py index 4118831b31..41d48805f6 100644 --- a/dcpy/test/lifecycle/package/test_shapefiles.py +++ b/dcpy/test/lifecycle/package/test_shapefiles.py @@ -75,12 +75,12 @@ def _get_info_from_file_fixture( raise Exception(f"Type: {file_type} is an ") elif file_type == "zip": path = request.getfixturevalue(fixture) # Retrieve fixture by name - shp_name = path.stem + layer = path.stem elif file_type == "nonzip": path_fixture = request.getfixturevalue(fixture) path = path_fixture.parent # Retrieve fixture by name - shp_name = path_fixture.name - return {"path": path, "shp_name": shp_name} + layer = path_fixture.name + return {"path": path, "layer": layer} @fixture @@ -110,7 +110,7 @@ def org_metadata(package_and_dist_test_resources): ), ], ) -def test_write_shapefile_xml_metadata( +def test_write_metadata( request, path_fixture, file_type, @@ -126,18 +126,18 @@ def test_write_shapefile_xml_metadata( fields = Metadata.model_fields # write metadata - shapefiles.write_shapefile_xml_metadata( + shapefiles.write_metadata( product_name="colp", dataset_name="colp", path=fixture_info["path"], - shp_name=fixture_info["shp_name"], + layer=fixture_info["layer"], zip_subdir=subdir, org_md=org_metadata, ) # read it back shp = shp_utils.from_path( - path=fixture_info["path"], shp_name=fixture_info["shp_name"], zip_subdir=subdir + path=fixture_info["path"], shp_name=fixture_info["layer"], zip_subdir=subdir ) metadata = shp.read_metadata() From c6dee69ecff8aed5866cc0b759bdf3084d506035 Mon Sep 17 00:00:00 2001 From: Jack Rosacker <18175209+jackrosacker@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:05:45 -0400 Subject: [PATCH 2/6] rough out fgdb write command --- dcpy/lifecycle/package/shapefiles.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/dcpy/lifecycle/package/shapefiles.py b/dcpy/lifecycle/package/shapefiles.py index 7efb201974..a28625f8f3 100644 --- a/dcpy/lifecycle/package/shapefiles.py +++ b/dcpy/lifecycle/package/shapefiles.py @@ -16,7 +16,7 @@ Metadata, ) from dcpy.models.product.metadata import OrgMetadata -from dcpy.utils.geospatial import shapefile as shp_utils +from dcpy.utils.geospatial import esri_metadata, fgdb from dcpy.utils.geospatial.shapefile import Shapefile from dcpy.utils.logging import logger @@ -179,9 +179,10 @@ def write_metadata( Args: product_name (str): Name of product. e.g. "lion" dataset_name (str): Name of dataset within a product. e.g. "pseudo-lots" - path (Path): Path to parent directory or zip file containing shapefile. - layer (str): Shapefile name, ending in ".shp". e.g. "shapefile_name.shp" - zip_subdir (str | None): Internal path, if shp is nested within a zip file. + path (Path): Path to parent directory or zip file containing shapefile, or geodatabase. + layer (str): Shapefile or feature class name. + zip_subdir (str | None): Internal path if shp is nested within a zip file. + Must be None for file geodatabase. org_md (Path | OrgMetadata | None): Metadata reference used to populate shapefile metadata. """ if isinstance(org_md, Path) or not org_md: @@ -189,7 +190,7 @@ def write_metadata( product_md = org_md.product(product_name).dataset(dataset_name) - metadata = shp_utils.generate_metadata() + metadata = esri_metadata.generate_metadata() # Set dataset-level values # TODO: define DCP organizationally required metadata fields @@ -207,8 +208,16 @@ def write_metadata( _create_attr_metadata(column) for column in product_md.columns ] - shp = Shapefile(path=path, shp_name=layer, zip_subdir=zip_subdir) - shp.write_metadata(metadata, overwrite=True) + if ".gdb" in path.suffixes: + if zip_subdir is not None: + raise ValueError( + "Nested zipped GDBs are not supported. The GDB must be at the top level of the zip." + ) + fgdb.write_metadata(gdb=path, layer=layer, metadata=metadata, overwrite=True) + + elif ".shp" in path.suffixes or layer.endswith(".shp"): + shp = Shapefile(path=path, shp_name=layer, zip_subdir=zip_subdir) + shp.write_metadata(metadata, overwrite=True) def _create_attr_metadata(column: DatasetColumn) -> Attr: From fea40c5b1fa1a7fd15862aa731a1dc856d576938 Mon Sep 17 00:00:00 2001 From: Jack Rosacker <18175209+jackrosacker@users.noreply.github.com> Date: Wed, 8 Apr 2026 13:08:19 -0400 Subject: [PATCH 3/6] move metadata code to esri.py --- dcpy/lifecycle/package/esri.py | 125 ++++++++++++++++++ dcpy/lifecycle/package/shapefiles.py | 121 ----------------- .../{test_shapefiles.py => test_esri.py} | 4 +- 3 files changed, 127 insertions(+), 123 deletions(-) rename dcpy/test/lifecycle/package/{test_shapefiles.py => test_esri.py} (98%) diff --git a/dcpy/lifecycle/package/esri.py b/dcpy/lifecycle/package/esri.py index f096c664bc..97ddd478bd 100644 --- a/dcpy/lifecycle/package/esri.py +++ b/dcpy/lifecycle/package/esri.py @@ -5,6 +5,15 @@ import yaml import dcpy.models.product.dataset.metadata as models +from dcpy.lifecycle import product_metadata +from dcpy.models.data.shapefile_metadata import Attr, Edom +from dcpy.models.product.dataset.metadata import ( + ColumnValue, + DatasetColumn, +) +from dcpy.models.product.metadata import OrgMetadata +from dcpy.utils.geospatial import esri_metadata, fgdb +from dcpy.utils.geospatial.shapefile import Shapefile from dcpy.utils.logging import logger @@ -83,3 +92,119 @@ def parse_pdf_text( output_path = output_path or Path("columns.yml") with open(output_path, "w") as outfile: yaml.dump(fields, outfile, sort_keys=False) + + +@app.command("write_metadata") +def _write_metadata( + product_name: str, + dataset_name: str, + path: Path, + layer: str, + org_md_path: Path | None = typer.Option( + None, + "--org-md-path", + help="Path to organizational metadata", + ), + zip_subdir: str | None = typer.Option( + None, + "--zip-subdir", + help="Directory structure within zip file, if relevant", + ), +): + write_metadata( + product_name=product_name, + dataset_name=dataset_name, + path=path, + layer=layer, + zip_subdir=zip_subdir, + org_md=org_md_path, + ) + logger.info(f"Wrote metadata to {layer} in {path}") + + +def write_metadata( + product_name: str, + dataset_name: str, + path: Path, + layer: str, + zip_subdir: str | None, + org_md: Path | OrgMetadata | None, # Allow passing OrgMetadata for testing purposes +): + """Write product metadata to the shapefile metadata XML. Generates a new XML with defaults, + and applies additional product-specific values. + + Args: + product_name (str): Name of product. e.g. "lion" + dataset_name (str): Name of dataset within a product. e.g. "pseudo-lots" + path (Path): Path to parent directory or zip file containing shapefile, or geodatabase. + layer (str): Shapefile or feature class name. + zip_subdir (str | None): Internal path if shp is nested within a zip file. + Must be None when path is a file geodatabase. + org_md (Path | OrgMetadata | None): Metadata reference used to populate shapefile metadata. + """ + if isinstance(org_md, Path) or not org_md: + org_md = product_metadata.load(org_md_path_override=org_md) + + product_md = org_md.product(product_name).dataset(dataset_name) + + metadata = esri_metadata.generate_metadata() + + # Set dataset-level values + # TODO: define DCP organizationally required metadata fields + metadata.md_hr_lv_name = product_md.attributes.display_name + metadata.data_id_info.id_abs = product_md.attributes.description + metadata.data_id_info.other_keys.keyword = product_md.attributes.tags + metadata.data_id_info.search_keys.keyword = product_md.attributes.tags + + metadata.eainfo.detailed.name = product_md.id + metadata.eainfo.detailed.enttyp.enttypl.value = product_md.id + metadata.eainfo.detailed.enttyp.enttypt.value = "Feature Class" + + # Build attribute metadata for each column + metadata.eainfo.detailed.attr = [ + _create_attr_metadata(column) for column in product_md.columns + ] + + if ".gdb" in path.suffixes: + if zip_subdir is not None: + raise ValueError( + "Nested zipped GDBs are not supported. The GDB must be at the top level of the zip." + ) + fgdb.write_metadata(gdb=path, layer=layer, metadata=metadata, overwrite=True) + + elif ".shp" in path.suffixes or layer.endswith(".shp"): + shp = Shapefile(path=path, shp_name=layer, zip_subdir=zip_subdir) + shp.write_metadata(metadata, overwrite=True) + + +def _create_attr_metadata(column: DatasetColumn) -> Attr: + """Create an Attr metadata object from a column specification.""" + attr = Attr() + + attr.attrlabl.value = "FID" if column.id == "uid" else column.id + attr.attalias.value = "FID" if column.name == "uid" else column.name + attr.attrdef.value = column.description + + # TODO: define column-level defaults (e.g. attrdefs = 'Esri' if column.name == 'uid') + # TODO: map DCP types to Esri types (e.g. attrtype = 'OID' if column.name == 'uid') Note DCP types != Esri types + # attr.attrtype.value = column.data_type + # attr.attwidth.value = None + # attr.atprecis.value = None + # attr.attscale.value = None + # attr.attrdefs.value = "" + + # Handle domain values if present + if hasattr(column, "values") and column.values: + attr.attrdomv.edom = [_create_edom_metadata(value) for value in column.values] + + # TODO: handle 'attrdomv.udom', with other esri value defaults + return attr + + +def _create_edom_metadata(column_value: ColumnValue) -> Edom: + """Create an Edom metadata object from a column value specification.""" + edom = Edom() + edom.edomv = column_value.value + edom.edomvd = column_value.description + + return edom diff --git a/dcpy/lifecycle/package/shapefiles.py b/dcpy/lifecycle/package/shapefiles.py index a28625f8f3..9c1b3aba80 100644 --- a/dcpy/lifecycle/package/shapefiles.py +++ b/dcpy/lifecycle/package/shapefiles.py @@ -6,8 +6,6 @@ import typer -from dcpy.lifecycle import product_metadata -from dcpy.models.data.shapefile_metadata import Attr, Edom from dcpy.models.product.dataset.metadata import ( COLUMN_TYPES, ColumnValue, @@ -15,9 +13,6 @@ DatasetColumn, Metadata, ) -from dcpy.models.product.metadata import OrgMetadata -from dcpy.utils.geospatial import esri_metadata, fgdb -from dcpy.utils.geospatial.shapefile import Shapefile from dcpy.utils.logging import logger _shapefile_to_dcpy_types: dict[str, COLUMN_TYPES] = { @@ -135,119 +130,3 @@ def _write_shapefile_metadata( out_path = output_path or Path("./metadata.yml") parse_shapefile_metadata(shp_xml_path).write_to_yaml(out_path) logger.info(f"Wrote metadata to {out_path}") - - -@app.command("write_metadata") -def _write_metadata( - product_name: str, - dataset_name: str, - path: Path, - layer: str, - org_md_path: Path | None = typer.Option( - None, - "--org-md-path", - help="Path to organizational metadata", - ), - zip_subdir: str | None = typer.Option( - None, - "--zip-subdir", - help="Directory structure within zip file, if relevant", - ), -): - write_metadata( - product_name=product_name, - dataset_name=dataset_name, - path=path, - layer=layer, - zip_subdir=zip_subdir, - org_md=org_md_path, - ) - logger.info(f"Wrote metadata to {layer} in {path}") - - -def write_metadata( - product_name: str, - dataset_name: str, - path: Path, - layer: str, - zip_subdir: str | None, - org_md: Path | OrgMetadata | None, # Allow passing OrgMetadata for testing purposes -): - """Write product metadata to the shapefile metadata XML. Generates a new XML with defaults, - and applies additional product-specific values. - - Args: - product_name (str): Name of product. e.g. "lion" - dataset_name (str): Name of dataset within a product. e.g. "pseudo-lots" - path (Path): Path to parent directory or zip file containing shapefile, or geodatabase. - layer (str): Shapefile or feature class name. - zip_subdir (str | None): Internal path if shp is nested within a zip file. - Must be None for file geodatabase. - org_md (Path | OrgMetadata | None): Metadata reference used to populate shapefile metadata. - """ - if isinstance(org_md, Path) or not org_md: - org_md = product_metadata.load(org_md_path_override=org_md) - - product_md = org_md.product(product_name).dataset(dataset_name) - - metadata = esri_metadata.generate_metadata() - - # Set dataset-level values - # TODO: define DCP organizationally required metadata fields - metadata.md_hr_lv_name = product_md.attributes.display_name - metadata.data_id_info.id_abs = product_md.attributes.description - metadata.data_id_info.other_keys.keyword = product_md.attributes.tags - metadata.data_id_info.search_keys.keyword = product_md.attributes.tags - - metadata.eainfo.detailed.name = product_md.id - metadata.eainfo.detailed.enttyp.enttypl.value = product_md.id - metadata.eainfo.detailed.enttyp.enttypt.value = "Feature Class" - - # Build attribute metadata for each column - metadata.eainfo.detailed.attr = [ - _create_attr_metadata(column) for column in product_md.columns - ] - - if ".gdb" in path.suffixes: - if zip_subdir is not None: - raise ValueError( - "Nested zipped GDBs are not supported. The GDB must be at the top level of the zip." - ) - fgdb.write_metadata(gdb=path, layer=layer, metadata=metadata, overwrite=True) - - elif ".shp" in path.suffixes or layer.endswith(".shp"): - shp = Shapefile(path=path, shp_name=layer, zip_subdir=zip_subdir) - shp.write_metadata(metadata, overwrite=True) - - -def _create_attr_metadata(column: DatasetColumn) -> Attr: - """Create an Attr metadata object from a column specification.""" - attr = Attr() - - attr.attrlabl.value = "FID" if column.id == "uid" else column.id - attr.attalias.value = "FID" if column.name == "uid" else column.name - attr.attrdef.value = column.description - - # TODO: define column-level defaults (e.g. attrdefs = 'Esri' if column.name == 'uid') - # TODO: map DCP types to Esri types (e.g. attrtype = 'OID' if column.name == 'uid') Note DCP types != Esri types - # attr.attrtype.value = column.data_type - # attr.attwidth.value = None - # attr.atprecis.value = None - # attr.attscale.value = None - # attr.attrdefs.value = "" - - # Handle domain values if present - if hasattr(column, "values") and column.values: - attr.attrdomv.edom = [_create_edom_metadata(value) for value in column.values] - - # TODO: handle 'attrdomv.udom', with other esri value defaults - return attr - - -def _create_edom_metadata(column_value: ColumnValue) -> Edom: - """Create an Edom metadata object from a column value specification.""" - edom = Edom() - edom.edomv = column_value.value - edom.edomvd = column_value.description - - return edom diff --git a/dcpy/test/lifecycle/package/test_shapefiles.py b/dcpy/test/lifecycle/package/test_esri.py similarity index 98% rename from dcpy/test/lifecycle/package/test_shapefiles.py rename to dcpy/test/lifecycle/package/test_esri.py index 41d48805f6..973c9dc87c 100644 --- a/dcpy/test/lifecycle/package/test_shapefiles.py +++ b/dcpy/test/lifecycle/package/test_esri.py @@ -6,7 +6,7 @@ import pytest from pytest import fixture -from dcpy.lifecycle.package import shapefiles +from dcpy.lifecycle.package import esri from dcpy.models.data.shapefile_metadata import Metadata from dcpy.models.product.metadata import OrgMetadata from dcpy.utils.geospatial import shapefile as shp_utils @@ -126,7 +126,7 @@ def test_write_metadata( fields = Metadata.model_fields # write metadata - shapefiles.write_metadata( + esri.write_metadata( product_name="colp", dataset_name="colp", path=fixture_info["path"], From 49f209e7691606109d6b000c8c467b23bb7477f1 Mon Sep 17 00:00:00 2001 From: Jack Rosacker <18175209+jackrosacker@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:31:50 -0400 Subject: [PATCH 4/6] var names --- dcpy/test/utils/geospatial/test_fgdb.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dcpy/test/utils/geospatial/test_fgdb.py b/dcpy/test/utils/geospatial/test_fgdb.py index 8cb2d04106..d7efa6078a 100644 --- a/dcpy/test/utils/geospatial/test_fgdb.py +++ b/dcpy/test/utils/geospatial/test_fgdb.py @@ -8,8 +8,8 @@ from dcpy.utils.geospatial import fgdb GDB_ZIP = "geodatabase.gdb.zip" -FEATURE_CLASS = "mappluto_one_row" -TABLE = "pluto_one_row" +SPATIAL_LAYER = "mappluto_one_row" +TABLE_LAYER = "pluto_one_row" METADATA_XML = "esri_metadata.xml" @@ -68,12 +68,12 @@ def path_fixture(request): @gdb_paths def test_get_layers(path_fixture): layers = fgdb.get_layers(path_fixture) - assert layers == [FEATURE_CLASS, TABLE] + assert layers == [SPATIAL_LAYER, TABLE_LAYER] @gdb_paths def test_read_metadata(path_fixture): - md = fgdb.read_metadata(gdb=path_fixture, layer=FEATURE_CLASS) + md = fgdb.read_metadata(gdb=path_fixture, layer=SPATIAL_LAYER) element = "esri" assert hasattr(md, element), f"Expected element '{element}', but found none" @@ -87,13 +87,13 @@ def test_write_metadata(path_fixture, temp_metadata_object): layers_before_md_write = fgdb.get_layers(path_fixture) fgdb.write_metadata( gdb=path_fixture, - layer=FEATURE_CLASS, + layer=SPATIAL_LAYER, metadata=temp_metadata_object, overwrite=True, ) layers_after_md_write = fgdb.get_layers(path_fixture) - md = fgdb.read_metadata(path_fixture, FEATURE_CLASS) + md = fgdb.read_metadata(path_fixture, SPATIAL_LAYER) element = "esri" assert hasattr(md, element), f"Expected element '{element}', but found none" @@ -105,14 +105,14 @@ def test_write_metadata(path_fixture, temp_metadata_object): @gdb_paths def test_metadata_exists(path_fixture): - originally_md_exists = fgdb.metadata_exists(gdb=path_fixture, layer=FEATURE_CLASS) + originally_md_exists = fgdb.metadata_exists(gdb=path_fixture, layer=SPATIAL_LAYER) # remove metadata fgdb.remove_metadata( gdb=path_fixture, - layer=FEATURE_CLASS, + layer=SPATIAL_LAYER, ) md_exists_after_removal = fgdb.metadata_exists( - gdb=path_fixture, layer=FEATURE_CLASS + gdb=path_fixture, layer=SPATIAL_LAYER ) assert originally_md_exists is True, "Expected layer metadata but found none" assert md_exists_after_removal is False, ( @@ -125,11 +125,11 @@ def test_remove_metadata(path_fixture): layers_before_md_removal = fgdb.get_layers(path_fixture) fgdb.remove_metadata( gdb=path_fixture, - layer=FEATURE_CLASS, + layer=SPATIAL_LAYER, ) layers_after_md_removal = fgdb.get_layers(path_fixture) - md = fgdb.read_metadata(path_fixture, FEATURE_CLASS) + md = fgdb.read_metadata(path_fixture, SPATIAL_LAYER) assert md is None # confirm that no gdb layers were lost during md removal assert sorted(layers_before_md_removal) == sorted(layers_after_md_removal) From 3ee7766a8184396610f4ea05ceef3931ccf02b3f Mon Sep 17 00:00:00 2001 From: Jack Rosacker <18175209+jackrosacker@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:32:05 -0400 Subject: [PATCH 5/6] add fgdb metadata tests --- dcpy/test/lifecycle/package/test_esri.py | 77 +++++++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/dcpy/test/lifecycle/package/test_esri.py b/dcpy/test/lifecycle/package/test_esri.py index 973c9dc87c..b1cf9a53d8 100644 --- a/dcpy/test/lifecycle/package/test_esri.py +++ b/dcpy/test/lifecycle/package/test_esri.py @@ -9,11 +9,16 @@ from dcpy.lifecycle.package import esri from dcpy.models.data.shapefile_metadata import Metadata from dcpy.models.product.metadata import OrgMetadata +from dcpy.utils.geospatial import fgdb from dcpy.utils.geospatial import shapefile as shp_utils SHP_ZIP_NO_MD = "shapefile_single_pluto_feature_no_metadata.shp.zip" SHP_ZIP_WITH_MD = "shapefile_single_pluto_feature_with_metadata.shp.zip" +GDB_ZIP = "geodatabase.gdb.zip" +SPATIAL_LAYER = "mappluto_one_row" +TABLE_LAYER = "pluto_one_row" + @fixture def temp_shp_zip_no_md_path(utils_resources_path, tmp_path): @@ -57,11 +62,32 @@ def temp_nonzipped_shp_with_md_path(temp_shp_zip_with_md_path, tmp_path): return shp_path +@fixture +def temp_gdb_zip_path(utils_resources_path, tmp_path): + shutil.copy2( + src=utils_resources_path / GDB_ZIP, + dst=tmp_path / GDB_ZIP, + ) + assert zipfile.is_zipfile(tmp_path / GDB_ZIP), ( + f"'{GDB_ZIP}' should be a valid zip file" + ) + return tmp_path / GDB_ZIP + + +@fixture +def temp_gdb_nonzipped_path(temp_gdb_zip_path, tmp_path): + shutil.unpack_archive(filename=temp_gdb_zip_path, extract_dir=tmp_path) + gdb_path = tmp_path / temp_gdb_zip_path.stem + assert gdb_path.is_dir(), "Expected a gdb directory, but found none" + return gdb_path + + def _get_info_from_file_fixture( request: pytest.FixtureRequest, fixture: str, file_type: str ) -> dict: - """Calculate path and shp name for a given shapefile fixture. + """Calculate path and layer name for a given fixture. Calculation differs between zipped and non-zipped fixtures. + Supports shapefiles and file geodatabases. Args: request (pytest.FixtureRequest): @@ -69,17 +95,25 @@ def _get_info_from_file_fixture( file_type (str): type of fixture - either "zip" or "nonzip" Returns: - dict: path and shapefile name for given fixture + dict: path and layer name for given fixture """ + if file_type not in ["zip", "nonzip"]: raise Exception(f"Type: {file_type} is an ") elif file_type == "zip": - path = request.getfixturevalue(fixture) # Retrieve fixture by name - layer = path.stem + path_fixture = request.getfixturevalue(fixture) # Retrieve fixture by name + if ".gdb" in path_fixture.suffixes: + layer = SPATIAL_LAYER + elif ".shp" in path_fixture.suffixes: + layer = path_fixture.stem + path = path_fixture elif file_type == "nonzip": path_fixture = request.getfixturevalue(fixture) path = path_fixture.parent # Retrieve fixture by name - layer = path_fixture.name + if ".gdb" in path_fixture.suffixes: + layer = SPATIAL_LAYER + elif ".shp" in path_fixture.suffixes: + layer = path_fixture.name return {"path": path, "layer": layer} @@ -108,6 +142,24 @@ def org_metadata(package_and_dist_test_resources): None, id="add_md_to_nonzip_shp_w_no_md", ), + pytest.param( + "temp_shp_zip_with_md_path", + "zip", + None, + id="add_md_to_zip_shp_with_md", + ), + pytest.param( + "temp_nonzipped_shp_with_md_path", + "nonzip", + None, + id="add_md_to_nonzip_shp_with_md", + ), + pytest.param( + "temp_gdb_zip_path", + "zip", + None, + id="add_md_to_zip_gdb", + ), ], ) def test_write_metadata( @@ -136,10 +188,17 @@ def test_write_metadata( ) # read it back - shp = shp_utils.from_path( - path=fixture_info["path"], shp_name=fixture_info["layer"], zip_subdir=subdir - ) - metadata = shp.read_metadata() + metadata = None + + if ".gdb" in fixture_info["path"].suffixes: + metadata = fgdb.read_metadata( + gdb=fixture_info["path"], layer=fixture_info["layer"] + ) + if ".shp" in fixture_info["layer"]: + shp = shp_utils.from_path( + path=fixture_info["path"], shp_name=fixture_info["layer"], zip_subdir=subdir + ) + metadata = shp.read_metadata() if metadata is None: pytest.fail("Expected metadata to exist") From 3daaab7f8ac96a8e7abfa937931f6ec14d665f06 Mon Sep 17 00:00:00 2001 From: Jack Rosacker <18175209+jackrosacker@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:07:31 -0400 Subject: [PATCH 6/6] rename --- .../test/lifecycle/package/{test_esri.py => test_package_esri.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dcpy/test/lifecycle/package/{test_esri.py => test_package_esri.py} (100%) diff --git a/dcpy/test/lifecycle/package/test_esri.py b/dcpy/test/lifecycle/package/test_package_esri.py similarity index 100% rename from dcpy/test/lifecycle/package/test_esri.py rename to dcpy/test/lifecycle/package/test_package_esri.py